diff options
Diffstat (limited to 'tests/test_project.py')
-rw-r--r-- | tests/test_project.py | 297 |
1 files changed, 249 insertions, 48 deletions
diff --git a/tests/test_project.py b/tests/test_project.py index 77126dff..9b2cc4e9 100644 --- a/tests/test_project.py +++ b/tests/test_project.py | |||
@@ -1,5 +1,3 @@ | |||
1 | # -*- coding:utf-8 -*- | ||
2 | # | ||
3 | # Copyright (C) 2019 The Android Open Source Project | 1 | # Copyright (C) 2019 The Android Open Source Project |
4 | # | 2 | # |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
@@ -16,8 +14,6 @@ | |||
16 | 14 | ||
17 | """Unittests for the project.py module.""" | 15 | """Unittests for the project.py module.""" |
18 | 16 | ||
19 | from __future__ import print_function | ||
20 | |||
21 | import contextlib | 17 | import contextlib |
22 | import os | 18 | import os |
23 | import shutil | 19 | import shutil |
@@ -25,7 +21,10 @@ import subprocess | |||
25 | import tempfile | 21 | import tempfile |
26 | import unittest | 22 | import unittest |
27 | 23 | ||
24 | import error | ||
25 | import git_command | ||
28 | import git_config | 26 | import git_config |
27 | import platform_utils | ||
29 | import project | 28 | import project |
30 | 29 | ||
31 | 30 | ||
@@ -36,49 +35,22 @@ def TempGitTree(): | |||
36 | # Python 2 support entirely. | 35 | # Python 2 support entirely. |
37 | try: | 36 | try: |
38 | tempdir = tempfile.mkdtemp(prefix='repo-tests') | 37 | tempdir = tempfile.mkdtemp(prefix='repo-tests') |
39 | subprocess.check_call(['git', 'init'], cwd=tempdir) | 38 | |
39 | # Tests need to assume, that main is default branch at init, | ||
40 | # which is not supported in config until 2.28. | ||
41 | cmd = ['git', 'init'] | ||
42 | if git_command.git_require((2, 28, 0)): | ||
43 | cmd += ['--initial-branch=main'] | ||
44 | else: | ||
45 | # Use template dir for init. | ||
46 | templatedir = tempfile.mkdtemp(prefix='.test-template') | ||
47 | with open(os.path.join(templatedir, 'HEAD'), 'w') as fp: | ||
48 | fp.write('ref: refs/heads/main\n') | ||
49 | cmd += ['--template', templatedir] | ||
50 | subprocess.check_call(cmd, cwd=tempdir) | ||
40 | yield tempdir | 51 | yield tempdir |
41 | finally: | 52 | finally: |
42 | shutil.rmtree(tempdir) | 53 | platform_utils.rmtree(tempdir) |
43 | |||
44 | |||
45 | class RepoHookShebang(unittest.TestCase): | ||
46 | """Check shebang parsing in RepoHook.""" | ||
47 | |||
48 | def test_no_shebang(self): | ||
49 | """Lines w/out shebangs should be rejected.""" | ||
50 | DATA = ( | ||
51 | '', | ||
52 | '# -*- coding:utf-8 -*-\n', | ||
53 | '#\n# foo\n', | ||
54 | '# Bad shebang in script\n#!/foo\n' | ||
55 | ) | ||
56 | for data in DATA: | ||
57 | self.assertIsNone(project.RepoHook._ExtractInterpFromShebang(data)) | ||
58 | |||
59 | def test_direct_interp(self): | ||
60 | """Lines whose shebang points directly to the interpreter.""" | ||
61 | DATA = ( | ||
62 | ('#!/foo', '/foo'), | ||
63 | ('#! /foo', '/foo'), | ||
64 | ('#!/bin/foo ', '/bin/foo'), | ||
65 | ('#! /usr/foo ', '/usr/foo'), | ||
66 | ('#! /usr/foo -args', '/usr/foo'), | ||
67 | ) | ||
68 | for shebang, interp in DATA: | ||
69 | self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang), | ||
70 | interp) | ||
71 | |||
72 | def test_env_interp(self): | ||
73 | """Lines whose shebang launches through `env`.""" | ||
74 | DATA = ( | ||
75 | ('#!/usr/bin/env foo', 'foo'), | ||
76 | ('#!/bin/env foo', 'foo'), | ||
77 | ('#! /bin/env /bin/foo ', '/bin/foo'), | ||
78 | ) | ||
79 | for shebang, interp in DATA: | ||
80 | self.assertEqual(project.RepoHook._ExtractInterpFromShebang(shebang), | ||
81 | interp) | ||
82 | 54 | ||
83 | 55 | ||
84 | class FakeProject(object): | 56 | class FakeProject(object): |
@@ -114,7 +86,7 @@ class ReviewableBranchTests(unittest.TestCase): | |||
114 | 86 | ||
115 | # Start off with the normal details. | 87 | # Start off with the normal details. |
116 | rb = project.ReviewableBranch( | 88 | rb = project.ReviewableBranch( |
117 | fakeproj, fakeproj.config.GetBranch('work'), 'master') | 89 | fakeproj, fakeproj.config.GetBranch('work'), 'main') |
118 | self.assertEqual('work', rb.name) | 90 | self.assertEqual('work', rb.name) |
119 | self.assertEqual(1, len(rb.commits)) | 91 | self.assertEqual(1, len(rb.commits)) |
120 | self.assertIn('Del file', rb.commits[0]) | 92 | self.assertIn('Del file', rb.commits[0]) |
@@ -127,10 +99,239 @@ class ReviewableBranchTests(unittest.TestCase): | |||
127 | self.assertTrue(rb.date) | 99 | self.assertTrue(rb.date) |
128 | 100 | ||
129 | # Now delete the tracking branch! | 101 | # Now delete the tracking branch! |
130 | fakeproj.work_git.branch('-D', 'master') | 102 | fakeproj.work_git.branch('-D', 'main') |
131 | rb = project.ReviewableBranch( | 103 | rb = project.ReviewableBranch( |
132 | fakeproj, fakeproj.config.GetBranch('work'), 'master') | 104 | fakeproj, fakeproj.config.GetBranch('work'), 'main') |
133 | self.assertEqual(0, len(rb.commits)) | 105 | self.assertEqual(0, len(rb.commits)) |
134 | self.assertFalse(rb.base_exists) | 106 | self.assertFalse(rb.base_exists) |
135 | # Hard to assert anything useful about this. | 107 | # Hard to assert anything useful about this. |
136 | self.assertTrue(rb.date) | 108 | self.assertTrue(rb.date) |
109 | |||
110 | |||
111 | class CopyLinkTestCase(unittest.TestCase): | ||
112 | """TestCase for stub repo client checkouts. | ||
113 | |||
114 | It'll have a layout like: | ||
115 | tempdir/ # self.tempdir | ||
116 | checkout/ # self.topdir | ||
117 | git-project/ # self.worktree | ||
118 | |||
119 | Attributes: | ||
120 | tempdir: A dedicated temporary directory. | ||
121 | worktree: The top of the repo client checkout. | ||
122 | topdir: The top of a project checkout. | ||
123 | """ | ||
124 | |||
125 | def setUp(self): | ||
126 | self.tempdir = tempfile.mkdtemp(prefix='repo_tests') | ||
127 | self.topdir = os.path.join(self.tempdir, 'checkout') | ||
128 | self.worktree = os.path.join(self.topdir, 'git-project') | ||
129 | os.makedirs(self.topdir) | ||
130 | os.makedirs(self.worktree) | ||
131 | |||
132 | def tearDown(self): | ||
133 | shutil.rmtree(self.tempdir, ignore_errors=True) | ||
134 | |||
135 | @staticmethod | ||
136 | def touch(path): | ||
137 | with open(path, 'w'): | ||
138 | pass | ||
139 | |||
140 | def assertExists(self, path, msg=None): | ||
141 | """Make sure |path| exists.""" | ||
142 | if os.path.exists(path): | ||
143 | return | ||
144 | |||
145 | if msg is None: | ||
146 | msg = ['path is missing: %s' % path] | ||
147 | while path != '/': | ||
148 | path = os.path.dirname(path) | ||
149 | if not path: | ||
150 | # If we're given something like "foo", abort once we get to "". | ||
151 | break | ||
152 | result = os.path.exists(path) | ||
153 | msg.append('\tos.path.exists(%s): %s' % (path, result)) | ||
154 | if result: | ||
155 | msg.append('\tcontents: %r' % os.listdir(path)) | ||
156 | break | ||
157 | msg = '\n'.join(msg) | ||
158 | |||
159 | raise self.failureException(msg) | ||
160 | |||
161 | |||
162 | class CopyFile(CopyLinkTestCase): | ||
163 | """Check _CopyFile handling.""" | ||
164 | |||
165 | def CopyFile(self, src, dest): | ||
166 | return project._CopyFile(self.worktree, src, self.topdir, dest) | ||
167 | |||
168 | def test_basic(self): | ||
169 | """Basic test of copying a file from a project to the toplevel.""" | ||
170 | src = os.path.join(self.worktree, 'foo.txt') | ||
171 | self.touch(src) | ||
172 | cf = self.CopyFile('foo.txt', 'foo') | ||
173 | cf._Copy() | ||
174 | self.assertExists(os.path.join(self.topdir, 'foo')) | ||
175 | |||
176 | def test_src_subdir(self): | ||
177 | """Copy a file from a subdir of a project.""" | ||
178 | src = os.path.join(self.worktree, 'bar', 'foo.txt') | ||
179 | os.makedirs(os.path.dirname(src)) | ||
180 | self.touch(src) | ||
181 | cf = self.CopyFile('bar/foo.txt', 'new.txt') | ||
182 | cf._Copy() | ||
183 | self.assertExists(os.path.join(self.topdir, 'new.txt')) | ||
184 | |||
185 | def test_dest_subdir(self): | ||
186 | """Copy a file to a subdir of a checkout.""" | ||
187 | src = os.path.join(self.worktree, 'foo.txt') | ||
188 | self.touch(src) | ||
189 | cf = self.CopyFile('foo.txt', 'sub/dir/new.txt') | ||
190 | self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) | ||
191 | cf._Copy() | ||
192 | self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'new.txt')) | ||
193 | |||
194 | def test_update(self): | ||
195 | """Make sure changed files get copied again.""" | ||
196 | src = os.path.join(self.worktree, 'foo.txt') | ||
197 | dest = os.path.join(self.topdir, 'bar') | ||
198 | with open(src, 'w') as f: | ||
199 | f.write('1st') | ||
200 | cf = self.CopyFile('foo.txt', 'bar') | ||
201 | cf._Copy() | ||
202 | self.assertExists(dest) | ||
203 | with open(dest) as f: | ||
204 | self.assertEqual(f.read(), '1st') | ||
205 | |||
206 | with open(src, 'w') as f: | ||
207 | f.write('2nd!') | ||
208 | cf._Copy() | ||
209 | with open(dest) as f: | ||
210 | self.assertEqual(f.read(), '2nd!') | ||
211 | |||
212 | def test_src_block_symlink(self): | ||
213 | """Do not allow reading from a symlinked path.""" | ||
214 | src = os.path.join(self.worktree, 'foo.txt') | ||
215 | sym = os.path.join(self.worktree, 'sym') | ||
216 | self.touch(src) | ||
217 | platform_utils.symlink('foo.txt', sym) | ||
218 | self.assertExists(sym) | ||
219 | cf = self.CopyFile('sym', 'foo') | ||
220 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
221 | |||
222 | def test_src_block_symlink_traversal(self): | ||
223 | """Do not allow reading through a symlink dir.""" | ||
224 | realfile = os.path.join(self.tempdir, 'file.txt') | ||
225 | self.touch(realfile) | ||
226 | src = os.path.join(self.worktree, 'bar', 'file.txt') | ||
227 | platform_utils.symlink(self.tempdir, os.path.join(self.worktree, 'bar')) | ||
228 | self.assertExists(src) | ||
229 | cf = self.CopyFile('bar/file.txt', 'foo') | ||
230 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
231 | |||
232 | def test_src_block_copy_from_dir(self): | ||
233 | """Do not allow copying from a directory.""" | ||
234 | src = os.path.join(self.worktree, 'dir') | ||
235 | os.makedirs(src) | ||
236 | cf = self.CopyFile('dir', 'foo') | ||
237 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
238 | |||
239 | def test_dest_block_symlink(self): | ||
240 | """Do not allow writing to a symlink.""" | ||
241 | src = os.path.join(self.worktree, 'foo.txt') | ||
242 | self.touch(src) | ||
243 | platform_utils.symlink('dest', os.path.join(self.topdir, 'sym')) | ||
244 | cf = self.CopyFile('foo.txt', 'sym') | ||
245 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
246 | |||
247 | def test_dest_block_symlink_traversal(self): | ||
248 | """Do not allow writing through a symlink dir.""" | ||
249 | src = os.path.join(self.worktree, 'foo.txt') | ||
250 | self.touch(src) | ||
251 | platform_utils.symlink(tempfile.gettempdir(), | ||
252 | os.path.join(self.topdir, 'sym')) | ||
253 | cf = self.CopyFile('foo.txt', 'sym/foo.txt') | ||
254 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
255 | |||
256 | def test_src_block_copy_to_dir(self): | ||
257 | """Do not allow copying to a directory.""" | ||
258 | src = os.path.join(self.worktree, 'foo.txt') | ||
259 | self.touch(src) | ||
260 | os.makedirs(os.path.join(self.topdir, 'dir')) | ||
261 | cf = self.CopyFile('foo.txt', 'dir') | ||
262 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | ||
263 | |||
264 | |||
265 | class LinkFile(CopyLinkTestCase): | ||
266 | """Check _LinkFile handling.""" | ||
267 | |||
268 | def LinkFile(self, src, dest): | ||
269 | return project._LinkFile(self.worktree, src, self.topdir, dest) | ||
270 | |||
271 | def test_basic(self): | ||
272 | """Basic test of linking a file from a project into the toplevel.""" | ||
273 | src = os.path.join(self.worktree, 'foo.txt') | ||
274 | self.touch(src) | ||
275 | lf = self.LinkFile('foo.txt', 'foo') | ||
276 | lf._Link() | ||
277 | dest = os.path.join(self.topdir, 'foo') | ||
278 | self.assertExists(dest) | ||
279 | self.assertTrue(os.path.islink(dest)) | ||
280 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) | ||
281 | |||
282 | def test_src_subdir(self): | ||
283 | """Link to a file in a subdir of a project.""" | ||
284 | src = os.path.join(self.worktree, 'bar', 'foo.txt') | ||
285 | os.makedirs(os.path.dirname(src)) | ||
286 | self.touch(src) | ||
287 | lf = self.LinkFile('bar/foo.txt', 'foo') | ||
288 | lf._Link() | ||
289 | self.assertExists(os.path.join(self.topdir, 'foo')) | ||
290 | |||
291 | def test_src_self(self): | ||
292 | """Link to the project itself.""" | ||
293 | dest = os.path.join(self.topdir, 'foo', 'bar') | ||
294 | lf = self.LinkFile('.', 'foo/bar') | ||
295 | lf._Link() | ||
296 | self.assertExists(dest) | ||
297 | self.assertEqual(os.path.join('..', 'git-project'), os.readlink(dest)) | ||
298 | |||
299 | def test_dest_subdir(self): | ||
300 | """Link a file to a subdir of a checkout.""" | ||
301 | src = os.path.join(self.worktree, 'foo.txt') | ||
302 | self.touch(src) | ||
303 | lf = self.LinkFile('foo.txt', 'sub/dir/foo/bar') | ||
304 | self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) | ||
305 | lf._Link() | ||
306 | self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'foo', 'bar')) | ||
307 | |||
308 | def test_src_block_relative(self): | ||
309 | """Do not allow relative symlinks.""" | ||
310 | BAD_SOURCES = ( | ||
311 | './', | ||
312 | '..', | ||
313 | '../', | ||
314 | 'foo/.', | ||
315 | 'foo/./bar', | ||
316 | 'foo/..', | ||
317 | 'foo/../foo', | ||
318 | ) | ||
319 | for src in BAD_SOURCES: | ||
320 | lf = self.LinkFile(src, 'foo') | ||
321 | self.assertRaises(error.ManifestInvalidPathError, lf._Link) | ||
322 | |||
323 | def test_update(self): | ||
324 | """Make sure changed targets get updated.""" | ||
325 | dest = os.path.join(self.topdir, 'sym') | ||
326 | |||
327 | src = os.path.join(self.worktree, 'foo.txt') | ||
328 | self.touch(src) | ||
329 | lf = self.LinkFile('foo.txt', 'sym') | ||
330 | lf._Link() | ||
331 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) | ||
332 | |||
333 | # Point the symlink somewhere else. | ||
334 | os.unlink(dest) | ||
335 | platform_utils.symlink(self.tempdir, dest) | ||
336 | lf._Link() | ||
337 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) | ||