blob: c0c1f260ce647081ebba40c8b7c59d432c370936 [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
Diana Picus81089db2017-05-05 22:26:49 +020084 def llvm_projects(cls, *args, **kwargs):
85 """
86 Build a list representing a llvm projects subcommand with the given
87 args. Unless otherwise specified in kwargs, this uses the values for
88 repos and env that it finds in cls.
89 """
90 command = [cls.python, cls.script]
91
92 repos = cls.repos
93 if "repos" in kwargs:
94 repos = kwargs["repos"]
95 if repos:
96 command.append("--repos")
97 command.append(repos)
98
99 env = cls.env
100 if "env" in kwargs:
101 env = kwargs["env"]
102 if env:
103 command.append("--env")
104 command.append(env)
105
106 command.append("projects")
107
108 if len(args):
109 command.extend(args)
110
111 return command
112
113 @classmethod
Diana Picus3b2ef822016-10-13 16:53:18 +0300114 def setUpClass(cls):
115 """Create the file structure and environment that llvmprojs expects"""
116 cls.llvm_root = mkdtemp()
117 cls.repos = os.path.join(cls.llvm_root, "repos")
118
119 cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb",
120 "libcxx", "libcxxabi", "libunwind", "test-suite")
121
Diana Picus3b2ef822016-10-13 16:53:18 +0300122 # Create dummy repos
123 for reponame in cls.all_repos:
124 cls.__create_dummy_repo(cls.__get_subproj_repo(reponame))
125
126 @classmethod
127 def tearDownClass(cls):
128 shutil.rmtree(cls.llvm_root)
129
130 @classmethod
131 def setUp(cls):
Diana Picus81089db2017-05-05 22:26:49 +0200132 cls.env = os.path.join(cls.llvm_root, "env" + str(uuid4()))
133 cls.llvm_src = os.path.join(cls.env, "llvm")
Diana Picus3b2ef822016-10-13 16:53:18 +0300134
135 # Create LLVM worktree
136 cls.branch = "br" + str(uuid4())
137 cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src,
138 cls.branch)
139
Diana Picus3b2ef822016-10-13 16:53:18 +0300140 @classmethod
141 def tearDown(cls):
142 # Clean up the directories where we might have added subprojects.
143 # This isn't 100% clean, because we don't clean up the repos between
144 # tests (so any branches will remain), but it's good enough for the
145 # current tests.
146 for subprojdir in (os.path.join(cls.llvm_src, "projects"),
147 os.path.join(cls.llvm_src, "tools")):
148 if os.path.isdir(subprojdir):
149 shutil.rmtree(subprojdir)
150 os.makedirs(subprojdir)
151
152 # Run prune on the original repos, to remove any dangling worktrees.
153 for reponame in cls.all_repos:
154 repopath = cls.__get_subproj_repo(reponame)
155 with cd(repopath):
156 cls.run_quietly(["git", "worktree", "prune"])
157
Diana Picus81089db2017-05-05 22:26:49 +0200158 def test_repos_arg_is_compulsory(self):
159 """
160 Test that we must pass in the repos for various combinations of input
161 args.
162 """
163 with self.assertRaises(subprocess.CalledProcessError) as context:
164 self.run_with_output(self.llvm_projects(repos=None))
165
166 self.assertRegex(
167 str(context.exception.output),
168 "(.*\n)*.*the following arguments are required:(.*)--repos(.*\n)*")
169
170 with self.assertRaises(subprocess.CalledProcessError) as context:
171 self.run_with_output(
172 self.llvm_projects(
173 "--add", "clang", repos=None))
174
175 self.assertRegex(
176 str(context.exception.output),
177 "(.*\n)*.*the following arguments are required:(.*)--repos(.*\n)*")
178
179 with self.assertRaises(subprocess.CalledProcessError) as context:
180 self.run_with_output(
181 self.llvm_projects(
182 "--remove",
183 "clang",
184 repos=None))
185
186 self.assertRegex(
187 str(context.exception.output),
188 "(.*\n)*.*the following arguments are required:(.*)--repos(.*\n)*")
189
190 def test_env_arg_is_compulsory(self):
191 """
192 Test that we must pass in the environment for various combinations of
193 input args.
194 """
195 with self.assertRaises(subprocess.CalledProcessError) as context:
196 self.run_with_output(self.llvm_projects(env=None))
197
198 self.assertRegex(
199 str(context.exception.output),
200 "(.*\n)*.*the following arguments are required:(.*)--env(.*\n)*")
201
202 with self.assertRaises(subprocess.CalledProcessError) as context:
203 self.run_with_output(
204 self.llvm_projects(
205 "--add", "clang", env=None))
206
207 self.assertRegex(
208 str(context.exception.output),
209 "(.*\n)*.*the following arguments are required:(.*)--env(.*\n)*")
210
211 with self.assertRaises(subprocess.CalledProcessError) as context:
212 self.run_with_output(
213 self.llvm_projects(
214 "--remove", "clang", env=None))
215
216 self.assertRegex(
217 str(context.exception.output),
218 "(.*\n)*.*the following arguments are required:(.*)--env(.*\n)*")
219
Diana Picus3b2ef822016-10-13 16:53:18 +0300220 def test_dump_empty_config(self):
221 """
222 Test that we're correctly dumping an empty configuration (i.e. no
223 projects linked) when running llvmprojs without arguments.
224 """
Diana Picus81089db2017-05-05 22:26:49 +0200225 output = self.run_with_output(self.llvm_projects())
Diana Picusb4307602017-04-05 19:48:39 +0200226 self.assertRegex(output, "Projects linked:.*\n.*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300227
228 def test_add_remove_subprojects(self):
229 """
230 Test that we can add and remove one or several subprojects.
231 """
Diana Picus81089db2017-05-05 22:26:49 +0200232 output = self.run_with_output(self.llvm_projects("--add", "clang"))
Diana Picusb4307602017-04-05 19:48:39 +0200233 self.assertRegex(output, "Projects linked:.*\n.*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300234
Diana Picus81089db2017-05-05 22:26:49 +0200235 output = self.run_with_output(
236 self.llvm_projects(
237 "--add", "libcxx", "lldb"))
Diana Picusb4307602017-04-05 19:48:39 +0200238 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300239 output,
240 "Projects linked:.*\n" +
241 ".*clang.*\n" +
242 ".*libcxx.*\n" +
243 ".*lldb.*")
244
Diana Picus81089db2017-05-05 22:26:49 +0200245 output = self.run_with_output(self.llvm_projects("--remove", "libcxx"))
Diana Picusb4307602017-04-05 19:48:39 +0200246 self.assertRegex(output,
247 "Projects linked:.*\n" +
248 ".*clang.*\n" +
249 ".*lldb.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300250
Diana Picus81089db2017-05-05 22:26:49 +0200251 output = self.run_with_output(
252 self.llvm_projects(
253 "--remove", "clang", "lldb"))
Diana Picusb4307602017-04-05 19:48:39 +0200254 self.assertRegex(output,
255 "Projects linked:.*\n" +
256 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300257
258 def test_add_remove_invalid_subprojects(self):
259 """
260 Test that we error out nicely when trying to add/remove invalid
261 subprojects.
262 """
263 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus81089db2017-05-05 22:26:49 +0200264 self.run_with_output(
265 self.llvm_projects(
266 "--add", "inventedsubproject"))
Diana Picus3b2ef822016-10-13 16:53:18 +0300267
Diana Picusb4307602017-04-05 19:48:39 +0200268 self.assertRegex(
269 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300270 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
271
272 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus81089db2017-05-05 22:26:49 +0200273 self.run_with_output(
274 self.llvm_projects(
275 "--remove",
276 "inventedsubproject"))
Diana Picus3b2ef822016-10-13 16:53:18 +0300277
Diana Picusb4307602017-04-05 19:48:39 +0200278 self.assertRegex(
279 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300280 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
281
282 def test_duplicate_add_remove(self):
283 """
284 Test that we don't crash when trying to add / remove the same subproject
285 twice with the same command.
286 """
Diana Picus81089db2017-05-05 22:26:49 +0200287 output = self.run_with_output(
288 self.llvm_projects(
289 "--add", "clang", "lld", "clang"))
Diana Picusb4307602017-04-05 19:48:39 +0200290 self.assertRegex(output,
291 "Projects linked:.*\n" +
292 ".*clang.*\n" +
293 ".*lld.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300294
Diana Picus81089db2017-05-05 22:26:49 +0200295 output = self.run_with_output(
296 self.llvm_projects(
297 "--remove", "lld", "lld", "clang"))
Diana Picusb4307602017-04-05 19:48:39 +0200298 self.assertRegex(output,
299 "Projects linked:.*\n" +
300 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300301
302 def test_redundant_add_remove(self):
303 """
304 Test that we can add a subproject that already exists (either because it
305 was added by our script or manually) or remove a subproject that doesn't
306 exist.
307 """
308 self.__add_worktree(self.__get_subproj_repo("clang"),
309 os.path.join(self.llvm_src, "tools", "clang"),
310 self.branch)
311 self.__add_worktree(
312 self.__get_subproj_repo("compiler-rt"),
313 os.path.join(self.llvm_src, "projects", "compiler-rt"),
314 self.branch)
315
Diana Picus81089db2017-05-05 22:26:49 +0200316 output = self.run_with_output(self.llvm_projects("--add", "clang"))
Diana Picusb4307602017-04-05 19:48:39 +0200317 self.assertRegex(output,
318 "Projects linked:.*\n" +
319 ".*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300320
Diana Picus81089db2017-05-05 22:26:49 +0200321 output = self.run_with_output(
322 self.llvm_projects(
323 "--add", "compiler-rt", "lld"))
Diana Picusb4307602017-04-05 19:48:39 +0200324 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300325 output,
326 "Projects linked:.*\n" +
327 ".*clang.*\n" +
328 ".*compiler-rt.*\n" +
329 ".*lld.*")
330
Diana Picus81089db2017-05-05 22:26:49 +0200331 output = self.run_with_output(
332 self.llvm_projects(
333 "--remove", "lldb", "libcxx", "lld"))
Diana Picusb4307602017-04-05 19:48:39 +0200334 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300335 output,
336 "Projects linked:.*\n" +
337 ".*clang.*\n" +
338 ".*compiler-rt.*")
339
340 def test_simultaneous_add_remove(self):
341 """
342 Test that we error out when someone is trying to add and remove the same
343 project with the same command.
344 """
345 # Try the really basic case and make sure we're not touching anything
346 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus81089db2017-05-05 22:26:49 +0200347 self.run_with_output(
348 self.llvm_projects(
349 "--add",
350 "clang",
351 "--remove",
352 "clang"))
Diana Picus3b2ef822016-10-13 16:53:18 +0300353
Diana Picusb4307602017-04-05 19:48:39 +0200354 self.assertRegex(
355 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300356 "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*")
357
358 self.assertFalse(
359 os.path.exists(
360 os.path.join(self.llvm_src, "tools", "clang")))
361
362 # Try something a bit more complicated and make sure we're not touching
363 # anything
364 self.__add_worktree(
365 self.__get_subproj_repo("lld"),
366 os.path.join(self.llvm_src, "tools", "lld"),
367 self.branch)
368
369 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus81089db2017-05-05 22:26:49 +0200370 self.run_with_output(
371 self.llvm_projects(
372 "--add",
373 "clang",
374 "lld",
375 "libcxx",
376 "--remove",
377 "lld",
378 "libcxx"))
Diana Picusb4307602017-04-05 19:48:39 +0200379 self.assertRegex(
380 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300381 "(.*\n)*" +
382 ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*")
383
384 # Make sure we didn't touch anything
385 self.assertFalse(
386 os.path.exists(
387 os.path.join(self.llvm_src, "tools", "clang")))
388 self.assertTrue(
389 os.path.exists(
390 os.path.join(self.llvm_src, "tools", "lld")))
391 self.assertFalse(
392 os.path.exists(
393 os.path.join(self.llvm_src, "projects", "libcxx")))
394
395 def test_multiple_adds_removes(self):
396 """
397 Test that we can have multiple --add and --remove options in the same
398 command and that only the last one of each kind matters.
399 """
Diana Picus81089db2017-05-05 22:26:49 +0200400 output = self.run_with_output(
401 self.llvm_projects(
402 "--add",
403 "libcxxabi",
404 "--remove",
405 "lld",
406 "lldb",
407 "--add",
408 "clang",
409 "libcxx",
410 "--remove",
411 "libunwind"))
Diana Picusb4307602017-04-05 19:48:39 +0200412 self.assertRegex(output,
413 "Projects linked:.*\n" +
414 ".*clang.*\n" +
415 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300416
Diana Picus81089db2017-05-05 22:26:49 +0200417 output = self.run_with_output(
418 self.llvm_projects(
419 "--add",
420 "libunwind",
421 "libcxxabi",
422 "--remove",
423 "clang",
424 "libcxx",
425 "--add",
426 "compiler-rt",
427 "--remove",
428 "libcxxabi"))
Diana Picusb4307602017-04-05 19:48:39 +0200429 self.assertRegex(output,
430 "Projects linked:.*\n" +
431 ".*clang.*\n" +
432 ".*compiler-rt.*\n" +
433 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300434
Diana Picus81089db2017-05-05 22:26:49 +0200435 output = self.run_with_output(
436 self.llvm_projects(
437 "--add",
438 "libcxx",
439 "--remove",
440 "lld",
441 "--add",
442 "lld",
443 "--remove",
444 "libcxx"))
Diana Picusb4307602017-04-05 19:48:39 +0200445 self.assertRegex(output,
446 "Projects linked:.*\n" +
447 ".*clang.*\n" +
448 ".*compiler-rt.*\n" +
449 ".*lld.*\n")
Diana Picus81089db2017-05-05 22:26:49 +0200450
451 def test_different_env(self):
452 """
453 Test that we can have different environments in completely different
454 paths and they don't interfere when we try to add/remove projects.
455 """
456 # Create a separate environment
457 new_env = mkdtemp()
458 new_branch = "br" + str(uuid4())
459 self.__add_worktree(self.__get_subproj_repo("llvm"),
460 os.path.join(new_env, "llvm"), new_branch)
461
462 # Check that we start with a clean slate in both the new environment and
463 # the one that's already set up
464 output = self.run_with_output(self.llvm_projects())
465 self.assertRegex(output, "Projects linked:.*\n.*none.*")
466
467 output = self.run_with_output(self.llvm_projects(env=new_env))
468 self.assertRegex(output, "Projects linked:.*\n.*none.*")
469
470 # Make sure that adding projects works
471 output = self.run_with_output(
472 self.llvm_projects(
473 "--add", "clang", "lld"))
474 self.assertRegex(output, "Projects linked:.*\n.*clang.*\n.*lld.*\n")
475
476 output = self.run_with_output(
477 self.llvm_projects(
478 "--add",
479 "libcxx",
480 "libcxxabi",
481 env=new_env))
482 self.assertRegex(output,
483 "Projects linked:.*\n.*libcxx.*\n.*libcxxabi.*\n.*")
484
485 output = self.run_with_output(self.llvm_projects())
486 self.assertRegex(output, "Projects linked:.*\n.*clang.*\n.*lld.*\n")
487
488 # Make sure that removing projects works
489 output = self.run_with_output(self.llvm_projects("--remove", "lld"))
490 self.assertRegex(output, "Projects linked:.*\n.*clang.*\n")
491
492 output = self.run_with_output(self.llvm_projects("--remove", "libcxx",
493 env=new_env))
494 self.assertRegex(output,
495 "Projects linked:.*\n.*libcxxabi.*\n.*")
496
497 output = self.run_with_output(self.llvm_projects())
498 self.assertRegex(output, "Projects linked:.*\n.*clang.*\n")
499
500 shutil.rmtree(new_env)