summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/conftest.py4
-rw-r--r--tests/test_editor.py44
-rw-r--r--tests/test_error.py60
-rw-r--r--tests/test_git_command.py222
-rw-r--r--tests/test_git_config.py318
-rw-r--r--tests/test_git_superproject.py782
-rw-r--r--tests/test_git_trace2_event_log.py725
-rw-r--r--tests/test_hooks.py63
-rw-r--r--tests/test_manifest_xml.py1414
-rw-r--r--tests/test_platform_utils.py54
-rw-r--r--tests/test_project.py855
-rw-r--r--tests/test_repo_trace.py68
-rw-r--r--tests/test_ssh.py90
-rw-r--r--tests/test_subcmds.py86
-rw-r--r--tests/test_subcmds_init.py51
-rw-r--r--tests/test_subcmds_sync.py215
-rw-r--r--tests/test_update_manpages.py10
-rw-r--r--tests/test_wrapper.py1029
18 files changed, 3275 insertions, 2815 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index 3e43f6d3..e1a2292a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -21,5 +21,5 @@ import repo_trace
21 21
22@pytest.fixture(autouse=True) 22@pytest.fixture(autouse=True)
23def disable_repo_trace(tmp_path): 23def disable_repo_trace(tmp_path):
24 """Set an environment marker to relax certain strict checks for test code.""" 24 """Set an environment marker to relax certain strict checks for test code.""" # noqa: E501
25 repo_trace._TRACE_FILE = str(tmp_path / 'TRACE_FILE_from_test') 25 repo_trace._TRACE_FILE = str(tmp_path / "TRACE_FILE_from_test")
diff --git a/tests/test_editor.py b/tests/test_editor.py
index cfd4f5ed..8f5d160e 100644
--- a/tests/test_editor.py
+++ b/tests/test_editor.py
@@ -20,37 +20,37 @@ from editor import Editor
20 20
21 21
22class EditorTestCase(unittest.TestCase): 22class EditorTestCase(unittest.TestCase):
23 """Take care of resetting Editor state across tests.""" 23 """Take care of resetting Editor state across tests."""
24 24
25 def setUp(self): 25 def setUp(self):
26 self.setEditor(None) 26 self.setEditor(None)
27 27
28 def tearDown(self): 28 def tearDown(self):
29 self.setEditor(None) 29 self.setEditor(None)
30 30
31 @staticmethod 31 @staticmethod
32 def setEditor(editor): 32 def setEditor(editor):
33 Editor._editor = editor 33 Editor._editor = editor
34 34
35 35
36class GetEditor(EditorTestCase): 36class GetEditor(EditorTestCase):
37 """Check GetEditor behavior.""" 37 """Check GetEditor behavior."""
38 38
39 def test_basic(self): 39 def test_basic(self):
40 """Basic checking of _GetEditor.""" 40 """Basic checking of _GetEditor."""
41 self.setEditor(':') 41 self.setEditor(":")
42 self.assertEqual(':', Editor._GetEditor()) 42 self.assertEqual(":", Editor._GetEditor())
43 43
44 44
45class EditString(EditorTestCase): 45class EditString(EditorTestCase):
46 """Check EditString behavior.""" 46 """Check EditString behavior."""
47 47
48 def test_no_editor(self): 48 def test_no_editor(self):
49 """Check behavior when no editor is available.""" 49 """Check behavior when no editor is available."""
50 self.setEditor(':') 50 self.setEditor(":")
51 self.assertEqual('foo', Editor.EditString('foo')) 51 self.assertEqual("foo", Editor.EditString("foo"))
52 52
53 def test_cat_editor(self): 53 def test_cat_editor(self):
54 """Check behavior when editor is `cat`.""" 54 """Check behavior when editor is `cat`."""
55 self.setEditor('cat') 55 self.setEditor("cat")
56 self.assertEqual('foo', Editor.EditString('foo')) 56 self.assertEqual("foo", Editor.EditString("foo"))
diff --git a/tests/test_error.py b/tests/test_error.py
index 82b00c24..784e2d57 100644
--- a/tests/test_error.py
+++ b/tests/test_error.py
@@ -22,32 +22,34 @@ import error
22 22
23 23
24class PickleTests(unittest.TestCase): 24class PickleTests(unittest.TestCase):
25 """Make sure all our custom exceptions can be pickled.""" 25 """Make sure all our custom exceptions can be pickled."""
26 26
27 def getExceptions(self): 27 def getExceptions(self):
28 """Return all our custom exceptions.""" 28 """Return all our custom exceptions."""
29 for name in dir(error): 29 for name in dir(error):
30 cls = getattr(error, name) 30 cls = getattr(error, name)
31 if isinstance(cls, type) and issubclass(cls, Exception): 31 if isinstance(cls, type) and issubclass(cls, Exception):
32 yield cls 32 yield cls
33 33
34 def testExceptionLookup(self): 34 def testExceptionLookup(self):
35 """Make sure our introspection logic works.""" 35 """Make sure our introspection logic works."""
36 classes = list(self.getExceptions()) 36 classes = list(self.getExceptions())
37 self.assertIn(error.HookError, classes) 37 self.assertIn(error.HookError, classes)
38 # Don't assert the exact number to avoid being a change-detector test. 38 # Don't assert the exact number to avoid being a change-detector test.
39 self.assertGreater(len(classes), 10) 39 self.assertGreater(len(classes), 10)
40 40
41 def testPickle(self): 41 def testPickle(self):
42 """Try to pickle all the exceptions.""" 42 """Try to pickle all the exceptions."""
43 for cls in self.getExceptions(): 43 for cls in self.getExceptions():
44 args = inspect.getfullargspec(cls.__init__).args[1:] 44 args = inspect.getfullargspec(cls.__init__).args[1:]
45 obj = cls(*args) 45 obj = cls(*args)
46 p = pickle.dumps(obj) 46 p = pickle.dumps(obj)
47 try: 47 try:
48 newobj = pickle.loads(p) 48 newobj = pickle.loads(p)
49 except Exception as e: # pylint: disable=broad-except 49 except Exception as e: # pylint: disable=broad-except
50 self.fail('Class %s is unable to be pickled: %s\n' 50 self.fail(
51 'Incomplete super().__init__(...) call?' % (cls, e)) 51 "Class %s is unable to be pickled: %s\n"
52 self.assertIsInstance(newobj, cls) 52 "Incomplete super().__init__(...) call?" % (cls, e)
53 self.assertEqual(str(obj), str(newobj)) 53 )
54 self.assertIsInstance(newobj, cls)
55 self.assertEqual(str(obj), str(newobj))
diff --git a/tests/test_git_command.py b/tests/test_git_command.py
index 96408a23..c4c3a4c5 100644
--- a/tests/test_git_command.py
+++ b/tests/test_git_command.py
@@ -19,138 +19,146 @@ import os
19import unittest 19import unittest
20 20
21try: 21try:
22 from unittest import mock 22 from unittest import mock
23except ImportError: 23except ImportError:
24 import mock 24 import mock
25 25
26import git_command 26import git_command
27import wrapper 27import wrapper
28 28
29 29
30class GitCommandTest(unittest.TestCase): 30class GitCommandTest(unittest.TestCase):
31 """Tests the GitCommand class (via git_command.git).""" 31 """Tests the GitCommand class (via git_command.git)."""
32 32
33 def setUp(self): 33 def setUp(self):
34 def realpath_mock(val):
35 return val
34 36
35 def realpath_mock(val): 37 mock.patch.object(
36 return val 38 os.path, "realpath", side_effect=realpath_mock
39 ).start()
37 40
38 mock.patch.object(os.path, 'realpath', side_effect=realpath_mock).start() 41 def tearDown(self):
42 mock.patch.stopall()
39 43
40 def tearDown(self): 44 def test_alternative_setting_when_matching(self):
41 mock.patch.stopall() 45 r = git_command._build_env(
46 objdir=os.path.join("zap", "objects"), gitdir="zap"
47 )
42 48
43 def test_alternative_setting_when_matching(self): 49 self.assertIsNone(r.get("GIT_ALTERNATE_OBJECT_DIRECTORIES"))
44 r = git_command._build_env( 50 self.assertEqual(
45 objdir = os.path.join('zap', 'objects'), 51 r.get("GIT_OBJECT_DIRECTORY"), os.path.join("zap", "objects")
46 gitdir = 'zap' 52 )
47 )
48 53
49 self.assertIsNone(r.get('GIT_ALTERNATE_OBJECT_DIRECTORIES')) 54 def test_alternative_setting_when_different(self):
50 self.assertEqual(r.get('GIT_OBJECT_DIRECTORY'), os.path.join('zap', 'objects')) 55 r = git_command._build_env(
56 objdir=os.path.join("wow", "objects"), gitdir="zap"
57 )
51 58
52 def test_alternative_setting_when_different(self): 59 self.assertEqual(
53 r = git_command._build_env( 60 r.get("GIT_ALTERNATE_OBJECT_DIRECTORIES"),
54 objdir = os.path.join('wow', 'objects'), 61 os.path.join("zap", "objects"),
55 gitdir = 'zap' 62 )
56 ) 63 self.assertEqual(
57 64 r.get("GIT_OBJECT_DIRECTORY"), os.path.join("wow", "objects")
58 self.assertEqual(r.get('GIT_ALTERNATE_OBJECT_DIRECTORIES'), os.path.join('zap', 'objects')) 65 )
59 self.assertEqual(r.get('GIT_OBJECT_DIRECTORY'), os.path.join('wow', 'objects'))
60 66
61 67
62class GitCallUnitTest(unittest.TestCase): 68class GitCallUnitTest(unittest.TestCase):
63 """Tests the _GitCall class (via git_command.git).""" 69 """Tests the _GitCall class (via git_command.git)."""
64 70
65 def test_version_tuple(self): 71 def test_version_tuple(self):
66 """Check git.version_tuple() handling.""" 72 """Check git.version_tuple() handling."""
67 ver = git_command.git.version_tuple() 73 ver = git_command.git.version_tuple()
68 self.assertIsNotNone(ver) 74 self.assertIsNotNone(ver)
69 75
70 # We don't dive too deep into the values here to avoid having to update 76 # We don't dive too deep into the values here to avoid having to update
71 # whenever git versions change. We do check relative to this min version 77 # whenever git versions change. We do check relative to this min
72 # as this is what `repo` itself requires via MIN_GIT_VERSION. 78 # version as this is what `repo` itself requires via MIN_GIT_VERSION.
73 MIN_GIT_VERSION = (2, 10, 2) 79 MIN_GIT_VERSION = (2, 10, 2)
74 self.assertTrue(isinstance(ver.major, int)) 80 self.assertTrue(isinstance(ver.major, int))
75 self.assertTrue(isinstance(ver.minor, int)) 81 self.assertTrue(isinstance(ver.minor, int))
76 self.assertTrue(isinstance(ver.micro, int)) 82 self.assertTrue(isinstance(ver.micro, int))
77 83
78 self.assertGreater(ver.major, MIN_GIT_VERSION[0] - 1) 84 self.assertGreater(ver.major, MIN_GIT_VERSION[0] - 1)
79 self.assertGreaterEqual(ver.micro, 0) 85 self.assertGreaterEqual(ver.micro, 0)
80 self.assertGreaterEqual(ver.major, 0) 86 self.assertGreaterEqual(ver.major, 0)
81 87
82 self.assertGreaterEqual(ver, MIN_GIT_VERSION) 88 self.assertGreaterEqual(ver, MIN_GIT_VERSION)
83 self.assertLess(ver, (9999, 9999, 9999)) 89 self.assertLess(ver, (9999, 9999, 9999))
84 90
85 self.assertNotEqual('', ver.full) 91 self.assertNotEqual("", ver.full)
86 92
87 93
88class UserAgentUnitTest(unittest.TestCase): 94class UserAgentUnitTest(unittest.TestCase):
89 """Tests the UserAgent function.""" 95 """Tests the UserAgent function."""
90 96
91 def test_smoke_os(self): 97 def test_smoke_os(self):
92 """Make sure UA OS setting returns something useful.""" 98 """Make sure UA OS setting returns something useful."""
93 os_name = git_command.user_agent.os 99 os_name = git_command.user_agent.os
94 # We can't dive too deep because of OS/tool differences, but we can check 100 # We can't dive too deep because of OS/tool differences, but we can
95 # the general form. 101 # check the general form.
96 m = re.match(r'^[^ ]+$', os_name) 102 m = re.match(r"^[^ ]+$", os_name)
97 self.assertIsNotNone(m) 103 self.assertIsNotNone(m)
98 104
99 def test_smoke_repo(self): 105 def test_smoke_repo(self):
100 """Make sure repo UA returns something useful.""" 106 """Make sure repo UA returns something useful."""
101 ua = git_command.user_agent.repo 107 ua = git_command.user_agent.repo
102 # We can't dive too deep because of OS/tool differences, but we can check 108 # We can't dive too deep because of OS/tool differences, but we can
103 # the general form. 109 # check the general form.
104 m = re.match(r'^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+', ua) 110 m = re.match(r"^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+", ua)
105 self.assertIsNotNone(m) 111 self.assertIsNotNone(m)
106 112
107 def test_smoke_git(self): 113 def test_smoke_git(self):
108 """Make sure git UA returns something useful.""" 114 """Make sure git UA returns something useful."""
109 ua = git_command.user_agent.git 115 ua = git_command.user_agent.git
110 # We can't dive too deep because of OS/tool differences, but we can check 116 # We can't dive too deep because of OS/tool differences, but we can
111 # the general form. 117 # check the general form.
112 m = re.match(r'^git/[^ ]+ ([^ ]+) git-repo/[^ ]+', ua) 118 m = re.match(r"^git/[^ ]+ ([^ ]+) git-repo/[^ ]+", ua)
113 self.assertIsNotNone(m) 119 self.assertIsNotNone(m)
114 120
115 121
116class GitRequireTests(unittest.TestCase): 122class GitRequireTests(unittest.TestCase):
117 """Test the git_require helper.""" 123 """Test the git_require helper."""
118 124
119 def setUp(self): 125 def setUp(self):
120 self.wrapper = wrapper.Wrapper() 126 self.wrapper = wrapper.Wrapper()
121 ver = self.wrapper.GitVersion(1, 2, 3, 4) 127 ver = self.wrapper.GitVersion(1, 2, 3, 4)
122 mock.patch.object(git_command.git, 'version_tuple', return_value=ver).start() 128 mock.patch.object(
123 129 git_command.git, "version_tuple", return_value=ver
124 def tearDown(self): 130 ).start()
125 mock.patch.stopall() 131
126 132 def tearDown(self):
127 def test_older_nonfatal(self): 133 mock.patch.stopall()
128 """Test non-fatal require calls with old versions.""" 134
129 self.assertFalse(git_command.git_require((2,))) 135 def test_older_nonfatal(self):
130 self.assertFalse(git_command.git_require((1, 3))) 136 """Test non-fatal require calls with old versions."""
131 self.assertFalse(git_command.git_require((1, 2, 4))) 137 self.assertFalse(git_command.git_require((2,)))
132 self.assertFalse(git_command.git_require((1, 2, 3, 5))) 138 self.assertFalse(git_command.git_require((1, 3)))
133 139 self.assertFalse(git_command.git_require((1, 2, 4)))
134 def test_newer_nonfatal(self): 140 self.assertFalse(git_command.git_require((1, 2, 3, 5)))
135 """Test non-fatal require calls with newer versions.""" 141
136 self.assertTrue(git_command.git_require((0,))) 142 def test_newer_nonfatal(self):
137 self.assertTrue(git_command.git_require((1, 0))) 143 """Test non-fatal require calls with newer versions."""
138 self.assertTrue(git_command.git_require((1, 2, 0))) 144 self.assertTrue(git_command.git_require((0,)))
139 self.assertTrue(git_command.git_require((1, 2, 3, 0))) 145 self.assertTrue(git_command.git_require((1, 0)))
140 146 self.assertTrue(git_command.git_require((1, 2, 0)))
141 def test_equal_nonfatal(self): 147 self.assertTrue(git_command.git_require((1, 2, 3, 0)))
142 """Test require calls with equal values.""" 148
143 self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=False)) 149 def test_equal_nonfatal(self):
144 self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=True)) 150 """Test require calls with equal values."""
145 151 self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=False))
146 def test_older_fatal(self): 152 self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=True))
147 """Test fatal require calls with old versions.""" 153
148 with self.assertRaises(SystemExit) as e: 154 def test_older_fatal(self):
149 git_command.git_require((2,), fail=True) 155 """Test fatal require calls with old versions."""
150 self.assertNotEqual(0, e.code) 156 with self.assertRaises(SystemExit) as e:
151 157 git_command.git_require((2,), fail=True)
152 def test_older_fatal_msg(self): 158 self.assertNotEqual(0, e.code)
153 """Test fatal require calls with old versions and message.""" 159
154 with self.assertRaises(SystemExit) as e: 160 def test_older_fatal_msg(self):
155 git_command.git_require((2,), fail=True, msg='so sad') 161 """Test fatal require calls with old versions and message."""
156 self.assertNotEqual(0, e.code) 162 with self.assertRaises(SystemExit) as e:
163 git_command.git_require((2,), fail=True, msg="so sad")
164 self.assertNotEqual(0, e.code)
diff --git a/tests/test_git_config.py b/tests/test_git_config.py
index 3b0aa8b4..a44dca0f 100644
--- a/tests/test_git_config.py
+++ b/tests/test_git_config.py
@@ -22,167 +22,169 @@ import git_config
22 22
23 23
24def fixture(*paths): 24def fixture(*paths):
25 """Return a path relative to test/fixtures. 25 """Return a path relative to test/fixtures."""
26 """ 26 return os.path.join(os.path.dirname(__file__), "fixtures", *paths)
27 return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
28 27
29 28
30class GitConfigReadOnlyTests(unittest.TestCase): 29class GitConfigReadOnlyTests(unittest.TestCase):
31 """Read-only tests of the GitConfig class.""" 30 """Read-only tests of the GitConfig class."""
32 31
33 def setUp(self): 32 def setUp(self):
34 """Create a GitConfig object using the test.gitconfig fixture. 33 """Create a GitConfig object using the test.gitconfig fixture."""
35 """ 34 config_fixture = fixture("test.gitconfig")
36 config_fixture = fixture('test.gitconfig') 35 self.config = git_config.GitConfig(config_fixture)
37 self.config = git_config.GitConfig(config_fixture) 36
38 37 def test_GetString_with_empty_config_values(self):
39 def test_GetString_with_empty_config_values(self): 38 """
40 """ 39 Test config entries with no value.
41 Test config entries with no value. 40
42 41 [section]
43 [section] 42 empty
44 empty 43
45 44 """
46 """ 45 val = self.config.GetString("section.empty")
47 val = self.config.GetString('section.empty') 46 self.assertEqual(val, None)
48 self.assertEqual(val, None) 47
49 48 def test_GetString_with_true_value(self):
50 def test_GetString_with_true_value(self): 49 """
51 """ 50 Test config entries with a string value.
52 Test config entries with a string value. 51
53 52 [section]
54 [section] 53 nonempty = true
55 nonempty = true 54
56 55 """
57 """ 56 val = self.config.GetString("section.nonempty")
58 val = self.config.GetString('section.nonempty') 57 self.assertEqual(val, "true")
59 self.assertEqual(val, 'true') 58
60 59 def test_GetString_from_missing_file(self):
61 def test_GetString_from_missing_file(self): 60 """
62 """ 61 Test missing config file
63 Test missing config file 62 """
64 """ 63 config_fixture = fixture("not.present.gitconfig")
65 config_fixture = fixture('not.present.gitconfig') 64 config = git_config.GitConfig(config_fixture)
66 config = git_config.GitConfig(config_fixture) 65 val = config.GetString("empty")
67 val = config.GetString('empty') 66 self.assertEqual(val, None)
68 self.assertEqual(val, None) 67
69 68 def test_GetBoolean_undefined(self):
70 def test_GetBoolean_undefined(self): 69 """Test GetBoolean on key that doesn't exist."""
71 """Test GetBoolean on key that doesn't exist.""" 70 self.assertIsNone(self.config.GetBoolean("section.missing"))
72 self.assertIsNone(self.config.GetBoolean('section.missing')) 71
73 72 def test_GetBoolean_invalid(self):
74 def test_GetBoolean_invalid(self): 73 """Test GetBoolean on invalid boolean value."""
75 """Test GetBoolean on invalid boolean value.""" 74 self.assertIsNone(self.config.GetBoolean("section.boolinvalid"))
76 self.assertIsNone(self.config.GetBoolean('section.boolinvalid')) 75
77 76 def test_GetBoolean_true(self):
78 def test_GetBoolean_true(self): 77 """Test GetBoolean on valid true boolean."""
79 """Test GetBoolean on valid true boolean.""" 78 self.assertTrue(self.config.GetBoolean("section.booltrue"))
80 self.assertTrue(self.config.GetBoolean('section.booltrue')) 79
81 80 def test_GetBoolean_false(self):
82 def test_GetBoolean_false(self): 81 """Test GetBoolean on valid false boolean."""
83 """Test GetBoolean on valid false boolean.""" 82 self.assertFalse(self.config.GetBoolean("section.boolfalse"))
84 self.assertFalse(self.config.GetBoolean('section.boolfalse')) 83
85 84 def test_GetInt_undefined(self):
86 def test_GetInt_undefined(self): 85 """Test GetInt on key that doesn't exist."""
87 """Test GetInt on key that doesn't exist.""" 86 self.assertIsNone(self.config.GetInt("section.missing"))
88 self.assertIsNone(self.config.GetInt('section.missing')) 87
89 88 def test_GetInt_invalid(self):
90 def test_GetInt_invalid(self): 89 """Test GetInt on invalid integer value."""
91 """Test GetInt on invalid integer value.""" 90 self.assertIsNone(self.config.GetBoolean("section.intinvalid"))
92 self.assertIsNone(self.config.GetBoolean('section.intinvalid')) 91
93 92 def test_GetInt_valid(self):
94 def test_GetInt_valid(self): 93 """Test GetInt on valid integers."""
95 """Test GetInt on valid integers.""" 94 TESTS = (
96 TESTS = ( 95 ("inthex", 16),
97 ('inthex', 16), 96 ("inthexk", 16384),
98 ('inthexk', 16384), 97 ("int", 10),
99 ('int', 10), 98 ("intk", 10240),
100 ('intk', 10240), 99 ("intm", 10485760),
101 ('intm', 10485760), 100 ("intg", 10737418240),
102 ('intg', 10737418240), 101 )
103 ) 102 for key, value in TESTS:
104 for key, value in TESTS: 103 self.assertEqual(value, self.config.GetInt("section.%s" % (key,)))
105 self.assertEqual(value, self.config.GetInt('section.%s' % (key,)))
106 104
107 105
108class GitConfigReadWriteTests(unittest.TestCase): 106class GitConfigReadWriteTests(unittest.TestCase):
109 """Read/write tests of the GitConfig class.""" 107 """Read/write tests of the GitConfig class."""
110 108
111 def setUp(self): 109 def setUp(self):
112 self.tmpfile = tempfile.NamedTemporaryFile() 110 self.tmpfile = tempfile.NamedTemporaryFile()
113 self.config = self.get_config() 111 self.config = self.get_config()
114 112
115 def get_config(self): 113 def get_config(self):
116 """Get a new GitConfig instance.""" 114 """Get a new GitConfig instance."""
117 return git_config.GitConfig(self.tmpfile.name) 115 return git_config.GitConfig(self.tmpfile.name)
118 116
119 def test_SetString(self): 117 def test_SetString(self):
120 """Test SetString behavior.""" 118 """Test SetString behavior."""
121 # Set a value. 119 # Set a value.
122 self.assertIsNone(self.config.GetString('foo.bar')) 120 self.assertIsNone(self.config.GetString("foo.bar"))
123 self.config.SetString('foo.bar', 'val') 121 self.config.SetString("foo.bar", "val")
124 self.assertEqual('val', self.config.GetString('foo.bar')) 122 self.assertEqual("val", self.config.GetString("foo.bar"))
125 123
126 # Make sure the value was actually written out. 124 # Make sure the value was actually written out.
127 config = self.get_config() 125 config = self.get_config()
128 self.assertEqual('val', config.GetString('foo.bar')) 126 self.assertEqual("val", config.GetString("foo.bar"))
129 127
130 # Update the value. 128 # Update the value.
131 self.config.SetString('foo.bar', 'valll') 129 self.config.SetString("foo.bar", "valll")
132 self.assertEqual('valll', self.config.GetString('foo.bar')) 130 self.assertEqual("valll", self.config.GetString("foo.bar"))
133 config = self.get_config() 131 config = self.get_config()
134 self.assertEqual('valll', config.GetString('foo.bar')) 132 self.assertEqual("valll", config.GetString("foo.bar"))
135 133
136 # Delete the value. 134 # Delete the value.
137 self.config.SetString('foo.bar', None) 135 self.config.SetString("foo.bar", None)
138 self.assertIsNone(self.config.GetString('foo.bar')) 136 self.assertIsNone(self.config.GetString("foo.bar"))
139 config = self.get_config() 137 config = self.get_config()
140 self.assertIsNone(config.GetString('foo.bar')) 138 self.assertIsNone(config.GetString("foo.bar"))
141 139
142 def test_SetBoolean(self): 140 def test_SetBoolean(self):
143 """Test SetBoolean behavior.""" 141 """Test SetBoolean behavior."""
144 # Set a true value. 142 # Set a true value.
145 self.assertIsNone(self.config.GetBoolean('foo.bar')) 143 self.assertIsNone(self.config.GetBoolean("foo.bar"))
146 for val in (True, 1): 144 for val in (True, 1):
147 self.config.SetBoolean('foo.bar', val) 145 self.config.SetBoolean("foo.bar", val)
148 self.assertTrue(self.config.GetBoolean('foo.bar')) 146 self.assertTrue(self.config.GetBoolean("foo.bar"))
149 147
150 # Make sure the value was actually written out. 148 # Make sure the value was actually written out.
151 config = self.get_config() 149 config = self.get_config()
152 self.assertTrue(config.GetBoolean('foo.bar')) 150 self.assertTrue(config.GetBoolean("foo.bar"))
153 self.assertEqual('true', config.GetString('foo.bar')) 151 self.assertEqual("true", config.GetString("foo.bar"))
154 152
155 # Set a false value. 153 # Set a false value.
156 for val in (False, 0): 154 for val in (False, 0):
157 self.config.SetBoolean('foo.bar', val) 155 self.config.SetBoolean("foo.bar", val)
158 self.assertFalse(self.config.GetBoolean('foo.bar')) 156 self.assertFalse(self.config.GetBoolean("foo.bar"))
159 157
160 # Make sure the value was actually written out. 158 # Make sure the value was actually written out.
161 config = self.get_config() 159 config = self.get_config()
162 self.assertFalse(config.GetBoolean('foo.bar')) 160 self.assertFalse(config.GetBoolean("foo.bar"))
163 self.assertEqual('false', config.GetString('foo.bar')) 161 self.assertEqual("false", config.GetString("foo.bar"))
164 162
165 # Delete the value. 163 # Delete the value.
166 self.config.SetBoolean('foo.bar', None) 164 self.config.SetBoolean("foo.bar", None)
167 self.assertIsNone(self.config.GetBoolean('foo.bar')) 165 self.assertIsNone(self.config.GetBoolean("foo.bar"))
168 config = self.get_config() 166 config = self.get_config()
169 self.assertIsNone(config.GetBoolean('foo.bar')) 167 self.assertIsNone(config.GetBoolean("foo.bar"))
170 168
171 def test_GetSyncAnalysisStateData(self): 169 def test_GetSyncAnalysisStateData(self):
172 """Test config entries with a sync state analysis data.""" 170 """Test config entries with a sync state analysis data."""
173 superproject_logging_data = {} 171 superproject_logging_data = {}
174 superproject_logging_data['test'] = False 172 superproject_logging_data["test"] = False
175 options = type('options', (object,), {})() 173 options = type("options", (object,), {})()
176 options.verbose = 'true' 174 options.verbose = "true"
177 options.mp_update = 'false' 175 options.mp_update = "false"
178 TESTS = ( 176 TESTS = (
179 ('superproject.test', 'false'), 177 ("superproject.test", "false"),
180 ('options.verbose', 'true'), 178 ("options.verbose", "true"),
181 ('options.mpupdate', 'false'), 179 ("options.mpupdate", "false"),
182 ('main.version', '1'), 180 ("main.version", "1"),
183 ) 181 )
184 self.config.UpdateSyncAnalysisState(options, superproject_logging_data) 182 self.config.UpdateSyncAnalysisState(options, superproject_logging_data)
185 sync_data = self.config.GetSyncAnalysisStateData() 183 sync_data = self.config.GetSyncAnalysisStateData()
186 for key, value in TESTS: 184 for key, value in TESTS:
187 self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value) 185 self.assertEqual(
188 self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime']) 186 sync_data[f"{git_config.SYNC_STATE_PREFIX}{key}"], value
187 )
188 self.assertTrue(
189 sync_data[f"{git_config.SYNC_STATE_PREFIX}main.synctime"]
190 )
diff --git a/tests/test_git_superproject.py b/tests/test_git_superproject.py
index b9b597a6..eb542c60 100644
--- a/tests/test_git_superproject.py
+++ b/tests/test_git_superproject.py
@@ -28,297 +28,369 @@ from test_manifest_xml import sort_attributes
28 28
29 29
30class SuperprojectTestCase(unittest.TestCase): 30class SuperprojectTestCase(unittest.TestCase):
31 """TestCase for the Superproject module.""" 31 """TestCase for the Superproject module."""
32 32
33 PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID' 33 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID"
34 PARENT_SID_VALUE = 'parent_sid' 34 PARENT_SID_VALUE = "parent_sid"
35 SELF_SID_REGEX = r'repo-\d+T\d+Z-.*' 35 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*"
36 FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX) 36 FULL_SID_REGEX = r"^%s/%s" % (PARENT_SID_VALUE, SELF_SID_REGEX)
37 37
38 def setUp(self): 38 def setUp(self):
39 """Set up superproject every time.""" 39 """Set up superproject every time."""
40 self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests') 40 self.tempdirobj = tempfile.TemporaryDirectory(prefix="repo_tests")
41 self.tempdir = self.tempdirobj.name 41 self.tempdir = self.tempdirobj.name
42 self.repodir = os.path.join(self.tempdir, '.repo') 42 self.repodir = os.path.join(self.tempdir, ".repo")
43 self.manifest_file = os.path.join( 43 self.manifest_file = os.path.join(
44 self.repodir, manifest_xml.MANIFEST_FILE_NAME) 44 self.repodir, manifest_xml.MANIFEST_FILE_NAME
45 os.mkdir(self.repodir) 45 )
46 self.platform = platform.system().lower() 46 os.mkdir(self.repodir)
47 47 self.platform = platform.system().lower()
48 # By default we initialize with the expected case where 48
49 # repo launches us (so GIT_TRACE2_PARENT_SID is set). 49 # By default we initialize with the expected case where
50 env = { 50 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
51 self.PARENT_SID_KEY: self.PARENT_SID_VALUE, 51 env = {
52 } 52 self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
53 self.git_event_log = git_trace2_event_log.EventLog(env=env) 53 }
54 54 self.git_event_log = git_trace2_event_log.EventLog(env=env)
55 # The manifest parsing really wants a git repo currently. 55
56 gitdir = os.path.join(self.repodir, 'manifests.git') 56 # The manifest parsing really wants a git repo currently.
57 os.mkdir(gitdir) 57 gitdir = os.path.join(self.repodir, "manifests.git")
58 with open(os.path.join(gitdir, 'config'), 'w') as fp: 58 os.mkdir(gitdir)
59 fp.write("""[remote "origin"] 59 with open(os.path.join(gitdir, "config"), "w") as fp:
60 fp.write(
61 """[remote "origin"]
60 url = https://localhost:0/manifest 62 url = https://localhost:0/manifest
61""") 63"""
64 )
62 65
63 manifest = self.getXmlManifest(""" 66 manifest = self.getXmlManifest(
67 """
64<manifest> 68<manifest>
65 <remote name="default-remote" fetch="http://localhost" /> 69 <remote name="default-remote" fetch="http://localhost" />
66 <default remote="default-remote" revision="refs/heads/main" /> 70 <default remote="default-remote" revision="refs/heads/main" />
67 <superproject name="superproject"/> 71 <superproject name="superproject"/>
68 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """ 72 <project path="art" name="platform/art" groups="notdefault,platform-"""
73 + self.platform
74 + """
69 " /></manifest> 75 " /></manifest>
70""") 76"""
71 self._superproject = git_superproject.Superproject( 77 )
72 manifest, name='superproject', 78 self._superproject = git_superproject.Superproject(
73 remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'), 79 manifest,
74 revision='refs/heads/main') 80 name="superproject",
75 81 remote=manifest.remotes.get("default-remote").ToRemoteSpec(
76 def tearDown(self): 82 "superproject"
77 """Tear down superproject every time.""" 83 ),
78 self.tempdirobj.cleanup() 84 revision="refs/heads/main",
79 85 )
80 def getXmlManifest(self, data): 86
81 """Helper to initialize a manifest for testing.""" 87 def tearDown(self):
82 with open(self.manifest_file, 'w') as fp: 88 """Tear down superproject every time."""
83 fp.write(data) 89 self.tempdirobj.cleanup()
84 return manifest_xml.XmlManifest(self.repodir, self.manifest_file) 90
85 91 def getXmlManifest(self, data):
86 def verifyCommonKeys(self, log_entry, expected_event_name, full_sid=True): 92 """Helper to initialize a manifest for testing."""
87 """Helper function to verify common event log keys.""" 93 with open(self.manifest_file, "w") as fp:
88 self.assertIn('event', log_entry) 94 fp.write(data)
89 self.assertIn('sid', log_entry) 95 return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
90 self.assertIn('thread', log_entry) 96
91 self.assertIn('time', log_entry) 97 def verifyCommonKeys(self, log_entry, expected_event_name, full_sid=True):
92 98 """Helper function to verify common event log keys."""
93 # Do basic data format validation. 99 self.assertIn("event", log_entry)
94 self.assertEqual(expected_event_name, log_entry['event']) 100 self.assertIn("sid", log_entry)
95 if full_sid: 101 self.assertIn("thread", log_entry)
96 self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX) 102 self.assertIn("time", log_entry)
97 else: 103
98 self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX) 104 # Do basic data format validation.
99 self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$') 105 self.assertEqual(expected_event_name, log_entry["event"])
100 106 if full_sid:
101 def readLog(self, log_path): 107 self.assertRegex(log_entry["sid"], self.FULL_SID_REGEX)
102 """Helper function to read log data into a list.""" 108 else:
103 log_data = [] 109 self.assertRegex(log_entry["sid"], self.SELF_SID_REGEX)
104 with open(log_path, mode='rb') as f: 110 self.assertRegex(log_entry["time"], r"^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$")
105 for line in f: 111
106 log_data.append(json.loads(line)) 112 def readLog(self, log_path):
107 return log_data 113 """Helper function to read log data into a list."""
108 114 log_data = []
109 def verifyErrorEvent(self): 115 with open(log_path, mode="rb") as f:
110 """Helper to verify that error event is written.""" 116 for line in f:
111 117 log_data.append(json.loads(line))
112 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: 118 return log_data
113 log_path = self.git_event_log.Write(path=tempdir) 119
114 self.log_data = self.readLog(log_path) 120 def verifyErrorEvent(self):
115 121 """Helper to verify that error event is written."""
116 self.assertEqual(len(self.log_data), 2) 122
117 error_event = self.log_data[1] 123 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
118 self.verifyCommonKeys(self.log_data[0], expected_event_name='version') 124 log_path = self.git_event_log.Write(path=tempdir)
119 self.verifyCommonKeys(error_event, expected_event_name='error') 125 self.log_data = self.readLog(log_path)
120 # Check for 'error' event specific fields. 126
121 self.assertIn('msg', error_event) 127 self.assertEqual(len(self.log_data), 2)
122 self.assertIn('fmt', error_event) 128 error_event = self.log_data[1]
123 129 self.verifyCommonKeys(self.log_data[0], expected_event_name="version")
124 def test_superproject_get_superproject_no_superproject(self): 130 self.verifyCommonKeys(error_event, expected_event_name="error")
125 """Test with no url.""" 131 # Check for 'error' event specific fields.
126 manifest = self.getXmlManifest(""" 132 self.assertIn("msg", error_event)
133 self.assertIn("fmt", error_event)
134
135 def test_superproject_get_superproject_no_superproject(self):
136 """Test with no url."""
137 manifest = self.getXmlManifest(
138 """
127<manifest> 139<manifest>
128</manifest> 140</manifest>
129""") 141"""
130 self.assertIsNone(manifest.superproject) 142 )
131 143 self.assertIsNone(manifest.superproject)
132 def test_superproject_get_superproject_invalid_url(self): 144
133 """Test with an invalid url.""" 145 def test_superproject_get_superproject_invalid_url(self):
134 manifest = self.getXmlManifest(""" 146 """Test with an invalid url."""
147 manifest = self.getXmlManifest(
148 """
135<manifest> 149<manifest>
136 <remote name="test-remote" fetch="localhost" /> 150 <remote name="test-remote" fetch="localhost" />
137 <default remote="test-remote" revision="refs/heads/main" /> 151 <default remote="test-remote" revision="refs/heads/main" />
138 <superproject name="superproject"/> 152 <superproject name="superproject"/>
139</manifest> 153</manifest>
140""") 154"""
141 superproject = git_superproject.Superproject( 155 )
142 manifest, name='superproject', 156 superproject = git_superproject.Superproject(
143 remote=manifest.remotes.get('test-remote').ToRemoteSpec('superproject'), 157 manifest,
144 revision='refs/heads/main') 158 name="superproject",
145 sync_result = superproject.Sync(self.git_event_log) 159 remote=manifest.remotes.get("test-remote").ToRemoteSpec(
146 self.assertFalse(sync_result.success) 160 "superproject"
147 self.assertTrue(sync_result.fatal) 161 ),
148 162 revision="refs/heads/main",
149 def test_superproject_get_superproject_invalid_branch(self): 163 )
150 """Test with an invalid branch.""" 164 sync_result = superproject.Sync(self.git_event_log)
151 manifest = self.getXmlManifest(""" 165 self.assertFalse(sync_result.success)
166 self.assertTrue(sync_result.fatal)
167
168 def test_superproject_get_superproject_invalid_branch(self):
169 """Test with an invalid branch."""
170 manifest = self.getXmlManifest(
171 """
152<manifest> 172<manifest>
153 <remote name="test-remote" fetch="localhost" /> 173 <remote name="test-remote" fetch="localhost" />
154 <default remote="test-remote" revision="refs/heads/main" /> 174 <default remote="test-remote" revision="refs/heads/main" />
155 <superproject name="superproject"/> 175 <superproject name="superproject"/>
156</manifest> 176</manifest>
157""") 177"""
158 self._superproject = git_superproject.Superproject( 178 )
159 manifest, name='superproject', 179 self._superproject = git_superproject.Superproject(
160 remote=manifest.remotes.get('test-remote').ToRemoteSpec('superproject'), 180 manifest,
161 revision='refs/heads/main') 181 name="superproject",
162 with mock.patch.object(self._superproject, '_branch', 'junk'): 182 remote=manifest.remotes.get("test-remote").ToRemoteSpec(
163 sync_result = self._superproject.Sync(self.git_event_log) 183 "superproject"
164 self.assertFalse(sync_result.success) 184 ),
165 self.assertTrue(sync_result.fatal) 185 revision="refs/heads/main",
166 self.verifyErrorEvent() 186 )
167 187 with mock.patch.object(self._superproject, "_branch", "junk"):
168 def test_superproject_get_superproject_mock_init(self): 188 sync_result = self._superproject.Sync(self.git_event_log)
169 """Test with _Init failing.""" 189 self.assertFalse(sync_result.success)
170 with mock.patch.object(self._superproject, '_Init', return_value=False): 190 self.assertTrue(sync_result.fatal)
171 sync_result = self._superproject.Sync(self.git_event_log) 191 self.verifyErrorEvent()
172 self.assertFalse(sync_result.success) 192
173 self.assertTrue(sync_result.fatal) 193 def test_superproject_get_superproject_mock_init(self):
174 194 """Test with _Init failing."""
175 def test_superproject_get_superproject_mock_fetch(self): 195 with mock.patch.object(self._superproject, "_Init", return_value=False):
176 """Test with _Fetch failing.""" 196 sync_result = self._superproject.Sync(self.git_event_log)
177 with mock.patch.object(self._superproject, '_Init', return_value=True): 197 self.assertFalse(sync_result.success)
178 os.mkdir(self._superproject._superproject_path) 198 self.assertTrue(sync_result.fatal)
179 with mock.patch.object(self._superproject, '_Fetch', return_value=False): 199
180 sync_result = self._superproject.Sync(self.git_event_log) 200 def test_superproject_get_superproject_mock_fetch(self):
181 self.assertFalse(sync_result.success) 201 """Test with _Fetch failing."""
182 self.assertTrue(sync_result.fatal) 202 with mock.patch.object(self._superproject, "_Init", return_value=True):
183 203 os.mkdir(self._superproject._superproject_path)
184 def test_superproject_get_all_project_commit_ids_mock_ls_tree(self): 204 with mock.patch.object(
185 """Test with LsTree being a mock.""" 205 self._superproject, "_Fetch", return_value=False
186 data = ('120000 blob 158258bdf146f159218e2b90f8b699c4d85b5804\tAndroid.bp\x00' 206 ):
187 '160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00' 207 sync_result = self._superproject.Sync(self.git_event_log)
188 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00' 208 self.assertFalse(sync_result.success)
189 '120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00' 209 self.assertTrue(sync_result.fatal)
190 '160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00') 210
191 with mock.patch.object(self._superproject, '_Init', return_value=True): 211 def test_superproject_get_all_project_commit_ids_mock_ls_tree(self):
192 with mock.patch.object(self._superproject, '_Fetch', return_value=True): 212 """Test with LsTree being a mock."""
193 with mock.patch.object(self._superproject, '_LsTree', return_value=data): 213 data = (
194 commit_ids_result = self._superproject._GetAllProjectsCommitIds() 214 "120000 blob 158258bdf146f159218e2b90f8b699c4d85b5804\tAndroid.bp\x00"
195 self.assertEqual(commit_ids_result.commit_ids, { 215 "160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00"
196 'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea', 216 "160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00"
197 'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06', 217 "120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00"
198 'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928' 218 "160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00"
199 }) 219 )
200 self.assertFalse(commit_ids_result.fatal) 220 with mock.patch.object(self._superproject, "_Init", return_value=True):
201 221 with mock.patch.object(
202 def test_superproject_write_manifest_file(self): 222 self._superproject, "_Fetch", return_value=True
203 """Test with writing manifest to a file after setting revisionId.""" 223 ):
204 self.assertEqual(len(self._superproject._manifest.projects), 1) 224 with mock.patch.object(
205 project = self._superproject._manifest.projects[0] 225 self._superproject, "_LsTree", return_value=data
206 project.SetRevisionId('ABCDEF') 226 ):
207 # Create temporary directory so that it can write the file. 227 commit_ids_result = (
208 os.mkdir(self._superproject._superproject_path) 228 self._superproject._GetAllProjectsCommitIds()
209 manifest_path = self._superproject._WriteManifestFile() 229 )
210 self.assertIsNotNone(manifest_path) 230 self.assertEqual(
211 with open(manifest_path, 'r') as fp: 231 commit_ids_result.commit_ids,
212 manifest_xml_data = fp.read() 232 {
213 self.assertEqual( 233 "art": "2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea",
214 sort_attributes(manifest_xml_data), 234 "bootable/recovery": "e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06",
215 '<?xml version="1.0" ?><manifest>' 235 "build/bazel": "ade9b7a0d874e25fff4bf2552488825c6f111928",
216 '<remote fetch="http://localhost" name="default-remote"/>' 236 },
217 '<default remote="default-remote" revision="refs/heads/main"/>' 237 )
218 '<project groups="notdefault,platform-' + self.platform + '" ' 238 self.assertFalse(commit_ids_result.fatal)
219 'name="platform/art" path="art" revision="ABCDEF" upstream="refs/heads/main"/>' 239
220 '<superproject name="superproject"/>' 240 def test_superproject_write_manifest_file(self):
221 '</manifest>') 241 """Test with writing manifest to a file after setting revisionId."""
222 242 self.assertEqual(len(self._superproject._manifest.projects), 1)
223 def test_superproject_update_project_revision_id(self): 243 project = self._superproject._manifest.projects[0]
224 """Test with LsTree being a mock.""" 244 project.SetRevisionId("ABCDEF")
225 self.assertEqual(len(self._superproject._manifest.projects), 1) 245 # Create temporary directory so that it can write the file.
226 projects = self._superproject._manifest.projects 246 os.mkdir(self._superproject._superproject_path)
227 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00' 247 manifest_path = self._superproject._WriteManifestFile()
228 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00') 248 self.assertIsNotNone(manifest_path)
229 with mock.patch.object(self._superproject, '_Init', return_value=True): 249 with open(manifest_path, "r") as fp:
230 with mock.patch.object(self._superproject, '_Fetch', return_value=True):
231 with mock.patch.object(self._superproject,
232 '_LsTree',
233 return_value=data):
234 # Create temporary directory so that it can write the file.
235 os.mkdir(self._superproject._superproject_path)
236 update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log)
237 self.assertIsNotNone(update_result.manifest_path)
238 self.assertFalse(update_result.fatal)
239 with open(update_result.manifest_path, 'r') as fp:
240 manifest_xml_data = fp.read() 250 manifest_xml_data = fp.read()
241 self.assertEqual( 251 self.assertEqual(
242 sort_attributes(manifest_xml_data), 252 sort_attributes(manifest_xml_data),
243 '<?xml version="1.0" ?><manifest>' 253 '<?xml version="1.0" ?><manifest>'
244 '<remote fetch="http://localhost" name="default-remote"/>' 254 '<remote fetch="http://localhost" name="default-remote"/>'
245 '<default remote="default-remote" revision="refs/heads/main"/>' 255 '<default remote="default-remote" revision="refs/heads/main"/>'
246 '<project groups="notdefault,platform-' + self.platform + '" ' 256 '<project groups="notdefault,platform-' + self.platform + '" '
247 'name="platform/art" path="art" ' 257 'name="platform/art" path="art" revision="ABCDEF" upstream="refs/heads/main"/>'
248 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>' 258 '<superproject name="superproject"/>'
249 '<superproject name="superproject"/>' 259 "</manifest>",
250 '</manifest>') 260 )
251 261
252 def test_superproject_update_project_revision_id_no_superproject_tag(self): 262 def test_superproject_update_project_revision_id(self):
253 """Test update of commit ids of a manifest without superproject tag.""" 263 """Test with LsTree being a mock."""
254 manifest = self.getXmlManifest(""" 264 self.assertEqual(len(self._superproject._manifest.projects), 1)
265 projects = self._superproject._manifest.projects
266 data = (
267 "160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00"
268 "160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00"
269 )
270 with mock.patch.object(self._superproject, "_Init", return_value=True):
271 with mock.patch.object(
272 self._superproject, "_Fetch", return_value=True
273 ):
274 with mock.patch.object(
275 self._superproject, "_LsTree", return_value=data
276 ):
277 # Create temporary directory so that it can write the file.
278 os.mkdir(self._superproject._superproject_path)
279 update_result = self._superproject.UpdateProjectsRevisionId(
280 projects, self.git_event_log
281 )
282 self.assertIsNotNone(update_result.manifest_path)
283 self.assertFalse(update_result.fatal)
284 with open(update_result.manifest_path, "r") as fp:
285 manifest_xml_data = fp.read()
286 self.assertEqual(
287 sort_attributes(manifest_xml_data),
288 '<?xml version="1.0" ?><manifest>'
289 '<remote fetch="http://localhost" name="default-remote"/>'
290 '<default remote="default-remote" revision="refs/heads/main"/>'
291 '<project groups="notdefault,platform-'
292 + self.platform
293 + '" '
294 'name="platform/art" path="art" '
295 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
296 '<superproject name="superproject"/>'
297 "</manifest>",
298 )
299
300 def test_superproject_update_project_revision_id_no_superproject_tag(self):
301 """Test update of commit ids of a manifest without superproject tag."""
302 manifest = self.getXmlManifest(
303 """
255<manifest> 304<manifest>
256 <remote name="default-remote" fetch="http://localhost" /> 305 <remote name="default-remote" fetch="http://localhost" />
257 <default remote="default-remote" revision="refs/heads/main" /> 306 <default remote="default-remote" revision="refs/heads/main" />
258 <project name="test-name"/> 307 <project name="test-name"/>
259</manifest> 308</manifest>
260""") 309"""
261 self.maxDiff = None 310 )
262 self.assertIsNone(manifest.superproject) 311 self.maxDiff = None
263 self.assertEqual( 312 self.assertIsNone(manifest.superproject)
264 sort_attributes(manifest.ToXml().toxml()), 313 self.assertEqual(
265 '<?xml version="1.0" ?><manifest>' 314 sort_attributes(manifest.ToXml().toxml()),
266 '<remote fetch="http://localhost" name="default-remote"/>' 315 '<?xml version="1.0" ?><manifest>'
267 '<default remote="default-remote" revision="refs/heads/main"/>' 316 '<remote fetch="http://localhost" name="default-remote"/>'
268 '<project name="test-name"/>' 317 '<default remote="default-remote" revision="refs/heads/main"/>'
269 '</manifest>') 318 '<project name="test-name"/>'
270 319 "</manifest>",
271 def test_superproject_update_project_revision_id_from_local_manifest_group(self): 320 )
272 """Test update of commit ids of a manifest that have local manifest no superproject group.""" 321
273 local_group = manifest_xml.LOCAL_MANIFEST_GROUP_PREFIX + ':local' 322 def test_superproject_update_project_revision_id_from_local_manifest_group(
274 manifest = self.getXmlManifest(""" 323 self,
324 ):
325 """Test update of commit ids of a manifest that have local manifest no superproject group."""
326 local_group = manifest_xml.LOCAL_MANIFEST_GROUP_PREFIX + ":local"
327 manifest = self.getXmlManifest(
328 """
275<manifest> 329<manifest>
276 <remote name="default-remote" fetch="http://localhost" /> 330 <remote name="default-remote" fetch="http://localhost" />
277 <remote name="goog" fetch="http://localhost2" /> 331 <remote name="goog" fetch="http://localhost2" />
278 <default remote="default-remote" revision="refs/heads/main" /> 332 <default remote="default-remote" revision="refs/heads/main" />
279 <superproject name="superproject"/> 333 <superproject name="superproject"/>
280 <project path="vendor/x" name="platform/vendor/x" remote="goog" 334 <project path="vendor/x" name="platform/vendor/x" remote="goog"
281 groups=\"""" + local_group + """ 335 groups=\""""
336 + local_group
337 + """
282 " revision="master-with-vendor" clone-depth="1" /> 338 " revision="master-with-vendor" clone-depth="1" />
283 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """ 339 <project path="art" name="platform/art" groups="notdefault,platform-"""
340 + self.platform
341 + """
284 " /></manifest> 342 " /></manifest>
285""") 343"""
286 self.maxDiff = None 344 )
287 self._superproject = git_superproject.Superproject( 345 self.maxDiff = None
288 manifest, name='superproject', 346 self._superproject = git_superproject.Superproject(
289 remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'), 347 manifest,
290 revision='refs/heads/main') 348 name="superproject",
291 self.assertEqual(len(self._superproject._manifest.projects), 2) 349 remote=manifest.remotes.get("default-remote").ToRemoteSpec(
292 projects = self._superproject._manifest.projects 350 "superproject"
293 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00') 351 ),
294 with mock.patch.object(self._superproject, '_Init', return_value=True): 352 revision="refs/heads/main",
295 with mock.patch.object(self._superproject, '_Fetch', return_value=True): 353 )
296 with mock.patch.object(self._superproject, 354 self.assertEqual(len(self._superproject._manifest.projects), 2)
297 '_LsTree', 355 projects = self._superproject._manifest.projects
298 return_value=data): 356 data = "160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00"
299 # Create temporary directory so that it can write the file. 357 with mock.patch.object(self._superproject, "_Init", return_value=True):
300 os.mkdir(self._superproject._superproject_path) 358 with mock.patch.object(
301 update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log) 359 self._superproject, "_Fetch", return_value=True
302 self.assertIsNotNone(update_result.manifest_path) 360 ):
303 self.assertFalse(update_result.fatal) 361 with mock.patch.object(
304 with open(update_result.manifest_path, 'r') as fp: 362 self._superproject, "_LsTree", return_value=data
305 manifest_xml_data = fp.read() 363 ):
306 # Verify platform/vendor/x's project revision hasn't changed. 364 # Create temporary directory so that it can write the file.
307 self.assertEqual( 365 os.mkdir(self._superproject._superproject_path)
308 sort_attributes(manifest_xml_data), 366 update_result = self._superproject.UpdateProjectsRevisionId(
309 '<?xml version="1.0" ?><manifest>' 367 projects, self.git_event_log
310 '<remote fetch="http://localhost" name="default-remote"/>' 368 )
311 '<remote fetch="http://localhost2" name="goog"/>' 369 self.assertIsNotNone(update_result.manifest_path)
312 '<default remote="default-remote" revision="refs/heads/main"/>' 370 self.assertFalse(update_result.fatal)
313 '<project groups="notdefault,platform-' + self.platform + '" ' 371 with open(update_result.manifest_path, "r") as fp:
314 'name="platform/art" path="art" ' 372 manifest_xml_data = fp.read()
315 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>' 373 # Verify platform/vendor/x's project revision hasn't
316 '<superproject name="superproject"/>' 374 # changed.
317 '</manifest>') 375 self.assertEqual(
318 376 sort_attributes(manifest_xml_data),
319 def test_superproject_update_project_revision_id_with_pinned_manifest(self): 377 '<?xml version="1.0" ?><manifest>'
320 """Test update of commit ids of a pinned manifest.""" 378 '<remote fetch="http://localhost" name="default-remote"/>'
321 manifest = self.getXmlManifest(""" 379 '<remote fetch="http://localhost2" name="goog"/>'
380 '<default remote="default-remote" revision="refs/heads/main"/>'
381 '<project groups="notdefault,platform-'
382 + self.platform
383 + '" '
384 'name="platform/art" path="art" '
385 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
386 '<superproject name="superproject"/>'
387 "</manifest>",
388 )
389
390 def test_superproject_update_project_revision_id_with_pinned_manifest(self):
391 """Test update of commit ids of a pinned manifest."""
392 manifest = self.getXmlManifest(
393 """
322<manifest> 394<manifest>
323 <remote name="default-remote" fetch="http://localhost" /> 395 <remote name="default-remote" fetch="http://localhost" />
324 <default remote="default-remote" revision="refs/heads/main" /> 396 <default remote="default-remote" revision="refs/heads/main" />
@@ -326,80 +398,132 @@ class SuperprojectTestCase(unittest.TestCase):
326 <project path="vendor/x" name="platform/vendor/x" revision="" /> 398 <project path="vendor/x" name="platform/vendor/x" revision="" />
327 <project path="vendor/y" name="platform/vendor/y" 399 <project path="vendor/y" name="platform/vendor/y"
328 revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f" /> 400 revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f" />
329 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """ 401 <project path="art" name="platform/art" groups="notdefault,platform-"""
402 + self.platform
403 + """
330 " /></manifest> 404 " /></manifest>
331""") 405"""
332 self.maxDiff = None 406 )
333 self._superproject = git_superproject.Superproject( 407 self.maxDiff = None
334 manifest, name='superproject', 408 self._superproject = git_superproject.Superproject(
335 remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'), 409 manifest,
336 revision='refs/heads/main') 410 name="superproject",
337 self.assertEqual(len(self._superproject._manifest.projects), 3) 411 remote=manifest.remotes.get("default-remote").ToRemoteSpec(
338 projects = self._superproject._manifest.projects 412 "superproject"
339 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00' 413 ),
340 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tvendor/x\x00') 414 revision="refs/heads/main",
341 with mock.patch.object(self._superproject, '_Init', return_value=True): 415 )
342 with mock.patch.object(self._superproject, '_Fetch', return_value=True): 416 self.assertEqual(len(self._superproject._manifest.projects), 3)
343 with mock.patch.object(self._superproject, 417 projects = self._superproject._manifest.projects
344 '_LsTree', 418 data = (
345 return_value=data): 419 "160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00"
346 # Create temporary directory so that it can write the file. 420 "160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tvendor/x\x00"
347 os.mkdir(self._superproject._superproject_path) 421 )
348 update_result = self._superproject.UpdateProjectsRevisionId(projects, self.git_event_log) 422 with mock.patch.object(self._superproject, "_Init", return_value=True):
349 self.assertIsNotNone(update_result.manifest_path) 423 with mock.patch.object(
350 self.assertFalse(update_result.fatal) 424 self._superproject, "_Fetch", return_value=True
351 with open(update_result.manifest_path, 'r') as fp: 425 ):
352 manifest_xml_data = fp.read() 426 with mock.patch.object(
353 # Verify platform/vendor/x's project revision hasn't changed. 427 self._superproject, "_LsTree", return_value=data
354 self.assertEqual( 428 ):
355 sort_attributes(manifest_xml_data), 429 # Create temporary directory so that it can write the file.
356 '<?xml version="1.0" ?><manifest>' 430 os.mkdir(self._superproject._superproject_path)
357 '<remote fetch="http://localhost" name="default-remote"/>' 431 update_result = self._superproject.UpdateProjectsRevisionId(
358 '<default remote="default-remote" revision="refs/heads/main"/>' 432 projects, self.git_event_log
359 '<project groups="notdefault,platform-' + self.platform + '" ' 433 )
360 'name="platform/art" path="art" ' 434 self.assertIsNotNone(update_result.manifest_path)
361 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>' 435 self.assertFalse(update_result.fatal)
362 '<project name="platform/vendor/x" path="vendor/x" ' 436 with open(update_result.manifest_path, "r") as fp:
363 'revision="e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06" upstream="refs/heads/main"/>' 437 manifest_xml_data = fp.read()
364 '<project name="platform/vendor/y" path="vendor/y" ' 438 # Verify platform/vendor/x's project revision hasn't
365 'revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f"/>' 439 # changed.
366 '<superproject name="superproject"/>' 440 self.assertEqual(
367 '</manifest>') 441 sort_attributes(manifest_xml_data),
368 442 '<?xml version="1.0" ?><manifest>'
369 def test_Fetch(self): 443 '<remote fetch="http://localhost" name="default-remote"/>'
370 manifest = self.getXmlManifest(""" 444 '<default remote="default-remote" revision="refs/heads/main"/>'
445 '<project groups="notdefault,platform-'
446 + self.platform
447 + '" '
448 'name="platform/art" path="art" '
449 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
450 '<project name="platform/vendor/x" path="vendor/x" '
451 'revision="e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06" upstream="refs/heads/main"/>'
452 '<project name="platform/vendor/y" path="vendor/y" '
453 'revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f"/>'
454 '<superproject name="superproject"/>'
455 "</manifest>",
456 )
457
458 def test_Fetch(self):
459 manifest = self.getXmlManifest(
460 """
371<manifest> 461<manifest>
372 <remote name="default-remote" fetch="http://localhost" /> 462 <remote name="default-remote" fetch="http://localhost" />
373 <default remote="default-remote" revision="refs/heads/main" /> 463 <default remote="default-remote" revision="refs/heads/main" />
374 <superproject name="superproject"/> 464 <superproject name="superproject"/>
375 " /></manifest> 465 " /></manifest>
376""") 466"""
377 self.maxDiff = None 467 )
378 self._superproject = git_superproject.Superproject( 468 self.maxDiff = None
379 manifest, name='superproject', 469 self._superproject = git_superproject.Superproject(
380 remote=manifest.remotes.get('default-remote').ToRemoteSpec('superproject'), 470 manifest,
381 revision='refs/heads/main') 471 name="superproject",
382 os.mkdir(self._superproject._superproject_path) 472 remote=manifest.remotes.get("default-remote").ToRemoteSpec(
383 os.mkdir(self._superproject._work_git) 473 "superproject"
384 with mock.patch.object(self._superproject, '_Init', return_value=True): 474 ),
385 with mock.patch('git_superproject.GitCommand', autospec=True) as mock_git_command: 475 revision="refs/heads/main",
386 with mock.patch('git_superproject.GitRefs.get', autospec=True) as mock_git_refs: 476 )
387 instance = mock_git_command.return_value 477 os.mkdir(self._superproject._superproject_path)
388 instance.Wait.return_value = 0 478 os.mkdir(self._superproject._work_git)
389 mock_git_refs.side_effect = ['', '1234'] 479 with mock.patch.object(self._superproject, "_Init", return_value=True):
390 480 with mock.patch(
391 self.assertTrue(self._superproject._Fetch()) 481 "git_superproject.GitCommand", autospec=True
392 self.assertEqual(mock_git_command.call_args.args,(None, [ 482 ) as mock_git_command:
393 'fetch', 'http://localhost/superproject', '--depth', '1', 483 with mock.patch(
394 '--force', '--no-tags', '--filter', 'blob:none', 484 "git_superproject.GitRefs.get", autospec=True
395 'refs/heads/main:refs/heads/main' 485 ) as mock_git_refs:
396 ])) 486 instance = mock_git_command.return_value
397 487 instance.Wait.return_value = 0
398 # If branch for revision exists, set as --negotiation-tip. 488 mock_git_refs.side_effect = ["", "1234"]
399 self.assertTrue(self._superproject._Fetch()) 489
400 self.assertEqual(mock_git_command.call_args.args,(None, [ 490 self.assertTrue(self._superproject._Fetch())
401 'fetch', 'http://localhost/superproject', '--depth', '1', 491 self.assertEqual(
402 '--force', '--no-tags', '--filter', 'blob:none', 492 mock_git_command.call_args.args,
403 '--negotiation-tip', '1234', 493 (
404 'refs/heads/main:refs/heads/main' 494 None,
405 ])) 495 [
496 "fetch",
497 "http://localhost/superproject",
498 "--depth",
499 "1",
500 "--force",
501 "--no-tags",
502 "--filter",
503 "blob:none",
504 "refs/heads/main:refs/heads/main",
505 ],
506 ),
507 )
508
509 # If branch for revision exists, set as --negotiation-tip.
510 self.assertTrue(self._superproject._Fetch())
511 self.assertEqual(
512 mock_git_command.call_args.args,
513 (
514 None,
515 [
516 "fetch",
517 "http://localhost/superproject",
518 "--depth",
519 "1",
520 "--force",
521 "--no-tags",
522 "--filter",
523 "blob:none",
524 "--negotiation-tip",
525 "1234",
526 "refs/heads/main:refs/heads/main",
527 ],
528 ),
529 )
diff --git a/tests/test_git_trace2_event_log.py b/tests/test_git_trace2_event_log.py
index 7e7dfb7a..a6078d38 100644
--- a/tests/test_git_trace2_event_log.py
+++ b/tests/test_git_trace2_event_log.py
@@ -27,361 +27,382 @@ import platform_utils
27 27
28 28
29def serverLoggingThread(socket_path, server_ready, received_traces): 29def serverLoggingThread(socket_path, server_ready, received_traces):
30 """Helper function to receive logs over a Unix domain socket. 30 """Helper function to receive logs over a Unix domain socket.
31
32 Appends received messages on the provided socket and appends to received_traces.
33
34 Args:
35 socket_path: path to a Unix domain socket on which to listen for traces
36 server_ready: a threading.Condition used to signal to the caller that this thread is ready to
37 accept connections
38 received_traces: a list to which received traces will be appended (after decoding to a utf-8
39 string).
40 """
41 platform_utils.remove(socket_path, missing_ok=True)
42 data = b''
43 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
44 sock.bind(socket_path)
45 sock.listen(0)
46 with server_ready:
47 server_ready.notify()
48 with sock.accept()[0] as conn:
49 while True:
50 recved = conn.recv(4096)
51 if not recved:
52 break
53 data += recved
54 received_traces.extend(data.decode('utf-8').splitlines())
55 31
32 Appends received messages on the provided socket and appends to
33 received_traces.
56 34
57class EventLogTestCase(unittest.TestCase): 35 Args:
58 """TestCase for the EventLog module.""" 36 socket_path: path to a Unix domain socket on which to listen for traces
59 37 server_ready: a threading.Condition used to signal to the caller that
60 PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID' 38 this thread is ready to accept connections
61 PARENT_SID_VALUE = 'parent_sid' 39 received_traces: a list to which received traces will be appended (after
62 SELF_SID_REGEX = r'repo-\d+T\d+Z-.*' 40 decoding to a utf-8 string).
63 FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX)
64
65 def setUp(self):
66 """Load the event_log module every time."""
67 self._event_log_module = None
68 # By default we initialize with the expected case where
69 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
70 env = {
71 self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
72 }
73 self._event_log_module = git_trace2_event_log.EventLog(env=env)
74 self._log_data = None
75
76 def verifyCommonKeys(self, log_entry, expected_event_name=None, full_sid=True):
77 """Helper function to verify common event log keys."""
78 self.assertIn('event', log_entry)
79 self.assertIn('sid', log_entry)
80 self.assertIn('thread', log_entry)
81 self.assertIn('time', log_entry)
82
83 # Do basic data format validation.
84 if expected_event_name:
85 self.assertEqual(expected_event_name, log_entry['event'])
86 if full_sid:
87 self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
88 else:
89 self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX)
90 self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$')
91
92 def readLog(self, log_path):
93 """Helper function to read log data into a list."""
94 log_data = []
95 with open(log_path, mode='rb') as f:
96 for line in f:
97 log_data.append(json.loads(line))
98 return log_data
99
100 def remove_prefix(self, s, prefix):
101 """Return a copy string after removing |prefix| from |s|, if present or the original string."""
102 if s.startswith(prefix):
103 return s[len(prefix):]
104 else:
105 return s
106
107 def test_initial_state_with_parent_sid(self):
108 """Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
109 self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX)
110
111 def test_initial_state_no_parent_sid(self):
112 """Test initial state when 'GIT_TRACE2_PARENT_SID' is not set."""
113 # Setup an empty environment dict (no parent sid).
114 self._event_log_module = git_trace2_event_log.EventLog(env={})
115 self.assertRegex(self._event_log_module.full_sid, self.SELF_SID_REGEX)
116
117 def test_version_event(self):
118 """Test 'version' event data is valid.
119
120 Verify that the 'version' event is written even when no other
121 events are addded.
122
123 Expected event log:
124 <version event>
125 """
126 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
127 log_path = self._event_log_module.Write(path=tempdir)
128 self._log_data = self.readLog(log_path)
129
130 # A log with no added events should only have the version entry.
131 self.assertEqual(len(self._log_data), 1)
132 version_event = self._log_data[0]
133 self.verifyCommonKeys(version_event, expected_event_name='version')
134 # Check for 'version' event specific fields.
135 self.assertIn('evt', version_event)
136 self.assertIn('exe', version_event)
137 # Verify "evt" version field is a string.
138 self.assertIsInstance(version_event['evt'], str)
139
140 def test_start_event(self):
141 """Test and validate 'start' event data is valid.
142
143 Expected event log:
144 <version event>
145 <start event>
146 """
147 self._event_log_module.StartEvent()
148 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
149 log_path = self._event_log_module.Write(path=tempdir)
150 self._log_data = self.readLog(log_path)
151
152 self.assertEqual(len(self._log_data), 2)
153 start_event = self._log_data[1]
154 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
155 self.verifyCommonKeys(start_event, expected_event_name='start')
156 # Check for 'start' event specific fields.
157 self.assertIn('argv', start_event)
158 self.assertTrue(isinstance(start_event['argv'], list))
159
160 def test_exit_event_result_none(self):
161 """Test 'exit' event data is valid when result is None.
162
163 We expect None result to be converted to 0 in the exit event data.
164
165 Expected event log:
166 <version event>
167 <exit event>
168 """
169 self._event_log_module.ExitEvent(None)
170 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
171 log_path = self._event_log_module.Write(path=tempdir)
172 self._log_data = self.readLog(log_path)
173
174 self.assertEqual(len(self._log_data), 2)
175 exit_event = self._log_data[1]
176 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
177 self.verifyCommonKeys(exit_event, expected_event_name='exit')
178 # Check for 'exit' event specific fields.
179 self.assertIn('code', exit_event)
180 # 'None' result should convert to 0 (successful) return code.
181 self.assertEqual(exit_event['code'], 0)
182
183 def test_exit_event_result_integer(self):
184 """Test 'exit' event data is valid when result is an integer.
185
186 Expected event log:
187 <version event>
188 <exit event>
189 """
190 self._event_log_module.ExitEvent(2)
191 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
192 log_path = self._event_log_module.Write(path=tempdir)
193 self._log_data = self.readLog(log_path)
194
195 self.assertEqual(len(self._log_data), 2)
196 exit_event = self._log_data[1]
197 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
198 self.verifyCommonKeys(exit_event, expected_event_name='exit')
199 # Check for 'exit' event specific fields.
200 self.assertIn('code', exit_event)
201 self.assertEqual(exit_event['code'], 2)
202
203 def test_command_event(self):
204 """Test and validate 'command' event data is valid.
205
206 Expected event log:
207 <version event>
208 <command event>
209 """
210 name = 'repo'
211 subcommands = ['init' 'this']
212 self._event_log_module.CommandEvent(name='repo', subcommands=subcommands)
213 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
214 log_path = self._event_log_module.Write(path=tempdir)
215 self._log_data = self.readLog(log_path)
216
217 self.assertEqual(len(self._log_data), 2)
218 command_event = self._log_data[1]
219 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
220 self.verifyCommonKeys(command_event, expected_event_name='command')
221 # Check for 'command' event specific fields.
222 self.assertIn('name', command_event)
223 self.assertIn('subcommands', command_event)
224 self.assertEqual(command_event['name'], name)
225 self.assertEqual(command_event['subcommands'], subcommands)
226
227 def test_def_params_event_repo_config(self):
228 """Test 'def_params' event data outputs only repo config keys.
229
230 Expected event log:
231 <version event>
232 <def_param event>
233 <def_param event>
234 """ 41 """
235 config = { 42 platform_utils.remove(socket_path, missing_ok=True)
236 'git.foo': 'bar', 43 data = b""
237 'repo.partialclone': 'true', 44 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
238 'repo.partialclonefilter': 'blob:none', 45 sock.bind(socket_path)
239 } 46 sock.listen(0)
240 self._event_log_module.DefParamRepoEvents(config)
241
242 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
243 log_path = self._event_log_module.Write(path=tempdir)
244 self._log_data = self.readLog(log_path)
245
246 self.assertEqual(len(self._log_data), 3)
247 def_param_events = self._log_data[1:]
248 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
249
250 for event in def_param_events:
251 self.verifyCommonKeys(event, expected_event_name='def_param')
252 # Check for 'def_param' event specific fields.
253 self.assertIn('param', event)
254 self.assertIn('value', event)
255 self.assertTrue(event['param'].startswith('repo.'))
256
257 def test_def_params_event_no_repo_config(self):
258 """Test 'def_params' event data won't output non-repo config keys.
259
260 Expected event log:
261 <version event>
262 """
263 config = {
264 'git.foo': 'bar',
265 'git.core.foo2': 'baz',
266 }
267 self._event_log_module.DefParamRepoEvents(config)
268
269 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
270 log_path = self._event_log_module.Write(path=tempdir)
271 self._log_data = self.readLog(log_path)
272
273 self.assertEqual(len(self._log_data), 1)
274 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
275
276 def test_data_event_config(self):
277 """Test 'data' event data outputs all config keys.
278
279 Expected event log:
280 <version event>
281 <data event>
282 <data event>
283 """
284 config = {
285 'git.foo': 'bar',
286 'repo.partialclone': 'false',
287 'repo.syncstate.superproject.hassuperprojecttag': 'true',
288 'repo.syncstate.superproject.sys.argv': ['--', 'sync', 'protobuf'],
289 }
290 prefix_value = 'prefix'
291 self._event_log_module.LogDataConfigEvents(config, prefix_value)
292
293 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
294 log_path = self._event_log_module.Write(path=tempdir)
295 self._log_data = self.readLog(log_path)
296
297 self.assertEqual(len(self._log_data), 5)
298 data_events = self._log_data[1:]
299 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
300
301 for event in data_events:
302 self.verifyCommonKeys(event)
303 # Check for 'data' event specific fields.
304 self.assertIn('key', event)
305 self.assertIn('value', event)
306 key = event['key']
307 key = self.remove_prefix(key, f'{prefix_value}/')
308 value = event['value']
309 self.assertEqual(self._event_log_module.GetDataEventName(value), event['event'])
310 self.assertTrue(key in config and value == config[key])
311
312 def test_error_event(self):
313 """Test and validate 'error' event data is valid.
314
315 Expected event log:
316 <version event>
317 <error event>
318 """
319 msg = 'invalid option: --cahced'
320 fmt = 'invalid option: %s'
321 self._event_log_module.ErrorEvent(msg, fmt)
322 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
323 log_path = self._event_log_module.Write(path=tempdir)
324 self._log_data = self.readLog(log_path)
325
326 self.assertEqual(len(self._log_data), 2)
327 error_event = self._log_data[1]
328 self.verifyCommonKeys(self._log_data[0], expected_event_name='version')
329 self.verifyCommonKeys(error_event, expected_event_name='error')
330 # Check for 'error' event specific fields.
331 self.assertIn('msg', error_event)
332 self.assertIn('fmt', error_event)
333 self.assertEqual(error_event['msg'], msg)
334 self.assertEqual(error_event['fmt'], fmt)
335
336 def test_write_with_filename(self):
337 """Test Write() with a path to a file exits with None."""
338 self.assertIsNone(self._event_log_module.Write(path='path/to/file'))
339
340 def test_write_with_git_config(self):
341 """Test Write() uses the git config path when 'git config' call succeeds."""
342 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
343 with mock.patch.object(self._event_log_module,
344 '_GetEventTargetPath', return_value=tempdir):
345 self.assertEqual(os.path.dirname(self._event_log_module.Write()), tempdir)
346
347 def test_write_no_git_config(self):
348 """Test Write() with no git config variable present exits with None."""
349 with mock.patch.object(self._event_log_module,
350 '_GetEventTargetPath', return_value=None):
351 self.assertIsNone(self._event_log_module.Write())
352
353 def test_write_non_string(self):
354 """Test Write() with non-string type for |path| throws TypeError."""
355 with self.assertRaises(TypeError):
356 self._event_log_module.Write(path=1234)
357
358 def test_write_socket(self):
359 """Test Write() with Unix domain socket for |path| and validate received traces."""
360 received_traces = []
361 with tempfile.TemporaryDirectory(prefix='test_server_sockets') as tempdir:
362 socket_path = os.path.join(tempdir, "server.sock")
363 server_ready = threading.Condition()
364 # Start "server" listening on Unix domain socket at socket_path.
365 try:
366 server_thread = threading.Thread(
367 target=serverLoggingThread,
368 args=(socket_path, server_ready, received_traces))
369 server_thread.start()
370
371 with server_ready: 47 with server_ready:
372 server_ready.wait(timeout=120) 48 server_ready.notify()
49 with sock.accept()[0] as conn:
50 while True:
51 recved = conn.recv(4096)
52 if not recved:
53 break
54 data += recved
55 received_traces.extend(data.decode("utf-8").splitlines())
56
373 57
58class EventLogTestCase(unittest.TestCase):
59 """TestCase for the EventLog module."""
60
61 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID"
62 PARENT_SID_VALUE = "parent_sid"
63 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*"
64 FULL_SID_REGEX = r"^%s/%s" % (PARENT_SID_VALUE, SELF_SID_REGEX)
65
66 def setUp(self):
67 """Load the event_log module every time."""
68 self._event_log_module = None
69 # By default we initialize with the expected case where
70 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
71 env = {
72 self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
73 }
74 self._event_log_module = git_trace2_event_log.EventLog(env=env)
75 self._log_data = None
76
77 def verifyCommonKeys(
78 self, log_entry, expected_event_name=None, full_sid=True
79 ):
80 """Helper function to verify common event log keys."""
81 self.assertIn("event", log_entry)
82 self.assertIn("sid", log_entry)
83 self.assertIn("thread", log_entry)
84 self.assertIn("time", log_entry)
85
86 # Do basic data format validation.
87 if expected_event_name:
88 self.assertEqual(expected_event_name, log_entry["event"])
89 if full_sid:
90 self.assertRegex(log_entry["sid"], self.FULL_SID_REGEX)
91 else:
92 self.assertRegex(log_entry["sid"], self.SELF_SID_REGEX)
93 self.assertRegex(log_entry["time"], r"^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$")
94
95 def readLog(self, log_path):
96 """Helper function to read log data into a list."""
97 log_data = []
98 with open(log_path, mode="rb") as f:
99 for line in f:
100 log_data.append(json.loads(line))
101 return log_data
102
103 def remove_prefix(self, s, prefix):
104 """Return a copy string after removing |prefix| from |s|, if present or
105 the original string."""
106 if s.startswith(prefix):
107 return s[len(prefix) :]
108 else:
109 return s
110
111 def test_initial_state_with_parent_sid(self):
112 """Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
113 self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX)
114
115 def test_initial_state_no_parent_sid(self):
116 """Test initial state when 'GIT_TRACE2_PARENT_SID' is not set."""
117 # Setup an empty environment dict (no parent sid).
118 self._event_log_module = git_trace2_event_log.EventLog(env={})
119 self.assertRegex(self._event_log_module.full_sid, self.SELF_SID_REGEX)
120
121 def test_version_event(self):
122 """Test 'version' event data is valid.
123
124 Verify that the 'version' event is written even when no other
125 events are addded.
126
127 Expected event log:
128 <version event>
129 """
130 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
131 log_path = self._event_log_module.Write(path=tempdir)
132 self._log_data = self.readLog(log_path)
133
134 # A log with no added events should only have the version entry.
135 self.assertEqual(len(self._log_data), 1)
136 version_event = self._log_data[0]
137 self.verifyCommonKeys(version_event, expected_event_name="version")
138 # Check for 'version' event specific fields.
139 self.assertIn("evt", version_event)
140 self.assertIn("exe", version_event)
141 # Verify "evt" version field is a string.
142 self.assertIsInstance(version_event["evt"], str)
143
144 def test_start_event(self):
145 """Test and validate 'start' event data is valid.
146
147 Expected event log:
148 <version event>
149 <start event>
150 """
374 self._event_log_module.StartEvent() 151 self._event_log_module.StartEvent()
375 path = self._event_log_module.Write(path=f'af_unix:{socket_path}') 152 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
376 finally: 153 log_path = self._event_log_module.Write(path=tempdir)
377 server_thread.join(timeout=5) 154 self._log_data = self.readLog(log_path)
378 155
379 self.assertEqual(path, f'af_unix:stream:{socket_path}') 156 self.assertEqual(len(self._log_data), 2)
380 self.assertEqual(len(received_traces), 2) 157 start_event = self._log_data[1]
381 version_event = json.loads(received_traces[0]) 158 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
382 start_event = json.loads(received_traces[1]) 159 self.verifyCommonKeys(start_event, expected_event_name="start")
383 self.verifyCommonKeys(version_event, expected_event_name='version') 160 # Check for 'start' event specific fields.
384 self.verifyCommonKeys(start_event, expected_event_name='start') 161 self.assertIn("argv", start_event)
385 # Check for 'start' event specific fields. 162 self.assertTrue(isinstance(start_event["argv"], list))
386 self.assertIn('argv', start_event) 163
387 self.assertIsInstance(start_event['argv'], list) 164 def test_exit_event_result_none(self):
165 """Test 'exit' event data is valid when result is None.
166
167 We expect None result to be converted to 0 in the exit event data.
168
169 Expected event log:
170 <version event>
171 <exit event>
172 """
173 self._event_log_module.ExitEvent(None)
174 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
175 log_path = self._event_log_module.Write(path=tempdir)
176 self._log_data = self.readLog(log_path)
177
178 self.assertEqual(len(self._log_data), 2)
179 exit_event = self._log_data[1]
180 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
181 self.verifyCommonKeys(exit_event, expected_event_name="exit")
182 # Check for 'exit' event specific fields.
183 self.assertIn("code", exit_event)
184 # 'None' result should convert to 0 (successful) return code.
185 self.assertEqual(exit_event["code"], 0)
186
187 def test_exit_event_result_integer(self):
188 """Test 'exit' event data is valid when result is an integer.
189
190 Expected event log:
191 <version event>
192 <exit event>
193 """
194 self._event_log_module.ExitEvent(2)
195 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
196 log_path = self._event_log_module.Write(path=tempdir)
197 self._log_data = self.readLog(log_path)
198
199 self.assertEqual(len(self._log_data), 2)
200 exit_event = self._log_data[1]
201 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
202 self.verifyCommonKeys(exit_event, expected_event_name="exit")
203 # Check for 'exit' event specific fields.
204 self.assertIn("code", exit_event)
205 self.assertEqual(exit_event["code"], 2)
206
207 def test_command_event(self):
208 """Test and validate 'command' event data is valid.
209
210 Expected event log:
211 <version event>
212 <command event>
213 """
214 name = "repo"
215 subcommands = ["init" "this"]
216 self._event_log_module.CommandEvent(
217 name="repo", subcommands=subcommands
218 )
219 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
220 log_path = self._event_log_module.Write(path=tempdir)
221 self._log_data = self.readLog(log_path)
222
223 self.assertEqual(len(self._log_data), 2)
224 command_event = self._log_data[1]
225 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
226 self.verifyCommonKeys(command_event, expected_event_name="command")
227 # Check for 'command' event specific fields.
228 self.assertIn("name", command_event)
229 self.assertIn("subcommands", command_event)
230 self.assertEqual(command_event["name"], name)
231 self.assertEqual(command_event["subcommands"], subcommands)
232
233 def test_def_params_event_repo_config(self):
234 """Test 'def_params' event data outputs only repo config keys.
235
236 Expected event log:
237 <version event>
238 <def_param event>
239 <def_param event>
240 """
241 config = {
242 "git.foo": "bar",
243 "repo.partialclone": "true",
244 "repo.partialclonefilter": "blob:none",
245 }
246 self._event_log_module.DefParamRepoEvents(config)
247
248 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
249 log_path = self._event_log_module.Write(path=tempdir)
250 self._log_data = self.readLog(log_path)
251
252 self.assertEqual(len(self._log_data), 3)
253 def_param_events = self._log_data[1:]
254 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
255
256 for event in def_param_events:
257 self.verifyCommonKeys(event, expected_event_name="def_param")
258 # Check for 'def_param' event specific fields.
259 self.assertIn("param", event)
260 self.assertIn("value", event)
261 self.assertTrue(event["param"].startswith("repo."))
262
263 def test_def_params_event_no_repo_config(self):
264 """Test 'def_params' event data won't output non-repo config keys.
265
266 Expected event log:
267 <version event>
268 """
269 config = {
270 "git.foo": "bar",
271 "git.core.foo2": "baz",
272 }
273 self._event_log_module.DefParamRepoEvents(config)
274
275 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
276 log_path = self._event_log_module.Write(path=tempdir)
277 self._log_data = self.readLog(log_path)
278
279 self.assertEqual(len(self._log_data), 1)
280 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
281
282 def test_data_event_config(self):
283 """Test 'data' event data outputs all config keys.
284
285 Expected event log:
286 <version event>
287 <data event>
288 <data event>
289 """
290 config = {
291 "git.foo": "bar",
292 "repo.partialclone": "false",
293 "repo.syncstate.superproject.hassuperprojecttag": "true",
294 "repo.syncstate.superproject.sys.argv": ["--", "sync", "protobuf"],
295 }
296 prefix_value = "prefix"
297 self._event_log_module.LogDataConfigEvents(config, prefix_value)
298
299 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
300 log_path = self._event_log_module.Write(path=tempdir)
301 self._log_data = self.readLog(log_path)
302
303 self.assertEqual(len(self._log_data), 5)
304 data_events = self._log_data[1:]
305 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
306
307 for event in data_events:
308 self.verifyCommonKeys(event)
309 # Check for 'data' event specific fields.
310 self.assertIn("key", event)
311 self.assertIn("value", event)
312 key = event["key"]
313 key = self.remove_prefix(key, f"{prefix_value}/")
314 value = event["value"]
315 self.assertEqual(
316 self._event_log_module.GetDataEventName(value), event["event"]
317 )
318 self.assertTrue(key in config and value == config[key])
319
320 def test_error_event(self):
321 """Test and validate 'error' event data is valid.
322
323 Expected event log:
324 <version event>
325 <error event>
326 """
327 msg = "invalid option: --cahced"
328 fmt = "invalid option: %s"
329 self._event_log_module.ErrorEvent(msg, fmt)
330 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
331 log_path = self._event_log_module.Write(path=tempdir)
332 self._log_data = self.readLog(log_path)
333
334 self.assertEqual(len(self._log_data), 2)
335 error_event = self._log_data[1]
336 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
337 self.verifyCommonKeys(error_event, expected_event_name="error")
338 # Check for 'error' event specific fields.
339 self.assertIn("msg", error_event)
340 self.assertIn("fmt", error_event)
341 self.assertEqual(error_event["msg"], msg)
342 self.assertEqual(error_event["fmt"], fmt)
343
344 def test_write_with_filename(self):
345 """Test Write() with a path to a file exits with None."""
346 self.assertIsNone(self._event_log_module.Write(path="path/to/file"))
347
348 def test_write_with_git_config(self):
349 """Test Write() uses the git config path when 'git config' call
350 succeeds."""
351 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
352 with mock.patch.object(
353 self._event_log_module,
354 "_GetEventTargetPath",
355 return_value=tempdir,
356 ):
357 self.assertEqual(
358 os.path.dirname(self._event_log_module.Write()), tempdir
359 )
360
361 def test_write_no_git_config(self):
362 """Test Write() with no git config variable present exits with None."""
363 with mock.patch.object(
364 self._event_log_module, "_GetEventTargetPath", return_value=None
365 ):
366 self.assertIsNone(self._event_log_module.Write())
367
368 def test_write_non_string(self):
369 """Test Write() with non-string type for |path| throws TypeError."""
370 with self.assertRaises(TypeError):
371 self._event_log_module.Write(path=1234)
372
373 def test_write_socket(self):
374 """Test Write() with Unix domain socket for |path| and validate received
375 traces."""
376 received_traces = []
377 with tempfile.TemporaryDirectory(
378 prefix="test_server_sockets"
379 ) as tempdir:
380 socket_path = os.path.join(tempdir, "server.sock")
381 server_ready = threading.Condition()
382 # Start "server" listening on Unix domain socket at socket_path.
383 try:
384 server_thread = threading.Thread(
385 target=serverLoggingThread,
386 args=(socket_path, server_ready, received_traces),
387 )
388 server_thread.start()
389
390 with server_ready:
391 server_ready.wait(timeout=120)
392
393 self._event_log_module.StartEvent()
394 path = self._event_log_module.Write(
395 path=f"af_unix:{socket_path}"
396 )
397 finally:
398 server_thread.join(timeout=5)
399
400 self.assertEqual(path, f"af_unix:stream:{socket_path}")
401 self.assertEqual(len(received_traces), 2)
402 version_event = json.loads(received_traces[0])
403 start_event = json.loads(received_traces[1])
404 self.verifyCommonKeys(version_event, expected_event_name="version")
405 self.verifyCommonKeys(start_event, expected_event_name="start")
406 # Check for 'start' event specific fields.
407 self.assertIn("argv", start_event)
408 self.assertIsInstance(start_event["argv"], list)
diff --git a/tests/test_hooks.py b/tests/test_hooks.py
index 6632b3e5..78277128 100644
--- a/tests/test_hooks.py
+++ b/tests/test_hooks.py
@@ -17,39 +17,38 @@
17import hooks 17import hooks
18import unittest 18import unittest
19 19
20
20class RepoHookShebang(unittest.TestCase): 21class RepoHookShebang(unittest.TestCase):
21 """Check shebang parsing in RepoHook.""" 22 """Check shebang parsing in RepoHook."""
22 23
23 def test_no_shebang(self): 24 def test_no_shebang(self):
24 """Lines w/out shebangs should be rejected.""" 25 """Lines w/out shebangs should be rejected."""
25 DATA = ( 26 DATA = ("", "#\n# foo\n", "# Bad shebang in script\n#!/foo\n")
26 '', 27 for data in DATA:
27 '#\n# foo\n', 28 self.assertIsNone(hooks.RepoHook._ExtractInterpFromShebang(data))
28 '# Bad shebang in script\n#!/foo\n'
29 )
30 for data in DATA:
31 self.assertIsNone(hooks.RepoHook._ExtractInterpFromShebang(data))
32 29
33 def test_direct_interp(self): 30 def test_direct_interp(self):
34 """Lines whose shebang points directly to the interpreter.""" 31 """Lines whose shebang points directly to the interpreter."""
35 DATA = ( 32 DATA = (
36 ('#!/foo', '/foo'), 33 ("#!/foo", "/foo"),
37 ('#! /foo', '/foo'), 34 ("#! /foo", "/foo"),
38 ('#!/bin/foo ', '/bin/foo'), 35 ("#!/bin/foo ", "/bin/foo"),
39 ('#! /usr/foo ', '/usr/foo'), 36 ("#! /usr/foo ", "/usr/foo"),
40 ('#! /usr/foo -args', '/usr/foo'), 37 ("#! /usr/foo -args", "/usr/foo"),
41 ) 38 )
42 for shebang, interp in DATA: 39 for shebang, interp in DATA:
43 self.assertEqual(hooks.RepoHook._ExtractInterpFromShebang(shebang), 40 self.assertEqual(
44 interp) 41 hooks.RepoHook._ExtractInterpFromShebang(shebang), interp
42 )
45 43
46 def test_env_interp(self): 44 def test_env_interp(self):
47 """Lines whose shebang launches through `env`.""" 45 """Lines whose shebang launches through `env`."""
48 DATA = ( 46 DATA = (
49 ('#!/usr/bin/env foo', 'foo'), 47 ("#!/usr/bin/env foo", "foo"),
50 ('#!/bin/env foo', 'foo'), 48 ("#!/bin/env foo", "foo"),
51 ('#! /bin/env /bin/foo ', '/bin/foo'), 49 ("#! /bin/env /bin/foo ", "/bin/foo"),
52 ) 50 )
53 for shebang, interp in DATA: 51 for shebang, interp in DATA:
54 self.assertEqual(hooks.RepoHook._ExtractInterpFromShebang(shebang), 52 self.assertEqual(
55 interp) 53 hooks.RepoHook._ExtractInterpFromShebang(shebang), interp
54 )
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index 3634701f..648acde8 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -27,291 +27,318 @@ import manifest_xml
27 27
28# Invalid paths that we don't want in the filesystem. 28# Invalid paths that we don't want in the filesystem.
29INVALID_FS_PATHS = ( 29INVALID_FS_PATHS = (
30 '', 30 "",
31 '.', 31 ".",
32 '..', 32 "..",
33 '../', 33 "../",
34 './', 34 "./",
35 './/', 35 ".//",
36 'foo/', 36 "foo/",
37 './foo', 37 "./foo",
38 '../foo', 38 "../foo",
39 'foo/./bar', 39 "foo/./bar",
40 'foo/../../bar', 40 "foo/../../bar",
41 '/foo', 41 "/foo",
42 './../foo', 42 "./../foo",
43 '.git/foo', 43 ".git/foo",
44 # Check case folding. 44 # Check case folding.
45 '.GIT/foo', 45 ".GIT/foo",
46 'blah/.git/foo', 46 "blah/.git/foo",
47 '.repo/foo', 47 ".repo/foo",
48 '.repoconfig', 48 ".repoconfig",
49 # Block ~ due to 8.3 filenames on Windows filesystems. 49 # Block ~ due to 8.3 filenames on Windows filesystems.
50 '~', 50 "~",
51 'foo~', 51 "foo~",
52 'blah/foo~', 52 "blah/foo~",
53 # Block Unicode characters that get normalized out by filesystems. 53 # Block Unicode characters that get normalized out by filesystems.
54 u'foo\u200Cbar', 54 "foo\u200Cbar",
55 # Block newlines. 55 # Block newlines.
56 'f\n/bar', 56 "f\n/bar",
57 'f\r/bar', 57 "f\r/bar",
58) 58)
59 59
60# Make sure platforms that use path separators (e.g. Windows) are also 60# Make sure platforms that use path separators (e.g. Windows) are also
61# rejected properly. 61# rejected properly.
62if os.path.sep != '/': 62if os.path.sep != "/":
63 INVALID_FS_PATHS += tuple(x.replace('/', os.path.sep) for x in INVALID_FS_PATHS) 63 INVALID_FS_PATHS += tuple(
64 x.replace("/", os.path.sep) for x in INVALID_FS_PATHS
65 )
64 66
65 67
66def sort_attributes(manifest): 68def sort_attributes(manifest):
67 """Sort the attributes of all elements alphabetically. 69 """Sort the attributes of all elements alphabetically.
68 70
69 This is needed because different versions of the toxml() function from 71 This is needed because different versions of the toxml() function from
70 xml.dom.minidom outputs the attributes of elements in different orders. 72 xml.dom.minidom outputs the attributes of elements in different orders.
71 Before Python 3.8 they were output alphabetically, later versions preserve 73 Before Python 3.8 they were output alphabetically, later versions preserve
72 the order specified by the user. 74 the order specified by the user.
73 75
74 Args: 76 Args:
75 manifest: String containing an XML manifest. 77 manifest: String containing an XML manifest.
76 78
77 Returns: 79 Returns:
78 The XML manifest with the attributes of all elements sorted alphabetically. 80 The XML manifest with the attributes of all elements sorted
79 """ 81 alphabetically.
80 new_manifest = '' 82 """
81 # This will find every element in the XML manifest, whether they have 83 new_manifest = ""
82 # attributes or not. This simplifies recreating the manifest below. 84 # This will find every element in the XML manifest, whether they have
83 matches = re.findall(r'(<[/?]?[a-z-]+\s*)((?:\S+?="[^"]+"\s*?)*)(\s*[/?]?>)', manifest) 85 # attributes or not. This simplifies recreating the manifest below.
84 for head, attrs, tail in matches: 86 matches = re.findall(
85 m = re.findall(r'\S+?="[^"]+"', attrs) 87 r'(<[/?]?[a-z-]+\s*)((?:\S+?="[^"]+"\s*?)*)(\s*[/?]?>)', manifest
86 new_manifest += head + ' '.join(sorted(m)) + tail 88 )
87 return new_manifest 89 for head, attrs, tail in matches:
90 m = re.findall(r'\S+?="[^"]+"', attrs)
91 new_manifest += head + " ".join(sorted(m)) + tail
92 return new_manifest
88 93
89 94
90class ManifestParseTestCase(unittest.TestCase): 95class ManifestParseTestCase(unittest.TestCase):
91 """TestCase for parsing manifests.""" 96 """TestCase for parsing manifests."""
92 97
93 def setUp(self): 98 def setUp(self):
94 self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests') 99 self.tempdirobj = tempfile.TemporaryDirectory(prefix="repo_tests")
95 self.tempdir = self.tempdirobj.name 100 self.tempdir = self.tempdirobj.name
96 self.repodir = os.path.join(self.tempdir, '.repo') 101 self.repodir = os.path.join(self.tempdir, ".repo")
97 self.manifest_dir = os.path.join(self.repodir, 'manifests') 102 self.manifest_dir = os.path.join(self.repodir, "manifests")
98 self.manifest_file = os.path.join( 103 self.manifest_file = os.path.join(
99 self.repodir, manifest_xml.MANIFEST_FILE_NAME) 104 self.repodir, manifest_xml.MANIFEST_FILE_NAME
100 self.local_manifest_dir = os.path.join( 105 )
101 self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME) 106 self.local_manifest_dir = os.path.join(
102 os.mkdir(self.repodir) 107 self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME
103 os.mkdir(self.manifest_dir) 108 )
104 109 os.mkdir(self.repodir)
105 # The manifest parsing really wants a git repo currently. 110 os.mkdir(self.manifest_dir)
106 gitdir = os.path.join(self.repodir, 'manifests.git') 111
107 os.mkdir(gitdir) 112 # The manifest parsing really wants a git repo currently.
108 with open(os.path.join(gitdir, 'config'), 'w') as fp: 113 gitdir = os.path.join(self.repodir, "manifests.git")
109 fp.write("""[remote "origin"] 114 os.mkdir(gitdir)
115 with open(os.path.join(gitdir, "config"), "w") as fp:
116 fp.write(
117 """[remote "origin"]
110 url = https://localhost:0/manifest 118 url = https://localhost:0/manifest
111""") 119"""
120 )
112 121
113 def tearDown(self): 122 def tearDown(self):
114 self.tempdirobj.cleanup() 123 self.tempdirobj.cleanup()
115 124
116 def getXmlManifest(self, data): 125 def getXmlManifest(self, data):
117 """Helper to initialize a manifest for testing.""" 126 """Helper to initialize a manifest for testing."""
118 with open(self.manifest_file, 'w', encoding="utf-8") as fp: 127 with open(self.manifest_file, "w", encoding="utf-8") as fp:
119 fp.write(data) 128 fp.write(data)
120 return manifest_xml.XmlManifest(self.repodir, self.manifest_file) 129 return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
121 130
122 @staticmethod 131 @staticmethod
123 def encodeXmlAttr(attr): 132 def encodeXmlAttr(attr):
124 """Encode |attr| using XML escape rules.""" 133 """Encode |attr| using XML escape rules."""
125 return attr.replace('\r', '&#x000d;').replace('\n', '&#x000a;') 134 return attr.replace("\r", "&#x000d;").replace("\n", "&#x000a;")
126 135
127 136
128class ManifestValidateFilePaths(unittest.TestCase): 137class ManifestValidateFilePaths(unittest.TestCase):
129 """Check _ValidateFilePaths helper. 138 """Check _ValidateFilePaths helper.
130 139
131 This doesn't access a real filesystem. 140 This doesn't access a real filesystem.
132 """ 141 """
133 142
134 def check_both(self, *args): 143 def check_both(self, *args):
135 manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args) 144 manifest_xml.XmlManifest._ValidateFilePaths("copyfile", *args)
136 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args) 145 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args)
137 146
138 def test_normal_path(self): 147 def test_normal_path(self):
139 """Make sure good paths are accepted.""" 148 """Make sure good paths are accepted."""
140 self.check_both('foo', 'bar') 149 self.check_both("foo", "bar")
141 self.check_both('foo/bar', 'bar') 150 self.check_both("foo/bar", "bar")
142 self.check_both('foo', 'bar/bar') 151 self.check_both("foo", "bar/bar")
143 self.check_both('foo/bar', 'bar/bar') 152 self.check_both("foo/bar", "bar/bar")
144 153
145 def test_symlink_targets(self): 154 def test_symlink_targets(self):
146 """Some extra checks for symlinks.""" 155 """Some extra checks for symlinks."""
147 def check(*args): 156
148 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args) 157 def check(*args):
149 158 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args)
150 # We allow symlinks to end in a slash since we allow them to point to dirs 159
151 # in general. Technically the slash isn't necessary. 160 # We allow symlinks to end in a slash since we allow them to point to
152 check('foo/', 'bar') 161 # dirs in general. Technically the slash isn't necessary.
153 # We allow a single '.' to get a reference to the project itself. 162 check("foo/", "bar")
154 check('.', 'bar') 163 # We allow a single '.' to get a reference to the project itself.
155 164 check(".", "bar")
156 def test_bad_paths(self): 165
157 """Make sure bad paths (src & dest) are rejected.""" 166 def test_bad_paths(self):
158 for path in INVALID_FS_PATHS: 167 """Make sure bad paths (src & dest) are rejected."""
159 self.assertRaises( 168 for path in INVALID_FS_PATHS:
160 error.ManifestInvalidPathError, self.check_both, path, 'a') 169 self.assertRaises(
161 self.assertRaises( 170 error.ManifestInvalidPathError, self.check_both, path, "a"
162 error.ManifestInvalidPathError, self.check_both, 'a', path) 171 )
172 self.assertRaises(
173 error.ManifestInvalidPathError, self.check_both, "a", path
174 )
163 175
164 176
165class ValueTests(unittest.TestCase): 177class ValueTests(unittest.TestCase):
166 """Check utility parsing code.""" 178 """Check utility parsing code."""
167 179
168 def _get_node(self, text): 180 def _get_node(self, text):
169 return xml.dom.minidom.parseString(text).firstChild 181 return xml.dom.minidom.parseString(text).firstChild
170 182
171 def test_bool_default(self): 183 def test_bool_default(self):
172 """Check XmlBool default handling.""" 184 """Check XmlBool default handling."""
173 node = self._get_node('<node/>') 185 node = self._get_node("<node/>")
174 self.assertIsNone(manifest_xml.XmlBool(node, 'a')) 186 self.assertIsNone(manifest_xml.XmlBool(node, "a"))
175 self.assertIsNone(manifest_xml.XmlBool(node, 'a', None)) 187 self.assertIsNone(manifest_xml.XmlBool(node, "a", None))
176 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) 188 self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123))
177 189
178 node = self._get_node('<node a=""/>') 190 node = self._get_node('<node a=""/>')
179 self.assertIsNone(manifest_xml.XmlBool(node, 'a')) 191 self.assertIsNone(manifest_xml.XmlBool(node, "a"))
180 192
181 def test_bool_invalid(self): 193 def test_bool_invalid(self):
182 """Check XmlBool invalid handling.""" 194 """Check XmlBool invalid handling."""
183 node = self._get_node('<node a="moo"/>') 195 node = self._get_node('<node a="moo"/>')
184 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) 196 self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123))
185 197
186 def test_bool_true(self): 198 def test_bool_true(self):
187 """Check XmlBool true values.""" 199 """Check XmlBool true values."""
188 for value in ('yes', 'true', '1'): 200 for value in ("yes", "true", "1"):
189 node = self._get_node('<node a="%s"/>' % (value,)) 201 node = self._get_node('<node a="%s"/>' % (value,))
190 self.assertTrue(manifest_xml.XmlBool(node, 'a')) 202 self.assertTrue(manifest_xml.XmlBool(node, "a"))
191 203
192 def test_bool_false(self): 204 def test_bool_false(self):
193 """Check XmlBool false values.""" 205 """Check XmlBool false values."""
194 for value in ('no', 'false', '0'): 206 for value in ("no", "false", "0"):
195 node = self._get_node('<node a="%s"/>' % (value,)) 207 node = self._get_node('<node a="%s"/>' % (value,))
196 self.assertFalse(manifest_xml.XmlBool(node, 'a')) 208 self.assertFalse(manifest_xml.XmlBool(node, "a"))
197 209
198 def test_int_default(self): 210 def test_int_default(self):
199 """Check XmlInt default handling.""" 211 """Check XmlInt default handling."""
200 node = self._get_node('<node/>') 212 node = self._get_node("<node/>")
201 self.assertIsNone(manifest_xml.XmlInt(node, 'a')) 213 self.assertIsNone(manifest_xml.XmlInt(node, "a"))
202 self.assertIsNone(manifest_xml.XmlInt(node, 'a', None)) 214 self.assertIsNone(manifest_xml.XmlInt(node, "a", None))
203 self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123)) 215 self.assertEqual(123, manifest_xml.XmlInt(node, "a", 123))
204 216
205 node = self._get_node('<node a=""/>') 217 node = self._get_node('<node a=""/>')
206 self.assertIsNone(manifest_xml.XmlInt(node, 'a')) 218 self.assertIsNone(manifest_xml.XmlInt(node, "a"))
207 219
208 def test_int_good(self): 220 def test_int_good(self):
209 """Check XmlInt numeric handling.""" 221 """Check XmlInt numeric handling."""
210 for value in (-1, 0, 1, 50000): 222 for value in (-1, 0, 1, 50000):
211 node = self._get_node('<node a="%s"/>' % (value,)) 223 node = self._get_node('<node a="%s"/>' % (value,))
212 self.assertEqual(value, manifest_xml.XmlInt(node, 'a')) 224 self.assertEqual(value, manifest_xml.XmlInt(node, "a"))
213 225
214 def test_int_invalid(self): 226 def test_int_invalid(self):
215 """Check XmlInt invalid handling.""" 227 """Check XmlInt invalid handling."""
216 with self.assertRaises(error.ManifestParseError): 228 with self.assertRaises(error.ManifestParseError):
217 node = self._get_node('<node a="xx"/>') 229 node = self._get_node('<node a="xx"/>')
218 manifest_xml.XmlInt(node, 'a') 230 manifest_xml.XmlInt(node, "a")
219 231
220 232
221class XmlManifestTests(ManifestParseTestCase): 233class XmlManifestTests(ManifestParseTestCase):
222 """Check manifest processing.""" 234 """Check manifest processing."""
223 235
224 def test_empty(self): 236 def test_empty(self):
225 """Parse an 'empty' manifest file.""" 237 """Parse an 'empty' manifest file."""
226 manifest = self.getXmlManifest( 238 manifest = self.getXmlManifest(
227 '<?xml version="1.0" encoding="UTF-8"?>' 239 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
228 '<manifest></manifest>') 240 )
229 self.assertEqual(manifest.remotes, {}) 241 self.assertEqual(manifest.remotes, {})
230 self.assertEqual(manifest.projects, []) 242 self.assertEqual(manifest.projects, [])
231 243
232 def test_link(self): 244 def test_link(self):
233 """Verify Link handling with new names.""" 245 """Verify Link handling with new names."""
234 manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file) 246 manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
235 with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp: 247 with open(os.path.join(self.manifest_dir, "foo.xml"), "w") as fp:
236 fp.write('<manifest></manifest>') 248 fp.write("<manifest></manifest>")
237 manifest.Link('foo.xml') 249 manifest.Link("foo.xml")
238 with open(self.manifest_file) as fp: 250 with open(self.manifest_file) as fp:
239 self.assertIn('<include name="foo.xml" />', fp.read()) 251 self.assertIn('<include name="foo.xml" />', fp.read())
240 252
241 def test_toxml_empty(self): 253 def test_toxml_empty(self):
242 """Verify the ToXml() helper.""" 254 """Verify the ToXml() helper."""
243 manifest = self.getXmlManifest( 255 manifest = self.getXmlManifest(
244 '<?xml version="1.0" encoding="UTF-8"?>' 256 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
245 '<manifest></manifest>') 257 )
246 self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>') 258 self.assertEqual(
247 259 manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>'
248 def test_todict_empty(self): 260 )
249 """Verify the ToDict() helper.""" 261
250 manifest = self.getXmlManifest( 262 def test_todict_empty(self):
251 '<?xml version="1.0" encoding="UTF-8"?>' 263 """Verify the ToDict() helper."""
252 '<manifest></manifest>') 264 manifest = self.getXmlManifest(
253 self.assertEqual(manifest.ToDict(), {}) 265 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
254 266 )
255 def test_toxml_omit_local(self): 267 self.assertEqual(manifest.ToDict(), {})
256 """Does not include local_manifests projects when omit_local=True.""" 268
257 manifest = self.getXmlManifest( 269 def test_toxml_omit_local(self):
258 '<?xml version="1.0" encoding="UTF-8"?><manifest>' 270 """Does not include local_manifests projects when omit_local=True."""
259 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>' 271 manifest = self.getXmlManifest(
260 '<project name="p" groups="local::me"/>' 272 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
261 '<project name="q"/>' 273 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
262 '<project name="r" groups="keep"/>' 274 '<project name="p" groups="local::me"/>'
263 '</manifest>') 275 '<project name="q"/>'
264 self.assertEqual( 276 '<project name="r" groups="keep"/>'
265 sort_attributes(manifest.ToXml(omit_local=True).toxml()), 277 "</manifest>"
266 '<?xml version="1.0" ?><manifest>' 278 )
267 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>' 279 self.assertEqual(
268 '<project name="q"/><project groups="keep" name="r"/></manifest>') 280 sort_attributes(manifest.ToXml(omit_local=True).toxml()),
269 281 '<?xml version="1.0" ?><manifest>'
270 def test_toxml_with_local(self): 282 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
271 """Does include local_manifests projects when omit_local=False.""" 283 '<project name="q"/><project groups="keep" name="r"/></manifest>',
272 manifest = self.getXmlManifest( 284 )
273 '<?xml version="1.0" encoding="UTF-8"?><manifest>' 285
274 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>' 286 def test_toxml_with_local(self):
275 '<project name="p" groups="local::me"/>' 287 """Does include local_manifests projects when omit_local=False."""
276 '<project name="q"/>' 288 manifest = self.getXmlManifest(
277 '<project name="r" groups="keep"/>' 289 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
278 '</manifest>') 290 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
279 self.assertEqual( 291 '<project name="p" groups="local::me"/>'
280 sort_attributes(manifest.ToXml(omit_local=False).toxml()), 292 '<project name="q"/>'
281 '<?xml version="1.0" ?><manifest>' 293 '<project name="r" groups="keep"/>'
282 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>' 294 "</manifest>"
283 '<project groups="local::me" name="p"/>' 295 )
284 '<project name="q"/><project groups="keep" name="r"/></manifest>') 296 self.assertEqual(
285 297 sort_attributes(manifest.ToXml(omit_local=False).toxml()),
286 def test_repo_hooks(self): 298 '<?xml version="1.0" ?><manifest>'
287 """Check repo-hooks settings.""" 299 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
288 manifest = self.getXmlManifest(""" 300 '<project groups="local::me" name="p"/>'
301 '<project name="q"/><project groups="keep" name="r"/></manifest>',
302 )
303
304 def test_repo_hooks(self):
305 """Check repo-hooks settings."""
306 manifest = self.getXmlManifest(
307 """
289<manifest> 308<manifest>
290 <remote name="test-remote" fetch="http://localhost" /> 309 <remote name="test-remote" fetch="http://localhost" />
291 <default remote="test-remote" revision="refs/heads/main" /> 310 <default remote="test-remote" revision="refs/heads/main" />
292 <project name="repohooks" path="src/repohooks"/> 311 <project name="repohooks" path="src/repohooks"/>
293 <repo-hooks in-project="repohooks" enabled-list="a, b"/> 312 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
294</manifest> 313</manifest>
295""") 314"""
296 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks') 315 )
297 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b']) 316 self.assertEqual(manifest.repo_hooks_project.name, "repohooks")
298 317 self.assertEqual(
299 def test_repo_hooks_unordered(self): 318 manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
300 """Check repo-hooks settings work even if the project def comes second.""" 319 )
301 manifest = self.getXmlManifest(""" 320
321 def test_repo_hooks_unordered(self):
322 """Check repo-hooks settings work even if the project def comes second.""" # noqa: E501
323 manifest = self.getXmlManifest(
324 """
302<manifest> 325<manifest>
303 <remote name="test-remote" fetch="http://localhost" /> 326 <remote name="test-remote" fetch="http://localhost" />
304 <default remote="test-remote" revision="refs/heads/main" /> 327 <default remote="test-remote" revision="refs/heads/main" />
305 <repo-hooks in-project="repohooks" enabled-list="a, b"/> 328 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
306 <project name="repohooks" path="src/repohooks"/> 329 <project name="repohooks" path="src/repohooks"/>
307</manifest> 330</manifest>
308""") 331"""
309 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks') 332 )
310 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b']) 333 self.assertEqual(manifest.repo_hooks_project.name, "repohooks")
311 334 self.assertEqual(
312 def test_unknown_tags(self): 335 manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
313 """Check superproject settings.""" 336 )
314 manifest = self.getXmlManifest(""" 337
338 def test_unknown_tags(self):
339 """Check superproject settings."""
340 manifest = self.getXmlManifest(
341 """
315<manifest> 342<manifest>
316 <remote name="test-remote" fetch="http://localhost" /> 343 <remote name="test-remote" fetch="http://localhost" />
317 <default remote="test-remote" revision="refs/heads/main" /> 344 <default remote="test-remote" revision="refs/heads/main" />
@@ -319,44 +346,54 @@ class XmlManifestTests(ManifestParseTestCase):
319 <iankaz value="unknown (possible) future tags are ignored"/> 346 <iankaz value="unknown (possible) future tags are ignored"/>
320 <x-custom-tag>X tags are always ignored</x-custom-tag> 347 <x-custom-tag>X tags are always ignored</x-custom-tag>
321</manifest> 348</manifest>
322""") 349"""
323 self.assertEqual(manifest.superproject.name, 'superproject') 350 )
324 self.assertEqual(manifest.superproject.remote.name, 'test-remote') 351 self.assertEqual(manifest.superproject.name, "superproject")
325 self.assertEqual( 352 self.assertEqual(manifest.superproject.remote.name, "test-remote")
326 sort_attributes(manifest.ToXml().toxml()), 353 self.assertEqual(
327 '<?xml version="1.0" ?><manifest>' 354 sort_attributes(manifest.ToXml().toxml()),
328 '<remote fetch="http://localhost" name="test-remote"/>' 355 '<?xml version="1.0" ?><manifest>'
329 '<default remote="test-remote" revision="refs/heads/main"/>' 356 '<remote fetch="http://localhost" name="test-remote"/>'
330 '<superproject name="superproject"/>' 357 '<default remote="test-remote" revision="refs/heads/main"/>'
331 '</manifest>') 358 '<superproject name="superproject"/>'
332 359 "</manifest>",
333 def test_remote_annotations(self): 360 )
334 """Check remote settings.""" 361
335 manifest = self.getXmlManifest(""" 362 def test_remote_annotations(self):
363 """Check remote settings."""
364 manifest = self.getXmlManifest(
365 """
336<manifest> 366<manifest>
337 <remote name="test-remote" fetch="http://localhost"> 367 <remote name="test-remote" fetch="http://localhost">
338 <annotation name="foo" value="bar"/> 368 <annotation name="foo" value="bar"/>
339 </remote> 369 </remote>
340</manifest> 370</manifest>
341""") 371"""
342 self.assertEqual(manifest.remotes['test-remote'].annotations[0].name, 'foo') 372 )
343 self.assertEqual(manifest.remotes['test-remote'].annotations[0].value, 'bar') 373 self.assertEqual(
344 self.assertEqual( 374 manifest.remotes["test-remote"].annotations[0].name, "foo"
345 sort_attributes(manifest.ToXml().toxml()), 375 )
346 '<?xml version="1.0" ?><manifest>' 376 self.assertEqual(
347 '<remote fetch="http://localhost" name="test-remote">' 377 manifest.remotes["test-remote"].annotations[0].value, "bar"
348 '<annotation name="foo" value="bar"/>' 378 )
349 '</remote>' 379 self.assertEqual(
350 '</manifest>') 380 sort_attributes(manifest.ToXml().toxml()),
381 '<?xml version="1.0" ?><manifest>'
382 '<remote fetch="http://localhost" name="test-remote">'
383 '<annotation name="foo" value="bar"/>'
384 "</remote>"
385 "</manifest>",
386 )
351 387
352 388
353class IncludeElementTests(ManifestParseTestCase): 389class IncludeElementTests(ManifestParseTestCase):
354 """Tests for <include>.""" 390 """Tests for <include>."""
355 391
356 def test_group_levels(self): 392 def test_group_levels(self):
357 root_m = os.path.join(self.manifest_dir, 'root.xml') 393 root_m = os.path.join(self.manifest_dir, "root.xml")
358 with open(root_m, 'w') as fp: 394 with open(root_m, "w") as fp:
359 fp.write(""" 395 fp.write(
396 """
360<manifest> 397<manifest>
361 <remote name="test-remote" fetch="http://localhost" /> 398 <remote name="test-remote" fetch="http://localhost" />
362 <default remote="test-remote" revision="refs/heads/main" /> 399 <default remote="test-remote" revision="refs/heads/main" />
@@ -364,438 +401,524 @@ class IncludeElementTests(ManifestParseTestCase):
364 <project name="root-name1" path="root-path1" /> 401 <project name="root-name1" path="root-path1" />
365 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" /> 402 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
366</manifest> 403</manifest>
367""") 404"""
368 with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp: 405 )
369 fp.write(""" 406 with open(os.path.join(self.manifest_dir, "level1.xml"), "w") as fp:
407 fp.write(
408 """
370<manifest> 409<manifest>
371 <include name="level2.xml" groups="level2-group" /> 410 <include name="level2.xml" groups="level2-group" />
372 <project name="level1-name1" path="level1-path1" /> 411 <project name="level1-name1" path="level1-path1" />
373</manifest> 412</manifest>
374""") 413"""
375 with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp: 414 )
376 fp.write(""" 415 with open(os.path.join(self.manifest_dir, "level2.xml"), "w") as fp:
416 fp.write(
417 """
377<manifest> 418<manifest>
378 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" /> 419 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
379</manifest> 420</manifest>
380""") 421"""
381 include_m = manifest_xml.XmlManifest(self.repodir, root_m) 422 )
382 for proj in include_m.projects: 423 include_m = manifest_xml.XmlManifest(self.repodir, root_m)
383 if proj.name == 'root-name1': 424 for proj in include_m.projects:
384 # Check include group not set on root level proj. 425 if proj.name == "root-name1":
385 self.assertNotIn('level1-group', proj.groups) 426 # Check include group not set on root level proj.
386 if proj.name == 'root-name2': 427 self.assertNotIn("level1-group", proj.groups)
387 # Check root proj group not removed. 428 if proj.name == "root-name2":
388 self.assertIn('r2g1', proj.groups) 429 # Check root proj group not removed.
389 if proj.name == 'level1-name1': 430 self.assertIn("r2g1", proj.groups)
390 # Check level1 proj has inherited group level 1. 431 if proj.name == "level1-name1":
391 self.assertIn('level1-group', proj.groups) 432 # Check level1 proj has inherited group level 1.
392 if proj.name == 'level2-name1': 433 self.assertIn("level1-group", proj.groups)
393 # Check level2 proj has inherited group levels 1 and 2. 434 if proj.name == "level2-name1":
394 self.assertIn('level1-group', proj.groups) 435 # Check level2 proj has inherited group levels 1 and 2.
395 self.assertIn('level2-group', proj.groups) 436 self.assertIn("level1-group", proj.groups)
396 # Check level2 proj group not removed. 437 self.assertIn("level2-group", proj.groups)
397 self.assertIn('l2g1', proj.groups) 438 # Check level2 proj group not removed.
398 439 self.assertIn("l2g1", proj.groups)
399 def test_allow_bad_name_from_user(self): 440
400 """Check handling of bad name attribute from the user's input.""" 441 def test_allow_bad_name_from_user(self):
401 def parse(name): 442 """Check handling of bad name attribute from the user's input."""
402 name = self.encodeXmlAttr(name) 443
403 manifest = self.getXmlManifest(f""" 444 def parse(name):
445 name = self.encodeXmlAttr(name)
446 manifest = self.getXmlManifest(
447 f"""
404<manifest> 448<manifest>
405 <remote name="default-remote" fetch="http://localhost" /> 449 <remote name="default-remote" fetch="http://localhost" />
406 <default remote="default-remote" revision="refs/heads/main" /> 450 <default remote="default-remote" revision="refs/heads/main" />
407 <include name="{name}" /> 451 <include name="{name}" />
408</manifest> 452</manifest>
409""") 453"""
410 # Force the manifest to be parsed. 454 )
411 manifest.ToXml() 455 # Force the manifest to be parsed.
412 456 manifest.ToXml()
413 # Setup target of the include. 457
414 target = os.path.join(self.tempdir, 'target.xml') 458 # Setup target of the include.
415 with open(target, 'w') as fp: 459 target = os.path.join(self.tempdir, "target.xml")
416 fp.write('<manifest></manifest>') 460 with open(target, "w") as fp:
417 461 fp.write("<manifest></manifest>")
418 # Include with absolute path. 462
419 parse(os.path.abspath(target)) 463 # Include with absolute path.
420 464 parse(os.path.abspath(target))
421 # Include with relative path. 465
422 parse(os.path.relpath(target, self.manifest_dir)) 466 # Include with relative path.
423 467 parse(os.path.relpath(target, self.manifest_dir))
424 def test_bad_name_checks(self): 468
425 """Check handling of bad name attribute.""" 469 def test_bad_name_checks(self):
426 def parse(name): 470 """Check handling of bad name attribute."""
427 name = self.encodeXmlAttr(name) 471
428 # Setup target of the include. 472 def parse(name):
429 with open(os.path.join(self.manifest_dir, 'target.xml'), 'w', encoding="utf-8") as fp: 473 name = self.encodeXmlAttr(name)
430 fp.write(f'<manifest><include name="{name}"/></manifest>') 474 # Setup target of the include.
431 475 with open(
432 manifest = self.getXmlManifest(""" 476 os.path.join(self.manifest_dir, "target.xml"),
477 "w",
478 encoding="utf-8",
479 ) as fp:
480 fp.write(f'<manifest><include name="{name}"/></manifest>')
481
482 manifest = self.getXmlManifest(
483 """
433<manifest> 484<manifest>
434 <remote name="default-remote" fetch="http://localhost" /> 485 <remote name="default-remote" fetch="http://localhost" />
435 <default remote="default-remote" revision="refs/heads/main" /> 486 <default remote="default-remote" revision="refs/heads/main" />
436 <include name="target.xml" /> 487 <include name="target.xml" />
437</manifest> 488</manifest>
438""") 489"""
439 # Force the manifest to be parsed. 490 )
440 manifest.ToXml() 491 # Force the manifest to be parsed.
492 manifest.ToXml()
441 493
442 # Handle empty name explicitly because a different codepath rejects it. 494 # Handle empty name explicitly because a different codepath rejects it.
443 with self.assertRaises(error.ManifestParseError): 495 with self.assertRaises(error.ManifestParseError):
444 parse('') 496 parse("")
445 497
446 for path in INVALID_FS_PATHS: 498 for path in INVALID_FS_PATHS:
447 if not path: 499 if not path:
448 continue 500 continue
449 501
450 with self.assertRaises(error.ManifestInvalidPathError): 502 with self.assertRaises(error.ManifestInvalidPathError):
451 parse(path) 503 parse(path)
452 504
453 505
454class ProjectElementTests(ManifestParseTestCase): 506class ProjectElementTests(ManifestParseTestCase):
455 """Tests for <project>.""" 507 """Tests for <project>."""
456 508
457 def test_group(self): 509 def test_group(self):
458 """Check project group settings.""" 510 """Check project group settings."""
459 manifest = self.getXmlManifest(""" 511 manifest = self.getXmlManifest(
512 """
460<manifest> 513<manifest>
461 <remote name="test-remote" fetch="http://localhost" /> 514 <remote name="test-remote" fetch="http://localhost" />
462 <default remote="test-remote" revision="refs/heads/main" /> 515 <default remote="test-remote" revision="refs/heads/main" />
463 <project name="test-name" path="test-path"/> 516 <project name="test-name" path="test-path"/>
464 <project name="extras" path="path" groups="g1,g2,g1"/> 517 <project name="extras" path="path" groups="g1,g2,g1"/>
465</manifest> 518</manifest>
466""") 519"""
467 self.assertEqual(len(manifest.projects), 2) 520 )
468 # Ordering isn't guaranteed. 521 self.assertEqual(len(manifest.projects), 2)
469 result = { 522 # Ordering isn't guaranteed.
470 manifest.projects[0].name: manifest.projects[0].groups, 523 result = {
471 manifest.projects[1].name: manifest.projects[1].groups, 524 manifest.projects[0].name: manifest.projects[0].groups,
472 } 525 manifest.projects[1].name: manifest.projects[1].groups,
473 project = manifest.projects[0] 526 }
474 self.assertCountEqual( 527 self.assertCountEqual(
475 result['test-name'], 528 result["test-name"], ["name:test-name", "all", "path:test-path"]
476 ['name:test-name', 'all', 'path:test-path']) 529 )
477 self.assertCountEqual( 530 self.assertCountEqual(
478 result['extras'], 531 result["extras"],
479 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path']) 532 ["g1", "g2", "g1", "name:extras", "all", "path:path"],
480 groupstr = 'default,platform-' + platform.system().lower() 533 )
481 self.assertEqual(groupstr, manifest.GetGroupsStr()) 534 groupstr = "default,platform-" + platform.system().lower()
482 groupstr = 'g1,g2,g1' 535 self.assertEqual(groupstr, manifest.GetGroupsStr())
483 manifest.manifestProject.config.SetString('manifest.groups', groupstr) 536 groupstr = "g1,g2,g1"
484 self.assertEqual(groupstr, manifest.GetGroupsStr()) 537 manifest.manifestProject.config.SetString("manifest.groups", groupstr)
485 538 self.assertEqual(groupstr, manifest.GetGroupsStr())
486 def test_set_revision_id(self): 539
487 """Check setting of project's revisionId.""" 540 def test_set_revision_id(self):
488 manifest = self.getXmlManifest(""" 541 """Check setting of project's revisionId."""
542 manifest = self.getXmlManifest(
543 """
489<manifest> 544<manifest>
490 <remote name="default-remote" fetch="http://localhost" /> 545 <remote name="default-remote" fetch="http://localhost" />
491 <default remote="default-remote" revision="refs/heads/main" /> 546 <default remote="default-remote" revision="refs/heads/main" />
492 <project name="test-name"/> 547 <project name="test-name"/>
493</manifest> 548</manifest>
494""") 549"""
495 self.assertEqual(len(manifest.projects), 1) 550 )
496 project = manifest.projects[0] 551 self.assertEqual(len(manifest.projects), 1)
497 project.SetRevisionId('ABCDEF') 552 project = manifest.projects[0]
498 self.assertEqual( 553 project.SetRevisionId("ABCDEF")
499 sort_attributes(manifest.ToXml().toxml()), 554 self.assertEqual(
500 '<?xml version="1.0" ?><manifest>' 555 sort_attributes(manifest.ToXml().toxml()),
501 '<remote fetch="http://localhost" name="default-remote"/>' 556 '<?xml version="1.0" ?><manifest>'
502 '<default remote="default-remote" revision="refs/heads/main"/>' 557 '<remote fetch="http://localhost" name="default-remote"/>'
503 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>' 558 '<default remote="default-remote" revision="refs/heads/main"/>'
504 '</manifest>') 559 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>' # noqa: E501
505 560 "</manifest>",
506 def test_trailing_slash(self): 561 )
507 """Check handling of trailing slashes in attributes.""" 562
508 def parse(name, path): 563 def test_trailing_slash(self):
509 name = self.encodeXmlAttr(name) 564 """Check handling of trailing slashes in attributes."""
510 path = self.encodeXmlAttr(path) 565
511 return self.getXmlManifest(f""" 566 def parse(name, path):
567 name = self.encodeXmlAttr(name)
568 path = self.encodeXmlAttr(path)
569 return self.getXmlManifest(
570 f"""
512<manifest> 571<manifest>
513 <remote name="default-remote" fetch="http://localhost" /> 572 <remote name="default-remote" fetch="http://localhost" />
514 <default remote="default-remote" revision="refs/heads/main" /> 573 <default remote="default-remote" revision="refs/heads/main" />
515 <project name="{name}" path="{path}" /> 574 <project name="{name}" path="{path}" />
516</manifest> 575</manifest>
517""") 576"""
518 577 )
519 manifest = parse('a/path/', 'foo') 578
520 self.assertEqual(os.path.normpath(manifest.projects[0].gitdir), 579 manifest = parse("a/path/", "foo")
521 os.path.join(self.tempdir, '.repo', 'projects', 'foo.git')) 580 self.assertEqual(
522 self.assertEqual(os.path.normpath(manifest.projects[0].objdir), 581 os.path.normpath(manifest.projects[0].gitdir),
523 os.path.join(self.tempdir, '.repo', 'project-objects', 'a', 'path.git')) 582 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
524 583 )
525 manifest = parse('a/path', 'foo/') 584 self.assertEqual(
526 self.assertEqual(os.path.normpath(manifest.projects[0].gitdir), 585 os.path.normpath(manifest.projects[0].objdir),
527 os.path.join(self.tempdir, '.repo', 'projects', 'foo.git')) 586 os.path.join(
528 self.assertEqual(os.path.normpath(manifest.projects[0].objdir), 587 self.tempdir, ".repo", "project-objects", "a", "path.git"
529 os.path.join(self.tempdir, '.repo', 'project-objects', 'a', 'path.git')) 588 ),
530 589 )
531 manifest = parse('a/path', 'foo//////') 590
532 self.assertEqual(os.path.normpath(manifest.projects[0].gitdir), 591 manifest = parse("a/path", "foo/")
533 os.path.join(self.tempdir, '.repo', 'projects', 'foo.git')) 592 self.assertEqual(
534 self.assertEqual(os.path.normpath(manifest.projects[0].objdir), 593 os.path.normpath(manifest.projects[0].gitdir),
535 os.path.join(self.tempdir, '.repo', 'project-objects', 'a', 'path.git')) 594 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
536 595 )
537 def test_toplevel_path(self): 596 self.assertEqual(
538 """Check handling of path=. specially.""" 597 os.path.normpath(manifest.projects[0].objdir),
539 def parse(name, path): 598 os.path.join(
540 name = self.encodeXmlAttr(name) 599 self.tempdir, ".repo", "project-objects", "a", "path.git"
541 path = self.encodeXmlAttr(path) 600 ),
542 return self.getXmlManifest(f""" 601 )
602
603 manifest = parse("a/path", "foo//////")
604 self.assertEqual(
605 os.path.normpath(manifest.projects[0].gitdir),
606 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
607 )
608 self.assertEqual(
609 os.path.normpath(manifest.projects[0].objdir),
610 os.path.join(
611 self.tempdir, ".repo", "project-objects", "a", "path.git"
612 ),
613 )
614
615 def test_toplevel_path(self):
616 """Check handling of path=. specially."""
617
618 def parse(name, path):
619 name = self.encodeXmlAttr(name)
620 path = self.encodeXmlAttr(path)
621 return self.getXmlManifest(
622 f"""
543<manifest> 623<manifest>
544 <remote name="default-remote" fetch="http://localhost" /> 624 <remote name="default-remote" fetch="http://localhost" />
545 <default remote="default-remote" revision="refs/heads/main" /> 625 <default remote="default-remote" revision="refs/heads/main" />
546 <project name="{name}" path="{path}" /> 626 <project name="{name}" path="{path}" />
547</manifest> 627</manifest>
548""") 628"""
549 629 )
550 for path in ('.', './', './/', './//'): 630
551 manifest = parse('server/path', path) 631 for path in (".", "./", ".//", ".///"):
552 self.assertEqual(os.path.normpath(manifest.projects[0].gitdir), 632 manifest = parse("server/path", path)
553 os.path.join(self.tempdir, '.repo', 'projects', '..git')) 633 self.assertEqual(
554 634 os.path.normpath(manifest.projects[0].gitdir),
555 def test_bad_path_name_checks(self): 635 os.path.join(self.tempdir, ".repo", "projects", "..git"),
556 """Check handling of bad path & name attributes.""" 636 )
557 def parse(name, path): 637
558 name = self.encodeXmlAttr(name) 638 def test_bad_path_name_checks(self):
559 path = self.encodeXmlAttr(path) 639 """Check handling of bad path & name attributes."""
560 manifest = self.getXmlManifest(f""" 640
641 def parse(name, path):
642 name = self.encodeXmlAttr(name)
643 path = self.encodeXmlAttr(path)
644 manifest = self.getXmlManifest(
645 f"""
561<manifest> 646<manifest>
562 <remote name="default-remote" fetch="http://localhost" /> 647 <remote name="default-remote" fetch="http://localhost" />
563 <default remote="default-remote" revision="refs/heads/main" /> 648 <default remote="default-remote" revision="refs/heads/main" />
564 <project name="{name}" path="{path}" /> 649 <project name="{name}" path="{path}" />
565</manifest> 650</manifest>
566""") 651"""
567 # Force the manifest to be parsed. 652 )
568 manifest.ToXml() 653 # Force the manifest to be parsed.
654 manifest.ToXml()
569 655
570 # Verify the parser is valid by default to avoid buggy tests below. 656 # Verify the parser is valid by default to avoid buggy tests below.
571 parse('ok', 'ok') 657 parse("ok", "ok")
572 658
573 # Handle empty name explicitly because a different codepath rejects it. 659 # Handle empty name explicitly because a different codepath rejects it.
574 # Empty path is OK because it defaults to the name field. 660 # Empty path is OK because it defaults to the name field.
575 with self.assertRaises(error.ManifestParseError): 661 with self.assertRaises(error.ManifestParseError):
576 parse('', 'ok') 662 parse("", "ok")
577 663
578 for path in INVALID_FS_PATHS: 664 for path in INVALID_FS_PATHS:
579 if not path or path.endswith('/') or path.endswith(os.path.sep): 665 if not path or path.endswith("/") or path.endswith(os.path.sep):
580 continue 666 continue
581 667
582 with self.assertRaises(error.ManifestInvalidPathError): 668 with self.assertRaises(error.ManifestInvalidPathError):
583 parse(path, 'ok') 669 parse(path, "ok")
584 670
585 # We have a dedicated test for path=".". 671 # We have a dedicated test for path=".".
586 if path not in {'.'}: 672 if path not in {"."}:
587 with self.assertRaises(error.ManifestInvalidPathError): 673 with self.assertRaises(error.ManifestInvalidPathError):
588 parse('ok', path) 674 parse("ok", path)
589 675
590 676
591class SuperProjectElementTests(ManifestParseTestCase): 677class SuperProjectElementTests(ManifestParseTestCase):
592 """Tests for <superproject>.""" 678 """Tests for <superproject>."""
593 679
594 def test_superproject(self): 680 def test_superproject(self):
595 """Check superproject settings.""" 681 """Check superproject settings."""
596 manifest = self.getXmlManifest(""" 682 manifest = self.getXmlManifest(
683 """
597<manifest> 684<manifest>
598 <remote name="test-remote" fetch="http://localhost" /> 685 <remote name="test-remote" fetch="http://localhost" />
599 <default remote="test-remote" revision="refs/heads/main" /> 686 <default remote="test-remote" revision="refs/heads/main" />
600 <superproject name="superproject"/> 687 <superproject name="superproject"/>
601</manifest> 688</manifest>
602""") 689"""
603 self.assertEqual(manifest.superproject.name, 'superproject') 690 )
604 self.assertEqual(manifest.superproject.remote.name, 'test-remote') 691 self.assertEqual(manifest.superproject.name, "superproject")
605 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject') 692 self.assertEqual(manifest.superproject.remote.name, "test-remote")
606 self.assertEqual(manifest.superproject.revision, 'refs/heads/main') 693 self.assertEqual(
607 self.assertEqual( 694 manifest.superproject.remote.url, "http://localhost/superproject"
608 sort_attributes(manifest.ToXml().toxml()), 695 )
609 '<?xml version="1.0" ?><manifest>' 696 self.assertEqual(manifest.superproject.revision, "refs/heads/main")
610 '<remote fetch="http://localhost" name="test-remote"/>' 697 self.assertEqual(
611 '<default remote="test-remote" revision="refs/heads/main"/>' 698 sort_attributes(manifest.ToXml().toxml()),
612 '<superproject name="superproject"/>' 699 '<?xml version="1.0" ?><manifest>'
613 '</manifest>') 700 '<remote fetch="http://localhost" name="test-remote"/>'
614 701 '<default remote="test-remote" revision="refs/heads/main"/>'
615 def test_superproject_revision(self): 702 '<superproject name="superproject"/>'
616 """Check superproject settings with a different revision attribute""" 703 "</manifest>",
617 self.maxDiff = None 704 )
618 manifest = self.getXmlManifest(""" 705
706 def test_superproject_revision(self):
707 """Check superproject settings with a different revision attribute"""
708 self.maxDiff = None
709 manifest = self.getXmlManifest(
710 """
619<manifest> 711<manifest>
620 <remote name="test-remote" fetch="http://localhost" /> 712 <remote name="test-remote" fetch="http://localhost" />
621 <default remote="test-remote" revision="refs/heads/main" /> 713 <default remote="test-remote" revision="refs/heads/main" />
622 <superproject name="superproject" revision="refs/heads/stable" /> 714 <superproject name="superproject" revision="refs/heads/stable" />
623</manifest> 715</manifest>
624""") 716"""
625 self.assertEqual(manifest.superproject.name, 'superproject') 717 )
626 self.assertEqual(manifest.superproject.remote.name, 'test-remote') 718 self.assertEqual(manifest.superproject.name, "superproject")
627 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject') 719 self.assertEqual(manifest.superproject.remote.name, "test-remote")
628 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable') 720 self.assertEqual(
629 self.assertEqual( 721 manifest.superproject.remote.url, "http://localhost/superproject"
630 sort_attributes(manifest.ToXml().toxml()), 722 )
631 '<?xml version="1.0" ?><manifest>' 723 self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
632 '<remote fetch="http://localhost" name="test-remote"/>' 724 self.assertEqual(
633 '<default remote="test-remote" revision="refs/heads/main"/>' 725 sort_attributes(manifest.ToXml().toxml()),
634 '<superproject name="superproject" revision="refs/heads/stable"/>' 726 '<?xml version="1.0" ?><manifest>'
635 '</manifest>') 727 '<remote fetch="http://localhost" name="test-remote"/>'
636 728 '<default remote="test-remote" revision="refs/heads/main"/>'
637 def test_superproject_revision_default_negative(self): 729 '<superproject name="superproject" revision="refs/heads/stable"/>'
638 """Check superproject settings with a same revision attribute""" 730 "</manifest>",
639 self.maxDiff = None 731 )
640 manifest = self.getXmlManifest(""" 732
733 def test_superproject_revision_default_negative(self):
734 """Check superproject settings with a same revision attribute"""
735 self.maxDiff = None
736 manifest = self.getXmlManifest(
737 """
641<manifest> 738<manifest>
642 <remote name="test-remote" fetch="http://localhost" /> 739 <remote name="test-remote" fetch="http://localhost" />
643 <default remote="test-remote" revision="refs/heads/stable" /> 740 <default remote="test-remote" revision="refs/heads/stable" />
644 <superproject name="superproject" revision="refs/heads/stable" /> 741 <superproject name="superproject" revision="refs/heads/stable" />
645</manifest> 742</manifest>
646""") 743"""
647 self.assertEqual(manifest.superproject.name, 'superproject') 744 )
648 self.assertEqual(manifest.superproject.remote.name, 'test-remote') 745 self.assertEqual(manifest.superproject.name, "superproject")
649 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject') 746 self.assertEqual(manifest.superproject.remote.name, "test-remote")
650 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable') 747 self.assertEqual(
651 self.assertEqual( 748 manifest.superproject.remote.url, "http://localhost/superproject"
652 sort_attributes(manifest.ToXml().toxml()), 749 )
653 '<?xml version="1.0" ?><manifest>' 750 self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
654 '<remote fetch="http://localhost" name="test-remote"/>' 751 self.assertEqual(
655 '<default remote="test-remote" revision="refs/heads/stable"/>' 752 sort_attributes(manifest.ToXml().toxml()),
656 '<superproject name="superproject"/>' 753 '<?xml version="1.0" ?><manifest>'
657 '</manifest>') 754 '<remote fetch="http://localhost" name="test-remote"/>'
658 755 '<default remote="test-remote" revision="refs/heads/stable"/>'
659 def test_superproject_revision_remote(self): 756 '<superproject name="superproject"/>'
660 """Check superproject settings with a same revision attribute""" 757 "</manifest>",
661 self.maxDiff = None 758 )
662 manifest = self.getXmlManifest(""" 759
760 def test_superproject_revision_remote(self):
761 """Check superproject settings with a same revision attribute"""
762 self.maxDiff = None
763 manifest = self.getXmlManifest(
764 """
663<manifest> 765<manifest>
664 <remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" /> 766 <remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
665 <default remote="test-remote" /> 767 <default remote="test-remote" />
666 <superproject name="superproject" revision="refs/heads/stable" /> 768 <superproject name="superproject" revision="refs/heads/stable" />
667</manifest> 769</manifest>
668""") 770""" # noqa: E501
669 self.assertEqual(manifest.superproject.name, 'superproject') 771 )
670 self.assertEqual(manifest.superproject.remote.name, 'test-remote') 772 self.assertEqual(manifest.superproject.name, "superproject")
671 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject') 773 self.assertEqual(manifest.superproject.remote.name, "test-remote")
672 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable') 774 self.assertEqual(
673 self.assertEqual( 775 manifest.superproject.remote.url, "http://localhost/superproject"
674 sort_attributes(manifest.ToXml().toxml()), 776 )
675 '<?xml version="1.0" ?><manifest>' 777 self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
676 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>' 778 self.assertEqual(
677 '<default remote="test-remote"/>' 779 sort_attributes(manifest.ToXml().toxml()),
678 '<superproject name="superproject" revision="refs/heads/stable"/>' 780 '<?xml version="1.0" ?><manifest>'
679 '</manifest>') 781 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>' # noqa: E501
680 782 '<default remote="test-remote"/>'
681 def test_remote(self): 783 '<superproject name="superproject" revision="refs/heads/stable"/>'
682 """Check superproject settings with a remote.""" 784 "</manifest>",
683 manifest = self.getXmlManifest(""" 785 )
786
787 def test_remote(self):
788 """Check superproject settings with a remote."""
789 manifest = self.getXmlManifest(
790 """
684<manifest> 791<manifest>
685 <remote name="default-remote" fetch="http://localhost" /> 792 <remote name="default-remote" fetch="http://localhost" />
686 <remote name="superproject-remote" fetch="http://localhost" /> 793 <remote name="superproject-remote" fetch="http://localhost" />
687 <default remote="default-remote" revision="refs/heads/main" /> 794 <default remote="default-remote" revision="refs/heads/main" />
688 <superproject name="platform/superproject" remote="superproject-remote"/> 795 <superproject name="platform/superproject" remote="superproject-remote"/>
689</manifest> 796</manifest>
690""") 797"""
691 self.assertEqual(manifest.superproject.name, 'platform/superproject') 798 )
692 self.assertEqual(manifest.superproject.remote.name, 'superproject-remote') 799 self.assertEqual(manifest.superproject.name, "platform/superproject")
693 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/platform/superproject') 800 self.assertEqual(
694 self.assertEqual(manifest.superproject.revision, 'refs/heads/main') 801 manifest.superproject.remote.name, "superproject-remote"
695 self.assertEqual( 802 )
696 sort_attributes(manifest.ToXml().toxml()), 803 self.assertEqual(
697 '<?xml version="1.0" ?><manifest>' 804 manifest.superproject.remote.url,
698 '<remote fetch="http://localhost" name="default-remote"/>' 805 "http://localhost/platform/superproject",
699 '<remote fetch="http://localhost" name="superproject-remote"/>' 806 )
700 '<default remote="default-remote" revision="refs/heads/main"/>' 807 self.assertEqual(manifest.superproject.revision, "refs/heads/main")
701 '<superproject name="platform/superproject" remote="superproject-remote"/>' 808 self.assertEqual(
702 '</manifest>') 809 sort_attributes(manifest.ToXml().toxml()),
703 810 '<?xml version="1.0" ?><manifest>'
704 def test_defalut_remote(self): 811 '<remote fetch="http://localhost" name="default-remote"/>'
705 """Check superproject settings with a default remote.""" 812 '<remote fetch="http://localhost" name="superproject-remote"/>'
706 manifest = self.getXmlManifest(""" 813 '<default remote="default-remote" revision="refs/heads/main"/>'
814 '<superproject name="platform/superproject" remote="superproject-remote"/>' # noqa: E501
815 "</manifest>",
816 )
817
818 def test_defalut_remote(self):
819 """Check superproject settings with a default remote."""
820 manifest = self.getXmlManifest(
821 """
707<manifest> 822<manifest>
708 <remote name="default-remote" fetch="http://localhost" /> 823 <remote name="default-remote" fetch="http://localhost" />
709 <default remote="default-remote" revision="refs/heads/main" /> 824 <default remote="default-remote" revision="refs/heads/main" />
710 <superproject name="superproject" remote="default-remote"/> 825 <superproject name="superproject" remote="default-remote"/>
711</manifest> 826</manifest>
712""") 827"""
713 self.assertEqual(manifest.superproject.name, 'superproject') 828 )
714 self.assertEqual(manifest.superproject.remote.name, 'default-remote') 829 self.assertEqual(manifest.superproject.name, "superproject")
715 self.assertEqual(manifest.superproject.revision, 'refs/heads/main') 830 self.assertEqual(manifest.superproject.remote.name, "default-remote")
716 self.assertEqual( 831 self.assertEqual(manifest.superproject.revision, "refs/heads/main")
717 sort_attributes(manifest.ToXml().toxml()), 832 self.assertEqual(
718 '<?xml version="1.0" ?><manifest>' 833 sort_attributes(manifest.ToXml().toxml()),
719 '<remote fetch="http://localhost" name="default-remote"/>' 834 '<?xml version="1.0" ?><manifest>'
720 '<default remote="default-remote" revision="refs/heads/main"/>' 835 '<remote fetch="http://localhost" name="default-remote"/>'
721 '<superproject name="superproject"/>' 836 '<default remote="default-remote" revision="refs/heads/main"/>'
722 '</manifest>') 837 '<superproject name="superproject"/>'
838 "</manifest>",
839 )
723 840
724 841
725class ContactinfoElementTests(ManifestParseTestCase): 842class ContactinfoElementTests(ManifestParseTestCase):
726 """Tests for <contactinfo>.""" 843 """Tests for <contactinfo>."""
727 844
728 def test_contactinfo(self): 845 def test_contactinfo(self):
729 """Check contactinfo settings.""" 846 """Check contactinfo settings."""
730 bugurl = 'http://localhost/contactinfo' 847 bugurl = "http://localhost/contactinfo"
731 manifest = self.getXmlManifest(f""" 848 manifest = self.getXmlManifest(
849 f"""
732<manifest> 850<manifest>
733 <contactinfo bugurl="{bugurl}"/> 851 <contactinfo bugurl="{bugurl}"/>
734</manifest> 852</manifest>
735""") 853"""
736 self.assertEqual(manifest.contactinfo.bugurl, bugurl) 854 )
737 self.assertEqual( 855 self.assertEqual(manifest.contactinfo.bugurl, bugurl)
738 manifest.ToXml().toxml(), 856 self.assertEqual(
739 '<?xml version="1.0" ?><manifest>' 857 manifest.ToXml().toxml(),
740 f'<contactinfo bugurl="{bugurl}"/>' 858 '<?xml version="1.0" ?><manifest>'
741 '</manifest>') 859 f'<contactinfo bugurl="{bugurl}"/>'
860 "</manifest>",
861 )
742 862
743 863
744class DefaultElementTests(ManifestParseTestCase): 864class DefaultElementTests(ManifestParseTestCase):
745 """Tests for <default>.""" 865 """Tests for <default>."""
746 866
747 def test_default(self): 867 def test_default(self):
748 """Check default settings.""" 868 """Check default settings."""
749 a = manifest_xml._Default() 869 a = manifest_xml._Default()
750 a.revisionExpr = 'foo' 870 a.revisionExpr = "foo"
751 a.remote = manifest_xml._XmlRemote(name='remote') 871 a.remote = manifest_xml._XmlRemote(name="remote")
752 b = manifest_xml._Default() 872 b = manifest_xml._Default()
753 b.revisionExpr = 'bar' 873 b.revisionExpr = "bar"
754 self.assertEqual(a, a) 874 self.assertEqual(a, a)
755 self.assertNotEqual(a, b) 875 self.assertNotEqual(a, b)
756 self.assertNotEqual(b, a.remote) 876 self.assertNotEqual(b, a.remote)
757 self.assertNotEqual(a, 123) 877 self.assertNotEqual(a, 123)
758 self.assertNotEqual(a, None) 878 self.assertNotEqual(a, None)
759 879
760 880
761class RemoteElementTests(ManifestParseTestCase): 881class RemoteElementTests(ManifestParseTestCase):
762 """Tests for <remote>.""" 882 """Tests for <remote>."""
763 883
764 def test_remote(self): 884 def test_remote(self):
765 """Check remote settings.""" 885 """Check remote settings."""
766 a = manifest_xml._XmlRemote(name='foo') 886 a = manifest_xml._XmlRemote(name="foo")
767 a.AddAnnotation('key1', 'value1', 'true') 887 a.AddAnnotation("key1", "value1", "true")
768 b = manifest_xml._XmlRemote(name='foo') 888 b = manifest_xml._XmlRemote(name="foo")
769 b.AddAnnotation('key2', 'value1', 'true') 889 b.AddAnnotation("key2", "value1", "true")
770 c = manifest_xml._XmlRemote(name='foo') 890 c = manifest_xml._XmlRemote(name="foo")
771 c.AddAnnotation('key1', 'value2', 'true') 891 c.AddAnnotation("key1", "value2", "true")
772 d = manifest_xml._XmlRemote(name='foo') 892 d = manifest_xml._XmlRemote(name="foo")
773 d.AddAnnotation('key1', 'value1', 'false') 893 d.AddAnnotation("key1", "value1", "false")
774 self.assertEqual(a, a) 894 self.assertEqual(a, a)
775 self.assertNotEqual(a, b) 895 self.assertNotEqual(a, b)
776 self.assertNotEqual(a, c) 896 self.assertNotEqual(a, c)
777 self.assertNotEqual(a, d) 897 self.assertNotEqual(a, d)
778 self.assertNotEqual(a, manifest_xml._Default()) 898 self.assertNotEqual(a, manifest_xml._Default())
779 self.assertNotEqual(a, 123) 899 self.assertNotEqual(a, 123)
780 self.assertNotEqual(a, None) 900 self.assertNotEqual(a, None)
781 901
782 902
783class RemoveProjectElementTests(ManifestParseTestCase): 903class RemoveProjectElementTests(ManifestParseTestCase):
784 """Tests for <remove-project>.""" 904 """Tests for <remove-project>."""
785 905
786 def test_remove_one_project(self): 906 def test_remove_one_project(self):
787 manifest = self.getXmlManifest(""" 907 manifest = self.getXmlManifest(
908 """
788<manifest> 909<manifest>
789 <remote name="default-remote" fetch="http://localhost" /> 910 <remote name="default-remote" fetch="http://localhost" />
790 <default remote="default-remote" revision="refs/heads/main" /> 911 <default remote="default-remote" revision="refs/heads/main" />
791 <project name="myproject" /> 912 <project name="myproject" />
792 <remove-project name="myproject" /> 913 <remove-project name="myproject" />
793</manifest> 914</manifest>
794""") 915"""
795 self.assertEqual(manifest.projects, []) 916 )
917 self.assertEqual(manifest.projects, [])
796 918
797 def test_remove_one_project_one_remains(self): 919 def test_remove_one_project_one_remains(self):
798 manifest = self.getXmlManifest(""" 920 manifest = self.getXmlManifest(
921 """
799<manifest> 922<manifest>
800 <remote name="default-remote" fetch="http://localhost" /> 923 <remote name="default-remote" fetch="http://localhost" />
801 <default remote="default-remote" revision="refs/heads/main" /> 924 <default remote="default-remote" revision="refs/heads/main" />
@@ -803,51 +926,59 @@ class RemoveProjectElementTests(ManifestParseTestCase):
803 <project name="yourproject" /> 926 <project name="yourproject" />
804 <remove-project name="myproject" /> 927 <remove-project name="myproject" />
805</manifest> 928</manifest>
806""") 929"""
930 )
807 931
808 self.assertEqual(len(manifest.projects), 1) 932 self.assertEqual(len(manifest.projects), 1)
809 self.assertEqual(manifest.projects[0].name, 'yourproject') 933 self.assertEqual(manifest.projects[0].name, "yourproject")
810 934
811 def test_remove_one_project_doesnt_exist(self): 935 def test_remove_one_project_doesnt_exist(self):
812 with self.assertRaises(manifest_xml.ManifestParseError): 936 with self.assertRaises(manifest_xml.ManifestParseError):
813 manifest = self.getXmlManifest(""" 937 manifest = self.getXmlManifest(
938 """
814<manifest> 939<manifest>
815 <remote name="default-remote" fetch="http://localhost" /> 940 <remote name="default-remote" fetch="http://localhost" />
816 <default remote="default-remote" revision="refs/heads/main" /> 941 <default remote="default-remote" revision="refs/heads/main" />
817 <remove-project name="myproject" /> 942 <remove-project name="myproject" />
818</manifest> 943</manifest>
819""") 944"""
820 manifest.projects 945 )
946 manifest.projects
821 947
822 def test_remove_one_optional_project_doesnt_exist(self): 948 def test_remove_one_optional_project_doesnt_exist(self):
823 manifest = self.getXmlManifest(""" 949 manifest = self.getXmlManifest(
950 """
824<manifest> 951<manifest>
825 <remote name="default-remote" fetch="http://localhost" /> 952 <remote name="default-remote" fetch="http://localhost" />
826 <default remote="default-remote" revision="refs/heads/main" /> 953 <default remote="default-remote" revision="refs/heads/main" />
827 <remove-project name="myproject" optional="true" /> 954 <remove-project name="myproject" optional="true" />
828</manifest> 955</manifest>
829""") 956"""
830 self.assertEqual(manifest.projects, []) 957 )
958 self.assertEqual(manifest.projects, [])
831 959
832 960
833class ExtendProjectElementTests(ManifestParseTestCase): 961class ExtendProjectElementTests(ManifestParseTestCase):
834 """Tests for <extend-project>.""" 962 """Tests for <extend-project>."""
835 963
836 def test_extend_project_dest_path_single_match(self): 964 def test_extend_project_dest_path_single_match(self):
837 manifest = self.getXmlManifest(""" 965 manifest = self.getXmlManifest(
966 """
838<manifest> 967<manifest>
839 <remote name="default-remote" fetch="http://localhost" /> 968 <remote name="default-remote" fetch="http://localhost" />
840 <default remote="default-remote" revision="refs/heads/main" /> 969 <default remote="default-remote" revision="refs/heads/main" />
841 <project name="myproject" /> 970 <project name="myproject" />
842 <extend-project name="myproject" dest-path="bar" /> 971 <extend-project name="myproject" dest-path="bar" />
843</manifest> 972</manifest>
844""") 973"""
845 self.assertEqual(len(manifest.projects), 1) 974 )
846 self.assertEqual(manifest.projects[0].relpath, 'bar') 975 self.assertEqual(len(manifest.projects), 1)
847 976 self.assertEqual(manifest.projects[0].relpath, "bar")
848 def test_extend_project_dest_path_multi_match(self): 977
849 with self.assertRaises(manifest_xml.ManifestParseError): 978 def test_extend_project_dest_path_multi_match(self):
850 manifest = self.getXmlManifest(""" 979 with self.assertRaises(manifest_xml.ManifestParseError):
980 manifest = self.getXmlManifest(
981 """
851<manifest> 982<manifest>
852 <remote name="default-remote" fetch="http://localhost" /> 983 <remote name="default-remote" fetch="http://localhost" />
853 <default remote="default-remote" revision="refs/heads/main" /> 984 <default remote="default-remote" revision="refs/heads/main" />
@@ -855,11 +986,13 @@ class ExtendProjectElementTests(ManifestParseTestCase):
855 <project name="myproject" path="y" /> 986 <project name="myproject" path="y" />
856 <extend-project name="myproject" dest-path="bar" /> 987 <extend-project name="myproject" dest-path="bar" />
857</manifest> 988</manifest>
858""") 989"""
859 manifest.projects 990 )
991 manifest.projects
860 992
861 def test_extend_project_dest_path_multi_match_path_specified(self): 993 def test_extend_project_dest_path_multi_match_path_specified(self):
862 manifest = self.getXmlManifest(""" 994 manifest = self.getXmlManifest(
995 """
863<manifest> 996<manifest>
864 <remote name="default-remote" fetch="http://localhost" /> 997 <remote name="default-remote" fetch="http://localhost" />
865 <default remote="default-remote" revision="refs/heads/main" /> 998 <default remote="default-remote" revision="refs/heads/main" />
@@ -867,34 +1000,39 @@ class ExtendProjectElementTests(ManifestParseTestCase):
867 <project name="myproject" path="y" /> 1000 <project name="myproject" path="y" />
868 <extend-project name="myproject" path="x" dest-path="bar" /> 1001 <extend-project name="myproject" path="x" dest-path="bar" />
869</manifest> 1002</manifest>
870""") 1003"""
871 self.assertEqual(len(manifest.projects), 2) 1004 )
872 if manifest.projects[0].relpath == 'y': 1005 self.assertEqual(len(manifest.projects), 2)
873 self.assertEqual(manifest.projects[1].relpath, 'bar') 1006 if manifest.projects[0].relpath == "y":
874 else: 1007 self.assertEqual(manifest.projects[1].relpath, "bar")
875 self.assertEqual(manifest.projects[0].relpath, 'bar') 1008 else:
876 self.assertEqual(manifest.projects[1].relpath, 'y') 1009 self.assertEqual(manifest.projects[0].relpath, "bar")
877 1010 self.assertEqual(manifest.projects[1].relpath, "y")
878 def test_extend_project_dest_branch(self): 1011
879 manifest = self.getXmlManifest(""" 1012 def test_extend_project_dest_branch(self):
1013 manifest = self.getXmlManifest(
1014 """
880<manifest> 1015<manifest>
881 <remote name="default-remote" fetch="http://localhost" /> 1016 <remote name="default-remote" fetch="http://localhost" />
882 <default remote="default-remote" revision="refs/heads/main" dest-branch="foo" /> 1017 <default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
883 <project name="myproject" /> 1018 <project name="myproject" />
884 <extend-project name="myproject" dest-branch="bar" /> 1019 <extend-project name="myproject" dest-branch="bar" />
885</manifest> 1020</manifest>
886""") 1021""" # noqa: E501
887 self.assertEqual(len(manifest.projects), 1) 1022 )
888 self.assertEqual(manifest.projects[0].dest_branch, 'bar') 1023 self.assertEqual(len(manifest.projects), 1)
889 1024 self.assertEqual(manifest.projects[0].dest_branch, "bar")
890 def test_extend_project_upstream(self): 1025
891 manifest = self.getXmlManifest(""" 1026 def test_extend_project_upstream(self):
1027 manifest = self.getXmlManifest(
1028 """
892<manifest> 1029<manifest>
893 <remote name="default-remote" fetch="http://localhost" /> 1030 <remote name="default-remote" fetch="http://localhost" />
894 <default remote="default-remote" revision="refs/heads/main" /> 1031 <default remote="default-remote" revision="refs/heads/main" />
895 <project name="myproject" /> 1032 <project name="myproject" />
896 <extend-project name="myproject" upstream="bar" /> 1033 <extend-project name="myproject" upstream="bar" />
897</manifest> 1034</manifest>
898""") 1035"""
899 self.assertEqual(len(manifest.projects), 1) 1036 )
900 self.assertEqual(manifest.projects[0].upstream, 'bar') 1037 self.assertEqual(len(manifest.projects), 1)
1038 self.assertEqual(manifest.projects[0].upstream, "bar")
diff --git a/tests/test_platform_utils.py b/tests/test_platform_utils.py
index 55b7805c..7a42de01 100644
--- a/tests/test_platform_utils.py
+++ b/tests/test_platform_utils.py
@@ -22,29 +22,31 @@ import platform_utils
22 22
23 23
24class RemoveTests(unittest.TestCase): 24class RemoveTests(unittest.TestCase):
25 """Check remove() helper.""" 25 """Check remove() helper."""
26 26
27 def testMissingOk(self): 27 def testMissingOk(self):
28 """Check missing_ok handling.""" 28 """Check missing_ok handling."""
29 with tempfile.TemporaryDirectory() as tmpdir: 29 with tempfile.TemporaryDirectory() as tmpdir:
30 path = os.path.join(tmpdir, 'test') 30 path = os.path.join(tmpdir, "test")
31 31
32 # Should not fail. 32 # Should not fail.
33 platform_utils.remove(path, missing_ok=True) 33 platform_utils.remove(path, missing_ok=True)
34 34
35 # Should fail. 35 # Should fail.
36 self.assertRaises(OSError, platform_utils.remove, path) 36 self.assertRaises(OSError, platform_utils.remove, path)
37 self.assertRaises(OSError, platform_utils.remove, path, missing_ok=False) 37 self.assertRaises(
38 38 OSError, platform_utils.remove, path, missing_ok=False
39 # Should not fail if it exists. 39 )
40 open(path, 'w').close() 40
41 platform_utils.remove(path, missing_ok=True) 41 # Should not fail if it exists.
42 self.assertFalse(os.path.exists(path)) 42 open(path, "w").close()
43 43 platform_utils.remove(path, missing_ok=True)
44 open(path, 'w').close() 44 self.assertFalse(os.path.exists(path))
45 platform_utils.remove(path) 45
46 self.assertFalse(os.path.exists(path)) 46 open(path, "w").close()
47 47 platform_utils.remove(path)
48 open(path, 'w').close() 48 self.assertFalse(os.path.exists(path))
49 platform_utils.remove(path, missing_ok=False) 49
50 self.assertFalse(os.path.exists(path)) 50 open(path, "w").close()
51 platform_utils.remove(path, missing_ok=False)
52 self.assertFalse(os.path.exists(path))
diff --git a/tests/test_project.py b/tests/test_project.py
index c50d9940..bc8330b2 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -31,452 +31,493 @@ import project
31 31
32@contextlib.contextmanager 32@contextlib.contextmanager
33def TempGitTree(): 33def TempGitTree():
34 """Create a new empty git checkout for testing.""" 34 """Create a new empty git checkout for testing."""
35 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 35 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
36 # Tests need to assume, that main is default branch at init, 36 # Tests need to assume, that main is default branch at init,
37 # which is not supported in config until 2.28. 37 # which is not supported in config until 2.28.
38 cmd = ['git', 'init'] 38 cmd = ["git", "init"]
39 if git_command.git_require((2, 28, 0)): 39 if git_command.git_require((2, 28, 0)):
40 cmd += ['--initial-branch=main'] 40 cmd += ["--initial-branch=main"]
41 else: 41 else:
42 # Use template dir for init. 42 # Use template dir for init.
43 templatedir = tempfile.mkdtemp(prefix='.test-template') 43 templatedir = tempfile.mkdtemp(prefix=".test-template")
44 with open(os.path.join(templatedir, 'HEAD'), 'w') as fp: 44 with open(os.path.join(templatedir, "HEAD"), "w") as fp:
45 fp.write('ref: refs/heads/main\n') 45 fp.write("ref: refs/heads/main\n")
46 cmd += ['--template', templatedir] 46 cmd += ["--template", templatedir]
47 subprocess.check_call(cmd, cwd=tempdir) 47 subprocess.check_call(cmd, cwd=tempdir)
48 yield tempdir 48 yield tempdir
49 49
50 50
51class FakeProject(object): 51class FakeProject(object):
52 """A fake for Project for basic functionality.""" 52 """A fake for Project for basic functionality."""
53 53
54 def __init__(self, worktree): 54 def __init__(self, worktree):
55 self.worktree = worktree 55 self.worktree = worktree
56 self.gitdir = os.path.join(worktree, '.git') 56 self.gitdir = os.path.join(worktree, ".git")
57 self.name = 'fakeproject' 57 self.name = "fakeproject"
58 self.work_git = project.Project._GitGetByExec( 58 self.work_git = project.Project._GitGetByExec(
59 self, bare=False, gitdir=self.gitdir) 59 self, bare=False, gitdir=self.gitdir
60 self.bare_git = project.Project._GitGetByExec( 60 )
61 self, bare=True, gitdir=self.gitdir) 61 self.bare_git = project.Project._GitGetByExec(
62 self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir) 62 self, bare=True, gitdir=self.gitdir
63 )
64 self.config = git_config.GitConfig.ForRepository(gitdir=self.gitdir)
63 65
64 66
65class ReviewableBranchTests(unittest.TestCase): 67class ReviewableBranchTests(unittest.TestCase):
66 """Check ReviewableBranch behavior.""" 68 """Check ReviewableBranch behavior."""
67 69
68 def test_smoke(self): 70 def test_smoke(self):
69 """A quick run through everything.""" 71 """A quick run through everything."""
70 with TempGitTree() as tempdir: 72 with TempGitTree() as tempdir:
71 fakeproj = FakeProject(tempdir) 73 fakeproj = FakeProject(tempdir)
72 74
73 # Generate some commits. 75 # Generate some commits.
74 with open(os.path.join(tempdir, 'readme'), 'w') as fp: 76 with open(os.path.join(tempdir, "readme"), "w") as fp:
75 fp.write('txt') 77 fp.write("txt")
76 fakeproj.work_git.add('readme') 78 fakeproj.work_git.add("readme")
77 fakeproj.work_git.commit('-mAdd file') 79 fakeproj.work_git.commit("-mAdd file")
78 fakeproj.work_git.checkout('-b', 'work') 80 fakeproj.work_git.checkout("-b", "work")
79 fakeproj.work_git.rm('-f', 'readme') 81 fakeproj.work_git.rm("-f", "readme")
80 fakeproj.work_git.commit('-mDel file') 82 fakeproj.work_git.commit("-mDel file")
81 83
82 # Start off with the normal details. 84 # Start off with the normal details.
83 rb = project.ReviewableBranch( 85 rb = project.ReviewableBranch(
84 fakeproj, fakeproj.config.GetBranch('work'), 'main') 86 fakeproj, fakeproj.config.GetBranch("work"), "main"
85 self.assertEqual('work', rb.name) 87 )
86 self.assertEqual(1, len(rb.commits)) 88 self.assertEqual("work", rb.name)
87 self.assertIn('Del file', rb.commits[0]) 89 self.assertEqual(1, len(rb.commits))
88 d = rb.unabbrev_commits 90 self.assertIn("Del file", rb.commits[0])
89 self.assertEqual(1, len(d)) 91 d = rb.unabbrev_commits
90 short, long = next(iter(d.items())) 92 self.assertEqual(1, len(d))
91 self.assertTrue(long.startswith(short)) 93 short, long = next(iter(d.items()))
92 self.assertTrue(rb.base_exists) 94 self.assertTrue(long.startswith(short))
93 # Hard to assert anything useful about this. 95 self.assertTrue(rb.base_exists)
94 self.assertTrue(rb.date) 96 # Hard to assert anything useful about this.
95 97 self.assertTrue(rb.date)
96 # Now delete the tracking branch! 98
97 fakeproj.work_git.branch('-D', 'main') 99 # Now delete the tracking branch!
98 rb = project.ReviewableBranch( 100 fakeproj.work_git.branch("-D", "main")
99 fakeproj, fakeproj.config.GetBranch('work'), 'main') 101 rb = project.ReviewableBranch(
100 self.assertEqual(0, len(rb.commits)) 102 fakeproj, fakeproj.config.GetBranch("work"), "main"
101 self.assertFalse(rb.base_exists) 103 )
102 # Hard to assert anything useful about this. 104 self.assertEqual(0, len(rb.commits))
103 self.assertTrue(rb.date) 105 self.assertFalse(rb.base_exists)
106 # Hard to assert anything useful about this.
107 self.assertTrue(rb.date)
104 108
105 109
106class CopyLinkTestCase(unittest.TestCase): 110class CopyLinkTestCase(unittest.TestCase):
107 """TestCase for stub repo client checkouts. 111 """TestCase for stub repo client checkouts.
108 112
109 It'll have a layout like this: 113 It'll have a layout like this:
110 tempdir/ # self.tempdir 114 tempdir/ # self.tempdir
111 checkout/ # self.topdir 115 checkout/ # self.topdir
112 git-project/ # self.worktree 116 git-project/ # self.worktree
113 117
114 Attributes: 118 Attributes:
115 tempdir: A dedicated temporary directory. 119 tempdir: A dedicated temporary directory.
116 worktree: The top of the repo client checkout. 120 worktree: The top of the repo client checkout.
117 topdir: The top of a project checkout. 121 topdir: The top of a project checkout.
118 """ 122 """
119 123
120 def setUp(self): 124 def setUp(self):
121 self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests') 125 self.tempdirobj = tempfile.TemporaryDirectory(prefix="repo_tests")
122 self.tempdir = self.tempdirobj.name 126 self.tempdir = self.tempdirobj.name
123 self.topdir = os.path.join(self.tempdir, 'checkout') 127 self.topdir = os.path.join(self.tempdir, "checkout")
124 self.worktree = os.path.join(self.topdir, 'git-project') 128 self.worktree = os.path.join(self.topdir, "git-project")
125 os.makedirs(self.topdir) 129 os.makedirs(self.topdir)
126 os.makedirs(self.worktree) 130 os.makedirs(self.worktree)
127 131
128 def tearDown(self): 132 def tearDown(self):
129 self.tempdirobj.cleanup() 133 self.tempdirobj.cleanup()
130 134
131 @staticmethod 135 @staticmethod
132 def touch(path): 136 def touch(path):
133 with open(path, 'w'): 137 with open(path, "w"):
134 pass 138 pass
135 139
136 def assertExists(self, path, msg=None): 140 def assertExists(self, path, msg=None):
137 """Make sure |path| exists.""" 141 """Make sure |path| exists."""
138 if os.path.exists(path): 142 if os.path.exists(path):
139 return 143 return
140 144
141 if msg is None: 145 if msg is None:
142 msg = ['path is missing: %s' % path] 146 msg = ["path is missing: %s" % path]
143 while path != '/': 147 while path != "/":
144 path = os.path.dirname(path) 148 path = os.path.dirname(path)
145 if not path: 149 if not path:
146 # If we're given something like "foo", abort once we get to "". 150 # If we're given something like "foo", abort once we get to
147 break 151 # "".
148 result = os.path.exists(path) 152 break
149 msg.append('\tos.path.exists(%s): %s' % (path, result)) 153 result = os.path.exists(path)
150 if result: 154 msg.append("\tos.path.exists(%s): %s" % (path, result))
151 msg.append('\tcontents: %r' % os.listdir(path)) 155 if result:
152 break 156 msg.append("\tcontents: %r" % os.listdir(path))
153 msg = '\n'.join(msg) 157 break
154 158 msg = "\n".join(msg)
155 raise self.failureException(msg) 159
160 raise self.failureException(msg)
156 161
157 162
158class CopyFile(CopyLinkTestCase): 163class CopyFile(CopyLinkTestCase):
159 """Check _CopyFile handling.""" 164 """Check _CopyFile handling."""
160 165
161 def CopyFile(self, src, dest): 166 def CopyFile(self, src, dest):
162 return project._CopyFile(self.worktree, src, self.topdir, dest) 167 return project._CopyFile(self.worktree, src, self.topdir, dest)
163 168
164 def test_basic(self): 169 def test_basic(self):
165 """Basic test of copying a file from a project to the toplevel.""" 170 """Basic test of copying a file from a project to the toplevel."""
166 src = os.path.join(self.worktree, 'foo.txt') 171 src = os.path.join(self.worktree, "foo.txt")
167 self.touch(src) 172 self.touch(src)
168 cf = self.CopyFile('foo.txt', 'foo') 173 cf = self.CopyFile("foo.txt", "foo")
169 cf._Copy() 174 cf._Copy()
170 self.assertExists(os.path.join(self.topdir, 'foo')) 175 self.assertExists(os.path.join(self.topdir, "foo"))
171 176
172 def test_src_subdir(self): 177 def test_src_subdir(self):
173 """Copy a file from a subdir of a project.""" 178 """Copy a file from a subdir of a project."""
174 src = os.path.join(self.worktree, 'bar', 'foo.txt') 179 src = os.path.join(self.worktree, "bar", "foo.txt")
175 os.makedirs(os.path.dirname(src)) 180 os.makedirs(os.path.dirname(src))
176 self.touch(src) 181 self.touch(src)
177 cf = self.CopyFile('bar/foo.txt', 'new.txt') 182 cf = self.CopyFile("bar/foo.txt", "new.txt")
178 cf._Copy() 183 cf._Copy()
179 self.assertExists(os.path.join(self.topdir, 'new.txt')) 184 self.assertExists(os.path.join(self.topdir, "new.txt"))
180 185
181 def test_dest_subdir(self): 186 def test_dest_subdir(self):
182 """Copy a file to a subdir of a checkout.""" 187 """Copy a file to a subdir of a checkout."""
183 src = os.path.join(self.worktree, 'foo.txt') 188 src = os.path.join(self.worktree, "foo.txt")
184 self.touch(src) 189 self.touch(src)
185 cf = self.CopyFile('foo.txt', 'sub/dir/new.txt') 190 cf = self.CopyFile("foo.txt", "sub/dir/new.txt")
186 self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) 191 self.assertFalse(os.path.exists(os.path.join(self.topdir, "sub")))
187 cf._Copy() 192 cf._Copy()
188 self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'new.txt')) 193 self.assertExists(os.path.join(self.topdir, "sub", "dir", "new.txt"))
189 194
190 def test_update(self): 195 def test_update(self):
191 """Make sure changed files get copied again.""" 196 """Make sure changed files get copied again."""
192 src = os.path.join(self.worktree, 'foo.txt') 197 src = os.path.join(self.worktree, "foo.txt")
193 dest = os.path.join(self.topdir, 'bar') 198 dest = os.path.join(self.topdir, "bar")
194 with open(src, 'w') as f: 199 with open(src, "w") as f:
195 f.write('1st') 200 f.write("1st")
196 cf = self.CopyFile('foo.txt', 'bar') 201 cf = self.CopyFile("foo.txt", "bar")
197 cf._Copy() 202 cf._Copy()
198 self.assertExists(dest) 203 self.assertExists(dest)
199 with open(dest) as f: 204 with open(dest) as f:
200 self.assertEqual(f.read(), '1st') 205 self.assertEqual(f.read(), "1st")
201 206
202 with open(src, 'w') as f: 207 with open(src, "w") as f:
203 f.write('2nd!') 208 f.write("2nd!")
204 cf._Copy() 209 cf._Copy()
205 with open(dest) as f: 210 with open(dest) as f:
206 self.assertEqual(f.read(), '2nd!') 211 self.assertEqual(f.read(), "2nd!")
207 212
208 def test_src_block_symlink(self): 213 def test_src_block_symlink(self):
209 """Do not allow reading from a symlinked path.""" 214 """Do not allow reading from a symlinked path."""
210 src = os.path.join(self.worktree, 'foo.txt') 215 src = os.path.join(self.worktree, "foo.txt")
211 sym = os.path.join(self.worktree, 'sym') 216 sym = os.path.join(self.worktree, "sym")
212 self.touch(src) 217 self.touch(src)
213 platform_utils.symlink('foo.txt', sym) 218 platform_utils.symlink("foo.txt", sym)
214 self.assertExists(sym) 219 self.assertExists(sym)
215 cf = self.CopyFile('sym', 'foo') 220 cf = self.CopyFile("sym", "foo")
216 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 221 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
217 222
218 def test_src_block_symlink_traversal(self): 223 def test_src_block_symlink_traversal(self):
219 """Do not allow reading through a symlink dir.""" 224 """Do not allow reading through a symlink dir."""
220 realfile = os.path.join(self.tempdir, 'file.txt') 225 realfile = os.path.join(self.tempdir, "file.txt")
221 self.touch(realfile) 226 self.touch(realfile)
222 src = os.path.join(self.worktree, 'bar', 'file.txt') 227 src = os.path.join(self.worktree, "bar", "file.txt")
223 platform_utils.symlink(self.tempdir, os.path.join(self.worktree, 'bar')) 228 platform_utils.symlink(self.tempdir, os.path.join(self.worktree, "bar"))
224 self.assertExists(src) 229 self.assertExists(src)
225 cf = self.CopyFile('bar/file.txt', 'foo') 230 cf = self.CopyFile("bar/file.txt", "foo")
226 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 231 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
227 232
228 def test_src_block_copy_from_dir(self): 233 def test_src_block_copy_from_dir(self):
229 """Do not allow copying from a directory.""" 234 """Do not allow copying from a directory."""
230 src = os.path.join(self.worktree, 'dir') 235 src = os.path.join(self.worktree, "dir")
231 os.makedirs(src) 236 os.makedirs(src)
232 cf = self.CopyFile('dir', 'foo') 237 cf = self.CopyFile("dir", "foo")
233 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 238 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
234 239
235 def test_dest_block_symlink(self): 240 def test_dest_block_symlink(self):
236 """Do not allow writing to a symlink.""" 241 """Do not allow writing to a symlink."""
237 src = os.path.join(self.worktree, 'foo.txt') 242 src = os.path.join(self.worktree, "foo.txt")
238 self.touch(src) 243 self.touch(src)
239 platform_utils.symlink('dest', os.path.join(self.topdir, 'sym')) 244 platform_utils.symlink("dest", os.path.join(self.topdir, "sym"))
240 cf = self.CopyFile('foo.txt', 'sym') 245 cf = self.CopyFile("foo.txt", "sym")
241 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 246 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
242 247
243 def test_dest_block_symlink_traversal(self): 248 def test_dest_block_symlink_traversal(self):
244 """Do not allow writing through a symlink dir.""" 249 """Do not allow writing through a symlink dir."""
245 src = os.path.join(self.worktree, 'foo.txt') 250 src = os.path.join(self.worktree, "foo.txt")
246 self.touch(src) 251 self.touch(src)
247 platform_utils.symlink(tempfile.gettempdir(), 252 platform_utils.symlink(
248 os.path.join(self.topdir, 'sym')) 253 tempfile.gettempdir(), os.path.join(self.topdir, "sym")
249 cf = self.CopyFile('foo.txt', 'sym/foo.txt') 254 )
250 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 255 cf = self.CopyFile("foo.txt", "sym/foo.txt")
251 256 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
252 def test_src_block_copy_to_dir(self): 257
253 """Do not allow copying to a directory.""" 258 def test_src_block_copy_to_dir(self):
254 src = os.path.join(self.worktree, 'foo.txt') 259 """Do not allow copying to a directory."""
255 self.touch(src) 260 src = os.path.join(self.worktree, "foo.txt")
256 os.makedirs(os.path.join(self.topdir, 'dir')) 261 self.touch(src)
257 cf = self.CopyFile('foo.txt', 'dir') 262 os.makedirs(os.path.join(self.topdir, "dir"))
258 self.assertRaises(error.ManifestInvalidPathError, cf._Copy) 263 cf = self.CopyFile("foo.txt", "dir")
264 self.assertRaises(error.ManifestInvalidPathError, cf._Copy)
259 265
260 266
261class LinkFile(CopyLinkTestCase): 267class LinkFile(CopyLinkTestCase):
262 """Check _LinkFile handling.""" 268 """Check _LinkFile handling."""
263 269
264 def LinkFile(self, src, dest): 270 def LinkFile(self, src, dest):
265 return project._LinkFile(self.worktree, src, self.topdir, dest) 271 return project._LinkFile(self.worktree, src, self.topdir, dest)
266 272
267 def test_basic(self): 273 def test_basic(self):
268 """Basic test of linking a file from a project into the toplevel.""" 274 """Basic test of linking a file from a project into the toplevel."""
269 src = os.path.join(self.worktree, 'foo.txt') 275 src = os.path.join(self.worktree, "foo.txt")
270 self.touch(src) 276 self.touch(src)
271 lf = self.LinkFile('foo.txt', 'foo') 277 lf = self.LinkFile("foo.txt", "foo")
272 lf._Link() 278 lf._Link()
273 dest = os.path.join(self.topdir, 'foo') 279 dest = os.path.join(self.topdir, "foo")
274 self.assertExists(dest) 280 self.assertExists(dest)
275 self.assertTrue(os.path.islink(dest)) 281 self.assertTrue(os.path.islink(dest))
276 self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) 282 self.assertEqual(
277 283 os.path.join("git-project", "foo.txt"), os.readlink(dest)
278 def test_src_subdir(self): 284 )
279 """Link to a file in a subdir of a project.""" 285
280 src = os.path.join(self.worktree, 'bar', 'foo.txt') 286 def test_src_subdir(self):
281 os.makedirs(os.path.dirname(src)) 287 """Link to a file in a subdir of a project."""
282 self.touch(src) 288 src = os.path.join(self.worktree, "bar", "foo.txt")
283 lf = self.LinkFile('bar/foo.txt', 'foo') 289 os.makedirs(os.path.dirname(src))
284 lf._Link() 290 self.touch(src)
285 self.assertExists(os.path.join(self.topdir, 'foo')) 291 lf = self.LinkFile("bar/foo.txt", "foo")
286 292 lf._Link()
287 def test_src_self(self): 293 self.assertExists(os.path.join(self.topdir, "foo"))
288 """Link to the project itself.""" 294
289 dest = os.path.join(self.topdir, 'foo', 'bar') 295 def test_src_self(self):
290 lf = self.LinkFile('.', 'foo/bar') 296 """Link to the project itself."""
291 lf._Link() 297 dest = os.path.join(self.topdir, "foo", "bar")
292 self.assertExists(dest) 298 lf = self.LinkFile(".", "foo/bar")
293 self.assertEqual(os.path.join('..', 'git-project'), os.readlink(dest)) 299 lf._Link()
294 300 self.assertExists(dest)
295 def test_dest_subdir(self): 301 self.assertEqual(os.path.join("..", "git-project"), os.readlink(dest))
296 """Link a file to a subdir of a checkout.""" 302
297 src = os.path.join(self.worktree, 'foo.txt') 303 def test_dest_subdir(self):
298 self.touch(src) 304 """Link a file to a subdir of a checkout."""
299 lf = self.LinkFile('foo.txt', 'sub/dir/foo/bar') 305 src = os.path.join(self.worktree, "foo.txt")
300 self.assertFalse(os.path.exists(os.path.join(self.topdir, 'sub'))) 306 self.touch(src)
301 lf._Link() 307 lf = self.LinkFile("foo.txt", "sub/dir/foo/bar")
302 self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'foo', 'bar')) 308 self.assertFalse(os.path.exists(os.path.join(self.topdir, "sub")))
303 309 lf._Link()
304 def test_src_block_relative(self): 310 self.assertExists(os.path.join(self.topdir, "sub", "dir", "foo", "bar"))
305 """Do not allow relative symlinks.""" 311
306 BAD_SOURCES = ( 312 def test_src_block_relative(self):
307 './', 313 """Do not allow relative symlinks."""
308 '..', 314 BAD_SOURCES = (
309 '../', 315 "./",
310 'foo/.', 316 "..",
311 'foo/./bar', 317 "../",
312 'foo/..', 318 "foo/.",
313 'foo/../foo', 319 "foo/./bar",
314 ) 320 "foo/..",
315 for src in BAD_SOURCES: 321 "foo/../foo",
316 lf = self.LinkFile(src, 'foo') 322 )
317 self.assertRaises(error.ManifestInvalidPathError, lf._Link) 323 for src in BAD_SOURCES:
318 324 lf = self.LinkFile(src, "foo")
319 def test_update(self): 325 self.assertRaises(error.ManifestInvalidPathError, lf._Link)
320 """Make sure changed targets get updated.""" 326
321 dest = os.path.join(self.topdir, 'sym') 327 def test_update(self):
322 328 """Make sure changed targets get updated."""
323 src = os.path.join(self.worktree, 'foo.txt') 329 dest = os.path.join(self.topdir, "sym")
324 self.touch(src) 330
325 lf = self.LinkFile('foo.txt', 'sym') 331 src = os.path.join(self.worktree, "foo.txt")
326 lf._Link() 332 self.touch(src)
327 self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) 333 lf = self.LinkFile("foo.txt", "sym")
328 334 lf._Link()
329 # Point the symlink somewhere else. 335 self.assertEqual(
330 os.unlink(dest) 336 os.path.join("git-project", "foo.txt"), os.readlink(dest)
331 platform_utils.symlink(self.tempdir, dest) 337 )
332 lf._Link() 338
333 self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) 339 # Point the symlink somewhere else.
340 os.unlink(dest)
341 platform_utils.symlink(self.tempdir, dest)
342 lf._Link()
343 self.assertEqual(
344 os.path.join("git-project", "foo.txt"), os.readlink(dest)
345 )
334 346
335 347
336class MigrateWorkTreeTests(unittest.TestCase): 348class MigrateWorkTreeTests(unittest.TestCase):
337 """Check _MigrateOldWorkTreeGitDir handling.""" 349 """Check _MigrateOldWorkTreeGitDir handling."""
338 350
339 _SYMLINKS = { 351 _SYMLINKS = {
340 'config', 'description', 'hooks', 'info', 'logs', 'objects', 352 "config",
341 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn', 353 "description",
342 } 354 "hooks",
343 _FILES = { 355 "info",
344 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD', 356 "logs",
345 'unknown-file-should-be-migrated', 357 "objects",
346 } 358 "packed-refs",
347 _CLEAN_FILES = { 359 "refs",
348 'a-vim-temp-file~', '#an-emacs-temp-file#', 360 "rr-cache",
349 } 361 "shallow",
350 362 "svn",
351 @classmethod 363 }
352 @contextlib.contextmanager 364 _FILES = {
353 def _simple_layout(cls): 365 "COMMIT_EDITMSG",
354 """Create a simple repo client checkout to test against.""" 366 "FETCH_HEAD",
355 with tempfile.TemporaryDirectory() as tempdir: 367 "HEAD",
356 tempdir = Path(tempdir) 368 "index",
357 369 "ORIG_HEAD",
358 gitdir = tempdir / '.repo/projects/src/test.git' 370 "unknown-file-should-be-migrated",
359 gitdir.mkdir(parents=True) 371 }
360 cmd = ['git', 'init', '--bare', str(gitdir)] 372 _CLEAN_FILES = {
361 subprocess.check_call(cmd) 373 "a-vim-temp-file~",
362 374 "#an-emacs-temp-file#",
363 dotgit = tempdir / 'src/test/.git' 375 }
364 dotgit.mkdir(parents=True) 376
365 for name in cls._SYMLINKS: 377 @classmethod
366 (dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}') 378 @contextlib.contextmanager
367 for name in cls._FILES | cls._CLEAN_FILES: 379 def _simple_layout(cls):
368 (dotgit / name).write_text(name) 380 """Create a simple repo client checkout to test against."""
369 381 with tempfile.TemporaryDirectory() as tempdir:
370 yield tempdir 382 tempdir = Path(tempdir)
371 383
372 def test_standard(self): 384 gitdir = tempdir / ".repo/projects/src/test.git"
373 """Migrate a standard checkout that we expect.""" 385 gitdir.mkdir(parents=True)
374 with self._simple_layout() as tempdir: 386 cmd = ["git", "init", "--bare", str(gitdir)]
375 dotgit = tempdir / 'src/test/.git' 387 subprocess.check_call(cmd)
376 project.Project._MigrateOldWorkTreeGitDir(str(dotgit)) 388
377 389 dotgit = tempdir / "src/test/.git"
378 # Make sure the dir was transformed into a symlink. 390 dotgit.mkdir(parents=True)
379 self.assertTrue(dotgit.is_symlink()) 391 for name in cls._SYMLINKS:
380 self.assertEqual(os.readlink(dotgit), os.path.normpath('../../.repo/projects/src/test.git')) 392 (dotgit / name).symlink_to(
381 393 f"../../../.repo/projects/src/test.git/{name}"
382 # Make sure files were moved over. 394 )
383 gitdir = tempdir / '.repo/projects/src/test.git' 395 for name in cls._FILES | cls._CLEAN_FILES:
384 for name in self._FILES: 396 (dotgit / name).write_text(name)
385 self.assertEqual(name, (gitdir / name).read_text()) 397
386 # Make sure files were removed. 398 yield tempdir
387 for name in self._CLEAN_FILES: 399
388 self.assertFalse((gitdir / name).exists()) 400 def test_standard(self):
389 401 """Migrate a standard checkout that we expect."""
390 def test_unknown(self): 402 with self._simple_layout() as tempdir:
391 """A checkout with unknown files should abort.""" 403 dotgit = tempdir / "src/test/.git"
392 with self._simple_layout() as tempdir: 404 project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
393 dotgit = tempdir / 'src/test/.git' 405
394 (tempdir / '.repo/projects/src/test.git/random-file').write_text('one') 406 # Make sure the dir was transformed into a symlink.
395 (dotgit / 'random-file').write_text('two') 407 self.assertTrue(dotgit.is_symlink())
396 with self.assertRaises(error.GitError): 408 self.assertEqual(
397 project.Project._MigrateOldWorkTreeGitDir(str(dotgit)) 409 os.readlink(dotgit),
398 410 os.path.normpath("../../.repo/projects/src/test.git"),
399 # Make sure no content was actually changed. 411 )
400 self.assertTrue(dotgit.is_dir()) 412
401 for name in self._FILES: 413 # Make sure files were moved over.
402 self.assertTrue((dotgit / name).is_file()) 414 gitdir = tempdir / ".repo/projects/src/test.git"
403 for name in self._CLEAN_FILES: 415 for name in self._FILES:
404 self.assertTrue((dotgit / name).is_file()) 416 self.assertEqual(name, (gitdir / name).read_text())
405 for name in self._SYMLINKS: 417 # Make sure files were removed.
406 self.assertTrue((dotgit / name).is_symlink()) 418 for name in self._CLEAN_FILES:
419 self.assertFalse((gitdir / name).exists())
420
421 def test_unknown(self):
422 """A checkout with unknown files should abort."""
423 with self._simple_layout() as tempdir:
424 dotgit = tempdir / "src/test/.git"
425 (tempdir / ".repo/projects/src/test.git/random-file").write_text(
426 "one"
427 )
428 (dotgit / "random-file").write_text("two")
429 with self.assertRaises(error.GitError):
430 project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
431
432 # Make sure no content was actually changed.
433 self.assertTrue(dotgit.is_dir())
434 for name in self._FILES:
435 self.assertTrue((dotgit / name).is_file())
436 for name in self._CLEAN_FILES:
437 self.assertTrue((dotgit / name).is_file())
438 for name in self._SYMLINKS:
439 self.assertTrue((dotgit / name).is_symlink())
407 440
408 441
409class ManifestPropertiesFetchedCorrectly(unittest.TestCase): 442class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
410 """Ensure properties are fetched properly.""" 443 """Ensure properties are fetched properly."""
411 444
412 def setUpManifest(self, tempdir): 445 def setUpManifest(self, tempdir):
413 repodir = os.path.join(tempdir, '.repo') 446 repodir = os.path.join(tempdir, ".repo")
414 manifest_dir = os.path.join(repodir, 'manifests') 447 manifest_dir = os.path.join(repodir, "manifests")
415 manifest_file = os.path.join( 448 manifest_file = os.path.join(repodir, manifest_xml.MANIFEST_FILE_NAME)
416 repodir, manifest_xml.MANIFEST_FILE_NAME) 449 os.mkdir(repodir)
417 local_manifest_dir = os.path.join( 450 os.mkdir(manifest_dir)
418 repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME) 451 manifest = manifest_xml.XmlManifest(repodir, manifest_file)
419 os.mkdir(repodir)
420 os.mkdir(manifest_dir)
421 manifest = manifest_xml.XmlManifest(repodir, manifest_file)
422 452
423 return project.ManifestProject( 453 return project.ManifestProject(
424 manifest, 'test/manifest', os.path.join(tempdir, '.git'), tempdir) 454 manifest, "test/manifest", os.path.join(tempdir, ".git"), tempdir
455 )
425 456
426 def test_manifest_config_properties(self): 457 def test_manifest_config_properties(self):
427 """Test we are fetching the manifest config properties correctly.""" 458 """Test we are fetching the manifest config properties correctly."""
428 459
429 with TempGitTree() as tempdir: 460 with TempGitTree() as tempdir:
430 fakeproj = self.setUpManifest(tempdir) 461 fakeproj = self.setUpManifest(tempdir)
431 462
432 # Set property using the expected Set method, then ensure 463 # Set property using the expected Set method, then ensure
433 # the porperty functions are using the correct Get methods. 464 # the porperty functions are using the correct Get methods.
434 fakeproj.config.SetString( 465 fakeproj.config.SetString(
435 'manifest.standalone', 'https://chicken/manifest.git') 466 "manifest.standalone", "https://chicken/manifest.git"
436 self.assertEqual( 467 )
437 fakeproj.standalone_manifest_url, 'https://chicken/manifest.git') 468 self.assertEqual(
469 fakeproj.standalone_manifest_url, "https://chicken/manifest.git"
470 )
438 471
439 fakeproj.config.SetString('manifest.groups', 'test-group, admin-group') 472 fakeproj.config.SetString(
440 self.assertEqual(fakeproj.manifest_groups, 'test-group, admin-group') 473 "manifest.groups", "test-group, admin-group"
474 )
475 self.assertEqual(
476 fakeproj.manifest_groups, "test-group, admin-group"
477 )
441 478
442 fakeproj.config.SetString('repo.reference', 'mirror/ref') 479 fakeproj.config.SetString("repo.reference", "mirror/ref")
443 self.assertEqual(fakeproj.reference, 'mirror/ref') 480 self.assertEqual(fakeproj.reference, "mirror/ref")
444 481
445 fakeproj.config.SetBoolean('repo.dissociate', False) 482 fakeproj.config.SetBoolean("repo.dissociate", False)
446 self.assertFalse(fakeproj.dissociate) 483 self.assertFalse(fakeproj.dissociate)
447 484
448 fakeproj.config.SetBoolean('repo.archive', False) 485 fakeproj.config.SetBoolean("repo.archive", False)
449 self.assertFalse(fakeproj.archive) 486 self.assertFalse(fakeproj.archive)
450 487
451 fakeproj.config.SetBoolean('repo.mirror', False) 488 fakeproj.config.SetBoolean("repo.mirror", False)
452 self.assertFalse(fakeproj.mirror) 489 self.assertFalse(fakeproj.mirror)
453 490
454 fakeproj.config.SetBoolean('repo.worktree', False) 491 fakeproj.config.SetBoolean("repo.worktree", False)
455 self.assertFalse(fakeproj.use_worktree) 492 self.assertFalse(fakeproj.use_worktree)
456 493
457 fakeproj.config.SetBoolean('repo.clonebundle', False) 494 fakeproj.config.SetBoolean("repo.clonebundle", False)
458 self.assertFalse(fakeproj.clone_bundle) 495 self.assertFalse(fakeproj.clone_bundle)
459 496
460 fakeproj.config.SetBoolean('repo.submodules', False) 497 fakeproj.config.SetBoolean("repo.submodules", False)
461 self.assertFalse(fakeproj.submodules) 498 self.assertFalse(fakeproj.submodules)
462 499
463 fakeproj.config.SetBoolean('repo.git-lfs', False) 500 fakeproj.config.SetBoolean("repo.git-lfs", False)
464 self.assertFalse(fakeproj.git_lfs) 501 self.assertFalse(fakeproj.git_lfs)
465 502
466 fakeproj.config.SetBoolean('repo.superproject', False) 503 fakeproj.config.SetBoolean("repo.superproject", False)
467 self.assertFalse(fakeproj.use_superproject) 504 self.assertFalse(fakeproj.use_superproject)
468 505
469 fakeproj.config.SetBoolean('repo.partialclone', False) 506 fakeproj.config.SetBoolean("repo.partialclone", False)
470 self.assertFalse(fakeproj.partial_clone) 507 self.assertFalse(fakeproj.partial_clone)
471 508
472 fakeproj.config.SetString('repo.depth', '48') 509 fakeproj.config.SetString("repo.depth", "48")
473 self.assertEqual(fakeproj.depth, '48') 510 self.assertEqual(fakeproj.depth, "48")
474 511
475 fakeproj.config.SetString('repo.clonefilter', 'blob:limit=10M') 512 fakeproj.config.SetString("repo.clonefilter", "blob:limit=10M")
476 self.assertEqual(fakeproj.clone_filter, 'blob:limit=10M') 513 self.assertEqual(fakeproj.clone_filter, "blob:limit=10M")
477 514
478 fakeproj.config.SetString('repo.partialcloneexclude', 'third_party/big_repo') 515 fakeproj.config.SetString(
479 self.assertEqual(fakeproj.partial_clone_exclude, 'third_party/big_repo') 516 "repo.partialcloneexclude", "third_party/big_repo"
517 )
518 self.assertEqual(
519 fakeproj.partial_clone_exclude, "third_party/big_repo"
520 )
480 521
481 fakeproj.config.SetString('manifest.platform', 'auto') 522 fakeproj.config.SetString("manifest.platform", "auto")
482 self.assertEqual(fakeproj.manifest_platform, 'auto') 523 self.assertEqual(fakeproj.manifest_platform, "auto")
diff --git a/tests/test_repo_trace.py b/tests/test_repo_trace.py
index 5faf2938..e4aeb5de 100644
--- a/tests/test_repo_trace.py
+++ b/tests/test_repo_trace.py
@@ -22,35 +22,39 @@ import repo_trace
22 22
23 23
24class TraceTests(unittest.TestCase): 24class TraceTests(unittest.TestCase):
25 """Check Trace behavior.""" 25 """Check Trace behavior."""
26 26
27 def testTrace_MaxSizeEnforced(self): 27 def testTrace_MaxSizeEnforced(self):
28 content = 'git chicken' 28 content = "git chicken"
29 29
30 with repo_trace.Trace(content, first_trace=True): 30 with repo_trace.Trace(content, first_trace=True):
31 pass 31 pass
32 first_trace_size = os.path.getsize(repo_trace._TRACE_FILE) 32 first_trace_size = os.path.getsize(repo_trace._TRACE_FILE)
33 33
34 with repo_trace.Trace(content): 34 with repo_trace.Trace(content):
35 pass 35 pass
36 self.assertGreater( 36 self.assertGreater(
37 os.path.getsize(repo_trace._TRACE_FILE), first_trace_size) 37 os.path.getsize(repo_trace._TRACE_FILE), first_trace_size
38 38 )
39 # Check we clear everything is the last chunk is larger than _MAX_SIZE. 39
40 with mock.patch('repo_trace._MAX_SIZE', 0): 40 # Check we clear everything is the last chunk is larger than _MAX_SIZE.
41 with repo_trace.Trace(content, first_trace=True): 41 with mock.patch("repo_trace._MAX_SIZE", 0):
42 pass 42 with repo_trace.Trace(content, first_trace=True):
43 self.assertEqual(first_trace_size, 43 pass
44 os.path.getsize(repo_trace._TRACE_FILE)) 44 self.assertEqual(
45 45 first_trace_size, os.path.getsize(repo_trace._TRACE_FILE)
46 # Check we only clear the chunks we need to. 46 )
47 repo_trace._MAX_SIZE = (first_trace_size + 1) / (1024 * 1024) 47
48 with repo_trace.Trace(content, first_trace=True): 48 # Check we only clear the chunks we need to.
49 pass 49 repo_trace._MAX_SIZE = (first_trace_size + 1) / (1024 * 1024)
50 self.assertEqual(first_trace_size * 2, 50 with repo_trace.Trace(content, first_trace=True):
51 os.path.getsize(repo_trace._TRACE_FILE)) 51 pass
52 52 self.assertEqual(
53 with repo_trace.Trace(content, first_trace=True): 53 first_trace_size * 2, os.path.getsize(repo_trace._TRACE_FILE)
54 pass 54 )
55 self.assertEqual(first_trace_size * 2, 55
56 os.path.getsize(repo_trace._TRACE_FILE)) 56 with repo_trace.Trace(content, first_trace=True):
57 pass
58 self.assertEqual(
59 first_trace_size * 2, os.path.getsize(repo_trace._TRACE_FILE)
60 )
diff --git a/tests/test_ssh.py b/tests/test_ssh.py
index ffb5cb94..a9c1be7f 100644
--- a/tests/test_ssh.py
+++ b/tests/test_ssh.py
@@ -23,52 +23,56 @@ import ssh
23 23
24 24
25class SshTests(unittest.TestCase): 25class SshTests(unittest.TestCase):
26 """Tests the ssh functions.""" 26 """Tests the ssh functions."""
27 27
28 def test_parse_ssh_version(self): 28 def test_parse_ssh_version(self):
29 """Check _parse_ssh_version() handling.""" 29 """Check _parse_ssh_version() handling."""
30 ver = ssh._parse_ssh_version('Unknown\n') 30 ver = ssh._parse_ssh_version("Unknown\n")
31 self.assertEqual(ver, ()) 31 self.assertEqual(ver, ())
32 ver = ssh._parse_ssh_version('OpenSSH_1.0\n') 32 ver = ssh._parse_ssh_version("OpenSSH_1.0\n")
33 self.assertEqual(ver, (1, 0)) 33 self.assertEqual(ver, (1, 0))
34 ver = ssh._parse_ssh_version('OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13, OpenSSL 1.0.1f 6 Jan 2014\n') 34 ver = ssh._parse_ssh_version(
35 self.assertEqual(ver, (6, 6, 1)) 35 "OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13, OpenSSL 1.0.1f 6 Jan 2014\n"
36 ver = ssh._parse_ssh_version('OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017\n') 36 )
37 self.assertEqual(ver, (7, 6)) 37 self.assertEqual(ver, (6, 6, 1))
38 ver = ssh._parse_ssh_version(
39 "OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017\n"
40 )
41 self.assertEqual(ver, (7, 6))
38 42
39 def test_version(self): 43 def test_version(self):
40 """Check version() handling.""" 44 """Check version() handling."""
41 with mock.patch('ssh._run_ssh_version', return_value='OpenSSH_1.2\n'): 45 with mock.patch("ssh._run_ssh_version", return_value="OpenSSH_1.2\n"):
42 self.assertEqual(ssh.version(), (1, 2)) 46 self.assertEqual(ssh.version(), (1, 2))
43 47
44 def test_context_manager_empty(self): 48 def test_context_manager_empty(self):
45 """Verify context manager with no clients works correctly.""" 49 """Verify context manager with no clients works correctly."""
46 with multiprocessing.Manager() as manager: 50 with multiprocessing.Manager() as manager:
47 with ssh.ProxyManager(manager): 51 with ssh.ProxyManager(manager):
48 pass 52 pass
49 53
50 def test_context_manager_child_cleanup(self): 54 def test_context_manager_child_cleanup(self):
51 """Verify orphaned clients & masters get cleaned up.""" 55 """Verify orphaned clients & masters get cleaned up."""
52 with multiprocessing.Manager() as manager: 56 with multiprocessing.Manager() as manager:
53 with ssh.ProxyManager(manager) as ssh_proxy: 57 with ssh.ProxyManager(manager) as ssh_proxy:
54 client = subprocess.Popen(['sleep', '964853320']) 58 client = subprocess.Popen(["sleep", "964853320"])
55 ssh_proxy.add_client(client) 59 ssh_proxy.add_client(client)
56 master = subprocess.Popen(['sleep', '964853321']) 60 master = subprocess.Popen(["sleep", "964853321"])
57 ssh_proxy.add_master(master) 61 ssh_proxy.add_master(master)
58 # If the process still exists, these will throw timeout errors. 62 # If the process still exists, these will throw timeout errors.
59 client.wait(0) 63 client.wait(0)
60 master.wait(0) 64 master.wait(0)
61 65
62 def test_ssh_sock(self): 66 def test_ssh_sock(self):
63 """Check sock() function.""" 67 """Check sock() function."""
64 manager = multiprocessing.Manager() 68 manager = multiprocessing.Manager()
65 proxy = ssh.ProxyManager(manager) 69 proxy = ssh.ProxyManager(manager)
66 with mock.patch('tempfile.mkdtemp', return_value='/tmp/foo'): 70 with mock.patch("tempfile.mkdtemp", return_value="/tmp/foo"):
67 # old ssh version uses port 71 # Old ssh version uses port.
68 with mock.patch('ssh.version', return_value=(6, 6)): 72 with mock.patch("ssh.version", return_value=(6, 6)):
69 self.assertTrue(proxy.sock().endswith('%p')) 73 self.assertTrue(proxy.sock().endswith("%p"))
70 74
71 proxy._sock_path = None 75 proxy._sock_path = None
72 # new ssh version uses hash 76 # New ssh version uses hash.
73 with mock.patch('ssh.version', return_value=(6, 7)): 77 with mock.patch("ssh.version", return_value=(6, 7)):
74 self.assertTrue(proxy.sock().endswith('%C')) 78 self.assertTrue(proxy.sock().endswith("%C"))
diff --git a/tests/test_subcmds.py b/tests/test_subcmds.py
index bc53051a..73b66e3f 100644
--- a/tests/test_subcmds.py
+++ b/tests/test_subcmds.py
@@ -21,53 +21,57 @@ import subcmds
21 21
22 22
23class AllCommands(unittest.TestCase): 23class AllCommands(unittest.TestCase):
24 """Check registered all_commands.""" 24 """Check registered all_commands."""
25 25
26 def test_required_basic(self): 26 def test_required_basic(self):
27 """Basic checking of registered commands.""" 27 """Basic checking of registered commands."""
28 # NB: We don't test all subcommands as we want to avoid "change detection" 28 # NB: We don't test all subcommands as we want to avoid "change
29 # tests, so we just look for the most common/important ones here that are 29 # detection" tests, so we just look for the most common/important ones
30 # unlikely to ever change. 30 # here that are unlikely to ever change.
31 for cmd in {'cherry-pick', 'help', 'init', 'start', 'sync', 'upload'}: 31 for cmd in {"cherry-pick", "help", "init", "start", "sync", "upload"}:
32 self.assertIn(cmd, subcmds.all_commands) 32 self.assertIn(cmd, subcmds.all_commands)
33 33
34 def test_naming(self): 34 def test_naming(self):
35 """Verify we don't add things that we shouldn't.""" 35 """Verify we don't add things that we shouldn't."""
36 for cmd in subcmds.all_commands: 36 for cmd in subcmds.all_commands:
37 # Reject filename suffixes like "help.py". 37 # Reject filename suffixes like "help.py".
38 self.assertNotIn('.', cmd) 38 self.assertNotIn(".", cmd)
39 39
40 # Make sure all '_' were converted to '-'. 40 # Make sure all '_' were converted to '-'.
41 self.assertNotIn('_', cmd) 41 self.assertNotIn("_", cmd)
42 42
43 # Reject internal python paths like "__init__". 43 # Reject internal python paths like "__init__".
44 self.assertFalse(cmd.startswith('__')) 44 self.assertFalse(cmd.startswith("__"))
45 45
46 def test_help_desc_style(self): 46 def test_help_desc_style(self):
47 """Force some consistency in option descriptions. 47 """Force some consistency in option descriptions.
48 48
49 Python's optparse & argparse has a few default options like --help. Their 49 Python's optparse & argparse has a few default options like --help.
50 option description text uses lowercase sentence fragments, so enforce our 50 Their option description text uses lowercase sentence fragments, so
51 options follow the same style so UI is consistent. 51 enforce our options follow the same style so UI is consistent.
52 52
53 We enforce: 53 We enforce:
54 * Text starts with lowercase. 54 * Text starts with lowercase.
55 * Text doesn't end with period. 55 * Text doesn't end with period.
56 """ 56 """
57 for name, cls in subcmds.all_commands.items(): 57 for name, cls in subcmds.all_commands.items():
58 cmd = cls() 58 cmd = cls()
59 parser = cmd.OptionParser 59 parser = cmd.OptionParser
60 for option in parser.option_list: 60 for option in parser.option_list:
61 if option.help == optparse.SUPPRESS_HELP: 61 if option.help == optparse.SUPPRESS_HELP:
62 continue 62 continue
63 63
64 c = option.help[0] 64 c = option.help[0]
65 self.assertEqual( 65 self.assertEqual(
66 c.lower(), c, 66 c.lower(),
67 msg=f'subcmds/{name}.py: {option.get_opt_string()}: help text ' 67 c,
68 f'should start with lowercase: "{option.help}"') 68 msg=f"subcmds/{name}.py: {option.get_opt_string()}: "
69 f'help text should start with lowercase: "{option.help}"',
70 )
69 71
70 self.assertNotEqual( 72 self.assertNotEqual(
71 option.help[-1], '.', 73 option.help[-1],
72 msg=f'subcmds/{name}.py: {option.get_opt_string()}: help text ' 74 ".",
73 f'should not end in a period: "{option.help}"') 75 msg=f"subcmds/{name}.py: {option.get_opt_string()}: "
76 f'help text should not end in a period: "{option.help}"',
77 )
diff --git a/tests/test_subcmds_init.py b/tests/test_subcmds_init.py
index af4346de..25e5be56 100644
--- a/tests/test_subcmds_init.py
+++ b/tests/test_subcmds_init.py
@@ -20,30 +20,27 @@ from subcmds import init
20 20
21 21
22class InitCommand(unittest.TestCase): 22class InitCommand(unittest.TestCase):
23 """Check registered all_commands.""" 23 """Check registered all_commands."""
24 24
25 def setUp(self): 25 def setUp(self):
26 self.cmd = init.Init() 26 self.cmd = init.Init()
27 27
28 def test_cli_parser_good(self): 28 def test_cli_parser_good(self):
29 """Check valid command line options.""" 29 """Check valid command line options."""
30 ARGV = ( 30 ARGV = ([],)
31 [], 31 for argv in ARGV:
32 ) 32 opts, args = self.cmd.OptionParser.parse_args(argv)
33 for argv in ARGV: 33 self.cmd.ValidateOptions(opts, args)
34 opts, args = self.cmd.OptionParser.parse_args(argv) 34
35 self.cmd.ValidateOptions(opts, args) 35 def test_cli_parser_bad(self):
36 36 """Check invalid command line options."""
37 def test_cli_parser_bad(self): 37 ARGV = (
38 """Check invalid command line options.""" 38 # Too many arguments.
39 ARGV = ( 39 ["url", "asdf"],
40 # Too many arguments. 40 # Conflicting options.
41 ['url', 'asdf'], 41 ["--mirror", "--archive"],
42 42 )
43 # Conflicting options. 43 for argv in ARGV:
44 ['--mirror', '--archive'], 44 opts, args = self.cmd.OptionParser.parse_args(argv)
45 ) 45 with self.assertRaises(SystemExit):
46 for argv in ARGV: 46 self.cmd.ValidateOptions(opts, args)
47 opts, args = self.cmd.OptionParser.parse_args(argv)
48 with self.assertRaises(SystemExit):
49 self.cmd.ValidateOptions(opts, args)
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py
index 236d54e5..5c8e606e 100644
--- a/tests/test_subcmds_sync.py
+++ b/tests/test_subcmds_sync.py
@@ -23,111 +23,138 @@ import command
23from subcmds import sync 23from subcmds import sync
24 24
25 25
26@pytest.mark.parametrize('use_superproject, cli_args, result', [ 26@pytest.mark.parametrize(
27 (True, ['--current-branch'], True), 27 "use_superproject, cli_args, result",
28 (True, ['--no-current-branch'], True), 28 [
29 (True, [], True), 29 (True, ["--current-branch"], True),
30 (False, ['--current-branch'], True), 30 (True, ["--no-current-branch"], True),
31 (False, ['--no-current-branch'], False), 31 (True, [], True),
32 (False, [], None), 32 (False, ["--current-branch"], True),
33]) 33 (False, ["--no-current-branch"], False),
34 (False, [], None),
35 ],
36)
34def test_get_current_branch_only(use_superproject, cli_args, result): 37def test_get_current_branch_only(use_superproject, cli_args, result):
35 """Test Sync._GetCurrentBranchOnly logic. 38 """Test Sync._GetCurrentBranchOnly logic.
36 39
37 Sync._GetCurrentBranchOnly should return True if a superproject is requested, 40 Sync._GetCurrentBranchOnly should return True if a superproject is
38 and otherwise the value of the current_branch_only option. 41 requested, and otherwise the value of the current_branch_only option.
39 """ 42 """
40 cmd = sync.Sync() 43 cmd = sync.Sync()
41 opts, _ = cmd.OptionParser.parse_args(cli_args) 44 opts, _ = cmd.OptionParser.parse_args(cli_args)
42 45
43 with mock.patch('git_superproject.UseSuperproject', 46 with mock.patch(
44 return_value=use_superproject): 47 "git_superproject.UseSuperproject", return_value=use_superproject
45 assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result 48 ):
49 assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result
46 50
47 51
48# Used to patch os.cpu_count() for reliable results. 52# Used to patch os.cpu_count() for reliable results.
49OS_CPU_COUNT = 24 53OS_CPU_COUNT = 24
50 54
51@pytest.mark.parametrize('argv, jobs_manifest, jobs, jobs_net, jobs_check', [ 55
52 # No user or manifest settings. 56@pytest.mark.parametrize(
53 ([], None, OS_CPU_COUNT, 1, command.DEFAULT_LOCAL_JOBS), 57 "argv, jobs_manifest, jobs, jobs_net, jobs_check",
54 # No user settings, so manifest settings control. 58 [
55 ([], 3, 3, 3, 3), 59 # No user or manifest settings.
56 # User settings, but no manifest. 60 ([], None, OS_CPU_COUNT, 1, command.DEFAULT_LOCAL_JOBS),
57 (['--jobs=4'], None, 4, 4, 4), 61 # No user settings, so manifest settings control.
58 (['--jobs=4', '--jobs-network=5'], None, 4, 5, 4), 62 ([], 3, 3, 3, 3),
59 (['--jobs=4', '--jobs-checkout=6'], None, 4, 4, 6), 63 # User settings, but no manifest.
60 (['--jobs=4', '--jobs-network=5', '--jobs-checkout=6'], None, 4, 5, 6), 64 (["--jobs=4"], None, 4, 4, 4),
61 (['--jobs-network=5'], None, OS_CPU_COUNT, 5, command.DEFAULT_LOCAL_JOBS), 65 (["--jobs=4", "--jobs-network=5"], None, 4, 5, 4),
62 (['--jobs-checkout=6'], None, OS_CPU_COUNT, 1, 6), 66 (["--jobs=4", "--jobs-checkout=6"], None, 4, 4, 6),
63 (['--jobs-network=5', '--jobs-checkout=6'], None, OS_CPU_COUNT, 5, 6), 67 (["--jobs=4", "--jobs-network=5", "--jobs-checkout=6"], None, 4, 5, 6),
64 # User settings with manifest settings. 68 (
65 (['--jobs=4'], 3, 4, 4, 4), 69 ["--jobs-network=5"],
66 (['--jobs=4', '--jobs-network=5'], 3, 4, 5, 4), 70 None,
67 (['--jobs=4', '--jobs-checkout=6'], 3, 4, 4, 6), 71 OS_CPU_COUNT,
68 (['--jobs=4', '--jobs-network=5', '--jobs-checkout=6'], 3, 4, 5, 6), 72 5,
69 (['--jobs-network=5'], 3, 3, 5, 3), 73 command.DEFAULT_LOCAL_JOBS,
70 (['--jobs-checkout=6'], 3, 3, 3, 6), 74 ),
71 (['--jobs-network=5', '--jobs-checkout=6'], 3, 3, 5, 6), 75 (["--jobs-checkout=6"], None, OS_CPU_COUNT, 1, 6),
72 # Settings that exceed rlimits get capped. 76 (["--jobs-network=5", "--jobs-checkout=6"], None, OS_CPU_COUNT, 5, 6),
73 (['--jobs=1000000'], None, 83, 83, 83), 77 # User settings with manifest settings.
74 ([], 1000000, 83, 83, 83), 78 (["--jobs=4"], 3, 4, 4, 4),
75]) 79 (["--jobs=4", "--jobs-network=5"], 3, 4, 5, 4),
80 (["--jobs=4", "--jobs-checkout=6"], 3, 4, 4, 6),
81 (["--jobs=4", "--jobs-network=5", "--jobs-checkout=6"], 3, 4, 5, 6),
82 (["--jobs-network=5"], 3, 3, 5, 3),
83 (["--jobs-checkout=6"], 3, 3, 3, 6),
84 (["--jobs-network=5", "--jobs-checkout=6"], 3, 3, 5, 6),
85 # Settings that exceed rlimits get capped.
86 (["--jobs=1000000"], None, 83, 83, 83),
87 ([], 1000000, 83, 83, 83),
88 ],
89)
76def test_cli_jobs(argv, jobs_manifest, jobs, jobs_net, jobs_check): 90def test_cli_jobs(argv, jobs_manifest, jobs, jobs_net, jobs_check):
77 """Tests --jobs option behavior.""" 91 """Tests --jobs option behavior."""
78 mp = mock.MagicMock() 92 mp = mock.MagicMock()
79 mp.manifest.default.sync_j = jobs_manifest 93 mp.manifest.default.sync_j = jobs_manifest
80 94
81 cmd = sync.Sync() 95 cmd = sync.Sync()
82 opts, args = cmd.OptionParser.parse_args(argv) 96 opts, args = cmd.OptionParser.parse_args(argv)
83 cmd.ValidateOptions(opts, args) 97 cmd.ValidateOptions(opts, args)
84 98
85 with mock.patch.object(sync, '_rlimit_nofile', return_value=(256, 256)): 99 with mock.patch.object(sync, "_rlimit_nofile", return_value=(256, 256)):
86 with mock.patch.object(os, 'cpu_count', return_value=OS_CPU_COUNT): 100 with mock.patch.object(os, "cpu_count", return_value=OS_CPU_COUNT):
87 cmd._ValidateOptionsWithManifest(opts, mp) 101 cmd._ValidateOptionsWithManifest(opts, mp)
88 assert opts.jobs == jobs 102 assert opts.jobs == jobs
89 assert opts.jobs_network == jobs_net 103 assert opts.jobs_network == jobs_net
90 assert opts.jobs_checkout == jobs_check 104 assert opts.jobs_checkout == jobs_check
91 105
92 106
93class GetPreciousObjectsState(unittest.TestCase): 107class GetPreciousObjectsState(unittest.TestCase):
94 """Tests for _GetPreciousObjectsState.""" 108 """Tests for _GetPreciousObjectsState."""
95 109
96 def setUp(self): 110 def setUp(self):
97 """Common setup.""" 111 """Common setup."""
98 self.cmd = sync.Sync() 112 self.cmd = sync.Sync()
99 self.project = p = mock.MagicMock(use_git_worktrees=False, 113 self.project = p = mock.MagicMock(
100 UseAlternates=False) 114 use_git_worktrees=False, UseAlternates=False
101 p.manifest.GetProjectsWithName.return_value = [p] 115 )
102 116 p.manifest.GetProjectsWithName.return_value = [p]
103 self.opt = mock.Mock(spec_set=['this_manifest_only']) 117
104 self.opt.this_manifest_only = False 118 self.opt = mock.Mock(spec_set=["this_manifest_only"])
105 119 self.opt.this_manifest_only = False
106 def test_worktrees(self): 120
107 """False for worktrees.""" 121 def test_worktrees(self):
108 self.project.use_git_worktrees = True 122 """False for worktrees."""
109 self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt)) 123 self.project.use_git_worktrees = True
110 124 self.assertFalse(
111 def test_not_shared(self): 125 self.cmd._GetPreciousObjectsState(self.project, self.opt)
112 """Singleton project.""" 126 )
113 self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt)) 127
114 128 def test_not_shared(self):
115 def test_shared(self): 129 """Singleton project."""
116 """Shared project.""" 130 self.assertFalse(
117 self.project.manifest.GetProjectsWithName.return_value = [ 131 self.cmd._GetPreciousObjectsState(self.project, self.opt)
118 self.project, self.project 132 )
119 ] 133
120 self.assertTrue(self.cmd._GetPreciousObjectsState(self.project, self.opt)) 134 def test_shared(self):
121 135 """Shared project."""
122 def test_shared_with_alternates(self): 136 self.project.manifest.GetProjectsWithName.return_value = [
123 """Shared project, with alternates.""" 137 self.project,
124 self.project.manifest.GetProjectsWithName.return_value = [ 138 self.project,
125 self.project, self.project 139 ]
126 ] 140 self.assertTrue(
127 self.project.UseAlternates = True 141 self.cmd._GetPreciousObjectsState(self.project, self.opt)
128 self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt)) 142 )
129 143
130 def test_not_found(self): 144 def test_shared_with_alternates(self):
131 """Project not found in manifest.""" 145 """Shared project, with alternates."""
132 self.project.manifest.GetProjectsWithName.return_value = [] 146 self.project.manifest.GetProjectsWithName.return_value = [
133 self.assertFalse(self.cmd._GetPreciousObjectsState(self.project, self.opt)) 147 self.project,
148 self.project,
149 ]
150 self.project.UseAlternates = True
151 self.assertFalse(
152 self.cmd._GetPreciousObjectsState(self.project, self.opt)
153 )
154
155 def test_not_found(self):
156 """Project not found in manifest."""
157 self.project.manifest.GetProjectsWithName.return_value = []
158 self.assertFalse(
159 self.cmd._GetPreciousObjectsState(self.project, self.opt)
160 )
diff --git a/tests/test_update_manpages.py b/tests/test_update_manpages.py
index 0de85be9..12b19ec4 100644
--- a/tests/test_update_manpages.py
+++ b/tests/test_update_manpages.py
@@ -20,9 +20,9 @@ from release import update_manpages
20 20
21 21
22class UpdateManpagesTest(unittest.TestCase): 22class UpdateManpagesTest(unittest.TestCase):
23 """Tests the update-manpages code.""" 23 """Tests the update-manpages code."""
24 24
25 def test_replace_regex(self): 25 def test_replace_regex(self):
26 """Check that replace_regex works.""" 26 """Check that replace_regex works."""
27 data = '\n\033[1mSummary\033[m\n' 27 data = "\n\033[1mSummary\033[m\n"
28 self.assertEqual(update_manpages.replace_regex(data),'\nSummary\n') 28 self.assertEqual(update_manpages.replace_regex(data), "\nSummary\n")
diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py
index ef879a5d..21fa094d 100644
--- a/tests/test_wrapper.py
+++ b/tests/test_wrapper.py
@@ -28,528 +28,615 @@ import wrapper
28 28
29 29
30def fixture(*paths): 30def fixture(*paths):
31 """Return a path relative to tests/fixtures. 31 """Return a path relative to tests/fixtures."""
32 """ 32 return os.path.join(os.path.dirname(__file__), "fixtures", *paths)
33 return os.path.join(os.path.dirname(__file__), 'fixtures', *paths)
34 33
35 34
36class RepoWrapperTestCase(unittest.TestCase): 35class RepoWrapperTestCase(unittest.TestCase):
37 """TestCase for the wrapper module.""" 36 """TestCase for the wrapper module."""
38 37
39 def setUp(self): 38 def setUp(self):
40 """Load the wrapper module every time.""" 39 """Load the wrapper module every time."""
41 wrapper.Wrapper.cache_clear() 40 wrapper.Wrapper.cache_clear()
42 self.wrapper = wrapper.Wrapper() 41 self.wrapper = wrapper.Wrapper()
43 42
44 43
45class RepoWrapperUnitTest(RepoWrapperTestCase): 44class RepoWrapperUnitTest(RepoWrapperTestCase):
46 """Tests helper functions in the repo wrapper 45 """Tests helper functions in the repo wrapper"""
47 """ 46
48 47 def test_version(self):
49 def test_version(self): 48 """Make sure _Version works."""
50 """Make sure _Version works.""" 49 with self.assertRaises(SystemExit) as e:
51 with self.assertRaises(SystemExit) as e: 50 with mock.patch("sys.stdout", new_callable=StringIO) as stdout:
52 with mock.patch('sys.stdout', new_callable=StringIO) as stdout: 51 with mock.patch("sys.stderr", new_callable=StringIO) as stderr:
53 with mock.patch('sys.stderr', new_callable=StringIO) as stderr: 52 self.wrapper._Version()
54 self.wrapper._Version() 53 self.assertEqual(0, e.exception.code)
55 self.assertEqual(0, e.exception.code) 54 self.assertEqual("", stderr.getvalue())
56 self.assertEqual('', stderr.getvalue()) 55 self.assertIn("repo launcher version", stdout.getvalue())
57 self.assertIn('repo launcher version', stdout.getvalue()) 56
58 57 def test_python_constraints(self):
59 def test_python_constraints(self): 58 """The launcher should never require newer than main.py."""
60 """The launcher should never require newer than main.py.""" 59 self.assertGreaterEqual(
61 self.assertGreaterEqual(main.MIN_PYTHON_VERSION_HARD, 60 main.MIN_PYTHON_VERSION_HARD, self.wrapper.MIN_PYTHON_VERSION_HARD
62 self.wrapper.MIN_PYTHON_VERSION_HARD) 61 )
63 self.assertGreaterEqual(main.MIN_PYTHON_VERSION_SOFT, 62 self.assertGreaterEqual(
64 self.wrapper.MIN_PYTHON_VERSION_SOFT) 63 main.MIN_PYTHON_VERSION_SOFT, self.wrapper.MIN_PYTHON_VERSION_SOFT
65 # Make sure the versions are themselves in sync. 64 )
66 self.assertGreaterEqual(self.wrapper.MIN_PYTHON_VERSION_SOFT, 65 # Make sure the versions are themselves in sync.
67 self.wrapper.MIN_PYTHON_VERSION_HARD) 66 self.assertGreaterEqual(
68 67 self.wrapper.MIN_PYTHON_VERSION_SOFT,
69 def test_init_parser(self): 68 self.wrapper.MIN_PYTHON_VERSION_HARD,
70 """Make sure 'init' GetParser works.""" 69 )
71 parser = self.wrapper.GetParser(gitc_init=False) 70
72 opts, args = parser.parse_args([]) 71 def test_init_parser(self):
73 self.assertEqual([], args) 72 """Make sure 'init' GetParser works."""
74 self.assertIsNone(opts.manifest_url) 73 parser = self.wrapper.GetParser(gitc_init=False)
75 74 opts, args = parser.parse_args([])
76 def test_gitc_init_parser(self): 75 self.assertEqual([], args)
77 """Make sure 'gitc-init' GetParser works.""" 76 self.assertIsNone(opts.manifest_url)
78 parser = self.wrapper.GetParser(gitc_init=True) 77
79 opts, args = parser.parse_args([]) 78 def test_gitc_init_parser(self):
80 self.assertEqual([], args) 79 """Make sure 'gitc-init' GetParser works."""
81 self.assertIsNone(opts.manifest_file) 80 parser = self.wrapper.GetParser(gitc_init=True)
82 81 opts, args = parser.parse_args([])
83 def test_get_gitc_manifest_dir_no_gitc(self): 82 self.assertEqual([], args)
84 """ 83 self.assertIsNone(opts.manifest_file)
85 Test reading a missing gitc config file 84
86 """ 85 def test_get_gitc_manifest_dir_no_gitc(self):
87 self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config') 86 """
88 val = self.wrapper.get_gitc_manifest_dir() 87 Test reading a missing gitc config file
89 self.assertEqual(val, '') 88 """
90 89 self.wrapper.GITC_CONFIG_FILE = fixture("missing_gitc_config")
91 def test_get_gitc_manifest_dir(self): 90 val = self.wrapper.get_gitc_manifest_dir()
92 """ 91 self.assertEqual(val, "")
93 Test reading the gitc config file and parsing the directory 92
94 """ 93 def test_get_gitc_manifest_dir(self):
95 self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config') 94 """
96 val = self.wrapper.get_gitc_manifest_dir() 95 Test reading the gitc config file and parsing the directory
97 self.assertEqual(val, '/test/usr/local/google/gitc') 96 """
98 97 self.wrapper.GITC_CONFIG_FILE = fixture("gitc_config")
99 def test_gitc_parse_clientdir_no_gitc(self): 98 val = self.wrapper.get_gitc_manifest_dir()
100 """ 99 self.assertEqual(val, "/test/usr/local/google/gitc")
101 Test parsing the gitc clientdir without gitc running 100
102 """ 101 def test_gitc_parse_clientdir_no_gitc(self):
103 self.wrapper.GITC_CONFIG_FILE = fixture('missing_gitc_config') 102 """
104 self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None) 103 Test parsing the gitc clientdir without gitc running
105 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test') 104 """
106 105 self.wrapper.GITC_CONFIG_FILE = fixture("missing_gitc_config")
107 def test_gitc_parse_clientdir(self): 106 self.assertEqual(self.wrapper.gitc_parse_clientdir("/something"), None)
108 """ 107 self.assertEqual(
109 Test parsing the gitc clientdir 108 self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test"), "test"
110 """ 109 )
111 self.wrapper.GITC_CONFIG_FILE = fixture('gitc_config') 110
112 self.assertEqual(self.wrapper.gitc_parse_clientdir('/something'), None) 111 def test_gitc_parse_clientdir(self):
113 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test'), 'test') 112 """
114 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/'), 'test') 113 Test parsing the gitc clientdir
115 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/test/extra'), 'test') 114 """
116 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test'), 'test') 115 self.wrapper.GITC_CONFIG_FILE = fixture("gitc_config")
117 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/'), 'test') 116 self.assertEqual(self.wrapper.gitc_parse_clientdir("/something"), None)
118 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/test/extra'), 117 self.assertEqual(
119 'test') 118 self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test"), "test"
120 self.assertEqual(self.wrapper.gitc_parse_clientdir('/gitc/manifest-rw/'), None) 119 )
121 self.assertEqual(self.wrapper.gitc_parse_clientdir('/test/usr/local/google/gitc/'), None) 120 self.assertEqual(
121 self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test/"), "test"
122 )
123 self.assertEqual(
124 self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/test/extra"),
125 "test",
126 )
127 self.assertEqual(
128 self.wrapper.gitc_parse_clientdir(
129 "/test/usr/local/google/gitc/test"
130 ),
131 "test",
132 )
133 self.assertEqual(
134 self.wrapper.gitc_parse_clientdir(
135 "/test/usr/local/google/gitc/test/"
136 ),
137 "test",
138 )
139 self.assertEqual(
140 self.wrapper.gitc_parse_clientdir(
141 "/test/usr/local/google/gitc/test/extra"
142 ),
143 "test",
144 )
145 self.assertEqual(
146 self.wrapper.gitc_parse_clientdir("/gitc/manifest-rw/"), None
147 )
148 self.assertEqual(
149 self.wrapper.gitc_parse_clientdir("/test/usr/local/google/gitc/"),
150 None,
151 )
122 152
123 153
124class SetGitTrace2ParentSid(RepoWrapperTestCase): 154class SetGitTrace2ParentSid(RepoWrapperTestCase):
125 """Check SetGitTrace2ParentSid behavior.""" 155 """Check SetGitTrace2ParentSid behavior."""
126 156
127 KEY = 'GIT_TRACE2_PARENT_SID' 157 KEY = "GIT_TRACE2_PARENT_SID"
128 VALID_FORMAT = re.compile(r'^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$') 158 VALID_FORMAT = re.compile(r"^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$")
129 159
130 def test_first_set(self): 160 def test_first_set(self):
131 """Test env var not yet set.""" 161 """Test env var not yet set."""
132 env = {} 162 env = {}
133 self.wrapper.SetGitTrace2ParentSid(env) 163 self.wrapper.SetGitTrace2ParentSid(env)
134 self.assertIn(self.KEY, env) 164 self.assertIn(self.KEY, env)
135 value = env[self.KEY] 165 value = env[self.KEY]
136 self.assertRegex(value, self.VALID_FORMAT) 166 self.assertRegex(value, self.VALID_FORMAT)
137 167
138 def test_append(self): 168 def test_append(self):
139 """Test env var is appended.""" 169 """Test env var is appended."""
140 env = {self.KEY: 'pfx'} 170 env = {self.KEY: "pfx"}
141 self.wrapper.SetGitTrace2ParentSid(env) 171 self.wrapper.SetGitTrace2ParentSid(env)
142 self.assertIn(self.KEY, env) 172 self.assertIn(self.KEY, env)
143 value = env[self.KEY] 173 value = env[self.KEY]
144 self.assertTrue(value.startswith('pfx/')) 174 self.assertTrue(value.startswith("pfx/"))
145 self.assertRegex(value[4:], self.VALID_FORMAT) 175 self.assertRegex(value[4:], self.VALID_FORMAT)
146 176
147 def test_global_context(self): 177 def test_global_context(self):
148 """Check os.environ gets updated by default.""" 178 """Check os.environ gets updated by default."""
149 os.environ.pop(self.KEY, None) 179 os.environ.pop(self.KEY, None)
150 self.wrapper.SetGitTrace2ParentSid() 180 self.wrapper.SetGitTrace2ParentSid()
151 self.assertIn(self.KEY, os.environ) 181 self.assertIn(self.KEY, os.environ)
152 value = os.environ[self.KEY] 182 value = os.environ[self.KEY]
153 self.assertRegex(value, self.VALID_FORMAT) 183 self.assertRegex(value, self.VALID_FORMAT)
154 184
155 185
156class RunCommand(RepoWrapperTestCase): 186class RunCommand(RepoWrapperTestCase):
157 """Check run_command behavior.""" 187 """Check run_command behavior."""
158 188
159 def test_capture(self): 189 def test_capture(self):
160 """Check capture_output handling.""" 190 """Check capture_output handling."""
161 ret = self.wrapper.run_command(['echo', 'hi'], capture_output=True) 191 ret = self.wrapper.run_command(["echo", "hi"], capture_output=True)
162 # echo command appends OS specific linesep, but on Windows + Git Bash 192 # echo command appends OS specific linesep, but on Windows + Git Bash
163 # we get UNIX ending, so we allow both. 193 # we get UNIX ending, so we allow both.
164 self.assertIn(ret.stdout, ['hi' + os.linesep, 'hi\n']) 194 self.assertIn(ret.stdout, ["hi" + os.linesep, "hi\n"])
165 195
166 def test_check(self): 196 def test_check(self):
167 """Check check handling.""" 197 """Check check handling."""
168 self.wrapper.run_command(['true'], check=False) 198 self.wrapper.run_command(["true"], check=False)
169 self.wrapper.run_command(['true'], check=True) 199 self.wrapper.run_command(["true"], check=True)
170 self.wrapper.run_command(['false'], check=False) 200 self.wrapper.run_command(["false"], check=False)
171 with self.assertRaises(self.wrapper.RunError): 201 with self.assertRaises(self.wrapper.RunError):
172 self.wrapper.run_command(['false'], check=True) 202 self.wrapper.run_command(["false"], check=True)
173 203
174 204
175class RunGit(RepoWrapperTestCase): 205class RunGit(RepoWrapperTestCase):
176 """Check run_git behavior.""" 206 """Check run_git behavior."""
177 207
178 def test_capture(self): 208 def test_capture(self):
179 """Check capture_output handling.""" 209 """Check capture_output handling."""
180 ret = self.wrapper.run_git('--version') 210 ret = self.wrapper.run_git("--version")
181 self.assertIn('git', ret.stdout) 211 self.assertIn("git", ret.stdout)
182 212
183 def test_check(self): 213 def test_check(self):
184 """Check check handling.""" 214 """Check check handling."""
185 with self.assertRaises(self.wrapper.CloneFailure): 215 with self.assertRaises(self.wrapper.CloneFailure):
186 self.wrapper.run_git('--version-asdfasdf') 216 self.wrapper.run_git("--version-asdfasdf")
187 self.wrapper.run_git('--version-asdfasdf', check=False) 217 self.wrapper.run_git("--version-asdfasdf", check=False)
188 218
189 219
190class ParseGitVersion(RepoWrapperTestCase): 220class ParseGitVersion(RepoWrapperTestCase):
191 """Check ParseGitVersion behavior.""" 221 """Check ParseGitVersion behavior."""
192 222
193 def test_autoload(self): 223 def test_autoload(self):
194 """Check we can load the version from the live git.""" 224 """Check we can load the version from the live git."""
195 ret = self.wrapper.ParseGitVersion() 225 ret = self.wrapper.ParseGitVersion()
196 self.assertIsNotNone(ret) 226 self.assertIsNotNone(ret)
197 227
198 def test_bad_ver(self): 228 def test_bad_ver(self):
199 """Check handling of bad git versions.""" 229 """Check handling of bad git versions."""
200 ret = self.wrapper.ParseGitVersion(ver_str='asdf') 230 ret = self.wrapper.ParseGitVersion(ver_str="asdf")
201 self.assertIsNone(ret) 231 self.assertIsNone(ret)
202 232
203 def test_normal_ver(self): 233 def test_normal_ver(self):
204 """Check handling of normal git versions.""" 234 """Check handling of normal git versions."""
205 ret = self.wrapper.ParseGitVersion(ver_str='git version 2.25.1') 235 ret = self.wrapper.ParseGitVersion(ver_str="git version 2.25.1")
206 self.assertEqual(2, ret.major) 236 self.assertEqual(2, ret.major)
207 self.assertEqual(25, ret.minor) 237 self.assertEqual(25, ret.minor)
208 self.assertEqual(1, ret.micro) 238 self.assertEqual(1, ret.micro)
209 self.assertEqual('2.25.1', ret.full) 239 self.assertEqual("2.25.1", ret.full)
210 240
211 def test_extended_ver(self): 241 def test_extended_ver(self):
212 """Check handling of extended distro git versions.""" 242 """Check handling of extended distro git versions."""
213 ret = self.wrapper.ParseGitVersion( 243 ret = self.wrapper.ParseGitVersion(
214 ver_str='git version 1.30.50.696.g5e7596f4ac-goog') 244 ver_str="git version 1.30.50.696.g5e7596f4ac-goog"
215 self.assertEqual(1, ret.major) 245 )
216 self.assertEqual(30, ret.minor) 246 self.assertEqual(1, ret.major)
217 self.assertEqual(50, ret.micro) 247 self.assertEqual(30, ret.minor)
218 self.assertEqual('1.30.50.696.g5e7596f4ac-goog', ret.full) 248 self.assertEqual(50, ret.micro)
249 self.assertEqual("1.30.50.696.g5e7596f4ac-goog", ret.full)
219 250
220 251
221class CheckGitVersion(RepoWrapperTestCase): 252class CheckGitVersion(RepoWrapperTestCase):
222 """Check _CheckGitVersion behavior.""" 253 """Check _CheckGitVersion behavior."""
223 254
224 def test_unknown(self): 255 def test_unknown(self):
225 """Unknown versions should abort.""" 256 """Unknown versions should abort."""
226 with mock.patch.object(self.wrapper, 'ParseGitVersion', return_value=None): 257 with mock.patch.object(
227 with self.assertRaises(self.wrapper.CloneFailure): 258 self.wrapper, "ParseGitVersion", return_value=None
228 self.wrapper._CheckGitVersion() 259 ):
229 260 with self.assertRaises(self.wrapper.CloneFailure):
230 def test_old(self): 261 self.wrapper._CheckGitVersion()
231 """Old versions should abort.""" 262
232 with mock.patch.object( 263 def test_old(self):
233 self.wrapper, 'ParseGitVersion', 264 """Old versions should abort."""
234 return_value=self.wrapper.GitVersion(1, 0, 0, '1.0.0')): 265 with mock.patch.object(
235 with self.assertRaises(self.wrapper.CloneFailure): 266 self.wrapper,
236 self.wrapper._CheckGitVersion() 267 "ParseGitVersion",
237 268 return_value=self.wrapper.GitVersion(1, 0, 0, "1.0.0"),
238 def test_new(self): 269 ):
239 """Newer versions should run fine.""" 270 with self.assertRaises(self.wrapper.CloneFailure):
240 with mock.patch.object( 271 self.wrapper._CheckGitVersion()
241 self.wrapper, 'ParseGitVersion', 272
242 return_value=self.wrapper.GitVersion(100, 0, 0, '100.0.0')): 273 def test_new(self):
243 self.wrapper._CheckGitVersion() 274 """Newer versions should run fine."""
275 with mock.patch.object(
276 self.wrapper,
277 "ParseGitVersion",
278 return_value=self.wrapper.GitVersion(100, 0, 0, "100.0.0"),
279 ):
280 self.wrapper._CheckGitVersion()
244 281
245 282
246class Requirements(RepoWrapperTestCase): 283class Requirements(RepoWrapperTestCase):
247 """Check Requirements handling.""" 284 """Check Requirements handling."""
248 285
249 def test_missing_file(self): 286 def test_missing_file(self):
250 """Don't crash if the file is missing (old version).""" 287 """Don't crash if the file is missing (old version)."""
251 testdir = os.path.dirname(os.path.realpath(__file__)) 288 testdir = os.path.dirname(os.path.realpath(__file__))
252 self.assertIsNone(self.wrapper.Requirements.from_dir(testdir)) 289 self.assertIsNone(self.wrapper.Requirements.from_dir(testdir))
253 self.assertIsNone(self.wrapper.Requirements.from_file( 290 self.assertIsNone(
254 os.path.join(testdir, 'xxxxxxxxxxxxxxxxxxxxxxxx'))) 291 self.wrapper.Requirements.from_file(
255 292 os.path.join(testdir, "xxxxxxxxxxxxxxxxxxxxxxxx")
256 def test_corrupt_data(self): 293 )
257 """If the file can't be parsed, don't blow up.""" 294 )
258 self.assertIsNone(self.wrapper.Requirements.from_file(__file__)) 295
259 self.assertIsNone(self.wrapper.Requirements.from_data(b'x')) 296 def test_corrupt_data(self):
260 297 """If the file can't be parsed, don't blow up."""
261 def test_valid_data(self): 298 self.assertIsNone(self.wrapper.Requirements.from_file(__file__))
262 """Make sure we can parse the file we ship.""" 299 self.assertIsNone(self.wrapper.Requirements.from_data(b"x"))
263 self.assertIsNotNone(self.wrapper.Requirements.from_data(b'{}')) 300
264 rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 301 def test_valid_data(self):
265 self.assertIsNotNone(self.wrapper.Requirements.from_dir(rootdir)) 302 """Make sure we can parse the file we ship."""
266 self.assertIsNotNone(self.wrapper.Requirements.from_file(os.path.join( 303 self.assertIsNotNone(self.wrapper.Requirements.from_data(b"{}"))
267 rootdir, 'requirements.json'))) 304 rootdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
268 305 self.assertIsNotNone(self.wrapper.Requirements.from_dir(rootdir))
269 def test_format_ver(self): 306 self.assertIsNotNone(
270 """Check format_ver can format.""" 307 self.wrapper.Requirements.from_file(
271 self.assertEqual('1.2.3', self.wrapper.Requirements._format_ver((1, 2, 3))) 308 os.path.join(rootdir, "requirements.json")
272 self.assertEqual('1', self.wrapper.Requirements._format_ver([1])) 309 )
273 310 )
274 def test_assert_all_unknown(self): 311
275 """Check assert_all works with incompatible file.""" 312 def test_format_ver(self):
276 reqs = self.wrapper.Requirements({}) 313 """Check format_ver can format."""
277 reqs.assert_all() 314 self.assertEqual(
278 315 "1.2.3", self.wrapper.Requirements._format_ver((1, 2, 3))
279 def test_assert_all_new_repo(self): 316 )
280 """Check assert_all accepts new enough repo.""" 317 self.assertEqual("1", self.wrapper.Requirements._format_ver([1]))
281 reqs = self.wrapper.Requirements({'repo': {'hard': [1, 0]}}) 318
282 reqs.assert_all() 319 def test_assert_all_unknown(self):
283 320 """Check assert_all works with incompatible file."""
284 def test_assert_all_old_repo(self): 321 reqs = self.wrapper.Requirements({})
285 """Check assert_all rejects old repo.""" 322 reqs.assert_all()
286 reqs = self.wrapper.Requirements({'repo': {'hard': [99999, 0]}}) 323
287 with self.assertRaises(SystemExit): 324 def test_assert_all_new_repo(self):
288 reqs.assert_all() 325 """Check assert_all accepts new enough repo."""
289 326 reqs = self.wrapper.Requirements({"repo": {"hard": [1, 0]}})
290 def test_assert_all_new_python(self): 327 reqs.assert_all()
291 """Check assert_all accepts new enough python.""" 328
292 reqs = self.wrapper.Requirements({'python': {'hard': sys.version_info}}) 329 def test_assert_all_old_repo(self):
293 reqs.assert_all() 330 """Check assert_all rejects old repo."""
294 331 reqs = self.wrapper.Requirements({"repo": {"hard": [99999, 0]}})
295 def test_assert_all_old_python(self): 332 with self.assertRaises(SystemExit):
296 """Check assert_all rejects old python.""" 333 reqs.assert_all()
297 reqs = self.wrapper.Requirements({'python': {'hard': [99999, 0]}}) 334
298 with self.assertRaises(SystemExit): 335 def test_assert_all_new_python(self):
299 reqs.assert_all() 336 """Check assert_all accepts new enough python."""
300 337 reqs = self.wrapper.Requirements({"python": {"hard": sys.version_info}})
301 def test_assert_ver_unknown(self): 338 reqs.assert_all()
302 """Check assert_ver works with incompatible file.""" 339
303 reqs = self.wrapper.Requirements({}) 340 def test_assert_all_old_python(self):
304 reqs.assert_ver('xxx', (1, 0)) 341 """Check assert_all rejects old python."""
305 342 reqs = self.wrapper.Requirements({"python": {"hard": [99999, 0]}})
306 def test_assert_ver_new(self): 343 with self.assertRaises(SystemExit):
307 """Check assert_ver allows new enough versions.""" 344 reqs.assert_all()
308 reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}}) 345
309 reqs.assert_ver('git', (1, 0)) 346 def test_assert_ver_unknown(self):
310 reqs.assert_ver('git', (1, 5)) 347 """Check assert_ver works with incompatible file."""
311 reqs.assert_ver('git', (2, 0)) 348 reqs = self.wrapper.Requirements({})
312 reqs.assert_ver('git', (2, 5)) 349 reqs.assert_ver("xxx", (1, 0))
313 350
314 def test_assert_ver_old(self): 351 def test_assert_ver_new(self):
315 """Check assert_ver rejects old versions.""" 352 """Check assert_ver allows new enough versions."""
316 reqs = self.wrapper.Requirements({'git': {'hard': [1, 0], 'soft': [2, 0]}}) 353 reqs = self.wrapper.Requirements(
317 with self.assertRaises(SystemExit): 354 {"git": {"hard": [1, 0], "soft": [2, 0]}}
318 reqs.assert_ver('git', (0, 5)) 355 )
356 reqs.assert_ver("git", (1, 0))
357 reqs.assert_ver("git", (1, 5))
358 reqs.assert_ver("git", (2, 0))
359 reqs.assert_ver("git", (2, 5))
360
361 def test_assert_ver_old(self):
362 """Check assert_ver rejects old versions."""
363 reqs = self.wrapper.Requirements(
364 {"git": {"hard": [1, 0], "soft": [2, 0]}}
365 )
366 with self.assertRaises(SystemExit):
367 reqs.assert_ver("git", (0, 5))
319 368
320 369
321class NeedSetupGnuPG(RepoWrapperTestCase): 370class NeedSetupGnuPG(RepoWrapperTestCase):
322 """Check NeedSetupGnuPG behavior.""" 371 """Check NeedSetupGnuPG behavior."""
323 372
324 def test_missing_dir(self): 373 def test_missing_dir(self):
325 """The ~/.repoconfig tree doesn't exist yet.""" 374 """The ~/.repoconfig tree doesn't exist yet."""
326 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 375 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
327 self.wrapper.home_dot_repo = os.path.join(tempdir, 'foo') 376 self.wrapper.home_dot_repo = os.path.join(tempdir, "foo")
328 self.assertTrue(self.wrapper.NeedSetupGnuPG()) 377 self.assertTrue(self.wrapper.NeedSetupGnuPG())
329 378
330 def test_missing_keyring(self): 379 def test_missing_keyring(self):
331 """The keyring-version file doesn't exist yet.""" 380 """The keyring-version file doesn't exist yet."""
332 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 381 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
333 self.wrapper.home_dot_repo = tempdir 382 self.wrapper.home_dot_repo = tempdir
334 self.assertTrue(self.wrapper.NeedSetupGnuPG()) 383 self.assertTrue(self.wrapper.NeedSetupGnuPG())
335 384
336 def test_empty_keyring(self): 385 def test_empty_keyring(self):
337 """The keyring-version file exists, but is empty.""" 386 """The keyring-version file exists, but is empty."""
338 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 387 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
339 self.wrapper.home_dot_repo = tempdir 388 self.wrapper.home_dot_repo = tempdir
340 with open(os.path.join(tempdir, 'keyring-version'), 'w'): 389 with open(os.path.join(tempdir, "keyring-version"), "w"):
341 pass 390 pass
342 self.assertTrue(self.wrapper.NeedSetupGnuPG()) 391 self.assertTrue(self.wrapper.NeedSetupGnuPG())
343 392
344 def test_old_keyring(self): 393 def test_old_keyring(self):
345 """The keyring-version file exists, but it's old.""" 394 """The keyring-version file exists, but it's old."""
346 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 395 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
347 self.wrapper.home_dot_repo = tempdir 396 self.wrapper.home_dot_repo = tempdir
348 with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: 397 with open(os.path.join(tempdir, "keyring-version"), "w") as fp:
349 fp.write('1.0\n') 398 fp.write("1.0\n")
350 self.assertTrue(self.wrapper.NeedSetupGnuPG()) 399 self.assertTrue(self.wrapper.NeedSetupGnuPG())
351 400
352 def test_new_keyring(self): 401 def test_new_keyring(self):
353 """The keyring-version file exists, and is up-to-date.""" 402 """The keyring-version file exists, and is up-to-date."""
354 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 403 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
355 self.wrapper.home_dot_repo = tempdir 404 self.wrapper.home_dot_repo = tempdir
356 with open(os.path.join(tempdir, 'keyring-version'), 'w') as fp: 405 with open(os.path.join(tempdir, "keyring-version"), "w") as fp:
357 fp.write('1000.0\n') 406 fp.write("1000.0\n")
358 self.assertFalse(self.wrapper.NeedSetupGnuPG()) 407 self.assertFalse(self.wrapper.NeedSetupGnuPG())
359 408
360 409
361class SetupGnuPG(RepoWrapperTestCase): 410class SetupGnuPG(RepoWrapperTestCase):
362 """Check SetupGnuPG behavior.""" 411 """Check SetupGnuPG behavior."""
363 412
364 def test_full(self): 413 def test_full(self):
365 """Make sure it works completely.""" 414 """Make sure it works completely."""
366 with tempfile.TemporaryDirectory(prefix='repo-tests') as tempdir: 415 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir:
367 self.wrapper.home_dot_repo = tempdir 416 self.wrapper.home_dot_repo = tempdir
368 self.wrapper.gpg_dir = os.path.join(self.wrapper.home_dot_repo, 'gnupg') 417 self.wrapper.gpg_dir = os.path.join(
369 self.assertTrue(self.wrapper.SetupGnuPG(True)) 418 self.wrapper.home_dot_repo, "gnupg"
370 with open(os.path.join(tempdir, 'keyring-version'), 'r') as fp: 419 )
371 data = fp.read() 420 self.assertTrue(self.wrapper.SetupGnuPG(True))
372 self.assertEqual('.'.join(str(x) for x in self.wrapper.KEYRING_VERSION), 421 with open(os.path.join(tempdir, "keyring-version"), "r") as fp:
373 data.strip()) 422 data = fp.read()
423 self.assertEqual(
424 ".".join(str(x) for x in self.wrapper.KEYRING_VERSION),
425 data.strip(),
426 )
374 427
375 428
376class VerifyRev(RepoWrapperTestCase): 429class VerifyRev(RepoWrapperTestCase):
377 """Check verify_rev behavior.""" 430 """Check verify_rev behavior."""
378 431
379 def test_verify_passes(self): 432 def test_verify_passes(self):
380 """Check when we have a valid signed tag.""" 433 """Check when we have a valid signed tag."""
381 desc_result = self.wrapper.RunResult(0, 'v1.0\n', '') 434 desc_result = self.wrapper.RunResult(0, "v1.0\n", "")
382 gpg_result = self.wrapper.RunResult(0, '', '') 435 gpg_result = self.wrapper.RunResult(0, "", "")
383 with mock.patch.object(self.wrapper, 'run_git', 436 with mock.patch.object(
384 side_effect=(desc_result, gpg_result)): 437 self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
385 ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) 438 ):
386 self.assertEqual('v1.0^0', ret) 439 ret = self.wrapper.verify_rev(
387 440 "/", "refs/heads/stable", "1234", True
388 def test_unsigned_commit(self): 441 )
389 """Check we fall back to signed tag when we have an unsigned commit.""" 442 self.assertEqual("v1.0^0", ret)
390 desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '') 443
391 gpg_result = self.wrapper.RunResult(0, '', '') 444 def test_unsigned_commit(self):
392 with mock.patch.object(self.wrapper, 'run_git', 445 """Check we fall back to signed tag when we have an unsigned commit."""
393 side_effect=(desc_result, gpg_result)): 446 desc_result = self.wrapper.RunResult(0, "v1.0-10-g1234\n", "")
394 ret = self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) 447 gpg_result = self.wrapper.RunResult(0, "", "")
395 self.assertEqual('v1.0^0', ret) 448 with mock.patch.object(
396 449 self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
397 def test_verify_fails(self): 450 ):
398 """Check we fall back to signed tag when we have an unsigned commit.""" 451 ret = self.wrapper.verify_rev(
399 desc_result = self.wrapper.RunResult(0, 'v1.0-10-g1234\n', '') 452 "/", "refs/heads/stable", "1234", True
400 gpg_result = Exception 453 )
401 with mock.patch.object(self.wrapper, 'run_git', 454 self.assertEqual("v1.0^0", ret)
402 side_effect=(desc_result, gpg_result)): 455
403 with self.assertRaises(Exception): 456 def test_verify_fails(self):
404 self.wrapper.verify_rev('/', 'refs/heads/stable', '1234', True) 457 """Check we fall back to signed tag when we have an unsigned commit."""
458 desc_result = self.wrapper.RunResult(0, "v1.0-10-g1234\n", "")
459 gpg_result = Exception
460 with mock.patch.object(
461 self.wrapper, "run_git", side_effect=(desc_result, gpg_result)
462 ):
463 with self.assertRaises(Exception):
464 self.wrapper.verify_rev("/", "refs/heads/stable", "1234", True)
405 465
406 466
407class GitCheckoutTestCase(RepoWrapperTestCase): 467class GitCheckoutTestCase(RepoWrapperTestCase):
408 """Tests that use a real/small git checkout.""" 468 """Tests that use a real/small git checkout."""
409 469
410 GIT_DIR = None 470 GIT_DIR = None
411 REV_LIST = None 471 REV_LIST = None
412 472
413 @classmethod 473 @classmethod
414 def setUpClass(cls): 474 def setUpClass(cls):
415 # Create a repo to operate on, but do it once per-class. 475 # Create a repo to operate on, but do it once per-class.
416 cls.tempdirobj = tempfile.TemporaryDirectory(prefix='repo-rev-tests') 476 cls.tempdirobj = tempfile.TemporaryDirectory(prefix="repo-rev-tests")
417 cls.GIT_DIR = cls.tempdirobj.name 477 cls.GIT_DIR = cls.tempdirobj.name
418 run_git = wrapper.Wrapper().run_git 478 run_git = wrapper.Wrapper().run_git
419 479
420 remote = os.path.join(cls.GIT_DIR, 'remote') 480 remote = os.path.join(cls.GIT_DIR, "remote")
421 os.mkdir(remote) 481 os.mkdir(remote)
422 482
423 # Tests need to assume, that main is default branch at init, 483 # Tests need to assume, that main is default branch at init,
424 # which is not supported in config until 2.28. 484 # which is not supported in config until 2.28.
425 if git_command.git_require((2, 28, 0)): 485 if git_command.git_require((2, 28, 0)):
426 initstr = '--initial-branch=main' 486 initstr = "--initial-branch=main"
427 else: 487 else:
428 # Use template dir for init. 488 # Use template dir for init.
429 templatedir = tempfile.mkdtemp(prefix='.test-template') 489 templatedir = tempfile.mkdtemp(prefix=".test-template")
430 with open(os.path.join(templatedir, 'HEAD'), 'w') as fp: 490 with open(os.path.join(templatedir, "HEAD"), "w") as fp:
431 fp.write('ref: refs/heads/main\n') 491 fp.write("ref: refs/heads/main\n")
432 initstr = '--template=' + templatedir 492 initstr = "--template=" + templatedir
433 493
434 run_git('init', initstr, cwd=remote) 494 run_git("init", initstr, cwd=remote)
435 run_git('commit', '--allow-empty', '-minit', cwd=remote) 495 run_git("commit", "--allow-empty", "-minit", cwd=remote)
436 run_git('branch', 'stable', cwd=remote) 496 run_git("branch", "stable", cwd=remote)
437 run_git('tag', 'v1.0', cwd=remote) 497 run_git("tag", "v1.0", cwd=remote)
438 run_git('commit', '--allow-empty', '-m2nd commit', cwd=remote) 498 run_git("commit", "--allow-empty", "-m2nd commit", cwd=remote)
439 cls.REV_LIST = run_git('rev-list', 'HEAD', cwd=remote).stdout.splitlines() 499 cls.REV_LIST = run_git(
440 500 "rev-list", "HEAD", cwd=remote
441 run_git('init', cwd=cls.GIT_DIR) 501 ).stdout.splitlines()
442 run_git('fetch', remote, '+refs/heads/*:refs/remotes/origin/*', cwd=cls.GIT_DIR) 502
443 503 run_git("init", cwd=cls.GIT_DIR)
444 @classmethod 504 run_git(
445 def tearDownClass(cls): 505 "fetch",
446 if not cls.tempdirobj: 506 remote,
447 return 507 "+refs/heads/*:refs/remotes/origin/*",
448 508 cwd=cls.GIT_DIR,
449 cls.tempdirobj.cleanup() 509 )
510
511 @classmethod
512 def tearDownClass(cls):
513 if not cls.tempdirobj:
514 return
515
516 cls.tempdirobj.cleanup()
450 517
451 518
452class ResolveRepoRev(GitCheckoutTestCase): 519class ResolveRepoRev(GitCheckoutTestCase):
453 """Check resolve_repo_rev behavior.""" 520 """Check resolve_repo_rev behavior."""
454 521
455 def test_explicit_branch(self): 522 def test_explicit_branch(self):
456 """Check refs/heads/branch argument.""" 523 """Check refs/heads/branch argument."""
457 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/stable') 524 rrev, lrev = self.wrapper.resolve_repo_rev(
458 self.assertEqual('refs/heads/stable', rrev) 525 self.GIT_DIR, "refs/heads/stable"
459 self.assertEqual(self.REV_LIST[1], lrev) 526 )
460 527 self.assertEqual("refs/heads/stable", rrev)
461 with self.assertRaises(self.wrapper.CloneFailure): 528 self.assertEqual(self.REV_LIST[1], lrev)
462 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/heads/unknown') 529
463 530 with self.assertRaises(self.wrapper.CloneFailure):
464 def test_explicit_tag(self): 531 self.wrapper.resolve_repo_rev(self.GIT_DIR, "refs/heads/unknown")
465 """Check refs/tags/tag argument.""" 532
466 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/v1.0') 533 def test_explicit_tag(self):
467 self.assertEqual('refs/tags/v1.0', rrev) 534 """Check refs/tags/tag argument."""
468 self.assertEqual(self.REV_LIST[1], lrev) 535 rrev, lrev = self.wrapper.resolve_repo_rev(
469 536 self.GIT_DIR, "refs/tags/v1.0"
470 with self.assertRaises(self.wrapper.CloneFailure): 537 )
471 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'refs/tags/unknown') 538 self.assertEqual("refs/tags/v1.0", rrev)
472 539 self.assertEqual(self.REV_LIST[1], lrev)
473 def test_branch_name(self): 540
474 """Check branch argument.""" 541 with self.assertRaises(self.wrapper.CloneFailure):
475 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'stable') 542 self.wrapper.resolve_repo_rev(self.GIT_DIR, "refs/tags/unknown")
476 self.assertEqual('refs/heads/stable', rrev) 543
477 self.assertEqual(self.REV_LIST[1], lrev) 544 def test_branch_name(self):
478 545 """Check branch argument."""
479 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'main') 546 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "stable")
480 self.assertEqual('refs/heads/main', rrev) 547 self.assertEqual("refs/heads/stable", rrev)
481 self.assertEqual(self.REV_LIST[0], lrev) 548 self.assertEqual(self.REV_LIST[1], lrev)
482 549
483 def test_tag_name(self): 550 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "main")
484 """Check tag argument.""" 551 self.assertEqual("refs/heads/main", rrev)
485 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, 'v1.0') 552 self.assertEqual(self.REV_LIST[0], lrev)
486 self.assertEqual('refs/tags/v1.0', rrev) 553
487 self.assertEqual(self.REV_LIST[1], lrev) 554 def test_tag_name(self):
488 555 """Check tag argument."""
489 def test_full_commit(self): 556 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "v1.0")
490 """Check specific commit argument.""" 557 self.assertEqual("refs/tags/v1.0", rrev)
491 commit = self.REV_LIST[0] 558 self.assertEqual(self.REV_LIST[1], lrev)
492 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit) 559
493 self.assertEqual(commit, rrev) 560 def test_full_commit(self):
494 self.assertEqual(commit, lrev) 561 """Check specific commit argument."""
495 562 commit = self.REV_LIST[0]
496 def test_partial_commit(self): 563 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
497 """Check specific (partial) commit argument.""" 564 self.assertEqual(commit, rrev)
498 commit = self.REV_LIST[0][0:20] 565 self.assertEqual(commit, lrev)
499 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit) 566
500 self.assertEqual(self.REV_LIST[0], rrev) 567 def test_partial_commit(self):
501 self.assertEqual(self.REV_LIST[0], lrev) 568 """Check specific (partial) commit argument."""
502 569 commit = self.REV_LIST[0][0:20]
503 def test_unknown(self): 570 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit)
504 """Check unknown ref/commit argument.""" 571 self.assertEqual(self.REV_LIST[0], rrev)
505 with self.assertRaises(self.wrapper.CloneFailure): 572 self.assertEqual(self.REV_LIST[0], lrev)
506 self.wrapper.resolve_repo_rev(self.GIT_DIR, 'boooooooya') 573
574 def test_unknown(self):
575 """Check unknown ref/commit argument."""
576 with self.assertRaises(self.wrapper.CloneFailure):
577 self.wrapper.resolve_repo_rev(self.GIT_DIR, "boooooooya")
507 578
508 579
509class CheckRepoVerify(RepoWrapperTestCase): 580class CheckRepoVerify(RepoWrapperTestCase):
510 """Check check_repo_verify behavior.""" 581 """Check check_repo_verify behavior."""
511 582
512 def test_no_verify(self): 583 def test_no_verify(self):
513 """Always fail with --no-repo-verify.""" 584 """Always fail with --no-repo-verify."""
514 self.assertFalse(self.wrapper.check_repo_verify(False)) 585 self.assertFalse(self.wrapper.check_repo_verify(False))
515 586
516 def test_gpg_initialized(self): 587 def test_gpg_initialized(self):
517 """Should pass if gpg is setup already.""" 588 """Should pass if gpg is setup already."""
518 with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=False): 589 with mock.patch.object(
519 self.assertTrue(self.wrapper.check_repo_verify(True)) 590 self.wrapper, "NeedSetupGnuPG", return_value=False
591 ):
592 self.assertTrue(self.wrapper.check_repo_verify(True))
520 593
521 def test_need_gpg_setup(self): 594 def test_need_gpg_setup(self):
522 """Should pass/fail based on gpg setup.""" 595 """Should pass/fail based on gpg setup."""
523 with mock.patch.object(self.wrapper, 'NeedSetupGnuPG', return_value=True): 596 with mock.patch.object(
524 with mock.patch.object(self.wrapper, 'SetupGnuPG') as m: 597 self.wrapper, "NeedSetupGnuPG", return_value=True
525 m.return_value = True 598 ):
526 self.assertTrue(self.wrapper.check_repo_verify(True)) 599 with mock.patch.object(self.wrapper, "SetupGnuPG") as m:
600 m.return_value = True
601 self.assertTrue(self.wrapper.check_repo_verify(True))
527 602
528 m.return_value = False 603 m.return_value = False
529 self.assertFalse(self.wrapper.check_repo_verify(True)) 604 self.assertFalse(self.wrapper.check_repo_verify(True))
530 605
531 606
532class CheckRepoRev(GitCheckoutTestCase): 607class CheckRepoRev(GitCheckoutTestCase):
533 """Check check_repo_rev behavior.""" 608 """Check check_repo_rev behavior."""
534 609
535 def test_verify_works(self): 610 def test_verify_works(self):
536 """Should pass when verification passes.""" 611 """Should pass when verification passes."""
537 with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True): 612 with mock.patch.object(
538 with mock.patch.object(self.wrapper, 'verify_rev', return_value='12345'): 613 self.wrapper, "check_repo_verify", return_value=True
539 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable') 614 ):
540 self.assertEqual('refs/heads/stable', rrev) 615 with mock.patch.object(
541 self.assertEqual('12345', lrev) 616 self.wrapper, "verify_rev", return_value="12345"
542 617 ):
543 def test_verify_fails(self): 618 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, "stable")
544 """Should fail when verification fails.""" 619 self.assertEqual("refs/heads/stable", rrev)
545 with mock.patch.object(self.wrapper, 'check_repo_verify', return_value=True): 620 self.assertEqual("12345", lrev)
546 with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception): 621
547 with self.assertRaises(Exception): 622 def test_verify_fails(self):
548 self.wrapper.check_repo_rev(self.GIT_DIR, 'stable') 623 """Should fail when verification fails."""
549 624 with mock.patch.object(
550 def test_verify_ignore(self): 625 self.wrapper, "check_repo_verify", return_value=True
551 """Should pass when verification is disabled.""" 626 ):
552 with mock.patch.object(self.wrapper, 'verify_rev', side_effect=Exception): 627 with mock.patch.object(
553 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, 'stable', repo_verify=False) 628 self.wrapper, "verify_rev", side_effect=Exception
554 self.assertEqual('refs/heads/stable', rrev) 629 ):
555 self.assertEqual(self.REV_LIST[1], lrev) 630 with self.assertRaises(Exception):
631 self.wrapper.check_repo_rev(self.GIT_DIR, "stable")
632
633 def test_verify_ignore(self):
634 """Should pass when verification is disabled."""
635 with mock.patch.object(
636 self.wrapper, "verify_rev", side_effect=Exception
637 ):
638 rrev, lrev = self.wrapper.check_repo_rev(
639 self.GIT_DIR, "stable", repo_verify=False
640 )
641 self.assertEqual("refs/heads/stable", rrev)
642 self.assertEqual(self.REV_LIST[1], lrev)