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