| """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 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): |
| python = "python3" |
| 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) |
| |
| @staticmethod |
| def run_with_output(*args, **kwargs): |
| """Helper for running a command and capturing stdout and stderr""" |
| kwargs["stderr"] = subprocess.STDOUT |
| return str(subprocess.check_output(*args, **kwargs), 'utf-8') |
| |
| @staticmethod |
| def run_quietly(*args, **kwargs): |
| """ |
| Helper for running a command and ignoring stdout and stderr. Exceptions |
| are still thrown if something goes wrong |
| """ |
| kwargs["stdout"] = subprocess.DEVNULL |
| kwargs["stderr"] = subprocess.DEVNULL |
| return subprocess.check_call(*args, **kwargs) |
| |
| @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") |
| |
| # 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.env = "src" + str(uuid4()) |
| cls.llvm_src = os.path.join(cls.llvm_root, cls.env, "llvm") |
| |
| # 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 |
| |
| @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( |
| [self.python, self.script, self.env, "projects"]) |
| self.assertRegex(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( |
| [self.python, self.script, self.env, "projects", "--add", "clang"]) |
| self.assertRegex(output, "Projects linked:.*\n.*clang.*") |
| |
| output = self.run_with_output([self.python, self.script, self.env, "projects", |
| "--add", "libcxx", "lldb"]) |
| self.assertRegex( |
| output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*libcxx.*\n" + |
| ".*lldb.*") |
| |
| output = self.run_with_output( |
| [self.python, self.script, self.env, "projects", "--remove", "libcxx"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*lldb.*") |
| |
| output = self.run_with_output([self.python, self.script, self.env, "projects", |
| "--remove", "clang", "lldb"]) |
| self.assertRegex(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_with_output([self.python, self.script, self.env, "projects", |
| "--add", "inventedsubproject"]) |
| |
| self.assertRegex( |
| str(context.exception.output), |
| "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*") |
| |
| with self.assertRaises(subprocess.CalledProcessError) as context: |
| self.run_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--remove", |
| "inventedsubproject"]) |
| |
| self.assertRegex( |
| str(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([self.python, self.script, self.env, "projects", |
| "--add", "clang", "lld", "clang"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*lld.*") |
| |
| output = self.run_with_output([self.python, self.script, self.env, "projects", |
| "--remove", "lld", "lld", "clang"]) |
| self.assertRegex(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( |
| [self.python, self.script, self.env, "projects", "--add", "clang"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*") |
| |
| output = self.run_with_output([self.python, self.script, self.env, "projects", |
| "--add", "compiler-rt", "lld"]) |
| self.assertRegex( |
| output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*compiler-rt.*\n" + |
| ".*lld.*") |
| |
| output = self.run_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--remove", |
| "lldb", |
| "libcxx", |
| "lld"]) |
| self.assertRegex( |
| 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_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--add", |
| "clang", |
| "--remove", |
| "clang"]) |
| |
| self.assertRegex( |
| str(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_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--add", |
| "clang", |
| "lld", |
| "libcxx", |
| "--remove", |
| "lld", |
| "libcxx"]) |
| self.assertRegex( |
| str(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([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--add", |
| "libcxxabi", |
| "--remove", |
| "lld", |
| "lldb", |
| "--add", |
| "clang", |
| "libcxx", |
| "--remove", |
| "libunwind"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*libcxx.*\n") |
| |
| output = self.run_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--add", |
| "libunwind", |
| "libcxxabi", |
| "--remove", |
| "clang", |
| "libcxx", |
| "--add", |
| "compiler-rt", |
| "--remove", |
| "libcxxabi"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*compiler-rt.*\n" + |
| ".*libcxx.*\n") |
| |
| output = self.run_with_output([self.python, |
| self.script, |
| self.env, |
| "projects", |
| "--add", |
| "libcxx", |
| "--remove", |
| "lld", |
| "--add", |
| "lld", |
| "--remove", |
| "libcxx"]) |
| self.assertRegex(output, |
| "Projects linked:.*\n" + |
| ".*clang.*\n" + |
| ".*compiler-rt.*\n" + |
| ".*lld.*\n") |