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