blob: c33ddb592ac1534b9b9ef8f400791d8158db161d [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
13from functools import partial
14from tempfile import mkdtemp
15from uuid import uuid4
16
17from linaropy.cd import cd
18
19
20# TODO: move this somewhere more public (maybe linaropy?)
21def debug(test):
22 """
23 Decorator that dumps the output of any subprocess.CalledProcessError
24 exception. Use this to decorate a test function when you can't tell what the
25 problem is.
26 """
27 def wrapper(*args, **kwargs):
28 # Catch any exceptions so we can dump all the output
29 try:
30 test(*args, **kwargs)
31 except subprocess.CalledProcessError as exc:
32 print "Error in {}:".format(test.__name__)
33 print "Command {} exited with error code {}:\n{}".format(
34 exc.cmd, exc.returncode, exc.output)
35 return wrapper
36
37
38class Testllvmprojs(unittest.TestCase):
39 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
67 @classmethod
68 def setUpClass(cls):
69 """Create the file structure and environment that llvmprojs expects"""
70 cls.llvm_root = mkdtemp()
71 cls.repos = os.path.join(cls.llvm_root, "repos")
72
73 cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb",
74 "libcxx", "libcxxabi", "libunwind", "test-suite")
75
76 # Set up helper functions for running commands (this needs to be done
77 # before creating the repos)
78 # FIXME: In newer versions of Python (3.3+) we should be able to bind
79 # the stdout to DEVNULL, as below:
80 # cls.run_quietly = partial(subprocess.call, stdout=subprocess.DEVNULL)
81 # This doesn't work in Python 2, so we'll just have to ignore the output
82 # for now...
83 cls.run_with_output = partial(subprocess.check_output,
84 stderr=subprocess.STDOUT)
85 cls.run_quietly = cls.run_with_output
86
87 # Create dummy repos
88 for reponame in cls.all_repos:
89 cls.__create_dummy_repo(cls.__get_subproj_repo(reponame))
90
91 @classmethod
92 def tearDownClass(cls):
93 shutil.rmtree(cls.llvm_root)
94
95 @classmethod
96 def setUp(cls):
97 cls.llvm_src = os.path.join(cls.llvm_root, "src" + str(uuid4()))
98
99 # Create LLVM worktree
100 cls.branch = "br" + str(uuid4())
101 cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src,
102 cls.branch)
103
104 # Set up the environment variables
105 os.environ["LLVM_ROOT"] = cls.llvm_root
106 os.environ["LLVM_SRC"] = cls.llvm_src
107
108 @classmethod
109 def tearDown(cls):
110 # Clean up the directories where we might have added subprojects.
111 # This isn't 100% clean, because we don't clean up the repos between
112 # tests (so any branches will remain), but it's good enough for the
113 # current tests.
114 for subprojdir in (os.path.join(cls.llvm_src, "projects"),
115 os.path.join(cls.llvm_src, "tools")):
116 if os.path.isdir(subprojdir):
117 shutil.rmtree(subprojdir)
118 os.makedirs(subprojdir)
119
120 # Run prune on the original repos, to remove any dangling worktrees.
121 for reponame in cls.all_repos:
122 repopath = cls.__get_subproj_repo(reponame)
123 with cd(repopath):
124 cls.run_quietly(["git", "worktree", "prune"])
125
126 def test_dump_empty_config(self):
127 """
128 Test that we're correctly dumping an empty configuration (i.e. no
129 projects linked) when running llvmprojs without arguments.
130 """
131 output = self.run_with_output(["python", self.script, "projects"])
132 self.assertRegexpMatches(output, "Projects linked:.*\n.*none.*")
133
134 def test_add_remove_subprojects(self):
135 """
136 Test that we can add and remove one or several subprojects.
137 """
138 output = self.run_with_output(["python", self.script, "projects",
139 "--add", "clang"])
140 self.assertRegexpMatches(output, "Projects linked:.*\n.*clang.*")
141
142 output = self.run_with_output(["python", self.script, "projects",
143 "--add", "libcxx", "lldb"])
144 self.assertRegexpMatches(
145 output,
146 "Projects linked:.*\n" +
147 ".*clang.*\n" +
148 ".*libcxx.*\n" +
149 ".*lldb.*")
150
151 output = self.run_with_output(["python", self.script, "projects",
152 "--remove", "libcxx"])
153 self.assertRegexpMatches(output,
154 "Projects linked:.*\n" +
155 ".*clang.*\n" +
156 ".*lldb.*")
157
158 output = self.run_with_output(["python", self.script, "projects",
159 "--remove", "clang", "lldb"])
160 self.assertRegexpMatches(output,
161 "Projects linked:.*\n" +
162 ".*none.*")
163
164 def test_add_remove_invalid_subprojects(self):
165 """
166 Test that we error out nicely when trying to add/remove invalid
167 subprojects.
168 """
169 with self.assertRaises(subprocess.CalledProcessError) as context:
170 self.run_quietly(["python", self.script, "projects",
171 "--add", "inventedsubproject"])
172
173 self.assertRegexpMatches(
174 context.exception.output,
175 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
176
177 with self.assertRaises(subprocess.CalledProcessError) as context:
178 self.run_quietly(["python", self.script, "projects",
179 "--remove", "inventedsubproject"])
180
181 self.assertRegexpMatches(
182 context.exception.output,
183 "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
184
185 def test_duplicate_add_remove(self):
186 """
187 Test that we don't crash when trying to add / remove the same subproject
188 twice with the same command.
189 """
190 output = self.run_with_output(["python", self.script, "projects",
191 "--add", "clang", "lld", "clang"])
192 self.assertRegexpMatches(output,
193 "Projects linked:.*\n" +
194 ".*clang.*\n" +
195 ".*lld.*")
196
197 output = self.run_with_output(["python", self.script, "projects",
198 "--remove", "lld", "lld", "clang"])
199 self.assertRegexpMatches(output,
200 "Projects linked:.*\n" +
201 ".*none.*")
202
203 def test_redundant_add_remove(self):
204 """
205 Test that we can add a subproject that already exists (either because it
206 was added by our script or manually) or remove a subproject that doesn't
207 exist.
208 """
209 self.__add_worktree(self.__get_subproj_repo("clang"),
210 os.path.join(self.llvm_src, "tools", "clang"),
211 self.branch)
212 self.__add_worktree(
213 self.__get_subproj_repo("compiler-rt"),
214 os.path.join(self.llvm_src, "projects", "compiler-rt"),
215 self.branch)
216
217 output = self.run_with_output(["python", self.script, "projects",
218 "--add", "clang"])
219 self.assertRegexpMatches(output,
220 "Projects linked:.*\n" +
221 ".*clang.*")
222
223 output = self.run_with_output(["python", self.script, "projects",
224 "--add", "compiler-rt", "lld"])
225 self.assertRegexpMatches(
226 output,
227 "Projects linked:.*\n" +
228 ".*clang.*\n" +
229 ".*compiler-rt.*\n" +
230 ".*lld.*")
231
232 output = self.run_with_output(["python", self.script, "projects",
233 "--remove", "lldb", "libcxx", "lld"])
234 self.assertRegexpMatches(
235 output,
236 "Projects linked:.*\n" +
237 ".*clang.*\n" +
238 ".*compiler-rt.*")
239
240 def test_simultaneous_add_remove(self):
241 """
242 Test that we error out when someone is trying to add and remove the same
243 project with the same command.
244 """
245 # Try the really basic case and make sure we're not touching anything
246 with self.assertRaises(subprocess.CalledProcessError) as context:
247 self.run_quietly(["python", self.script, "projects",
248 "--add", "clang", "--remove", "clang"])
249
250 self.assertRegexpMatches(
251 context.exception.output,
252 "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*")
253
254 self.assertFalse(
255 os.path.exists(
256 os.path.join(self.llvm_src, "tools", "clang")))
257
258 # Try something a bit more complicated and make sure we're not touching
259 # anything
260 self.__add_worktree(
261 self.__get_subproj_repo("lld"),
262 os.path.join(self.llvm_src, "tools", "lld"),
263 self.branch)
264
265 with self.assertRaises(subprocess.CalledProcessError) as context:
266 self.run_quietly(["python", self.script, "projects",
267 "--add", "clang", "lld", "libcxx",
268 "--remove", "lld", "libcxx"])
269 self.assertRegexpMatches(
270 context.exception.output,
271 "(.*\n)*" +
272 ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*")
273
274 # Make sure we didn't touch anything
275 self.assertFalse(
276 os.path.exists(
277 os.path.join(self.llvm_src, "tools", "clang")))
278 self.assertTrue(
279 os.path.exists(
280 os.path.join(self.llvm_src, "tools", "lld")))
281 self.assertFalse(
282 os.path.exists(
283 os.path.join(self.llvm_src, "projects", "libcxx")))
284
285 def test_multiple_adds_removes(self):
286 """
287 Test that we can have multiple --add and --remove options in the same
288 command and that only the last one of each kind matters.
289 """
290 output = self.run_with_output(["python", self.script, "projects",
291 "--add", "libcxxabi",
292 "--remove", "lld", "lldb",
293 "--add", "clang", "libcxx",
294 "--remove", "libunwind"])
295 self.assertRegexpMatches(output,
296 "Projects linked:.*\n" +
297 ".*clang.*\n" +
298 ".*libcxx.*\n")
299
300 output = self.run_with_output(["python", self.script, "projects",
301 "--add", "libunwind", "libcxxabi",
302 "--remove", "clang", "libcxx",
303 "--add", "compiler-rt",
304 "--remove", "libcxxabi"])
305 self.assertRegexpMatches(output,
306 "Projects linked:.*\n" +
307 ".*clang.*\n" +
308 ".*compiler-rt.*\n" +
309 ".*libcxx.*\n")
310
311 output = self.run_with_output(["python", self.script, "projects",
312 "--add", "libcxx", "--remove", "lld",
313 "--add", "lld", "--remove", "libcxx"])
314 self.assertRegexpMatches(output,
315 "Projects linked:.*\n" +
316 ".*clang.*\n" +
317 ".*compiler-rt.*\n" +
318 ".*lld.*\n")