blob: 85c20733ca8bc41c7a3b2395bbb6980c13888bf3 [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
Mike Frysinger51e39d52020-12-04 05:32:06 -0500255 def test_repo_hooks(self):
256 """Check repo-hooks settings."""
257 manifest = self.getXmlManifest("""
258<manifest>
259 <remote name="test-remote" fetch="http://localhost" />
260 <default remote="test-remote" revision="refs/heads/main" />
261 <project name="repohooks" path="src/repohooks"/>
262 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
263</manifest>
264""")
265 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
266 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
267
Jack Neusa84f43a2021-09-21 22:23:55 +0000268 def test_repo_hooks_unordered(self):
269 """Check repo-hooks settings work even if the project def comes second."""
270 manifest = self.getXmlManifest("""
271<manifest>
272 <remote name="test-remote" fetch="http://localhost" />
273 <default remote="test-remote" revision="refs/heads/main" />
274 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
275 <project name="repohooks" path="src/repohooks"/>
276</manifest>
277""")
278 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
279 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
280
Raman Tenneti48b2d102021-01-11 12:18:47 -0800281 def test_unknown_tags(self):
282 """Check superproject settings."""
283 manifest = self.getXmlManifest("""
284<manifest>
285 <remote name="test-remote" fetch="http://localhost" />
286 <default remote="test-remote" revision="refs/heads/main" />
287 <superproject name="superproject"/>
288 <iankaz value="unknown (possible) future tags are ignored"/>
289 <x-custom-tag>X tags are always ignored</x-custom-tag>
290</manifest>
291""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000292 self.assertEqual(manifest.superproject.name, 'superproject')
293 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
Raman Tenneti48b2d102021-01-11 12:18:47 -0800294 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200295 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700296 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200297 '<remote fetch="http://localhost" name="test-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700298 '<default remote="test-remote" revision="refs/heads/main"/>'
299 '<superproject name="superproject"/>'
Raman Tenneti48b2d102021-01-11 12:18:47 -0800300 '</manifest>')
301
Jack Neus6ea0cae2021-07-20 20:52:33 +0000302 def test_remote_annotations(self):
303 """Check remote settings."""
304 manifest = self.getXmlManifest("""
305<manifest>
306 <remote name="test-remote" fetch="http://localhost">
307 <annotation name="foo" value="bar"/>
308 </remote>
309</manifest>
310""")
311 self.assertEqual(manifest.remotes['test-remote'].annotations[0].name, 'foo')
312 self.assertEqual(manifest.remotes['test-remote'].annotations[0].value, 'bar')
313 self.assertEqual(
314 sort_attributes(manifest.ToXml().toxml()),
315 '<?xml version="1.0" ?><manifest>'
316 '<remote fetch="http://localhost" name="test-remote">'
317 '<annotation name="foo" value="bar"/>'
318 '</remote>'
319 '</manifest>')
320
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200321
Mike Frysingera29424e2021-02-25 21:53:49 -0500322class IncludeElementTests(ManifestParseTestCase):
323 """Tests for <include>."""
Raman Tennetib5c5a5e2021-02-06 09:44:15 -0800324
Mike Frysingera29424e2021-02-25 21:53:49 -0500325 def test_group_levels(self):
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200326 root_m = os.path.join(self.manifest_dir, 'root.xml')
327 with open(root_m, 'w') as fp:
328 fp.write("""
329<manifest>
330 <remote name="test-remote" fetch="http://localhost" />
331 <default remote="test-remote" revision="refs/heads/main" />
332 <include name="level1.xml" groups="level1-group" />
333 <project name="root-name1" path="root-path1" />
334 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
335</manifest>
336""")
337 with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
338 fp.write("""
339<manifest>
340 <include name="level2.xml" groups="level2-group" />
341 <project name="level1-name1" path="level1-path1" />
342</manifest>
343""")
344 with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
345 fp.write("""
346<manifest>
347 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
348</manifest>
349""")
350 include_m = manifest_xml.XmlManifest(self.repodir, root_m)
351 for proj in include_m.projects:
352 if proj.name == 'root-name1':
353 # Check include group not set on root level proj.
354 self.assertNotIn('level1-group', proj.groups)
355 if proj.name == 'root-name2':
356 # Check root proj group not removed.
357 self.assertIn('r2g1', proj.groups)
358 if proj.name == 'level1-name1':
359 # Check level1 proj has inherited group level 1.
360 self.assertIn('level1-group', proj.groups)
361 if proj.name == 'level2-name1':
362 # Check level2 proj has inherited group levels 1 and 2.
363 self.assertIn('level1-group', proj.groups)
364 self.assertIn('level2-group', proj.groups)
365 # Check level2 proj group not removed.
366 self.assertIn('l2g1', proj.groups)
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500367
Mike Frysinger54133972021-03-01 21:38:08 -0500368 def test_allow_bad_name_from_user(self):
369 """Check handling of bad name attribute from the user's input."""
Mike Frysingera29424e2021-02-25 21:53:49 -0500370 def parse(name):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400371 name = self.encodeXmlAttr(name)
Mike Frysingera29424e2021-02-25 21:53:49 -0500372 manifest = self.getXmlManifest(f"""
373<manifest>
374 <remote name="default-remote" fetch="http://localhost" />
375 <default remote="default-remote" revision="refs/heads/main" />
376 <include name="{name}" />
377</manifest>
378""")
379 # Force the manifest to be parsed.
380 manifest.ToXml()
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500381
Mike Frysinger54133972021-03-01 21:38:08 -0500382 # Setup target of the include.
383 target = os.path.join(self.tempdir, 'target.xml')
384 with open(target, 'w') as fp:
385 fp.write('<manifest></manifest>')
386
387 # Include with absolute path.
388 parse(os.path.abspath(target))
389
390 # Include with relative path.
391 parse(os.path.relpath(target, self.manifest_dir))
392
393 def test_bad_name_checks(self):
394 """Check handling of bad name attribute."""
395 def parse(name):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400396 name = self.encodeXmlAttr(name)
Mike Frysinger54133972021-03-01 21:38:08 -0500397 # Setup target of the include.
398 with open(os.path.join(self.manifest_dir, 'target.xml'), 'w') as fp:
399 fp.write(f'<manifest><include name="{name}"/></manifest>')
400
401 manifest = self.getXmlManifest("""
402<manifest>
403 <remote name="default-remote" fetch="http://localhost" />
404 <default remote="default-remote" revision="refs/heads/main" />
405 <include name="target.xml" />
406</manifest>
407""")
408 # Force the manifest to be parsed.
409 manifest.ToXml()
410
Mike Frysingera29424e2021-02-25 21:53:49 -0500411 # Handle empty name explicitly because a different codepath rejects it.
412 with self.assertRaises(error.ManifestParseError):
413 parse('')
414
415 for path in INVALID_FS_PATHS:
416 if not path:
417 continue
418
419 with self.assertRaises(error.ManifestInvalidPathError):
420 parse(path)
421
422
423class ProjectElementTests(ManifestParseTestCase):
424 """Tests for <project>."""
425
426 def test_group(self):
427 """Check project group settings."""
428 manifest = self.getXmlManifest("""
429<manifest>
430 <remote name="test-remote" fetch="http://localhost" />
431 <default remote="test-remote" revision="refs/heads/main" />
432 <project name="test-name" path="test-path"/>
433 <project name="extras" path="path" groups="g1,g2,g1"/>
434</manifest>
435""")
436 self.assertEqual(len(manifest.projects), 2)
437 # Ordering isn't guaranteed.
438 result = {
439 manifest.projects[0].name: manifest.projects[0].groups,
440 manifest.projects[1].name: manifest.projects[1].groups,
441 }
442 project = manifest.projects[0]
443 self.assertCountEqual(
444 result['test-name'],
445 ['name:test-name', 'all', 'path:test-path'])
446 self.assertCountEqual(
447 result['extras'],
448 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
Raman Tenneti080877e2021-03-09 15:19:06 -0800449 groupstr = 'default,platform-' + platform.system().lower()
450 self.assertEqual(groupstr, manifest.GetGroupsStr())
451 groupstr = 'g1,g2,g1'
452 manifest.manifestProject.config.SetString('manifest.groups', groupstr)
453 self.assertEqual(groupstr, manifest.GetGroupsStr())
Mike Frysingera29424e2021-02-25 21:53:49 -0500454
455 def test_set_revision_id(self):
456 """Check setting of project's revisionId."""
457 manifest = self.getXmlManifest("""
458<manifest>
459 <remote name="default-remote" fetch="http://localhost" />
460 <default remote="default-remote" revision="refs/heads/main" />
461 <project name="test-name"/>
462</manifest>
463""")
464 self.assertEqual(len(manifest.projects), 1)
465 project = manifest.projects[0]
466 project.SetRevisionId('ABCDEF')
467 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200468 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700469 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200470 '<remote fetch="http://localhost" name="default-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700471 '<default remote="default-remote" revision="refs/heads/main"/>'
Xin Li0e776a52021-06-29 21:42:34 +0000472 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>'
Mike Frysingera29424e2021-02-25 21:53:49 -0500473 '</manifest>')
474
475 def test_trailing_slash(self):
476 """Check handling of trailing slashes in attributes."""
477 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400478 name = self.encodeXmlAttr(name)
479 path = self.encodeXmlAttr(path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500480 return self.getXmlManifest(f"""
481<manifest>
482 <remote name="default-remote" fetch="http://localhost" />
483 <default remote="default-remote" revision="refs/heads/main" />
484 <project name="{name}" path="{path}" />
485</manifest>
486""")
487
488 manifest = parse('a/path/', 'foo')
489 self.assertEqual(manifest.projects[0].gitdir,
490 os.path.join(self.tempdir, '.repo/projects/foo.git'))
491 self.assertEqual(manifest.projects[0].objdir,
492 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
493
494 manifest = parse('a/path', 'foo/')
495 self.assertEqual(manifest.projects[0].gitdir,
496 os.path.join(self.tempdir, '.repo/projects/foo.git'))
497 self.assertEqual(manifest.projects[0].objdir,
498 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
499
Mike Frysinger0458faa2021-03-10 23:35:44 -0500500 manifest = parse('a/path', 'foo//////')
501 self.assertEqual(manifest.projects[0].gitdir,
502 os.path.join(self.tempdir, '.repo/projects/foo.git'))
503 self.assertEqual(manifest.projects[0].objdir,
504 os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
505
506 def test_toplevel_path(self):
507 """Check handling of path=. specially."""
508 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400509 name = self.encodeXmlAttr(name)
510 path = self.encodeXmlAttr(path)
Mike Frysinger0458faa2021-03-10 23:35:44 -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 for path in ('.', './', './/', './//'):
520 manifest = parse('server/path', path)
521 self.assertEqual(manifest.projects[0].gitdir,
522 os.path.join(self.tempdir, '.repo/projects/..git'))
523
Mike Frysingera29424e2021-02-25 21:53:49 -0500524 def test_bad_path_name_checks(self):
525 """Check handling of bad path & name attributes."""
526 def parse(name, path):
Mike Frysingerf69c7ee2021-04-29 23:15:31 -0400527 name = self.encodeXmlAttr(name)
528 path = self.encodeXmlAttr(path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500529 manifest = self.getXmlManifest(f"""
530<manifest>
531 <remote name="default-remote" fetch="http://localhost" />
532 <default remote="default-remote" revision="refs/heads/main" />
533 <project name="{name}" path="{path}" />
534</manifest>
535""")
536 # Force the manifest to be parsed.
537 manifest.ToXml()
538
539 # Verify the parser is valid by default to avoid buggy tests below.
540 parse('ok', 'ok')
541
542 # Handle empty name explicitly because a different codepath rejects it.
543 # Empty path is OK because it defaults to the name field.
544 with self.assertRaises(error.ManifestParseError):
545 parse('', 'ok')
546
547 for path in INVALID_FS_PATHS:
548 if not path or path.endswith('/'):
549 continue
550
551 with self.assertRaises(error.ManifestInvalidPathError):
552 parse(path, 'ok')
Mike Frysinger0458faa2021-03-10 23:35:44 -0500553
554 # We have a dedicated test for path=".".
555 if path not in {'.'}:
556 with self.assertRaises(error.ManifestInvalidPathError):
557 parse('ok', path)
Mike Frysingera29424e2021-02-25 21:53:49 -0500558
559
560class SuperProjectElementTests(ManifestParseTestCase):
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500561 """Tests for <superproject>."""
562
563 def test_superproject(self):
564 """Check superproject settings."""
565 manifest = self.getXmlManifest("""
566<manifest>
567 <remote name="test-remote" fetch="http://localhost" />
568 <default remote="test-remote" revision="refs/heads/main" />
569 <superproject name="superproject"/>
570</manifest>
571""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000572 self.assertEqual(manifest.superproject.name, 'superproject')
573 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
574 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
575 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500576 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200577 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700578 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200579 '<remote fetch="http://localhost" name="test-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700580 '<default remote="test-remote" revision="refs/heads/main"/>'
581 '<superproject name="superproject"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500582 '</manifest>')
583
Xin Lie0b16a22021-09-26 23:20:32 -0700584 def test_superproject_revision(self):
585 """Check superproject settings with a different revision attribute"""
586 self.maxDiff = None
587 manifest = self.getXmlManifest("""
588<manifest>
589 <remote name="test-remote" fetch="http://localhost" />
590 <default remote="test-remote" revision="refs/heads/main" />
591 <superproject name="superproject" revision="refs/heads/stable" />
592</manifest>
593""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000594 self.assertEqual(manifest.superproject.name, 'superproject')
595 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
596 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
597 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700598 self.assertEqual(
599 sort_attributes(manifest.ToXml().toxml()),
600 '<?xml version="1.0" ?><manifest>'
601 '<remote fetch="http://localhost" name="test-remote"/>'
602 '<default remote="test-remote" revision="refs/heads/main"/>'
603 '<superproject name="superproject" revision="refs/heads/stable"/>'
604 '</manifest>')
605
606 def test_superproject_revision_default_negative(self):
607 """Check superproject settings with a same revision attribute"""
608 self.maxDiff = None
609 manifest = self.getXmlManifest("""
610<manifest>
611 <remote name="test-remote" fetch="http://localhost" />
612 <default remote="test-remote" revision="refs/heads/stable" />
613 <superproject name="superproject" revision="refs/heads/stable" />
614</manifest>
615""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000616 self.assertEqual(manifest.superproject.name, 'superproject')
617 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
618 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
619 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700620 self.assertEqual(
621 sort_attributes(manifest.ToXml().toxml()),
622 '<?xml version="1.0" ?><manifest>'
623 '<remote fetch="http://localhost" name="test-remote"/>'
624 '<default remote="test-remote" revision="refs/heads/stable"/>'
625 '<superproject name="superproject"/>'
626 '</manifest>')
627
628 def test_superproject_revision_remote(self):
629 """Check superproject settings with a same revision attribute"""
630 self.maxDiff = None
631 manifest = self.getXmlManifest("""
632<manifest>
633 <remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
634 <default remote="test-remote" />
635 <superproject name="superproject" revision="refs/heads/stable" />
636</manifest>
637""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000638 self.assertEqual(manifest.superproject.name, 'superproject')
639 self.assertEqual(manifest.superproject.remote.name, 'test-remote')
640 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/superproject')
641 self.assertEqual(manifest.superproject.revision, 'refs/heads/stable')
Xin Lie0b16a22021-09-26 23:20:32 -0700642 self.assertEqual(
643 sort_attributes(manifest.ToXml().toxml()),
644 '<?xml version="1.0" ?><manifest>'
645 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>'
646 '<default remote="test-remote"/>'
647 '<superproject name="superproject" revision="refs/heads/stable"/>'
648 '</manifest>')
649
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500650 def test_remote(self):
651 """Check superproject settings with a remote."""
652 manifest = self.getXmlManifest("""
653<manifest>
654 <remote name="default-remote" fetch="http://localhost" />
655 <remote name="superproject-remote" fetch="http://localhost" />
656 <default remote="default-remote" revision="refs/heads/main" />
657 <superproject name="platform/superproject" remote="superproject-remote"/>
658</manifest>
659""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000660 self.assertEqual(manifest.superproject.name, 'platform/superproject')
661 self.assertEqual(manifest.superproject.remote.name, 'superproject-remote')
662 self.assertEqual(manifest.superproject.remote.url, 'http://localhost/platform/superproject')
663 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500664 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200665 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700666 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200667 '<remote fetch="http://localhost" name="default-remote"/>'
668 '<remote fetch="http://localhost" name="superproject-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700669 '<default remote="default-remote" revision="refs/heads/main"/>'
670 '<superproject name="platform/superproject" remote="superproject-remote"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500671 '</manifest>')
672
673 def test_defalut_remote(self):
674 """Check superproject settings with a default remote."""
675 manifest = self.getXmlManifest("""
676<manifest>
677 <remote name="default-remote" fetch="http://localhost" />
678 <default remote="default-remote" revision="refs/heads/main" />
679 <superproject name="superproject" remote="default-remote"/>
680</manifest>
681""")
LaMont Jonesd56e2eb2022-04-07 18:14:46 +0000682 self.assertEqual(manifest.superproject.name, 'superproject')
683 self.assertEqual(manifest.superproject.remote.name, 'default-remote')
684 self.assertEqual(manifest.superproject.revision, 'refs/heads/main')
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500685 self.assertEqual(
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200686 sort_attributes(manifest.ToXml().toxml()),
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700687 '<?xml version="1.0" ?><manifest>'
Peter Kjellerstedt5d58c182021-04-12 21:16:36 +0200688 '<remote fetch="http://localhost" name="default-remote"/>'
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700689 '<default remote="default-remote" revision="refs/heads/main"/>'
690 '<superproject name="superproject"/>'
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500691 '</manifest>')
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700692
693
694class ContactinfoElementTests(ManifestParseTestCase):
695 """Tests for <contactinfo>."""
696
697 def test_contactinfo(self):
698 """Check contactinfo settings."""
699 bugurl = 'http://localhost/contactinfo'
700 manifest = self.getXmlManifest(f"""
701<manifest>
702 <contactinfo bugurl="{bugurl}"/>
703</manifest>
704""")
Raman Tenneti993af5e2021-05-12 12:00:31 -0700705 self.assertEqual(manifest.contactinfo.bugurl, bugurl)
Raman Tenneti1c3f57e2021-05-04 12:32:13 -0700706 self.assertEqual(
707 manifest.ToXml().toxml(),
Raman Tenneti2f8fdbe2021-05-04 18:54:52 -0700708 '<?xml version="1.0" ?><manifest>'
709 f'<contactinfo bugurl="{bugurl}"/>'
710 '</manifest>')
Jack Neus5ba21202021-06-09 15:21:25 +0000711
712
713class DefaultElementTests(ManifestParseTestCase):
714 """Tests for <default>."""
715
716 def test_default(self):
717 """Check default settings."""
718 a = manifest_xml._Default()
719 a.revisionExpr = 'foo'
720 a.remote = manifest_xml._XmlRemote(name='remote')
721 b = manifest_xml._Default()
722 b.revisionExpr = 'bar'
723 self.assertEqual(a, a)
724 self.assertNotEqual(a, b)
725 self.assertNotEqual(b, a.remote)
726 self.assertNotEqual(a, 123)
727 self.assertNotEqual(a, None)
728
729
730class RemoteElementTests(ManifestParseTestCase):
731 """Tests for <remote>."""
732
733 def test_remote(self):
734 """Check remote settings."""
735 a = manifest_xml._XmlRemote(name='foo')
Jack Neus6ea0cae2021-07-20 20:52:33 +0000736 a.AddAnnotation('key1', 'value1', 'true')
737 b = manifest_xml._XmlRemote(name='foo')
738 b.AddAnnotation('key2', 'value1', 'true')
739 c = manifest_xml._XmlRemote(name='foo')
740 c.AddAnnotation('key1', 'value2', 'true')
741 d = manifest_xml._XmlRemote(name='foo')
742 d.AddAnnotation('key1', 'value1', 'false')
Jack Neus5ba21202021-06-09 15:21:25 +0000743 self.assertEqual(a, a)
744 self.assertNotEqual(a, b)
Jack Neus6ea0cae2021-07-20 20:52:33 +0000745 self.assertNotEqual(a, c)
746 self.assertNotEqual(a, d)
Jack Neus5ba21202021-06-09 15:21:25 +0000747 self.assertNotEqual(a, manifest_xml._Default())
748 self.assertNotEqual(a, 123)
749 self.assertNotEqual(a, None)
Michael Kelly06da9982021-06-30 01:58:28 -0700750
751
752class RemoveProjectElementTests(ManifestParseTestCase):
753 """Tests for <remove-project>."""
754
755 def test_remove_one_project(self):
756 manifest = self.getXmlManifest("""
757<manifest>
758 <remote name="default-remote" fetch="http://localhost" />
759 <default remote="default-remote" revision="refs/heads/main" />
760 <project name="myproject" />
761 <remove-project name="myproject" />
762</manifest>
763""")
764 self.assertEqual(manifest.projects, [])
765
766 def test_remove_one_project_one_remains(self):
767 manifest = self.getXmlManifest("""
768<manifest>
769 <remote name="default-remote" fetch="http://localhost" />
770 <default remote="default-remote" revision="refs/heads/main" />
771 <project name="myproject" />
772 <project name="yourproject" />
773 <remove-project name="myproject" />
774</manifest>
775""")
776
777 self.assertEqual(len(manifest.projects), 1)
778 self.assertEqual(manifest.projects[0].name, 'yourproject')
779
780 def test_remove_one_project_doesnt_exist(self):
781 with self.assertRaises(manifest_xml.ManifestParseError):
782 manifest = self.getXmlManifest("""
783<manifest>
784 <remote name="default-remote" fetch="http://localhost" />
785 <default remote="default-remote" revision="refs/heads/main" />
786 <remove-project name="myproject" />
787</manifest>
788""")
789 manifest.projects
790
791 def test_remove_one_optional_project_doesnt_exist(self):
792 manifest = self.getXmlManifest("""
793<manifest>
794 <remote name="default-remote" fetch="http://localhost" />
795 <default remote="default-remote" revision="refs/heads/main" />
796 <remove-project name="myproject" optional="true" />
797</manifest>
798""")
799 self.assertEqual(manifest.projects, [])
Michael Kelly37c21c22020-06-13 02:10:40 -0700800
801
802class ExtendProjectElementTests(ManifestParseTestCase):
803 """Tests for <extend-project>."""
804
805 def test_extend_project_dest_path_single_match(self):
806 manifest = self.getXmlManifest("""
807<manifest>
808 <remote name="default-remote" fetch="http://localhost" />
809 <default remote="default-remote" revision="refs/heads/main" />
810 <project name="myproject" />
811 <extend-project name="myproject" dest-path="bar" />
812</manifest>
813""")
814 self.assertEqual(len(manifest.projects), 1)
815 self.assertEqual(manifest.projects[0].relpath, 'bar')
816
817 def test_extend_project_dest_path_multi_match(self):
818 with self.assertRaises(manifest_xml.ManifestParseError):
819 manifest = self.getXmlManifest("""
820<manifest>
821 <remote name="default-remote" fetch="http://localhost" />
822 <default remote="default-remote" revision="refs/heads/main" />
823 <project name="myproject" path="x" />
824 <project name="myproject" path="y" />
825 <extend-project name="myproject" dest-path="bar" />
826</manifest>
827""")
828 manifest.projects
829
830 def test_extend_project_dest_path_multi_match_path_specified(self):
831 manifest = self.getXmlManifest("""
832<manifest>
833 <remote name="default-remote" fetch="http://localhost" />
834 <default remote="default-remote" revision="refs/heads/main" />
835 <project name="myproject" path="x" />
836 <project name="myproject" path="y" />
837 <extend-project name="myproject" path="x" dest-path="bar" />
838</manifest>
839""")
840 self.assertEqual(len(manifest.projects), 2)
841 if manifest.projects[0].relpath == 'y':
842 self.assertEqual(manifest.projects[1].relpath, 'bar')
843 else:
844 self.assertEqual(manifest.projects[0].relpath, 'bar')
845 self.assertEqual(manifest.projects[1].relpath, 'y')