blob: e8bb8603793c8199552b6d1e3a19781b3bcba0b6 [file] [log] [blame]
Diana Picus3b2ef822016-10-13 16:53:18 +03001"""Command line interface tests for llvmprojs.py
2
3Note that although this uses the unittest framework, it does *not* contain unit
4tests.
5
6"""
7
8import shutil
9import os
10import subprocess
11import unittest
12
Diana Picus3b2ef822016-10-13 16:53:18 +030013from tempfile import mkdtemp
14from uuid import uuid4
15
16from linaropy.cd import cd
17
18
19# TODO: move this somewhere more public (maybe linaropy?)
20def debug(test):
21 """
22 Decorator that dumps the output of any subprocess.CalledProcessError
23 exception. Use this to decorate a test function when you can't tell what the
24 problem is.
25 """
26 def wrapper(*args, **kwargs):
27 # Catch any exceptions so we can dump all the output
28 try:
29 test(*args, **kwargs)
30 except subprocess.CalledProcessError as exc:
Diana Picusb4307602017-04-05 19:48:39 +020031 print("Error in {}:".format(test.__name__))
32 print("Command {} exited with error code {}:\n{}".format(
33 exc.cmd, exc.returncode, exc.output))
Diana Picus3b2ef822016-10-13 16:53:18 +030034 return wrapper
35
36
37class Testllvmprojs(unittest.TestCase):
Diana Picusb4307602017-04-05 19:48:39 +020038 python = "python3"
Diana Picus3b2ef822016-10-13 16:53:18 +030039 script = os.path.join("scripts", "llvm.py")
40
41 @classmethod
42 def __create_dummy_commit(cls):
43 filename = "filethatshouldntexist"
44 cls.run_quietly(["touch", filename])
45 cls.run_quietly(["git", "add", filename])
46 cls.run_quietly(["git", "commit", "-m", "Dummy commit"])
47
48 @classmethod
49 def __create_dummy_repo(cls, repopath):
50 if not os.path.isdir(repopath):
51 os.makedirs(repopath)
52
53 with cd(repopath):
54 cls.run_quietly(["git", "init"])
55 cls.__create_dummy_commit()
56
57 @classmethod
58 def __add_worktree(cls, repopath, worktreepath, branch):
59 with cd(repopath):
60 cls.run_quietly(["git", "worktree", "add", worktreepath,
61 "-b", branch])
62
63 @classmethod
64 def __get_subproj_repo(cls, subproj):
65 return os.path.join(cls.repos, subproj)
66
Diana Picusb4307602017-04-05 19:48:39 +020067 @staticmethod
68 def run_with_output(*args, **kwargs):
69 """Helper for running a command and capturing stdout and stderr"""
70 kwargs["stderr"] = subprocess.STDOUT
71 return str(subprocess.check_output(*args, **kwargs), 'utf-8')
72
73 @staticmethod
74 def run_quietly(*args, **kwargs):
75 """
76 Helper for running a command and ignoring stdout and stderr. Exceptions
77 are still thrown if something goes wrong
78 """
79 kwargs["stdout"] = subprocess.DEVNULL
80 kwargs["stderr"] = subprocess.DEVNULL
81 return subprocess.check_call(*args, **kwargs)
82
Diana Picus3b2ef822016-10-13 16:53:18 +030083 @classmethod
84 def setUpClass(cls):
85 """Create the file structure and environment that llvmprojs expects"""
86 cls.llvm_root = mkdtemp()
87 cls.repos = os.path.join(cls.llvm_root, "repos")
88
89 cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb",
90 "libcxx", "libcxxabi", "libunwind", "test-suite")
91
Diana Picus3b2ef822016-10-13 16:53:18 +030092 # Create dummy repos
93 for reponame in cls.all_repos:
94 cls.__create_dummy_repo(cls.__get_subproj_repo(reponame))
95
96 @classmethod
97 def tearDownClass(cls):
98 shutil.rmtree(cls.llvm_root)
99
100 @classmethod
101 def setUp(cls):
102 cls.llvm_src = os.path.join(cls.llvm_root, "src" + str(uuid4()))
103
104 # Create LLVM worktree
105 cls.branch = "br" + str(uuid4())
106 cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src,
107 cls.branch)
108
109 # Set up the environment variables
110 os.environ["LLVM_ROOT"] = cls.llvm_root
111 os.environ["LLVM_SRC"] = cls.llvm_src
112
113 @classmethod
114 def tearDown(cls):
115 # Clean up the directories where we might have added subprojects.
116 # This isn't 100% clean, because we don't clean up the repos between
117 # tests (so any branches will remain), but it's good enough for the
118 # current tests.
119 for subprojdir in (os.path.join(cls.llvm_src, "projects"),
120 os.path.join(cls.llvm_src, "tools")):
121 if os.path.isdir(subprojdir):
122 shutil.rmtree(subprojdir)
123 os.makedirs(subprojdir)
124
125 # Run prune on the original repos, to remove any dangling worktrees.
126 for reponame in cls.all_repos:
127 repopath = cls.__get_subproj_repo(reponame)
128 with cd(repopath):
129 cls.run_quietly(["git", "worktree", "prune"])
130
131 def test_dump_empty_config(self):
132 """
133 Test that we're correctly dumping an empty configuration (i.e. no
134 projects linked) when running llvmprojs without arguments.
135 """
Diana Picusb4307602017-04-05 19:48:39 +0200136 output = self.run_with_output([self.python, self.script, "projects"])
137 self.assertRegex(output, "Projects linked:.*\n.*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300138
139 def test_add_remove_subprojects(self):
140 """
141 Test that we can add and remove one or several subprojects.
142 """
Diana Picusb4307602017-04-05 19:48:39 +0200143 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300144 "--add", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200145 self.assertRegex(output, "Projects linked:.*\n.*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300146
Diana Picusb4307602017-04-05 19:48:39 +0200147 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300148 "--add", "libcxx", "lldb"])
Diana Picusb4307602017-04-05 19:48:39 +0200149 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300150 output,
151 "Projects linked:.*\n" +
152 ".*clang.*\n" +
153 ".*libcxx.*\n" +
154 ".*lldb.*")
155
Diana Picusb4307602017-04-05 19:48:39 +0200156 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300157 "--remove", "libcxx"])
Diana Picusb4307602017-04-05 19:48:39 +0200158 self.assertRegex(output,
159 "Projects linked:.*\n" +
160 ".*clang.*\n" +
161 ".*lldb.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300162
Diana Picusb4307602017-04-05 19:48:39 +0200163 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300164 "--remove", "clang", "lldb"])
Diana Picusb4307602017-04-05 19:48:39 +0200165 self.assertRegex(output,
166 "Projects linked:.*\n" +
167 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300168
169 def test_add_remove_invalid_subprojects(self):
170 """
171 Test that we error out nicely when trying to add/remove invalid
172 subprojects.
173 """
174 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picusb4307602017-04-05 19:48:39 +0200175 self.run_with_output([self.python, self.script, "projects",
176 "--add", "inventedsubproject"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300177
Diana Picusb4307602017-04-05 19:48:39 +0200178 self.assertRegex(
179 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300180 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
181
182 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picusb4307602017-04-05 19:48:39 +0200183 self.run_with_output([self.python, self.script, "projects",
184 "--remove", "inventedsubproject"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300185
Diana Picusb4307602017-04-05 19:48:39 +0200186 self.assertRegex(
187 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300188 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
189
190 def test_duplicate_add_remove(self):
191 """
192 Test that we don't crash when trying to add / remove the same subproject
193 twice with the same command.
194 """
Diana Picusb4307602017-04-05 19:48:39 +0200195 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300196 "--add", "clang", "lld", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200197 self.assertRegex(output,
198 "Projects linked:.*\n" +
199 ".*clang.*\n" +
200 ".*lld.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300201
Diana Picusb4307602017-04-05 19:48:39 +0200202 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300203 "--remove", "lld", "lld", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200204 self.assertRegex(output,
205 "Projects linked:.*\n" +
206 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300207
208 def test_redundant_add_remove(self):
209 """
210 Test that we can add a subproject that already exists (either because it
211 was added by our script or manually) or remove a subproject that doesn't
212 exist.
213 """
214 self.__add_worktree(self.__get_subproj_repo("clang"),
215 os.path.join(self.llvm_src, "tools", "clang"),
216 self.branch)
217 self.__add_worktree(
218 self.__get_subproj_repo("compiler-rt"),
219 os.path.join(self.llvm_src, "projects", "compiler-rt"),
220 self.branch)
221
Diana Picusb4307602017-04-05 19:48:39 +0200222 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300223 "--add", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200224 self.assertRegex(output,
225 "Projects linked:.*\n" +
226 ".*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300227
Diana Picusb4307602017-04-05 19:48:39 +0200228 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300229 "--add", "compiler-rt", "lld"])
Diana Picusb4307602017-04-05 19:48:39 +0200230 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300231 output,
232 "Projects linked:.*\n" +
233 ".*clang.*\n" +
234 ".*compiler-rt.*\n" +
235 ".*lld.*")
236
Diana Picusb4307602017-04-05 19:48:39 +0200237 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300238 "--remove", "lldb", "libcxx", "lld"])
Diana Picusb4307602017-04-05 19:48:39 +0200239 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300240 output,
241 "Projects linked:.*\n" +
242 ".*clang.*\n" +
243 ".*compiler-rt.*")
244
245 def test_simultaneous_add_remove(self):
246 """
247 Test that we error out when someone is trying to add and remove the same
248 project with the same command.
249 """
250 # Try the really basic case and make sure we're not touching anything
251 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picusb4307602017-04-05 19:48:39 +0200252 self.run_with_output([self.python, self.script, "projects",
253 "--add", "clang", "--remove", "clang"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300254
Diana Picusb4307602017-04-05 19:48:39 +0200255 self.assertRegex(
256 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300257 "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*")
258
259 self.assertFalse(
260 os.path.exists(
261 os.path.join(self.llvm_src, "tools", "clang")))
262
263 # Try something a bit more complicated and make sure we're not touching
264 # anything
265 self.__add_worktree(
266 self.__get_subproj_repo("lld"),
267 os.path.join(self.llvm_src, "tools", "lld"),
268 self.branch)
269
270 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picusb4307602017-04-05 19:48:39 +0200271 self.run_with_output([self.python, self.script, "projects",
272 "--add", "clang", "lld", "libcxx",
273 "--remove", "lld", "libcxx"])
274 self.assertRegex(
275 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300276 "(.*\n)*" +
277 ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*")
278
279 # Make sure we didn't touch anything
280 self.assertFalse(
281 os.path.exists(
282 os.path.join(self.llvm_src, "tools", "clang")))
283 self.assertTrue(
284 os.path.exists(
285 os.path.join(self.llvm_src, "tools", "lld")))
286 self.assertFalse(
287 os.path.exists(
288 os.path.join(self.llvm_src, "projects", "libcxx")))
289
290 def test_multiple_adds_removes(self):
291 """
292 Test that we can have multiple --add and --remove options in the same
293 command and that only the last one of each kind matters.
294 """
Diana Picusb4307602017-04-05 19:48:39 +0200295 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300296 "--add", "libcxxabi",
297 "--remove", "lld", "lldb",
298 "--add", "clang", "libcxx",
299 "--remove", "libunwind"])
Diana Picusb4307602017-04-05 19:48:39 +0200300 self.assertRegex(output,
301 "Projects linked:.*\n" +
302 ".*clang.*\n" +
303 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300304
Diana Picusb4307602017-04-05 19:48:39 +0200305 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300306 "--add", "libunwind", "libcxxabi",
307 "--remove", "clang", "libcxx",
308 "--add", "compiler-rt",
309 "--remove", "libcxxabi"])
Diana Picusb4307602017-04-05 19:48:39 +0200310 self.assertRegex(output,
311 "Projects linked:.*\n" +
312 ".*clang.*\n" +
313 ".*compiler-rt.*\n" +
314 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300315
Diana Picusb4307602017-04-05 19:48:39 +0200316 output = self.run_with_output([self.python, self.script, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300317 "--add", "libcxx", "--remove", "lld",
318 "--add", "lld", "--remove", "libcxx"])
Diana Picusb4307602017-04-05 19:48:39 +0200319 self.assertRegex(output,
320 "Projects linked:.*\n" +
321 ".*clang.*\n" +
322 ".*compiler-rt.*\n" +
323 ".*lld.*\n")