blob: 5e2cfa5730332bbdaf6f9f8e3a9c8c6b7a32720f [file] [log] [blame]
Dan Willemsen745b4ad2015-10-06 15:23:19 -07001# Copyright (C) 2015 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Mike Frysinger87deaef2019-07-26 21:14:55 -040015"""Unittests for the wrapper.py module."""
16
Mike Frysinger3599cc32020-02-29 02:53:41 -050017import contextlib
Dan Willemsen745b4ad2015-10-06 15:23:19 -070018import os
Mike Frysinger84094102020-02-11 02:10:28 -050019import re
Mike Frysingercfc81112020-02-29 02:56:32 -050020import shutil
21import tempfile
Dan Willemsen745b4ad2015-10-06 15:23:19 -070022import unittest
23
Fredrik de Groot6342d562020-12-01 15:58:53 +010024import git_command
Mike Frysinger1379a9b2021-01-04 23:29:45 -050025import main
Mike Frysinger3599cc32020-02-29 02:53:41 -050026import platform_utils
Mike Frysinger84094102020-02-11 02:10:28 -050027from pyversion import is_python3
Dan Willemsen745b4ad2015-10-06 15:23:19 -070028import wrapper
29
David Pursehouse819827a2020-02-12 15:20:19 +090030
Mike Frysinger8ddff5c2020-02-09 15:00:25 -050031if is_python3():
32 from unittest import mock
33 from io import StringIO
34else:
35 import mock
36 from StringIO import StringIO
37
38
Mike Frysinger3599cc32020-02-29 02:53:41 -050039@contextlib.contextmanager
40def TemporaryDirectory():
41 """Create a new empty git checkout for testing."""
42 # TODO(vapier): Convert this to tempfile.TemporaryDirectory once we drop
43 # Python 2 support entirely.
44 try:
45 tempdir = tempfile.mkdtemp(prefix='repo-tests')
46 yield tempdir
47 finally:
48 platform_utils.rmtree(tempdir)
49
50
Dan Willemsen745b4ad2015-10-06 15:23:19 -070051def fixture(*paths):
52 """Return a path relative to tests/fixtures.
53 """
54 return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
55
David Pursehouse819827a2020-02-12 15:20:19 +090056
Mike Frysinger84094102020-02-11 02:10:28 -050057class RepoWrapperTestCase(unittest.TestCase):
58 """TestCase for the wrapper module."""
David Pursehouse819827a2020-02-12 15:20:19 +090059
Dan Willemsen745b4ad2015-10-06 15:23:19 -070060 def setUp(self):
Mike Frysinger84094102020-02-11 02:10:28 -050061 """Load the wrapper module every time."""
Dan Willemsen745b4ad2015-10-06 15:23:19 -070062 wrapper._wrapper_module = None
63 self.wrapper = wrapper.Wrapper()
64
Mike Frysinger84094102020-02-11 02:10:28 -050065 if not is_python3():
66 self.assertRegex = self.assertRegexpMatches
67
68
69class RepoWrapperUnitTest(RepoWrapperTestCase):
70 """Tests helper functions in the repo wrapper
71 """
72
Mike Frysinger8ddff5c2020-02-09 15:00:25 -050073 def test_version(self):
74 """Make sure _Version works."""
75 with self.assertRaises(SystemExit) as e:
76 with mock.patch('sys.stdout', new_callable=StringIO) as stdout:
77 with mock.patch('sys.stderr', new_callable=StringIO) as stderr:
78 self.wrapper._Version()
79 self.assertEqual(0, e.exception.code)
80 self.assertEqual('', stderr.getvalue())
81 self.assertIn('repo launcher version', stdout.getvalue())
82
Mike Frysinger1379a9b2021-01-04 23:29:45 -050083 def test_python_constraints(self):
84 """The launcher should never require newer than main.py."""
85 self.assertGreaterEqual(main.MIN_PYTHON_VERSION_HARD,
86 wrapper.MIN_PYTHON_VERSION_HARD)
87 self.assertGreaterEqual(main.MIN_PYTHON_VERSION_SOFT,
88 wrapper.MIN_PYTHON_VERSION_SOFT)
89 # Make sure the versions are themselves in sync.
90 self.assertGreaterEqual(wrapper.MIN_PYTHON_VERSION_SOFT,
91 wrapper.MIN_PYTHON_VERSION_HARD)
92
Mike Frysingerd8fda902020-02-14 00:24:38 -050093 def test_init_parser(self):
94 """Make sure 'init' GetParser works."""
95 parser = self.wrapper.GetParser(gitc_init=False)
96 opts, args = parser.parse_args([])
97 self.assertEqual([], args)
98 self.assertIsNone(opts.manifest_url)
99
100 def test_gitc_init_parser(self):
101 """Make sure 'gitc-init' GetParser works."""
102 parser = self.wrapper.GetParser(gitc_init=True)
103 opts, args = parser.parse_args([])
104 self.assertEqual([], args)
105 self.assertIsNone(opts.manifest_file)
106
Dan Willemsen745b4ad2015-10-06 15:23:19 -0700107 def test_get_gitc_manifest_dir_no_gitc(self):
108 """
109 Test reading a missing gitc config file
110 """
111 self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
112 val = self.wrapper.get_gitc_manifest_dir()
113 self.assertEqual(val, '')
114
115 def test_get_gitc_manifest_dir(self):
116 """
117 Test reading the gitc config file and parsing the directory
118 """
119 self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
120 val = self.wrapper.get_gitc_manifest_dir()
121 self.assertEqual(val, '/test/usr/local/google/gitc')
122
123 def test_gitc_parse_clientdir_no_gitc(self):
124 """
125 Test parsing the gitc clientdir without gitc running
126 """
127 self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config')
128 self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
129 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
130
131 def test_gitc_parse_clientdir(self):
132 """
133 Test parsing the gitc clientdir
134 """
135 self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config')
136 self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None)
137 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test')
138 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/'), 'test')
139 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/extra'), 'test')
140 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test'), 'test')
141 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/'), 'test')
David Pursehouse3cda50a2020-02-13 13:17:03 +0900142 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/extra'),
143 'test')
Dan Willemsen745b4ad2015-10-06 15:23:19 -0700144 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/'), None)
145 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/'), None)
146
David Pursehouse819827a2020-02-12 15:20:19 +0900147
Mike Frysinger84094102020-02-11 02:10:28 -0500148class SetGitTrace2ParentSid(RepoWrapperTestCase):
149 """Check SetGitTrace2ParentSid behavior."""
150
151 KEY = 'GIT_TRACE2_PARENT_SID'
152 VALID_FORMAT = re.compile(r'^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$')
153
154 def test_first_set(self):
155 """Test env var not yet set."""
156 env = {}
157 self.wrapper.SetGitTrace2ParentSid(env)
158 self.assertIn(self.KEY, env)
159 value = env[self.KEY]
160 self.assertRegex(value, self.VALID_FORMAT)
161
162 def test_append(self):
163 """Test env var is appended."""
164 env = {self.KEY: 'pfx'}
165 self.wrapper.SetGitTrace2ParentSid(env)
166 self.assertIn(self.KEY, env)
167 value = env[self.KEY]
168 self.assertTrue(value.startswith('pfx/'))
169 self.assertRegex(value[4:], self.VALID_FORMAT)
170
171 def test_global_context(self):
172 """Check os.environ gets updated by default."""
173 os.environ.pop(self.KEY, None)
174 self.wrapper.SetGitTrace2ParentSid()
175 self.assertIn(self.KEY, os.environ)
176 value = os.environ[self.KEY]
177 self.assertRegex(value, self.VALID_FORMAT)
178
179
Mike Frysinger587f1622020-03-23 16:49:11 -0400180class RunCommand(RepoWrapperTestCase):
181 """Check run_command behavior."""
182
183 def test_capture(self):
184 """Check capture_output handling."""
185 ret = self.wrapper.run_command(['echo', 'hi'], capture_output=True)
186 self.assertEqual(ret.stdout, 'hi\n')
187
188 def test_check(self):
189 """Check check handling."""
190 self.wrapper.run_command(['true'], check=False)
191 self.wrapper.run_command(['true'], check=True)
192 self.wrapper.run_command(['false'], check=False)
193 with self.assertRaises(self.wrapper.RunError):
194 self.wrapper.run_command(['false'], check=True)
195
196
197class RunGit(RepoWrapperTestCase):
198 """Check run_git behavior."""
199
200 def test_capture(self):
201 """Check capture_output handling."""
202 ret = self.wrapper.run_git('--version')
203 self.assertIn('git', ret.stdout)
204
205 def test_check(self):
206 """Check check handling."""
207 with self.assertRaises(self.wrapper.CloneFailure):
208 self.wrapper.run_git('--version-asdfasdf')
209 self.wrapper.run_git('--version-asdfasdf', check=False)
210
211
212class ParseGitVersion(RepoWrapperTestCase):
213 """Check ParseGitVersion behavior."""
214
215 def test_autoload(self):
216 """Check we can load the version from the live git."""
217 ret = self.wrapper.ParseGitVersion()
218 self.assertIsNotNone(ret)
219
220 def test_bad_ver(self):
221 """Check handling of bad git versions."""
222 ret = self.wrapper.ParseGitVersion(ver_str='asdf')
223 self.assertIsNone(ret)
224
225 def test_normal_ver(self):
226 """Check handling of normal git versions."""
227 ret = self.wrapper.ParseGitVersion(ver_str='git version 2.25.1')
228 self.assertEqual(2, ret.major)
229 self.assertEqual(25, ret.minor)
230 self.assertEqual(1, ret.micro)
231 self.assertEqual('2.25.1', ret.full)
232
233 def test_extended_ver(self):
234 """Check handling of extended distro git versions."""
235 ret = self.wrapper.ParseGitVersion(
236 ver_str='git version 1.30.50.696.g5e7596f4ac-goog')
237 self.assertEqual(1, ret.major)
238 self.assertEqual(30, ret.minor)
239 self.assertEqual(50, ret.micro)
240 self.assertEqual('1.30.50.696.g5e7596f4ac-goog', ret.full)
241
242
243class CheckGitVersion(RepoWrapperTestCase):
244 """Check _CheckGitVersion behavior."""
245
246 def test_unknown(self):
247 """Unknown versions should abort."""
248 with mock.patch.object(self.wrapper, 'ParseGitVersion', return_value=None):
249 with self.assertRaises(self.wrapper.CloneFailure):
250 self.wrapper._CheckGitVersion()
251
252 def test_old(self):
253 """Old versions should abort."""
254 with mock.patch.object(
255 self.wrapper, 'ParseGitVersion',
256 return_value=self.wrapper.GitVersion(1, 0, 0, '1.0.0')):
257 with self.assertRaises(self.wrapper.CloneFailure):
258 self.wrapper._CheckGitVersion()
259
260 def test_new(self):
261 """Newer versions should run fine."""
262 with mock.patch.object(
263 self.wrapper, 'ParseGitVersion',
264 return_value=self.wrapper.GitVersion(100, 0, 0, '100.0.0')):
265 self.wrapper._CheckGitVersion()
266
267
Mike Frysinger3599cc32020-02-29 02:53:41 -0500268class NeedSetupGnuPG(RepoWrapperTestCase):
269 """Check NeedSetupGnuPG behavior."""
270
271 def test_missing_dir(self):
272 """The ~/.repoconfig tree doesn't exist yet."""
273 with TemporaryDirectory() as tempdir:
274 self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo')
275 self.assertTrue(self.wrapper.NeedSetupGnuPG())
276
277 def test_missing_keyring(self):
278 """The keyring-version file doesn't exist yet."""
279 with TemporaryDirectory() as tempdir:
280 self.wrapper.home_dot_repo = tempdir
281 self.assertTrue(self.wrapper.NeedSetupGnuPG())
282
283 def test_empty_keyring(self):
284 """The keyring-version file exists, but is empty."""
285 with TemporaryDirectory() as tempdir:
286 self.wrapper.home_dot_repo = tempdir
287 with open(os.path.join(tempdir, 'keyring-version'), 'w'):
288 pass
289 self.assertTrue(self.wrapper.NeedSetupGnuPG())
290
291 def test_old_keyring(self):
292 """The keyring-version file exists, but it's old."""
293 with TemporaryDirectory() as tempdir:
294 self.wrapper.home_dot_repo = tempdir
295 with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
296 fp.write('1.0\n')
297 self.assertTrue(self.wrapper.NeedSetupGnuPG())
298
299 def test_new_keyring(self):
300 """The keyring-version file exists, and is up-to-date."""
301 with TemporaryDirectory() as tempdir:
302 self.wrapper.home_dot_repo = tempdir
303 with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp:
304 fp.write('1000.0\n')
305 self.assertFalse(self.wrapper.NeedSetupGnuPG())
306
307
308class SetupGnuPG(RepoWrapperTestCase):
309 """Check SetupGnuPG behavior."""
310
311 def test_full(self):
312 """Make sure it works completely."""
313 with TemporaryDirectory() as tempdir:
314 self.wrapper.home_dot_repo = tempdir
Marcos Marado2735bfc2020-04-09 19:44:28 +0100315 self.wrapper.gpg_dir = os.path.join(self.wrapper.home_dot_repo, 'gnupg')
Mike Frysinger3599cc32020-02-29 02:53:41 -0500316 self.assertTrue(self.wrapper.SetupGnuPG(True))
317 with open(os.path.join(tempdir, 'keyring-version'), 'r') as fp:
318 data = fp.read()
319 self.assertEqual('.'.join(str(x) for x in self.wrapper.KEYRING_VERSION),
320 data.strip())
321
322
323class VerifyRev(RepoWrapperTestCase):
324 """Check verify_rev behavior."""
325
326 def test_verify_passes(self):
327 """Check when we have a valid signed tag."""
328 desc_result = self.wrapper.RunResult(0, 'v1.0\n', '')
329 gpg_result = self.wrapper.RunResult(0, '', '')
330 with mock.patch.object(self.wrapper, 'run_git',
331 side_effect=(desc_result, gpg_result)):
332 ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
333 self.assertEqual('v1.0^0', ret)
334
335 def test_unsigned_commit(self):
336 """Check we fall back to signed tag when we have an unsigned commit."""
337 desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '')
338 gpg_result = self.wrapper.RunResult(0, '', '')
339 with mock.patch.object(self.wrapper, 'run_git',
340 side_effect=(desc_result, gpg_result)):
341 ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
342 self.assertEqual('v1.0^0', ret)
343
344 def test_verify_fails(self):
345 """Check we fall back to signed tag when we have an unsigned commit."""
346 desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '')
347 gpg_result = Exception
348 with mock.patch.object(self.wrapper, 'run_git',
349 side_effect=(desc_result, gpg_result)):
350 with self.assertRaises(Exception):
351 self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True)
352
353
354class GitCheckoutTestCase(RepoWrapperTestCase):
355 """Tests that use a real/small git checkout."""
Mike Frysingercfc81112020-02-29 02:56:32 -0500356
357 GIT_DIR = None
358 REV_LIST = None
359
360 @classmethod
361 def setUpClass(cls):
362 # Create a repo to operate on, but do it once per-class.
363 cls.GIT_DIR = tempfile.mkdtemp(prefix='repo-rev-tests')
364 run_git = wrapper.Wrapper().run_git
365
366 remote = os.path.join(cls.GIT_DIR, 'remote')
367 os.mkdir(remote)
Fredrik de Groot6342d562020-12-01 15:58:53 +0100368
369 # Tests need to assume, that main is default branch at init,
370 # which is not supported in config until 2.28.
371 if git_command.git_require((2, 28, 0)):
372 initstr = '--initial-branch=main'
373 else:
374 # Use template dir for init.
375 templatedir = tempfile.mkdtemp(prefix='.test-template')
376 with open(os.path.join(templatedir, 'HEAD'), 'w') as fp:
377 fp.write('ref: refs/heads/main\n')
378 initstr = '--template=' + templatedir
379
380 run_git('init', initstr, cwd=remote)
Mike Frysingercfc81112020-02-29 02:56:32 -0500381 run_git('commit', '--allow-empty', '-minit', cwd=remote)
382 run_git('branch', 'stable', cwd=remote)
383 run_git('tag', 'v1.0', cwd=remote)
384 run_git('commit', '--allow-empty', '-m2nd commit', cwd=remote)
385 cls.REV_LIST = run_git('rev-list', 'HEAD', cwd=remote).stdout.splitlines()
386
387 run_git('init', cwd=cls.GIT_DIR)
388 run_git('fetch', remote, '+refs/heads/*:refs/remotes/origin/*', cwd=cls.GIT_DIR)
389
390 @classmethod
391 def tearDownClass(cls):
392 if not cls.GIT_DIR:
393 return
394
395 shutil.rmtree(cls.GIT_DIR)
396
Mike Frysinger3599cc32020-02-29 02:53:41 -0500397
398class ResolveRepoRev(GitCheckoutTestCase):
399 """Check resolve_repo_rev behavior."""
400
Mike Frysingercfc81112020-02-29 02:56:32 -0500401 def test_explicit_branch(self):
402 """Check refs/heads/branch argument."""
403 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/stable')
404 self.assertEqual('refs/heads/stable', rrev)
405 self.assertEqual(self.REV_LIST[1], lrev)
406
407 with self.assertRaises(wrapper.CloneFailure):
408 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/unknown')
409
410 def test_explicit_tag(self):
411 """Check refs/tags/tag argument."""
412 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/v1.0')
413 self.assertEqual('refs/tags/v1.0', rrev)
414 self.assertEqual(self.REV_LIST[1], lrev)
415
416 with self.assertRaises(wrapper.CloneFailure):
417 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/unknown')
418
419 def test_branch_name(self):
420 """Check branch argument."""
421 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'stable')
422 self.assertEqual('refs/heads/stable', rrev)
423 self.assertEqual(self.REV_LIST[1], lrev)
424
Mike Frysingere283b952020-11-16 22:56:35 -0500425 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'main')
426 self.assertEqual('refs/heads/main', rrev)
Mike Frysingercfc81112020-02-29 02:56:32 -0500427 self.assertEqual(self.REV_LIST[0], lrev)
428
429 def test_tag_name(self):
430 """Check tag argument."""
431 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'v1.0')
432 self.assertEqual('refs/tags/v1.0', rrev)
433 self.assertEqual(self.REV_LIST[1], lrev)
434
435 def test_full_commit(self):
436 """Check specific commit argument."""
437 commit = self.REV_LIST[0]
438 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
439 self.assertEqual(commit, rrev)
440 self.assertEqual(commit, lrev)
441
442 def test_partial_commit(self):
443 """Check specific (partial) commit argument."""
444 commit = self.REV_LIST[0][0:20]
445 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
446 self.assertEqual(self.REV_LIST[0], rrev)
447 self.assertEqual(self.REV_LIST[0], lrev)
448
449 def test_unknown(self):
450 """Check unknown ref/commit argument."""
451 with self.assertRaises(wrapper.CloneFailure):
452 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'boooooooya')
453
454
Mike Frysinger3599cc32020-02-29 02:53:41 -0500455class CheckRepoVerify(RepoWrapperTestCase):
456 """Check check_repo_verify behavior."""
457
458 def test_no_verify(self):
459 """Always fail with --no-repo-verify."""
460 self.assertFalse(self.wrapper.check_repo_verify(False))
461
462 def test_gpg_initialized(self):
463 """Should pass if gpg is setup already."""
464 with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=False):
465 self.assertTrue(self.wrapper.check_repo_verify(True))
466
467 def test_need_gpg_setup(self):
468 """Should pass/fail based on gpg setup."""
469 with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=True):
470 with mock.patch.object(self.wrapper, 'SetupGnuPG') as m:
471 m.return_value = True
472 self.assertTrue(self.wrapper.check_repo_verify(True))
473
474 m.return_value = False
475 self.assertFalse(self.wrapper.check_repo_verify(True))
476
477
478class CheckRepoRev(GitCheckoutTestCase):
479 """Check check_repo_rev behavior."""
480
481 def test_verify_works(self):
482 """Should pass when verification passes."""
483 with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True):
484 with mock.patch.object(self.wrapper, 'verify_rev', return_value='12345'):
485 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable')
486 self.assertEqual('refs/heads/stable', rrev)
487 self.assertEqual('12345', lrev)
488
489 def test_verify_fails(self):
490 """Should fail when verification fails."""
491 with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True):
492 with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception):
493 with self.assertRaises(Exception):
494 self.wrapper.check_repo_rev(self.GIT_DIR, 'stable')
495
496 def test_verify_ignore(self):
497 """Should pass when verification is disabled."""
498 with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception):
499 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable', repo_verify=False)
500 self.assertEqual('refs/heads/stable', rrev)
501 self.assertEqual(self.REV_LIST[1], lrev)
502
503
Dan Willemsen745b4ad2015-10-06 15:23:19 -0700504if __name__ == '__main__':
505 unittest.main()