aboutsummaryrefslogtreecommitdiff
path: root/tests/cli/testllvmprojects.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/cli/testllvmprojects.py')
-rw-r--r--tests/cli/testllvmprojects.py318
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")