summaryrefslogtreecommitdiffstats
path: root/tests/test_git_superproject.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_git_superproject.py')
-rw-r--r--tests/test_git_superproject.py376
1 files changed, 376 insertions, 0 deletions
diff --git a/tests/test_git_superproject.py b/tests/test_git_superproject.py
new file mode 100644
index 00000000..a24fc7f0
--- /dev/null
+++ b/tests/test_git_superproject.py
@@ -0,0 +1,376 @@
1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Unittests for the git_superproject.py module."""
16
17import json
18import os
19import platform
20import tempfile
21import unittest
22from unittest import mock
23
24import git_superproject
25import git_trace2_event_log
26import manifest_xml
27import platform_utils
28from test_manifest_xml import sort_attributes
29
30
31class SuperprojectTestCase(unittest.TestCase):
32 """TestCase for the Superproject module."""
33
34 PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID'
35 PARENT_SID_VALUE = 'parent_sid'
36 SELF_SID_REGEX = r'repo-\d+T\d+Z-.*'
37 FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX)
38
39 def setUp(self):
40 """Set up superproject every time."""
41 self.tempdir = tempfile.mkdtemp(prefix='repo_tests')
42 self.repodir = os.path.join(self.tempdir, '.repo')
43 self.manifest_file = os.path.join(
44 self.repodir, manifest_xml.MANIFEST_FILE_NAME)
45 os.mkdir(self.repodir)
46 self.platform = platform.system().lower()
47
48 # By default we initialize with the expected case where
49 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
50 env = {
51 self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
52 }
53 self.git_event_log = git_trace2_event_log.EventLog(env=env)
54
55 # The manifest parsing really wants a git repo currently.
56 gitdir = os.path.join(self.repodir, 'manifests.git')
57 os.mkdir(gitdir)
58 with open(os.path.join(gitdir, 'config'), 'w') as fp:
59 fp.write("""[remote "origin"]
60 url = https://localhost:0/manifest
61""")
62
63 manifest = self.getXmlManifest("""
64<manifest>
65 <remote name="default-remote" fetch="http://localhost" />
66 <default remote="default-remote" revision="refs/heads/main" />
67 <superproject name="superproject"/>
68 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
69 " /></manifest>
70""")
71 self._superproject = git_superproject.Superproject(manifest, self.repodir,
72 self.git_event_log)
73
74 def tearDown(self):
75 """Tear down superproject every time."""
76 platform_utils.rmtree(self.tempdir)
77
78 def getXmlManifest(self, data):
79 """Helper to initialize a manifest for testing."""
80 with open(self.manifest_file, 'w') as fp:
81 fp.write(data)
82 return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
83
84 def verifyCommonKeys(self, log_entry, expected_event_name, full_sid=True):
85 """Helper function to verify common event log keys."""
86 self.assertIn('event', log_entry)
87 self.assertIn('sid', log_entry)
88 self.assertIn('thread', log_entry)
89 self.assertIn('time', log_entry)
90
91 # Do basic data format validation.
92 self.assertEqual(expected_event_name, log_entry['event'])
93 if full_sid:
94 self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX)
95 else:
96 self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX)
97 self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$')
98
99 def readLog(self, log_path):
100 """Helper function to read log data into a list."""
101 log_data = []
102 with open(log_path, mode='rb') as f:
103 for line in f:
104 log_data.append(json.loads(line))
105 return log_data
106
107 def verifyErrorEvent(self):
108 """Helper to verify that error event is written."""
109
110 with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir:
111 log_path = self.git_event_log.Write(path=tempdir)
112 self.log_data = self.readLog(log_path)
113
114 self.assertEqual(len(self.log_data), 2)
115 error_event = self.log_data[1]
116 self.verifyCommonKeys(self.log_data[0], expected_event_name='version')
117 self.verifyCommonKeys(error_event, expected_event_name='error')
118 # Check for 'error' event specific fields.
119 self.assertIn('msg', error_event)
120 self.assertIn('fmt', error_event)
121
122 def test_superproject_get_superproject_no_superproject(self):
123 """Test with no url."""
124 manifest = self.getXmlManifest("""
125<manifest>
126</manifest>
127""")
128 superproject = git_superproject.Superproject(manifest, self.repodir, self.git_event_log)
129 # Test that exit condition is false when there is no superproject tag.
130 sync_result = superproject.Sync()
131 self.assertFalse(sync_result.success)
132 self.assertFalse(sync_result.fatal)
133 self.verifyErrorEvent()
134
135 def test_superproject_get_superproject_invalid_url(self):
136 """Test with an invalid url."""
137 manifest = self.getXmlManifest("""
138<manifest>
139 <remote name="test-remote" fetch="localhost" />
140 <default remote="test-remote" revision="refs/heads/main" />
141 <superproject name="superproject"/>
142</manifest>
143""")
144 superproject = git_superproject.Superproject(manifest, self.repodir, self.git_event_log)
145 sync_result = superproject.Sync()
146 self.assertFalse(sync_result.success)
147 self.assertTrue(sync_result.fatal)
148
149 def test_superproject_get_superproject_invalid_branch(self):
150 """Test with an invalid branch."""
151 manifest = self.getXmlManifest("""
152<manifest>
153 <remote name="test-remote" fetch="localhost" />
154 <default remote="test-remote" revision="refs/heads/main" />
155 <superproject name="superproject"/>
156</manifest>
157""")
158 self._superproject = git_superproject.Superproject(manifest, self.repodir,
159 self.git_event_log)
160 with mock.patch.object(self._superproject, '_branch', 'junk'):
161 sync_result = self._superproject.Sync()
162 self.assertFalse(sync_result.success)
163 self.assertTrue(sync_result.fatal)
164
165 def test_superproject_get_superproject_mock_init(self):
166 """Test with _Init failing."""
167 with mock.patch.object(self._superproject, '_Init', return_value=False):
168 sync_result = self._superproject.Sync()
169 self.assertFalse(sync_result.success)
170 self.assertTrue(sync_result.fatal)
171
172 def test_superproject_get_superproject_mock_fetch(self):
173 """Test with _Fetch failing."""
174 with mock.patch.object(self._superproject, '_Init', return_value=True):
175 os.mkdir(self._superproject._superproject_path)
176 with mock.patch.object(self._superproject, '_Fetch', return_value=False):
177 sync_result = self._superproject.Sync()
178 self.assertFalse(sync_result.success)
179 self.assertTrue(sync_result.fatal)
180
181 def test_superproject_get_all_project_commit_ids_mock_ls_tree(self):
182 """Test with LsTree being a mock."""
183 data = ('120000 blob 158258bdf146f159218e2b90f8b699c4d85b5804\tAndroid.bp\x00'
184 '160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
185 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00'
186 '120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00'
187 '160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00')
188 with mock.patch.object(self._superproject, '_Init', return_value=True):
189 with mock.patch.object(self._superproject, '_Fetch', return_value=True):
190 with mock.patch.object(self._superproject, '_LsTree', return_value=data):
191 commit_ids_result = self._superproject._GetAllProjectsCommitIds()
192 self.assertEqual(commit_ids_result.commit_ids, {
193 'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea',
194 'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06',
195 'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928'
196 })
197 self.assertFalse(commit_ids_result.fatal)
198
199 def test_superproject_write_manifest_file(self):
200 """Test with writing manifest to a file after setting revisionId."""
201 self.assertEqual(len(self._superproject._manifest.projects), 1)
202 project = self._superproject._manifest.projects[0]
203 project.SetRevisionId('ABCDEF')
204 # Create temporary directory so that it can write the file.
205 os.mkdir(self._superproject._superproject_path)
206 manifest_path = self._superproject._WriteManifestFile()
207 self.assertIsNotNone(manifest_path)
208 with open(manifest_path, 'r') as fp:
209 manifest_xml_data = fp.read()
210 self.assertEqual(
211 sort_attributes(manifest_xml_data),
212 '<?xml version="1.0" ?><manifest>'
213 '<remote fetch="http://localhost" name="default-remote"/>'
214 '<default remote="default-remote" revision="refs/heads/main"/>'
215 '<project groups="notdefault,platform-' + self.platform + '" '
216 'name="platform/art" path="art" revision="ABCDEF" upstream="refs/heads/main"/>'
217 '<superproject name="superproject"/>'
218 '</manifest>')
219
220 def test_superproject_update_project_revision_id(self):
221 """Test with LsTree being a mock."""
222 self.assertEqual(len(self._superproject._manifest.projects), 1)
223 projects = self._superproject._manifest.projects
224 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
225 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00')
226 with mock.patch.object(self._superproject, '_Init', return_value=True):
227 with mock.patch.object(self._superproject, '_Fetch', return_value=True):
228 with mock.patch.object(self._superproject,
229 '_LsTree',
230 return_value=data):
231 # Create temporary directory so that it can write the file.
232 os.mkdir(self._superproject._superproject_path)
233 update_result = self._superproject.UpdateProjectsRevisionId(projects)
234 self.assertIsNotNone(update_result.manifest_path)
235 self.assertFalse(update_result.fatal)
236 with open(update_result.manifest_path, 'r') as fp:
237 manifest_xml_data = fp.read()
238 self.assertEqual(
239 sort_attributes(manifest_xml_data),
240 '<?xml version="1.0" ?><manifest>'
241 '<remote fetch="http://localhost" name="default-remote"/>'
242 '<default remote="default-remote" revision="refs/heads/main"/>'
243 '<project groups="notdefault,platform-' + self.platform + '" '
244 'name="platform/art" path="art" '
245 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
246 '<superproject name="superproject"/>'
247 '</manifest>')
248
249 def test_superproject_update_project_revision_id_no_superproject_tag(self):
250 """Test update of commit ids of a manifest without superproject tag."""
251 manifest = self.getXmlManifest("""
252<manifest>
253 <remote name="default-remote" fetch="http://localhost" />
254 <default remote="default-remote" revision="refs/heads/main" />
255 <project name="test-name"/>
256</manifest>
257""")
258 self.maxDiff = None
259 self._superproject = git_superproject.Superproject(manifest, self.repodir,
260 self.git_event_log)
261 self.assertEqual(len(self._superproject._manifest.projects), 1)
262 projects = self._superproject._manifest.projects
263 project = projects[0]
264 project.SetRevisionId('ABCDEF')
265 update_result = self._superproject.UpdateProjectsRevisionId(projects)
266 self.assertIsNone(update_result.manifest_path)
267 self.assertFalse(update_result.fatal)
268 self.verifyErrorEvent()
269 self.assertEqual(
270 sort_attributes(manifest.ToXml().toxml()),
271 '<?xml version="1.0" ?><manifest>'
272 '<remote fetch="http://localhost" name="default-remote"/>'
273 '<default remote="default-remote" revision="refs/heads/main"/>'
274 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>'
275 '</manifest>')
276
277 def test_superproject_update_project_revision_id_from_local_manifest_group(self):
278 """Test update of commit ids of a manifest that have local manifest no superproject group."""
279 local_group = manifest_xml.LOCAL_MANIFEST_GROUP_PREFIX + ':local'
280 manifest = self.getXmlManifest("""
281<manifest>
282 <remote name="default-remote" fetch="http://localhost" />
283 <remote name="goog" fetch="http://localhost2" />
284 <default remote="default-remote" revision="refs/heads/main" />
285 <superproject name="superproject"/>
286 <project path="vendor/x" name="platform/vendor/x" remote="goog"
287 groups=\"""" + local_group + """
288 " revision="master-with-vendor" clone-depth="1" />
289 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
290 " /></manifest>
291""")
292 self.maxDiff = None
293 self._superproject = git_superproject.Superproject(manifest, self.repodir,
294 self.git_event_log)
295 self.assertEqual(len(self._superproject._manifest.projects), 2)
296 projects = self._superproject._manifest.projects
297 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00')
298 with mock.patch.object(self._superproject, '_Init', return_value=True):
299 with mock.patch.object(self._superproject, '_Fetch', return_value=True):
300 with mock.patch.object(self._superproject,
301 '_LsTree',
302 return_value=data):
303 # Create temporary directory so that it can write the file.
304 os.mkdir(self._superproject._superproject_path)
305 update_result = self._superproject.UpdateProjectsRevisionId(projects)
306 self.assertIsNotNone(update_result.manifest_path)
307 self.assertFalse(update_result.fatal)
308 with open(update_result.manifest_path, 'r') as fp:
309 manifest_xml_data = fp.read()
310 # Verify platform/vendor/x's project revision hasn't changed.
311 self.assertEqual(
312 sort_attributes(manifest_xml_data),
313 '<?xml version="1.0" ?><manifest>'
314 '<remote fetch="http://localhost" name="default-remote"/>'
315 '<remote fetch="http://localhost2" name="goog"/>'
316 '<default remote="default-remote" revision="refs/heads/main"/>'
317 '<project groups="notdefault,platform-' + self.platform + '" '
318 'name="platform/art" path="art" '
319 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
320 '<project clone-depth="1" groups="' + local_group + '" '
321 'name="platform/vendor/x" path="vendor/x" remote="goog" '
322 'revision="master-with-vendor"/>'
323 '<superproject name="superproject"/>'
324 '</manifest>')
325
326 def test_superproject_update_project_revision_id_with_pinned_manifest(self):
327 """Test update of commit ids of a pinned manifest."""
328 manifest = self.getXmlManifest("""
329<manifest>
330 <remote name="default-remote" fetch="http://localhost" />
331 <default remote="default-remote" revision="refs/heads/main" />
332 <superproject name="superproject"/>
333 <project path="vendor/x" name="platform/vendor/x" revision="" />
334 <project path="vendor/y" name="platform/vendor/y"
335 revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f" />
336 <project path="art" name="platform/art" groups="notdefault,platform-""" + self.platform + """
337 " /></manifest>
338""")
339 self.maxDiff = None
340 self._superproject = git_superproject.Superproject(manifest, self.repodir,
341 self.git_event_log)
342 self.assertEqual(len(self._superproject._manifest.projects), 3)
343 projects = self._superproject._manifest.projects
344 data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00'
345 '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tvendor/x\x00')
346 with mock.patch.object(self._superproject, '_Init', return_value=True):
347 with mock.patch.object(self._superproject, '_Fetch', return_value=True):
348 with mock.patch.object(self._superproject,
349 '_LsTree',
350 return_value=data):
351 # Create temporary directory so that it can write the file.
352 os.mkdir(self._superproject._superproject_path)
353 update_result = self._superproject.UpdateProjectsRevisionId(projects)
354 self.assertIsNotNone(update_result.manifest_path)
355 self.assertFalse(update_result.fatal)
356 with open(update_result.manifest_path, 'r') as fp:
357 manifest_xml_data = fp.read()
358 # Verify platform/vendor/x's project revision hasn't changed.
359 self.assertEqual(
360 sort_attributes(manifest_xml_data),
361 '<?xml version="1.0" ?><manifest>'
362 '<remote fetch="http://localhost" name="default-remote"/>'
363 '<default remote="default-remote" revision="refs/heads/main"/>'
364 '<project groups="notdefault,platform-' + self.platform + '" '
365 'name="platform/art" path="art" '
366 'revision="2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea" upstream="refs/heads/main"/>'
367 '<project name="platform/vendor/x" path="vendor/x" '
368 'revision="e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06" upstream="refs/heads/main"/>'
369 '<project name="platform/vendor/y" path="vendor/y" '
370 'revision="52d3c9f7c107839ece2319d077de0cd922aa9d8f"/>'
371 '<superproject name="superproject"/>'
372 '</manifest>')
373
374
375if __name__ == '__main__':
376 unittest.main()