blob: 8374affeb58b8aff5fa5cfba61cd94f3d9c2fc27 [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):
Diana Picus3d1a3012017-03-14 17:38:32 +0100102 cls.env = "src" + str(uuid4())
103 cls.llvm_src = os.path.join(cls.llvm_root, cls.env, "llvm")
Diana Picus3b2ef822016-10-13 16:53:18 +0300104
105 # Create LLVM worktree
106 cls.branch = "br" + str(uuid4())
107 cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src,
108 cls.branch)
109
110 # Set up the environment variables
111 os.environ["LLVM_ROOT"] = cls.llvm_root
Diana Picus3b2ef822016-10-13 16:53:18 +0300112
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 Picus3d1a3012017-03-14 17:38:32 +0100136 output = self.run_with_output(
137 [self.python, self.script, self.env, "projects"])
Diana Picusb4307602017-04-05 19:48:39 +0200138 self.assertRegex(output, "Projects linked:.*\n.*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300139
140 def test_add_remove_subprojects(self):
141 """
142 Test that we can add and remove one or several subprojects.
143 """
Diana Picus3d1a3012017-03-14 17:38:32 +0100144 output = self.run_with_output(
145 [self.python, self.script, self.env, "projects", "--add", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200146 self.assertRegex(output, "Projects linked:.*\n.*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300147
Diana Picus3d1a3012017-03-14 17:38:32 +0100148 output = self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300149 "--add", "libcxx", "lldb"])
Diana Picusb4307602017-04-05 19:48:39 +0200150 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300151 output,
152 "Projects linked:.*\n" +
153 ".*clang.*\n" +
154 ".*libcxx.*\n" +
155 ".*lldb.*")
156
Diana Picus3d1a3012017-03-14 17:38:32 +0100157 output = self.run_with_output(
158 [self.python, self.script, self.env, "projects", "--remove", "libcxx"])
Diana Picusb4307602017-04-05 19:48:39 +0200159 self.assertRegex(output,
160 "Projects linked:.*\n" +
161 ".*clang.*\n" +
162 ".*lldb.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300163
Diana Picus3d1a3012017-03-14 17:38:32 +0100164 output = self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300165 "--remove", "clang", "lldb"])
Diana Picusb4307602017-04-05 19:48:39 +0200166 self.assertRegex(output,
167 "Projects linked:.*\n" +
168 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300169
170 def test_add_remove_invalid_subprojects(self):
171 """
172 Test that we error out nicely when trying to add/remove invalid
173 subprojects.
174 """
175 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus3d1a3012017-03-14 17:38:32 +0100176 self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picusb4307602017-04-05 19:48:39 +0200177 "--add", "inventedsubproject"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300178
Diana Picusb4307602017-04-05 19:48:39 +0200179 self.assertRegex(
180 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300181 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
182
183 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus3d1a3012017-03-14 17:38:32 +0100184 self.run_with_output([self.python,
185 self.script,
186 self.env,
187 "projects",
188 "--remove",
189 "inventedsubproject"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300190
Diana Picusb4307602017-04-05 19:48:39 +0200191 self.assertRegex(
192 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300193 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
194
195 def test_duplicate_add_remove(self):
196 """
197 Test that we don't crash when trying to add / remove the same subproject
198 twice with the same command.
199 """
Diana Picus3d1a3012017-03-14 17:38:32 +0100200 output = self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300201 "--add", "clang", "lld", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200202 self.assertRegex(output,
203 "Projects linked:.*\n" +
204 ".*clang.*\n" +
205 ".*lld.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300206
Diana Picus3d1a3012017-03-14 17:38:32 +0100207 output = self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300208 "--remove", "lld", "lld", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200209 self.assertRegex(output,
210 "Projects linked:.*\n" +
211 ".*none.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300212
213 def test_redundant_add_remove(self):
214 """
215 Test that we can add a subproject that already exists (either because it
216 was added by our script or manually) or remove a subproject that doesn't
217 exist.
218 """
219 self.__add_worktree(self.__get_subproj_repo("clang"),
220 os.path.join(self.llvm_src, "tools", "clang"),
221 self.branch)
222 self.__add_worktree(
223 self.__get_subproj_repo("compiler-rt"),
224 os.path.join(self.llvm_src, "projects", "compiler-rt"),
225 self.branch)
226
Diana Picus3d1a3012017-03-14 17:38:32 +0100227 output = self.run_with_output(
228 [self.python, self.script, self.env, "projects", "--add", "clang"])
Diana Picusb4307602017-04-05 19:48:39 +0200229 self.assertRegex(output,
230 "Projects linked:.*\n" +
231 ".*clang.*")
Diana Picus3b2ef822016-10-13 16:53:18 +0300232
Diana Picus3d1a3012017-03-14 17:38:32 +0100233 output = self.run_with_output([self.python, self.script, self.env, "projects",
Diana Picus3b2ef822016-10-13 16:53:18 +0300234 "--add", "compiler-rt", "lld"])
Diana Picusb4307602017-04-05 19:48:39 +0200235 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300236 output,
237 "Projects linked:.*\n" +
238 ".*clang.*\n" +
239 ".*compiler-rt.*\n" +
240 ".*lld.*")
241
Diana Picus3d1a3012017-03-14 17:38:32 +0100242 output = self.run_with_output([self.python,
243 self.script,
244 self.env,
245 "projects",
246 "--remove",
247 "lldb",
248 "libcxx",
249 "lld"])
Diana Picusb4307602017-04-05 19:48:39 +0200250 self.assertRegex(
Diana Picus3b2ef822016-10-13 16:53:18 +0300251 output,
252 "Projects linked:.*\n" +
253 ".*clang.*\n" +
254 ".*compiler-rt.*")
255
256 def test_simultaneous_add_remove(self):
257 """
258 Test that we error out when someone is trying to add and remove the same
259 project with the same command.
260 """
261 # Try the really basic case and make sure we're not touching anything
262 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus3d1a3012017-03-14 17:38:32 +0100263 self.run_with_output([self.python,
264 self.script,
265 self.env,
266 "projects",
267 "--add",
268 "clang",
269 "--remove",
270 "clang"])
Diana Picus3b2ef822016-10-13 16:53:18 +0300271
Diana Picusb4307602017-04-05 19:48:39 +0200272 self.assertRegex(
273 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300274 "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*")
275
276 self.assertFalse(
277 os.path.exists(
278 os.path.join(self.llvm_src, "tools", "clang")))
279
280 # Try something a bit more complicated and make sure we're not touching
281 # anything
282 self.__add_worktree(
283 self.__get_subproj_repo("lld"),
284 os.path.join(self.llvm_src, "tools", "lld"),
285 self.branch)
286
287 with self.assertRaises(subprocess.CalledProcessError) as context:
Diana Picus3d1a3012017-03-14 17:38:32 +0100288 self.run_with_output([self.python,
289 self.script,
290 self.env,
291 "projects",
292 "--add",
293 "clang",
294 "lld",
295 "libcxx",
296 "--remove",
297 "lld",
298 "libcxx"])
Diana Picusb4307602017-04-05 19:48:39 +0200299 self.assertRegex(
300 str(context.exception.output),
Diana Picus3b2ef822016-10-13 16:53:18 +0300301 "(.*\n)*" +
302 ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*")
303
304 # Make sure we didn't touch anything
305 self.assertFalse(
306 os.path.exists(
307 os.path.join(self.llvm_src, "tools", "clang")))
308 self.assertTrue(
309 os.path.exists(
310 os.path.join(self.llvm_src, "tools", "lld")))
311 self.assertFalse(
312 os.path.exists(
313 os.path.join(self.llvm_src, "projects", "libcxx")))
314
315 def test_multiple_adds_removes(self):
316 """
317 Test that we can have multiple --add and --remove options in the same
318 command and that only the last one of each kind matters.
319 """
Diana Picus3d1a3012017-03-14 17:38:32 +0100320 output = self.run_with_output([self.python,
321 self.script,
322 self.env,
323 "projects",
324 "--add",
325 "libcxxabi",
326 "--remove",
327 "lld",
328 "lldb",
329 "--add",
330 "clang",
331 "libcxx",
332 "--remove",
333 "libunwind"])
Diana Picusb4307602017-04-05 19:48:39 +0200334 self.assertRegex(output,
335 "Projects linked:.*\n" +
336 ".*clang.*\n" +
337 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300338
Diana Picus3d1a3012017-03-14 17:38:32 +0100339 output = self.run_with_output([self.python,
340 self.script,
341 self.env,
342 "projects",
343 "--add",
344 "libunwind",
345 "libcxxabi",
346 "--remove",
347 "clang",
348 "libcxx",
349 "--add",
350 "compiler-rt",
351 "--remove",
352 "libcxxabi"])
Diana Picusb4307602017-04-05 19:48:39 +0200353 self.assertRegex(output,
354 "Projects linked:.*\n" +
355 ".*clang.*\n" +
356 ".*compiler-rt.*\n" +
357 ".*libcxx.*\n")
Diana Picus3b2ef822016-10-13 16:53:18 +0300358
Diana Picus3d1a3012017-03-14 17:38:32 +0100359 output = self.run_with_output([self.python,
360 self.script,
361 self.env,
362 "projects",
363 "--add",
364 "libcxx",
365 "--remove",
366 "lld",
367 "--add",
368 "lld",
369 "--remove",
370 "libcxx"])
Diana Picusb4307602017-04-05 19:48:39 +0200371 self.assertRegex(output,
372 "Projects linked:.*\n" +
373 ".*clang.*\n" +
374 ".*compiler-rt.*\n" +
375 ".*lld.*\n")