blob: 2c69fa30e09edd554ace6b1d1eaa8b76d1af633f [file] [log] [blame]
import os
import unittest
import uuid
from sh import git
from unittest.mock import MagicMock, call
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 list(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)
with self.assertRaises(EnvironmentError) as context:
config.get_enabled_subprojects()
self.assertRegex(
str(context.exception),
".*compiler-rt is not a worktree.*"
)
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)
with self.assertRaises(EnvironmentError) as context:
config.get_enabled_subprojects()
self.assertRegex(str(context.exception),
"compiler-rt is on branch {}, but should be on {}".format(branch,
self.temporaryLLVMbranch))
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.assertRegex(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.assertRegex(str(context.exception),
"{} is not a worktree.*"
.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.assertRegex(str(context.exception),
"lldb is on branch {}, but should be on {}.*"
.format(branch, self.temporaryLLVMbranch))
# 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 list(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=list(subprojs.keys()))
for subproj in list(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 list(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 list(subprojs.keys()):
config.update(remove=[subproj])
for subproj in list(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.assertRegex(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
def test_for_each_enabled(self):
sourcePath = self.temporaryLLVM.repodir
config = LLVMSourceConfig(self.proj, sourcePath)
logPath = unittest.mock.MagicMock()
config.for_each_enabled(logPath)
logPath.assert_called_with(self.temporaryLLVM.repodir)
subprojs = config.subprojs
enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
calls = []
for subproj in enabled:
path = subprojs[subproj].get_cmake_path(sourcePath)
worktree = Worktree.create(
self.proj,
self.__get_subproj_repo(subproj),
path,
self.temporaryLLVMbranch)
calls.append(call(path)) # Expect our mock to be called with path
logPath = unittest.mock.MagicMock()
config.for_each_enabled(logPath)
logPath.assert_has_calls(calls, any_order=True)
def test_for_each_enabled_error(self):
"""Test that we rethrow exceptions correctly."""
sourcePath = self.temporaryLLVM.repodir
config = LLVMSourceConfig(self.proj, sourcePath)
subprojs = config.subprojs
enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
for subproj in enabled:
path = subprojs[subproj].get_cmake_path(sourcePath)
worktree = Worktree.create(
self.proj,
self.__get_subproj_repo(subproj),
path,
self.temporaryLLVMbranch)
def throw(projPath):
if "lld" in projPath:
raise ValueError("An error has been!!1")
with self.assertRaises(RuntimeError) as context:
config.for_each_enabled(throw)
self.assertRegex(str(context.exception),
"Error while processing lld(.*\n)*")
self.assertRegex(str(context.exception.__cause__),
"An error has been!!1(.*\n)*")
def test_for_each_subproj(self):
sourcePath = self.temporaryLLVM.repodir
config = LLVMSourceConfig(self.proj, sourcePath)
subprojs = config.subprojs
enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
calls = []
for subproj in enabled:
# Make sure subproj looks enabled
path = subprojs[subproj].get_cmake_path(sourcePath)
worktree = Worktree.create(
self.proj,
self.__get_subproj_repo(subproj),
path,
self.temporaryLLVMbranch)
# Expect our mock to be called with the proper subproj and
# enabled == True
calls.append(call(subprojs[subproj], True))
for subproj in set(subprojs.keys()) - set(enabled):
# Expect our mock to be called with the proper subproj and
# enabled == False
calls.append(call(subprojs[subproj], False))
try:
action = MagicMock()
config.for_each_subproj(action)
except:
print("Exception during test?! ")
action.assert_has_calls(calls, any_order=True)
def test_for_each_subproj_error(self):
"""Test that we rethrow exceptions correctly."""
sourcePath = self.temporaryLLVM.repodir
config = LLVMSourceConfig(self.proj, sourcePath)
subprojs = config.subprojs
enabled = ["clang", "compiler-rt", "lld", "lldb", "libunwind"]
for subproj in enabled:
path = subprojs[subproj].get_cmake_path(sourcePath)
worktree = Worktree.create(
self.proj,
self.__get_subproj_repo(subproj),
path,
self.temporaryLLVMbranch)
def throw_enabled(subproj, enabled):
# Throw for one of the enabled projects (e.g. lld)
if "lld" in subproj.cmake_path:
raise ValueError("An error has been!!1")
def throw_disabled(subproj, enabled):
# Throw for one of the disabled projects (e.g. libcxx)
if "libcxx" in subproj.cmake_path:
raise ValueError("An error has been!!1")
with self.assertRaises(RuntimeError) as context:
config.for_each_subproj(throw_enabled)
self.assertRegex(str(context.exception),
"Error while processing lld(.*\n)*")
self.assertRegex(str(context.exception.__cause__),
"An error has been!!1(.*\n)*")
with self.assertRaises(RuntimeError) as context:
config.for_each_subproj(throw_disabled)
self.assertRegex(str(context.exception),
"Error while processing libcxx(.*\n)*")
self.assertRegex(str(context.exception.__cause__),
"An error has been!!1(.*\n)*")
if __name__ == "__main__":
unittest.main()