[llvmprojs] Rewrite llvm-projs in python
This is the first step in moving all our scripts to python. For now we keep the
current content of the repo, but we'll start adding a new directory hierarchy
for the python stuff:
* modules: should contain most of the python code, organized as a package that
can be imported by the scripts
* scripts: the scripts themselves, which should be parsing the command line
and calling stuff from 'modules' to do the actual work; can be
broken down into the same categories we had before (helpers, bisect
etc), or we could just have one big pile
* tests: should contain unittests (for the stuff in modules) and command line
interface tests (for the scripts)
The code is heavily using functionality from the tcwg-release-tools repo (which
probably needs to be renamed / reorganized), so you should have that in your
PYTHONPATH when trying to run any of the scripts. To run the tests, just invoke
check.sh.
One of the important changes is that we'll be using python's argparse module to
parse command line flags, which means we'll have to stick to a more traditional
interface for the scripts. In particular, we can't have short options like "+c"
anymore. This isn't much of a problem, because we will keep the bash scripts as
they are and just make them invoke a tool written in python (scripts/llvm.py) to
do the work. The tool will have subcommands for any functionality that we want,
for instance the new interface for adding/removing subprojects is:
llvm.py projects [-a subproject subproject ... subproject]
[-r subproject ... subproject]
The -a and -r options (followed by any number of subprojects) can be used to
selectively enable/disable things. You have to pass the full name of the
subproject (e.g. llvmprojs.py -a clang lld -r libcxx) for it to work. This is
invoked by the llvm-projs bash script, which has the old, more convenient
interface.
For now the bash scripts will live in the same directories as they used to, but
after the transition is complete we will want to move them to the scripts
directory.
Note that we're also eliding any dashes in the names of the scripts, in keeping
with Python best practices for module names (i.e. using valid Python identifiers
as names).
Change-Id: I9ec08632dbb17464673240d6f6881a90f45d5371
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/cli/__init__.py
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")
diff --git a/tests/unittests/__init__.py b/tests/unittests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/unittests/__init__.py
diff --git a/tests/unittests/testllvmsourceconfig.py b/tests/unittests/testllvmsourceconfig.py
new file mode 100644
index 0000000..1add13a
--- /dev/null
+++ b/tests/unittests/testllvmsourceconfig.py
@@ -0,0 +1,572 @@
+import os
+import unittest
+import uuid
+
+from sh import git
+
+from linaropy.cd import cd
+from linaropy.proj import Proj
+from linaropy.git.clone import Clone
+from linaropy.git.worktree import Worktree
+
+from modules.llvm import LLVMSourceConfig, LLVMSubproject
+
+
+class TestLLVMSourceConfig(unittest.TestCase):
+ testdirprefix = "SourceConfigUT"
+
+ def __create_dummy_commit(self):
+ filename = "file" + str(uuid.uuid4())
+ open(filename, "a").close()
+ git("add", filename)
+ git("commit", "-m", "Branches without commits confuse git")
+
+ def __create_dummy_repo(self, path):
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ with cd(path):
+ git("init")
+ self.__create_dummy_commit()
+
+ def __get_subproj_repo_path(self, subproj):
+ return os.path.join(self.originalLLVM.repodir, "..", subproj + "-repo")
+
+ def __get_subproj_repo(self, subproj):
+ return Clone(self.proj, self.__get_subproj_repo_path(subproj))
+
+ def setUp(self):
+ # We're going to create a hierarchy with [llvm|clang|whatever]-repo
+ # containing dummy repos, and llvm-copy containing a worktree of
+ # llvm-repo
+ self.proj = Proj(prefix=TestLLVMSourceConfig.testdirprefix)
+ path = os.path.join(self.proj.projdir, "llvm-repo")
+ self.__create_dummy_repo(path)
+ self.originalLLVM = Clone(self.proj, path)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ for subproj in subprojs.keys():
+ repo = self.__get_subproj_repo_path(subproj)
+ self.__create_dummy_repo(repo)
+
+ self.temporaryLLVMbranch = "a-branch"
+ self.temporaryLLVM = Worktree.create(
+ self.proj, self.originalLLVM, os.path.join(
+ self.proj.projdir, "llvm-copy"), self.temporaryLLVMbranch)
+
+ def tearDown(self):
+ self.proj.cleanup()
+
+ def test_detect_enabled_all(self):
+ subprojs = LLVMSubproject.get_all_subprojects()
+ sourcePath = self.temporaryLLVM.repodir
+
+ for subproj in subprojs:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertEqual(set(subprojs), set(enabled),
+ "Expected %s but detected only %s" %
+ (str(set(subprojs)), str(enabled)))
+
+ def test_detect_enabled_none(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ path = os.path.join(sourcePath, "unrelated")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "wrong", "place", "for", "lld")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "projects", "clang")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "tools", "libcxx")
+ os.makedirs(path)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_detect_enabled_some(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["lld", "libcxxabi", "clang"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertTrue("lld" in enabled, "Failed to detect lld")
+ self.assertTrue("clang" in enabled, "Failed to detect clang")
+ self.assertTrue("libcxxabi" in enabled,
+ "Failed to detect libcxxabi")
+
+ def test_detect_enabled_not_worktree(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ path = subprojs["compiler-rt"].get_cmake_path(sourcePath)
+ os.makedirs(path)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ # Check that if it's not a worktree, it's not enabled.
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_detect_enabled_wrong_branch(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ path = subprojs["compiler-rt"].get_cmake_path(sourcePath)
+ branch = "different-than-" + self.temporaryLLVMbranch
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("compiler-rt"),
+ path,
+ branch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ # Check that if it's a worktree on the wrong branch, it's not enabled.
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_add_invalid_subproject(self):
+ config = LLVMSourceConfig(self.proj, self.temporaryLLVM.repodir)
+ subproj = "not-an-llvm-subproject"
+ subprojPath = self.originalLLVM.repodir # Dummy path
+
+ with self.assertRaises(ValueError) as context:
+ config.update({subproj : Clone(self.proj, subprojPath)})
+
+ self.assertRegexpMatches(str(context.exception),
+ "Unknown llvm subproject %s" % subproj)
+
+ def test_add_each_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ for subproj in subprojs:
+ expectedPath = subprojs[subproj].get_cmake_path(sourcePath)
+ config.update({subproj : self.__get_subproj_repo(subproj)})
+ self.assertTrue(os.path.isdir(expectedPath),
+ "Failed to add subproject %s" % subproj)
+
+ def test_add_all_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ to_add = {}
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs:
+ to_add[subproj] = self.__get_subproj_repo(subproj)
+
+ config.update(to_add)
+
+ for subproj in subprojs:
+ expectedPath = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(expectedPath),
+ "Failed to add subproject %s" % subproj)
+
+ def test_add_some_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ to_add = {}
+ to_add["clang"] = self.__get_subproj_repo("clang")
+ to_add["compiler-rt"] = self.__get_subproj_repo("compiler-rt")
+
+ config.update(to_add)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ self.assertTrue(
+ os.path.isdir(subprojs["clang"].get_cmake_path(sourcePath)),
+ "Failed to add subproject clang")
+ self.assertTrue(
+ os.path.isdir(subprojs["compiler-rt"].get_cmake_path(sourcePath)),
+ "Failed to add subproject compiler-rt")
+
+ def test_add_existing_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lldb"),
+ existingPath,
+ self.temporaryLLVMbranch)
+
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_add_subproject_existing_branch(self):
+ """
+ Test that we can add a subproject that already has the branch that LLVM
+ is on. This can happen for instance if we have added and then removed
+ the subproject and now we're trying to add it again.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ clangRepo = self.__get_subproj_repo("clang")
+ with cd(clangRepo.repodir):
+ # Make sure that the branch that LLVM is on already exists in the
+ # clang repo as well.
+ git("checkout", "-b", self.temporaryLLVMbranch)
+ self.__create_dummy_commit()
+ git("checkout", "master")
+
+ config.update( { "clang" : clangRepo })
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ path = subprojs["clang"].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Failed to add subproject")
+
+ def test_add_subproject_not_a_worktree(self):
+ """
+ Test that we can't update a config to include a subproject that exists
+ but is not a worktree.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+ branch = "different-than-" + self.temporaryLLVMbranch
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ os.makedirs(existingPath)
+
+ with self.assertRaises(EnvironmentError) as context:
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ self.assertRegexpMatches(str(context.exception),
+ "{} already exists but is not a valid subproject directory.*"
+ .format(existingPath))
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_add_subproject_wrong_branch(self):
+ """
+ Test that we can't update a config to include a subproject that exists
+ but is on the wrong branch.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+ branch = "different-than-" + self.temporaryLLVMbranch
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lldb"),
+ existingPath,
+ branch)
+
+ with self.assertRaises(EnvironmentError) as context:
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ self.assertRegexpMatches(str(context.exception),
+ "{} already exists but is not a valid subproject directory.*"
+ .format(existingPath))
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_remove_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ lldPath = subprojs["lld"].get_cmake_path(sourcePath)
+ lldWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lld"),
+ lldPath,
+ self.temporaryLLVMbranch)
+
+ clangPath = subprojs["clang"].get_cmake_path(sourcePath)
+ clangWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("clang"),
+ clangPath,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["lld"])
+
+ self.assertFalse(os.path.isdir(lldPath), "Failed to remove subproject")
+ self.assertTrue(os.path.isdir(clangPath), "Removed sibling subproject")
+
+ def test_remove_some_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["compiler-rt", "lld"])
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_remove_all_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=subprojs.keys())
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ def test_remove_each_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ for subproj in subprojs.keys():
+ config.update(remove=[subproj])
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ def test_remove_duplicates(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["compiler-rt", "lld", "lld", "compiler-rt"])
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_remove_invalid_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subproj = "not-an-llvm-subproject"
+
+ with self.assertRaises(ValueError) as context:
+ config.update(remove=[subproj])
+
+ self.assertRegexpMatches(str(context.exception),
+ "Unknown llvm subproject %s" % subproj)
+
+ def test_remove_inexistent_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ lldPath = subprojs["lld"].get_cmake_path(sourcePath)
+ lldWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lld"),
+ lldPath,
+ self.temporaryLLVMbranch)
+
+ clangPath = subprojs["clang"].get_cmake_path(sourcePath)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["clang"])
+
+ self.assertFalse(
+ os.path.isdir(clangPath),
+ "Failed to remove subproject")
+ self.assertTrue(os.path.isdir(lldPath), "Removed sibling subproject")
+
+ def test_add_after_remove(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ lldbRepo = self.__get_subproj_repo("lldb")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ config.update({"lldb" : lldbRepo})
+ self.assertTrue(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to add lldb")
+
+ config.update(remove=["lldb"])
+ self.assertFalse(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to remove lldb")
+
+ config.update({"lldb" : lldbRepo })
+ self.assertTrue(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to add lldb")
+
+ def test_mixed_adds_removes(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update({
+ "libcxx" : self.__get_subproj_repo("libcxx"),
+ "libcxxabi" : self.__get_subproj_repo("libcxxabi")},
+ ["compiler-rt", "lld"])
+
+ for subproj in ["libcxx", "libcxxabi"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Failed to add subproject")
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_simultaneous_add_remove(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ clangRepo = self.__get_subproj_repo("clang")
+ lldRepo = self.__get_subproj_repo("lld")
+ libunwindRepo = self.__get_subproj_repo("libunwind")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ with self.assertRaises(ValueError) as context:
+ config.update(
+ { "clang" : clangRepo, "lld" : lldRepo, "libunwind" :
+ libunwindRepo}, ["libcxx", "lld", "libcxxabi"])
+
+ self.assertEqual(str(context.exception),
+ "Can't add and remove lld at the same time")
+
+ # Make sure we didn't add any of the others either
+ for subproj in ["clang", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Incorrectly added subproject")
+
+ # TODO: test with a different dictionary than the default one (not
+ # necessarily containing subprojects - it can contain "potato", "banana" and
+ # "gazpacho" for all we care); in fact, it would probably be best to move the
+ # existing tests to that...
+
+ # TODO: test that CMake gets our layout
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unittests/testllvmsubprojects.py b/tests/unittests/testllvmsubprojects.py
new file mode 100644
index 0000000..c39e7ae
--- /dev/null
+++ b/tests/unittests/testllvmsubprojects.py
@@ -0,0 +1,64 @@
+import os
+import unittest
+
+from modules.llvm import LLVMSubproject
+
+
+class TestLLVMSubproject(unittest.TestCase):
+ """Test that we have the right info on LLVM subprojects."""
+
+ def test_build_paths(self):
+ subprojects = LLVMSubproject.get_all_subprojects()
+
+ self.assertEqual(subprojects["clang"].cmake_path,
+ os.path.join("tools", "clang"))
+ self.assertEqual(subprojects["compiler-rt"].cmake_path,
+ os.path.join("projects", "compiler-rt"))
+ self.assertEqual(subprojects["libcxx"].cmake_path,
+ os.path.join("projects", "libcxx"))
+ self.assertEqual(subprojects["libcxxabi"].cmake_path,
+ os.path.join("projects", "libcxxabi"))
+ self.assertEqual(subprojects["libunwind"].cmake_path,
+ os.path.join("projects", "libunwind"))
+ self.assertEqual(subprojects["lld"].cmake_path,
+ os.path.join("tools", "lld"))
+ self.assertEqual(subprojects["lldb"].cmake_path,
+ os.path.join("tools", "lldb"))
+ self.assertEqual(subprojects["test-suite"].cmake_path,
+ os.path.join("projects", "test-suite"))
+
+ def test_cmake_vars(self):
+ subprojects = LLVMSubproject.get_all_subprojects()
+
+ self.assertEqual(
+ subprojects["clang"].cmake_var,
+ "LLVM_TOOL_CLANG_BUILD")
+ self.assertEqual(subprojects["compiler-rt"].cmake_var,
+ "LLVM_TOOL_COMPILER_RT_BUILD")
+ self.assertEqual(
+ subprojects["libcxx"].cmake_var,
+ "LLVM_TOOL_LIBCXX_BUILD")
+ self.assertEqual(subprojects["libcxxabi"].cmake_var,
+ "LLVM_TOOL_LIBCXXABI_BUILD")
+ self.assertEqual(
+ subprojects["libunwind"].cmake_var,
+ "LLVM_TOOL_LIBUNWIND_BUILD")
+ self.assertEqual(subprojects["lld"].cmake_var, "LLVM_TOOL_LLD_BUILD")
+ self.assertEqual(subprojects["lldb"].cmake_var, "LLVM_TOOL_LLDB_BUILD")
+ self.assertEqual(subprojects["test-suite"].cmake_var, None)
+
+ def test_get_cmake_path(self):
+ subproject = LLVMSubproject(
+ os.path.join(
+ "path",
+ "to",
+ "subproj"),
+ None)
+ cmake_path = subproject.get_cmake_path(os.path.join("llvm", "sources"))
+
+ self.assertEqual(
+ str(cmake_path),
+ os.path.join("llvm", "sources", "path", "to", "subproj"))
+
+if __name__ == "__main__":
+ unittest.main()