blob: e181b642351c8c95cfc3811f3426bb26fad435a6 [file] [log] [blame]
Mike Frysinger04122b72019-07-31 23:32:58 -04001# Copyright (C) 2019 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 manifest_xml.py module."""
16
Mike Frysingerd9254592020-02-19 22:36:26 -050017import os
Raman Tenneti080877e2021-03-09 15:19:06 -080018import platform
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +020019import re
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -040020import tempfile
Mike Frysinger04122b72019-07-31 23:32:58 -040021import unittest
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -050022import xml.dom.minidom
Mike Frysinger04122b72019-07-31 23:32:58 -040023
24import error
25import manifest_xml
26
27
Mike Frysingera29424e2021-02-25 21:53:49 -050028# Invalid paths that we don't want in the filesystem.
29INVALID_FS_PATHS = (
30 '',
31 '.',
32 '..',
33 '../',
34 './',
Mike Frysinger0458faa2021-03-10 23:35:44 -050035 './/',
Mike Frysingera29424e2021-02-25 21:53:49 -050036 'foo/',
37 './foo',
38 '../foo',
39 'foo/./bar',
40 'foo/../../bar',
41 '/foo',
42 './../foo',
43 '.git/foo',
44 # Check case folding.
45 '.GIT/foo',
46 'blah/.git/foo',
47 '.repo/foo',
48 '.repoconfig',
49 # Block ~ due to 8.3 filenames on Windows filesystems.
50 '~',
51 'foo~',
52 'blah/foo~',
53 # Block Unicode characters that get normalized out by filesystems.
54 u'foo\u200Cbar',
Mike Frysingerf69c7ee2021-04-29 23:15:31 -040055 # Block newlines.
56 'f\n/bar',
57 'f\r/bar',
Mike Frysingera29424e2021-02-25 21:53:49 -050058)
59
60# Make sure platforms that use path separators (e.g. Windows) are also
61# rejected properly.
62if os.path.sep != '/':
63 INVALID_FS_PATHS += tuple(x.replace('/', os.path.sep) for x in INVALID_FS_PATHS)
64
65
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +020066def sort_attributes(manifest):
67 """Sort the attributes of all elements alphabetically.
68
69 This is needed because different versions of the toxml() function from
70 xml.dom.minidom outputs the attributes of elements in different orders.
71 Before Python 3.8 they were output alphabetically, later versions preserve
72 the order specified by the user.
73
74 Args:
75 manifest: String containing an XML manifest.
76
77 Returns:
78 The XML manifest with the attributes of all elements sorted alphabetically.
79 """
80 new_manifest = ''
81 # This will find every element in the XML manifest, whether they have
82 # attributes or not. This simplifies recreating the manifest below.
83 matches = re.findall(r'(<[/?]?[a-z-]+\s*)((?:\S+?="[^"]+"\s*?)*)(\s*[/?]?>)', manifest)
84 for head, attrs, tail in matches:
85 m = re.findall(r'\S+?="[^"]+"', attrs)
86 new_manifest += head + ' '.join(sorted(m)) + tail
87 return new_manifest
88
89
Mike Frysinger37ac3d62021-02-25 04:54:56 -050090class ManifestParseTestCase(unittest.TestCase):
91 """TestCase for parsing manifests."""
92
93 def setUp(self):
Mike Frysinger74737da2022-05-20 06:26:50 -040094 self.tempdirobj = tempfile.TemporaryDirectory(prefix='repo_tests')
95 self.tempdir = self.tempdirobj.name
Mike Frysinger37ac3d62021-02-25 04:54:56 -050096 self.repodir = os.path.join(self.tempdir, '.repo')
97 self.manifest_dir = os.path.join(self.repodir, 'manifests')
98 self.manifest_file = os.path.join(
99 self.repodir, manifest_xml.MANIFEST_FILE_NAME)
100 self.local_manifest_dir = os.path.join(
101 self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME)
102 os.mkdir(self.repodir)
103 os.mkdir(self.manifest_dir)
104
105 # The manifest parsing really wants a git repo currently.
106 gitdir = os.path.join(self.repodir, 'manifests.git')
107 os.mkdir(gitdir)
108 with open(os.path.join(gitdir, 'config'), 'w') as fp:
109 fp.write("""[remote "origin"]
110 url = https://localhost:0/manifest
111""")
112
113 def tearDown(self):
Mike Frysinger74737da2022-05-20 06:26:50 -0400114 self.tempdirobj.cleanup()
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500115
116 def getXmlManifest(self, data):
117 """Helper to initialize a manifest for testing."""
118 with open(self.manifest_file, 'w') as fp:
119 fp.write(data)
120 return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
121
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400122 @staticmethod
123 def encodeXmlAttr(attr):
124 """Encode |attr| using XML escape rules."""
125 return attr.replace('\r', '&#x000d;').replace('\n', '&#x000a;')
126
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500127
Mike Frysinger04122b72019-07-31 23:32:58 -0400128class ManifestValidateFilePaths(unittest.TestCase):
129 """Check _ValidateFilePaths helper.
130
131 This doesn't access a real filesystem.
132 """
133
134 def check_both(self, *args):
135 manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args)
136 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
137
138 def test_normal_path(self):
139 """Make sure good paths are accepted."""
140 self.check_both('foo', 'bar')
141 self.check_both('foo/bar', 'bar')
142 self.check_both('foo', 'bar/bar')
143 self.check_both('foo/bar', 'bar/bar')
144
145 def test_symlink_targets(self):
146 """Some extra checks for symlinks."""
147 def check(*args):
148 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
149
150 # We allow symlinks to end in a slash since we allow them to point to dirs
151 # in general. Technically the slash isn't necessary.
152 check('foo/', 'bar')
Mike Frysingerae625412020-02-10 17:10:03 -0500153 # We allow a single '.' to get a reference to the project itself.
154 check('.', 'bar')
Mike Frysinger04122b72019-07-31 23:32:58 -0400155
156 def test_bad_paths(self):
157 """Make sure bad paths (src & dest) are rejected."""
Mike Frysingera29424e2021-02-25 21:53:49 -0500158 for path in INVALID_FS_PATHS:
Mike Frysinger04122b72019-07-31 23:32:58 -0400159 self.assertRaises(
160 error.ManifestInvalidPathError, self.check_both, path, 'a')
161 self.assertRaises(
162 error.ManifestInvalidPathError, self.check_both, 'a', path)
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -0500163
164
165class ValueTests(unittest.TestCase):
166 """Check utility parsing code."""
167
168 def _get_node(self, text):
169 return xml.dom.minidom.parseString(text).firstChild
170
171 def test_bool_default(self):
172 """Check XmlBool default handling."""
173 node = self._get_node('<node/>')
174 self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
175 self.assertIsNone(manifest_xml.XmlBool(node, 'a', None))
176 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
177
178 node = self._get_node('<node a=""/>')
179 self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
180
181 def test_bool_invalid(self):
182 """Check XmlBool invalid handling."""
183 node = self._get_node('<node a="moo"/>')
184 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
185
186 def test_bool_true(self):
187 """Check XmlBool true values."""
188 for value in ('yes', 'true', '1'):
189 node = self._get_node('<node a="%s"/>' % (value,))
190 self.assertTrue(manifest_xml.XmlBool(node, 'a'))
191
192 def test_bool_false(self):
193 """Check XmlBool false values."""
194 for value in ('no', 'false', '0'):
195 node = self._get_node('<node a="%s"/>' % (value,))
196 self.assertFalse(manifest_xml.XmlBool(node, 'a'))
197
198 def test_int_default(self):
199 """Check XmlInt default handling."""
200 node = self._get_node('<node/>')
201 self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
202 self.assertIsNone(manifest_xml.XmlInt(node, 'a', None))
203 self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123))
204
205 node = self._get_node('<node a=""/>')
206 self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
207
208 def test_int_good(self):
209 """Check XmlInt numeric handling."""
210 for value in (-1, 0, 1, 50000):
211 node = self._get_node('<node a="%s"/>' % (value,))
212 self.assertEqual(value, manifest_xml.XmlInt(node, 'a'))
213
214 def test_int_invalid(self):
215 """Check XmlInt invalid handling."""
216 with self.assertRaises(error.ManifestParseError):
217 node = self._get_node('<node a="xx"/>')
218 manifest_xml.XmlInt(node, 'a')
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400219
220
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500221class XmlManifestTests(ManifestParseTestCase):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400222 """Check manifest processing."""
223
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400224 def test_empty(self):
225 """Parse an 'empty' manifest file."""
226 manifest = self.getXmlManifest(
227 '<?xml version="1.0" encoding="UTF-8"?>'
228 '<manifest></manifest>')
229 self.assertEqual(manifest.remotes, {})
230 self.assertEqual(manifest.projects, [])
231
232 def test_link(self):
233 """Verify Link handling with new names."""
234 manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
235 with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp:
236 fp.write('<manifest></manifest>')
237 manifest.Link('foo.xml')
238 with open(self.manifest_file) as fp:
239 self.assertIn('<include name="foo.xml" />', fp.read())
240
241 def test_toxml_empty(self):
242 """Verify the ToXml() helper."""
243 manifest = self.getXmlManifest(
244 '<?xml version="1.0" encoding="UTF-8"?>'
245 '<manifest></manifest>')
246 self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>')
247
248 def test_todict_empty(self):
249 """Verify the ToDict() helper."""
250 manifest = self.getXmlManifest(
251 '<?xml version="1.0" encoding="UTF-8"?>'
252 '<manifest></manifest>')
253 self.assertEqual(manifest.ToDict(), {})
254
LaMont Jonesa8cf5752022-07-15 20:31:33 +0000255 def test_toxml_omit_local(self):
256 """Does not include local_manifests projects when omit_local=True."""
257 manifest = self.getXmlManifest(
258 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
259 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
260 '<project name="p" groups="local::me"/>'
261 '<project name="q"/>'
262 '<project name="r" groups="keep"/>'
263 '</manifest>')
264 self.assertEqual(
265 manifest.ToXml(omit_local=True).toxml(),
266 '<?xml version="1.0" ?><manifest>'
267 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
268 '<project name="q"/><project name="r" groups="keep"/></manifest>')
269
270 def test_toxml_with_local(self):
271 """Does include local_manifests projects when omit_local=False."""
272 manifest = self.getXmlManifest(
273 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
274 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
275 '<project name="p" groups="local::me"/>'
276 '<project name="q"/>'
277 '<project name="r" groups="keep"/>'
278 '</manifest>')
279 self.assertEqual(
280 manifest.ToXml(omit_local=False).toxml(),
281 '<?xml version="1.0" ?><manifest>'
282 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
283 '<project name="p" groups="local::me"/>'
284 '<project name="q"/><project name="r" groups="keep"/></manifest>')
285
Mike Frysinger51e39d52020-12-04 05:32:06 -0500286 def test_repo_hooks(self):
287 """Check repo-hooks settings."""
288 manifest = self.getXmlManifest("""
289<manifest>
290 <remote name="test-remote" fetch="http://localhost" />
291 <default remote="test-remote" revision="refs/heads/main" />
292 <project name="repohooks" path="src/repohooks"/>
293 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
294</manifest>
295""")
296 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
297 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
298
Jack Neusa84f43a2021-09-21 22:23:55 +0000299 def test_repo_hooks_unordered(self):
300 """Check repo-hooks settings work even if the project def comes second."""
301 manifest = self.getXmlManifest("""
302<manifest>
303 <remote name="test-remote" fetch="http://localhost" />
304 <default remote="test-remote" revision="refs/heads/main" />
305 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
306 <project name="repohooks" path="src/repohooks"/>
307</manifest>
308""")
309 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
310 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
311
Raman Tenneti48b2d102021-01-11 12:18:47 -0800312 def test_unknown_tags(self):
313 """Check superproject settings."""
314 manifest = self.getXmlManifest("""
315<manifest>
316 <remote name="test-remote" fetch="http://localhost" />
317 <default remote="test-remote" revision="refs/heads/main" />
318 <superproject name="superproject"/>
319 <iankaz value="unknown (possible) future tags are ignored"/>
320 <x-custom-tag>X tags are always ignored</x-custom-tag>
321</manifest>
322""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000323 self.assertEqual(manifest.superproject.name, 'superproject')
324 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
Raman Tenneti48b2d102021-01-11 12:18:47 -0800325 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200326 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700327 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200328 '<remote fetch="http://localhost" name="test-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700329 '<default remote="test-remote" revision="refs/heads/main"/>'
330 '<superproject name="superproject"/>'
Raman Tenneti48b2d102021-01-11 12:18:47 -0800331 '</manifest>')
332
Jack Neus6ea0cae2021-07-20 20:52:33 +0000333 def test_remote_annotations(self):
334 """Check remote settings."""
335 manifest = self.getXmlManifest("""
336<manifest>
337 <remote name="test-remote" fetch="http://localhost">
338 <annotation name="foo" value="bar"/>
339 </remote>
340</manifest>
341""")
342 self.assertEqual(manifest.remotes['test-remote'].annotations[0].name, 'foo')
343 self.assertEqual(manifest.remotes['test-remote'].annotations[0].value, 'bar')
344 self.assertEqual(
345 sort_attributes(manifest.ToXml().toxml()),
346 '<?xml version="1.0" ?><manifest>'
347 '<remote fetch="http://localhost" name="test-remote">'
348 '<annotation name="foo" value="bar"/>'
349 '</remote>'
350 '</manifest>')
351
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200352
Mike Frysingera29424e2021-02-25 21:53:49 -0500353class IncludeElementTests(ManifestParseTestCase):
354 """Tests for <include>."""
Raman Tennetib5c5a5e2021-02-06 09:44:15 -0800355
Mike Frysingera29424e2021-02-25 21:53:49 -0500356 def test_group_levels(self):
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200357 root_m = os.path.join(self.manifest_dir, 'root.xml')
358 with open(root_m, 'w') as fp:
359 fp.write("""
360<manifest>
361 <remote name="test-remote" fetch="http://localhost" />
362 <default remote="test-remote" revision="refs/heads/main" />
363 <include name="level1.xml" groups="level1-group" />
364 <project name="root-name1" path="root-path1" />
365 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
366</manifest>
367""")
368 with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
369 fp.write("""
370<manifest>
371 <include name="level2.xml" groups="level2-group" />
372 <project name="level1-name1" path="level1-path1" />
373</manifest>
374""")
375 with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
376 fp.write("""
377<manifest>
378 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
379</manifest>
380""")
381 include_m = manifest_xml.XmlManifest(self.repodir, root_m)
382 for proj in include_m.projects:
383 if proj.name == 'root-name1':
384 # Check include group not set on root level proj.
385 self.assertNotIn('level1-group', proj.groups)
386 if proj.name == 'root-name2':
387 # Check root proj group not removed.
388 self.assertIn('r2g1', proj.groups)
389 if proj.name == 'level1-name1':
390 # Check level1 proj has inherited group level 1.
391 self.assertIn('level1-group', proj.groups)
392 if proj.name == 'level2-name1':
393 # Check level2 proj has inherited group levels 1 and 2.
394 self.assertIn('level1-group', proj.groups)
395 self.assertIn('level2-group', proj.groups)
396 # Check level2 proj group not removed.
397 self.assertIn('l2g1', proj.groups)
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500398
Mike Frysinger54133972021-03-01 21:38:08 -0500399 def test_allow_bad_name_from_user(self):
400 """Check handling of bad name attribute from the user's input."""
Mike Frysingera29424e2021-02-25 21:53:49 -0500401 def parse(name):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400402 name = self.encodeXmlAttr(name)
Mike Frysingera29424e2021-02-25 21:53:49 -0500403 manifest = self.getXmlManifest(f"""
404<manifest>
405 <remote name="default-remote" fetch="http://localhost" />
406 <default remote="default-remote" revision="refs/heads/main" />
407 <include name="{name}" />
408</manifest>
409""")
410 # Force the manifest to be parsed.
411 manifest.ToXml()
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500412
Mike Frysinger54133972021-03-01 21:38:08 -0500413 # Setup target of the include.
414 target = os.path.join(self.tempdir, 'target.xml')
415 with open(target, 'w') as fp:
416 fp.write('<manifest></manifest>')
417
418 # Include with absolute path.
419 parse(os.path.abspath(target))
420
421 # Include with relative path.
422 parse(os.path.relpath(target, self.manifest_dir))
423
424 def test_bad_name_checks(self):
425 """Check handling of bad name attribute."""
426 def parse(name):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400427 name = self.encodeXmlAttr(name)
Mike Frysinger54133972021-03-01 21:38:08 -0500428 # Setup target of the include.
429 with open(os.path.join(self.manifest_dir, 'target.xml'), 'w') as fp:
430 fp.write(f'<manifest><include name="{name}"/></manifest>')
431
432 manifest = self.getXmlManifest("""
433<manifest>
434 <remote name="default-remote" fetch="http://localhost" />
435 <default remote="default-remote" revision="refs/heads/main" />
436 <include name="target.xml" />
437</manifest>
438""")
439 # Force the manifest to be parsed.
440 manifest.ToXml()
441
Mike Frysingera29424e2021-02-25 21:53:49 -0500442 # Handle empty name explicitly because a different codepath rejects it.
443 with self.assertRaises(error.ManifestParseError):
444 parse('')
445
446 for path in INVALID_FS_PATHS:
447 if not path:
448 continue
449
450 with self.assertRaises(error.ManifestInvalidPathError):
451 parse(path)
452
453
454class ProjectElementTests(ManifestParseTestCase):
455 """Tests for <project>."""
456
457 def test_group(self):
458 """Check project group settings."""
459 manifest = self.getXmlManifest("""
460<manifest>
461 <remote name="test-remote" fetch="http://localhost" />
462 <default remote="test-remote" revision="refs/heads/main" />
463 <project name="test-name" path="test-path"/>
464 <project name="extras" path="path" groups="g1,g2,g1"/>
465</manifest>
466""")
467 self.assertEqual(len(manifest.projects), 2)
468 # Ordering isn't guaranteed.
469 result = {
470 manifest.projects[0].name: manifest.projects[0].groups,
471 manifest.projects[1].name: manifest.projects[1].groups,
472 }
473 project = manifest.projects[0]
474 self.assertCountEqual(
475 result['test-name'],
476 ['name:test-name', 'all', 'path:test-path'])
477 self.assertCountEqual(
478 result['extras'],
479 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
Raman Tenneti080877e2021-03-09 15:19:06 -0800480 groupstr = 'default,platform-' + platform.system().lower()
481 self.assertEqual(groupstr, manifest.GetGroupsStr())
482 groupstr = 'g1,g2,g1'
483 manifest.manifestProject.config.SetString('manifest.groups', groupstr)
484 self.assertEqual(groupstr, manifest.GetGroupsStr())
Mike Frysingera29424e2021-02-25 21:53:49 -0500485
486 def test_set_revision_id(self):
487 """Check setting of project's revisionId."""
488 manifest = self.getXmlManifest("""
489<manifest>
490 <remote name="default-remote" fetch="http://localhost" />
491 <default remote="default-remote" revision="refs/heads/main" />
492 <project name="test-name"/>
493</manifest>
494""")
495 self.assertEqual(len(manifest.projects), 1)
496 project = manifest.projects[0]
497 project.SetRevisionId('ABCDEF')
498 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200499 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700500 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200501 '<remote fetch="http://localhost" name="default-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700502 '<default remote="default-remote" revision="refs/heads/main"/>'
Xin Li0e776a52021-06-29 21:42:34 +0000503 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>'
Mike Frysingera29424e2021-02-25 21:53:49 -0500504 '</manifest>')
505
506 def test_trailing_slash(self):
507 """Check handling of trailing slashes in attributes."""
508 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400509 name = self.encodeXmlAttr(name)
510 path = self.encodeXmlAttr(path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500511 return self.getXmlManifest(f"""
512<manifest>
513 <remote name="default-remote" fetch="http://localhost" />
514 <default remote="default-remote" revision="refs/heads/main" />
515 <project name="{name}" path="{path}" />
516</manifest>
517""")
518
519 manifest = parse('a/path/', 'foo')
520 self.assertEqual(manifest.projects[0].gitdir,
521 os.path.join(self.tempdir, '.repo/projects/foo.git'))
522 self.assertEqual(manifest.projects[0].objdir,
523 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
524
525 manifest = parse('a/path', 'foo/')
526 self.assertEqual(manifest.projects[0].gitdir,
527 os.path.join(self.tempdir, '.repo/projects/foo.git'))
528 self.assertEqual(manifest.projects[0].objdir,
529 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
530
Mike Frysinger0458faa2021-03-10 23:35:44 -0500531 manifest = parse('a/path', 'foo//////')
532 self.assertEqual(manifest.projects[0].gitdir,
533 os.path.join(self.tempdir, '.repo/projects/foo.git'))
534 self.assertEqual(manifest.projects[0].objdir,
535 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
536
537 def test_toplevel_path(self):
538 """Check handling of path=. specially."""
539 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400540 name = self.encodeXmlAttr(name)
541 path = self.encodeXmlAttr(path)
Mike Frysinger0458faa2021-03-10 23:35:44 -0500542 return self.getXmlManifest(f"""
543<manifest>
544 <remote name="default-remote" fetch="http://localhost" />
545 <default remote="default-remote" revision="refs/heads/main" />
546 <project name="{name}" path="{path}" />
547</manifest>
548""")
549
550 for path in ('.', './', './/', './//'):
551 manifest = parse('server/path', path)
552 self.assertEqual(manifest.projects[0].gitdir,
553 os.path.join(self.tempdir, '.repo/projects/..git'))
554
Mike Frysingera29424e2021-02-25 21:53:49 -0500555 def test_bad_path_name_checks(self):
556 """Check handling of bad path & name attributes."""
557 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400558 name = self.encodeXmlAttr(name)
559 path = self.encodeXmlAttr(path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500560 manifest = self.getXmlManifest(f"""
561<manifest>
562 <remote name="default-remote" fetch="http://localhost" />
563 <default remote="default-remote" revision="refs/heads/main" />
564 <project name="{name}" path="{path}" />
565</manifest>
566""")
567 # Force the manifest to be parsed.
568 manifest.ToXml()
569
570 # Verify the parser is valid by default to avoid buggy tests below.
571 parse('ok', 'ok')
572
573 # Handle empty name explicitly because a different codepath rejects it.
574 # Empty path is OK because it defaults to the name field.
575 with self.assertRaises(error.ManifestParseError):
576 parse('', 'ok')
577
578 for path in INVALID_FS_PATHS:
579 if not path or path.endswith('/'):
580 continue
581
582 with self.assertRaises(error.ManifestInvalidPathError):
583 parse(path, 'ok')
Mike Frysinger0458faa2021-03-10 23:35:44 -0500584
585 # We have a dedicated test for path=".".
586 if path not in {'.'}:
587 with self.assertRaises(error.ManifestInvalidPathError):
588 parse('ok', path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500589
590
591class SuperProjectElementTests(ManifestParseTestCase):
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500592 """Tests for <superproject>."""
593
594 def test_superproject(self):
595 """Check superproject settings."""
596 manifest = self.getXmlManifest("""
597<manifest>
598 <remote name="test-remote" fetch="http://localhost" />
599 <default remote="test-remote" revision="refs/heads/main" />
600 <superproject name="superproject"/>
601</manifest>
602""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000603 self.assertEqual(manifest.superproject.name, 'superproject')
604 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
605 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
606 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500607 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200608 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700609 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200610 '<remote fetch="http://localhost" name="test-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700611 '<default remote="test-remote" revision="refs/heads/main"/>'
612 '<superproject name="superproject"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500613 '</manifest>')
614
Xin Lie0b16a22021-09-26 23:20:32 -0700615 def test_superproject_revision(self):
616 """Check superproject settings with a different revision attribute"""
617 self.maxDiff = None
618 manifest = self.getXmlManifest("""
619<manifest>
620 <remote name="test-remote" fetch="http://localhost" />
621 <default remote="test-remote" revision="refs/heads/main" />
622 <superproject name="superproject" revision="refs/heads/stable" />
623</manifest>
624""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000625 self.assertEqual(manifest.superproject.name, 'superproject')
626 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
627 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
628 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700629 self.assertEqual(
630 sort_attributes(manifest.ToXml().toxml()),
631 '<?xml version="1.0" ?><manifest>'
632 '<remote fetch="http://localhost" name="test-remote"/>'
633 '<default remote="test-remote" revision="refs/heads/main"/>'
634 '<superproject name="superproject" revision="refs/heads/stable"/>'
635 '</manifest>')
636
637 def test_superproject_revision_default_negative(self):
638 """Check superproject settings with a same revision attribute"""
639 self.maxDiff = None
640 manifest = self.getXmlManifest("""
641<manifest>
642 <remote name="test-remote" fetch="http://localhost" />
643 <default remote="test-remote" revision="refs/heads/stable" />
644 <superproject name="superproject" revision="refs/heads/stable" />
645</manifest>
646""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000647 self.assertEqual(manifest.superproject.name, 'superproject')
648 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
649 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
650 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700651 self.assertEqual(
652 sort_attributes(manifest.ToXml().toxml()),
653 '<?xml version="1.0" ?><manifest>'
654 '<remote fetch="http://localhost" name="test-remote"/>'
655 '<default remote="test-remote" revision="refs/heads/stable"/>'
656 '<superproject name="superproject"/>'
657 '</manifest>')
658
659 def test_superproject_revision_remote(self):
660 """Check superproject settings with a same revision attribute"""
661 self.maxDiff = None
662 manifest = self.getXmlManifest("""
663<manifest>
664 <remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
665 <default remote="test-remote" />
666 <superproject name="superproject" revision="refs/heads/stable" />
667</manifest>
668""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000669 self.assertEqual(manifest.superproject.name, 'superproject')
670 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
671 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
672 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700673 self.assertEqual(
674 sort_attributes(manifest.ToXml().toxml()),
675 '<?xml version="1.0" ?><manifest>'
676 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>'
677 '<default remote="test-remote"/>'
678 '<superproject name="superproject" revision="refs/heads/stable"/>'
679 '</manifest>')
680
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500681 def test_remote(self):
682 """Check superproject settings with a remote."""
683 manifest = self.getXmlManifest("""
684<manifest>
685 <remote name="default-remote" fetch="http://localhost" />
686 <remote name="superproject-remote" fetch="http://localhost" />
687 <default remote="default-remote" revision="refs/heads/main" />
688 <superproject name="platform/superproject" remote="superproject-remote"/>
689</manifest>
690""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000691 self.assertEqual(manifest.superproject.name, 'platform/superproject')
692 self.assertEqual(manifest.superproject.remote.name, 'superproject-remote')
693 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/platform/superproject')
694 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500695 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200696 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700697 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200698 '<remote fetch="http://localhost" name="default-remote"/>'
699 '<remote fetch="http://localhost" name="superproject-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700700 '<default remote="default-remote" revision="refs/heads/main"/>'
701 '<superproject name="platform/superproject" remote="superproject-remote"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500702 '</manifest>')
703
704 def test_defalut_remote(self):
705 """Check superproject settings with a default remote."""
706 manifest = self.getXmlManifest("""
707<manifest>
708 <remote name="default-remote" fetch="http://localhost" />
709 <default remote="default-remote" revision="refs/heads/main" />
710 <superproject name="superproject" remote="default-remote"/>
711</manifest>
712""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000713 self.assertEqual(manifest.superproject.name, 'superproject')
714 self.assertEqual(manifest.superproject.remote.name, 'default-remote')
715 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500716 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200717 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700718 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200719 '<remote fetch="http://localhost" name="default-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700720 '<default remote="default-remote" revision="refs/heads/main"/>'
721 '<superproject name="superproject"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500722 '</manifest>')
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700723
724
725class ContactinfoElementTests(ManifestParseTestCase):
726 """Tests for <contactinfo>."""
727
728 def test_contactinfo(self):
729 """Check contactinfo settings."""
730 bugurl = 'http://localhost/contactinfo'
731 manifest = self.getXmlManifest(f"""
732<manifest>
733 <contactinfo bugurl="{bugurl}"/>
734</manifest>
735""")
Raman Tenneti993af5e2021-05-12 12:00:31 -0700736 self.assertEqual(manifest.contactinfo.bugurl, bugurl)
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700737 self.assertEqual(
738 manifest.ToXml().toxml(),
Raman Tenneti2f8fdbe2021-05-04 18:54:52 -0700739 '<?xml version="1.0" ?><manifest>'
740 f'<contactinfo bugurl="{bugurl}"/>'
741 '</manifest>')
Jack Neus5ba21202021-06-09 15:21:25 +0000742
743
744class DefaultElementTests(ManifestParseTestCase):
745 """Tests for <default>."""
746
747 def test_default(self):
748 """Check default settings."""
749 a = manifest_xml._Default()
750 a.revisionExpr = 'foo'
751 a.remote = manifest_xml._XmlRemote(name='remote')
752 b = manifest_xml._Default()
753 b.revisionExpr = 'bar'
754 self.assertEqual(a, a)
755 self.assertNotEqual(a, b)
756 self.assertNotEqual(b, a.remote)
757 self.assertNotEqual(a, 123)
758 self.assertNotEqual(a, None)
759
760
761class RemoteElementTests(ManifestParseTestCase):
762 """Tests for <remote>."""
763
764 def test_remote(self):
765 """Check remote settings."""
766 a = manifest_xml._XmlRemote(name='foo')
Jack Neus6ea0cae2021-07-20 20:52:33 +0000767 a.AddAnnotation('key1', 'value1', 'true')
768 b = manifest_xml._XmlRemote(name='foo')
769 b.AddAnnotation('key2', 'value1', 'true')
770 c = manifest_xml._XmlRemote(name='foo')
771 c.AddAnnotation('key1', 'value2', 'true')
772 d = manifest_xml._XmlRemote(name='foo')
773 d.AddAnnotation('key1', 'value1', 'false')
Jack Neus5ba21202021-06-09 15:21:25 +0000774 self.assertEqual(a, a)
775 self.assertNotEqual(a, b)
Jack Neus6ea0cae2021-07-20 20:52:33 +0000776 self.assertNotEqual(a, c)
777 self.assertNotEqual(a, d)
Jack Neus5ba21202021-06-09 15:21:25 +0000778 self.assertNotEqual(a, manifest_xml._Default())
779 self.assertNotEqual(a, 123)
780 self.assertNotEqual(a, None)
Michael Kelly06da9982021-06-30 01:58:28 -0700781
782
783class RemoveProjectElementTests(ManifestParseTestCase):
784 """Tests for <remove-project>."""
785
786 def test_remove_one_project(self):
787 manifest = self.getXmlManifest("""
788<manifest>
789 <remote name="default-remote" fetch="http://localhost" />
790 <default remote="default-remote" revision="refs/heads/main" />
791 <project name="myproject" />
792 <remove-project name="myproject" />
793</manifest>
794""")
795 self.assertEqual(manifest.projects, [])
796
797 def test_remove_one_project_one_remains(self):
798 manifest = self.getXmlManifest("""
799<manifest>
800 <remote name="default-remote" fetch="http://localhost" />
801 <default remote="default-remote" revision="refs/heads/main" />
802 <project name="myproject" />
803 <project name="yourproject" />
804 <remove-project name="myproject" />
805</manifest>
806""")
807
808 self.assertEqual(len(manifest.projects), 1)
809 self.assertEqual(manifest.projects[0].name, 'yourproject')
810
811 def test_remove_one_project_doesnt_exist(self):
812 with self.assertRaises(manifest_xml.ManifestParseError):
813 manifest = self.getXmlManifest("""
814<manifest>
815 <remote name="default-remote" fetch="http://localhost" />
816 <default remote="default-remote" revision="refs/heads/main" />
817 <remove-project name="myproject" />
818</manifest>
819""")
820 manifest.projects
821
822 def test_remove_one_optional_project_doesnt_exist(self):
823 manifest = self.getXmlManifest("""
824<manifest>
825 <remote name="default-remote" fetch="http://localhost" />
826 <default remote="default-remote" revision="refs/heads/main" />
827 <remove-project name="myproject" optional="true" />
828</manifest>
829""")
830 self.assertEqual(manifest.projects, [])
Michael Kelly37c21c22020-06-13 02:10:40 -0700831
832
833class ExtendProjectElementTests(ManifestParseTestCase):
834 """Tests for <extend-project>."""
835
836 def test_extend_project_dest_path_single_match(self):
837 manifest = self.getXmlManifest("""
838<manifest>
839 <remote name="default-remote" fetch="http://localhost" />
840 <default remote="default-remote" revision="refs/heads/main" />
841 <project name="myproject" />
842 <extend-project name="myproject" dest-path="bar" />
843</manifest>
844""")
845 self.assertEqual(len(manifest.projects), 1)
846 self.assertEqual(manifest.projects[0].relpath, 'bar')
847
848 def test_extend_project_dest_path_multi_match(self):
849 with self.assertRaises(manifest_xml.ManifestParseError):
850 manifest = self.getXmlManifest("""
851<manifest>
852 <remote name="default-remote" fetch="http://localhost" />
853 <default remote="default-remote" revision="refs/heads/main" />
854 <project name="myproject" path="x" />
855 <project name="myproject" path="y" />
856 <extend-project name="myproject" dest-path="bar" />
857</manifest>
858""")
859 manifest.projects
860
861 def test_extend_project_dest_path_multi_match_path_specified(self):
862 manifest = self.getXmlManifest("""
863<manifest>
864 <remote name="default-remote" fetch="http://localhost" />
865 <default remote="default-remote" revision="refs/heads/main" />
866 <project name="myproject" path="x" />
867 <project name="myproject" path="y" />
868 <extend-project name="myproject" path="x" dest-path="bar" />
869</manifest>
870""")
871 self.assertEqual(len(manifest.projects), 2)
872 if manifest.projects[0].relpath == 'y':
873 self.assertEqual(manifest.projects[1].relpath, 'bar')
874 else:
875 self.assertEqual(manifest.projects[0].relpath, 'bar')
876 self.assertEqual(manifest.projects[1].relpath, 'y')
Erik Elmeke4cdfdb72022-09-09 17:13:17 +0200877
878 def test_extend_project_dest_branch(self):
879 manifest = self.getXmlManifest("""
880<manifest>
881 <remote name="default-remote" fetch="http://localhost" />
882 <default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
883 <project name="myproject" />
884 <extend-project name="myproject" dest-branch="bar" />
885</manifest>
886""")
887 self.assertEqual(len(manifest.projects), 1)
888 self.assertEqual(manifest.projects[0].dest_branch, 'bar')
889
890 def test_extend_project_upstream(self):
891 manifest = self.getXmlManifest("""
892<manifest>
893 <remote name="default-remote" fetch="http://localhost" />
894 <default remote="default-remote" revision="refs/heads/main" />
895 <project name="myproject" />
896 <extend-project name="myproject" upstream="bar" />
897</manifest>
898""")
899 self.assertEqual(len(manifest.projects), 1)
900 self.assertEqual(manifest.projects[0].upstream, 'bar')