diff options
Diffstat (limited to 'tests/cli/testllvmprojects.py')
-rw-r--r-- | tests/cli/testllvmprojects.py | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/tests/cli/testllvmprojects.py b/tests/cli/testllvmprojects.py new file mode 100644 index 0000000..c33ddb5 --- /dev/null +++ b/tests/cli/testllvmprojects.py @@ -0,0 +1,318 @@ +"""Command line interface tests for llvmprojs.py + +Note that although this uses the unittest framework, it does *not* contain unit +tests. + +""" + +import shutil +import os +import subprocess +import unittest + +from functools import partial +from tempfile import mkdtemp +from uuid import uuid4 + +from linaropy.cd import cd + + +# TODO: move this somewhere more public (maybe linaropy?) +def debug(test): + """ + Decorator that dumps the output of any subprocess.CalledProcessError + exception. Use this to decorate a test function when you can't tell what the + problem is. + """ + def wrapper(*args, **kwargs): + # Catch any exceptions so we can dump all the output + try: + test(*args, **kwargs) + except subprocess.CalledProcessError as exc: + print "Error in {}:".format(test.__name__) + print "Command {} exited with error code {}:\n{}".format( + exc.cmd, exc.returncode, exc.output) + return wrapper + + +class Testllvmprojs(unittest.TestCase): + script = os.path.join("scripts", "llvm.py") + + @classmethod + def __create_dummy_commit(cls): + filename = "filethatshouldntexist" + cls.run_quietly(["touch", filename]) + cls.run_quietly(["git", "add", filename]) + cls.run_quietly(["git", "commit", "-m", "Dummy commit"]) + + @classmethod + def __create_dummy_repo(cls, repopath): + if not os.path.isdir(repopath): + os.makedirs(repopath) + + with cd(repopath): + cls.run_quietly(["git", "init"]) + cls.__create_dummy_commit() + + @classmethod + def __add_worktree(cls, repopath, worktreepath, branch): + with cd(repopath): + cls.run_quietly(["git", "worktree", "add", worktreepath, + "-b", branch]) + + @classmethod + def __get_subproj_repo(cls, subproj): + return os.path.join(cls.repos, subproj) + + @classmethod + def setUpClass(cls): + """Create the file structure and environment that llvmprojs expects""" + cls.llvm_root = mkdtemp() + cls.repos = os.path.join(cls.llvm_root, "repos") + + cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb", + "libcxx", "libcxxabi", "libunwind", "test-suite") + + # Set up helper functions for running commands (this needs to be done + # before creating the repos) + # FIXME: In newer versions of Python (3.3+) we should be able to bind + # the stdout to DEVNULL, as below: + # cls.run_quietly = partial(subprocess.call, stdout=subprocess.DEVNULL) + # This doesn't work in Python 2, so we'll just have to ignore the output + # for now... + cls.run_with_output = partial(subprocess.check_output, + stderr=subprocess.STDOUT) + cls.run_quietly = cls.run_with_output + + # Create dummy repos + for reponame in cls.all_repos: + cls.__create_dummy_repo(cls.__get_subproj_repo(reponame)) + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls.llvm_root) + + @classmethod + def setUp(cls): + cls.llvm_src = os.path.join(cls.llvm_root, "src" + str(uuid4())) + + # Create LLVM worktree + cls.branch = "br" + str(uuid4()) + cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src, + cls.branch) + + # Set up the environment variables + os.environ["LLVM_ROOT"] = cls.llvm_root + os.environ["LLVM_SRC"] = cls.llvm_src + + @classmethod + def tearDown(cls): + # Clean up the directories where we might have added subprojects. + # This isn't 100% clean, because we don't clean up the repos between + # tests (so any branches will remain), but it's good enough for the + # current tests. + for subprojdir in (os.path.join(cls.llvm_src, "projects"), + os.path.join(cls.llvm_src, "tools")): + if os.path.isdir(subprojdir): + shutil.rmtree(subprojdir) + os.makedirs(subprojdir) + + # Run prune on the original repos, to remove any dangling worktrees. + for reponame in cls.all_repos: + repopath = cls.__get_subproj_repo(reponame) + with cd(repopath): + cls.run_quietly(["git", "worktree", "prune"]) + + def test_dump_empty_config(self): + """ + Test that we're correctly dumping an empty configuration (i.e. no + projects linked) when running llvmprojs without arguments. + """ + output = self.run_with_output(["python", self.script, "projects"]) + self.assertRegexpMatches(output, "Projects linked:.*\n.*none.*") + + def test_add_remove_subprojects(self): + """ + Test that we can add and remove one or several subprojects. + """ + output = self.run_with_output(["python", self.script, "projects", + "--add", "clang"]) + self.assertRegexpMatches(output, "Projects linked:.*\n.*clang.*") + + output = self.run_with_output(["python", self.script, "projects", + "--add", "libcxx", "lldb"]) + self.assertRegexpMatches( + output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*libcxx.*\n" + + ".*lldb.*") + + output = self.run_with_output(["python", self.script, "projects", + "--remove", "libcxx"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*lldb.*") + + output = self.run_with_output(["python", self.script, "projects", + "--remove", "clang", "lldb"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*none.*") + + def test_add_remove_invalid_subprojects(self): + """ + Test that we error out nicely when trying to add/remove invalid + subprojects. + """ + with self.assertRaises(subprocess.CalledProcessError) as context: + self.run_quietly(["python", self.script, "projects", + "--add", "inventedsubproject"]) + + self.assertRegexpMatches( + context.exception.output, + "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*") + + with self.assertRaises(subprocess.CalledProcessError) as context: + self.run_quietly(["python", self.script, "projects", + "--remove", "inventedsubproject"]) + + self.assertRegexpMatches( + context.exception.output, + "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*") + + def test_duplicate_add_remove(self): + """ + Test that we don't crash when trying to add / remove the same subproject + twice with the same command. + """ + output = self.run_with_output(["python", self.script, "projects", + "--add", "clang", "lld", "clang"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*lld.*") + + output = self.run_with_output(["python", self.script, "projects", + "--remove", "lld", "lld", "clang"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*none.*") + + def test_redundant_add_remove(self): + """ + Test that we can add a subproject that already exists (either because it + was added by our script or manually) or remove a subproject that doesn't + exist. + """ + self.__add_worktree(self.__get_subproj_repo("clang"), + os.path.join(self.llvm_src, "tools", "clang"), + self.branch) + self.__add_worktree( + self.__get_subproj_repo("compiler-rt"), + os.path.join(self.llvm_src, "projects", "compiler-rt"), + self.branch) + + output = self.run_with_output(["python", self.script, "projects", + "--add", "clang"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*") + + output = self.run_with_output(["python", self.script, "projects", + "--add", "compiler-rt", "lld"]) + self.assertRegexpMatches( + output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*compiler-rt.*\n" + + ".*lld.*") + + output = self.run_with_output(["python", self.script, "projects", + "--remove", "lldb", "libcxx", "lld"]) + self.assertRegexpMatches( + output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*compiler-rt.*") + + def test_simultaneous_add_remove(self): + """ + Test that we error out when someone is trying to add and remove the same + project with the same command. + """ + # Try the really basic case and make sure we're not touching anything + with self.assertRaises(subprocess.CalledProcessError) as context: + self.run_quietly(["python", self.script, "projects", + "--add", "clang", "--remove", "clang"]) + + self.assertRegexpMatches( + context.exception.output, + "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*") + + self.assertFalse( + os.path.exists( + os.path.join(self.llvm_src, "tools", "clang"))) + + # Try something a bit more complicated and make sure we're not touching + # anything + self.__add_worktree( + self.__get_subproj_repo("lld"), + os.path.join(self.llvm_src, "tools", "lld"), + self.branch) + + with self.assertRaises(subprocess.CalledProcessError) as context: + self.run_quietly(["python", self.script, "projects", + "--add", "clang", "lld", "libcxx", + "--remove", "lld", "libcxx"]) + self.assertRegexpMatches( + context.exception.output, + "(.*\n)*" + + ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*") + + # Make sure we didn't touch anything + self.assertFalse( + os.path.exists( + os.path.join(self.llvm_src, "tools", "clang"))) + self.assertTrue( + os.path.exists( + os.path.join(self.llvm_src, "tools", "lld"))) + self.assertFalse( + os.path.exists( + os.path.join(self.llvm_src, "projects", "libcxx"))) + + def test_multiple_adds_removes(self): + """ + Test that we can have multiple --add and --remove options in the same + command and that only the last one of each kind matters. + """ + output = self.run_with_output(["python", self.script, "projects", + "--add", "libcxxabi", + "--remove", "lld", "lldb", + "--add", "clang", "libcxx", + "--remove", "libunwind"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*libcxx.*\n") + + output = self.run_with_output(["python", self.script, "projects", + "--add", "libunwind", "libcxxabi", + "--remove", "clang", "libcxx", + "--add", "compiler-rt", + "--remove", "libcxxabi"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*compiler-rt.*\n" + + ".*libcxx.*\n") + + output = self.run_with_output(["python", self.script, "projects", + "--add", "libcxx", "--remove", "lld", + "--add", "lld", "--remove", "libcxx"]) + self.assertRegexpMatches(output, + "Projects linked:.*\n" + + ".*clang.*\n" + + ".*compiler-rt.*\n" + + ".*lld.*\n") |