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