blob: 40388f93cb1b2c7bc16a698231bf8786b404f910 [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
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -040018import shutil
19import tempfile
Mike Frysinger04122b72019-07-31 23:32:58 -040020import unittest
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -050021import xml.dom.minidom
Mike Frysinger04122b72019-07-31 23:32:58 -040022
23import error
24import manifest_xml
25
26
Mike Frysinger37ac3d62021-02-25 04:54:56 -050027class ManifestParseTestCase(unittest.TestCase):
28 """TestCase for parsing manifests."""
29
30 def setUp(self):
31 self.tempdir = tempfile.mkdtemp(prefix='repo_tests')
32 self.repodir = os.path.join(self.tempdir, '.repo')
33 self.manifest_dir = os.path.join(self.repodir, 'manifests')
34 self.manifest_file = os.path.join(
35 self.repodir, manifest_xml.MANIFEST_FILE_NAME)
36 self.local_manifest_dir = os.path.join(
37 self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME)
38 os.mkdir(self.repodir)
39 os.mkdir(self.manifest_dir)
40
41 # The manifest parsing really wants a git repo currently.
42 gitdir = os.path.join(self.repodir, 'manifests.git')
43 os.mkdir(gitdir)
44 with open(os.path.join(gitdir, 'config'), 'w') as fp:
45 fp.write("""[remote "origin"]
46 url = https://localhost:0/manifest
47""")
48
49 def tearDown(self):
50 shutil.rmtree(self.tempdir, ignore_errors=True)
51
52 def getXmlManifest(self, data):
53 """Helper to initialize a manifest for testing."""
54 with open(self.manifest_file, 'w') as fp:
55 fp.write(data)
56 return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
57
58
Mike Frysinger04122b72019-07-31 23:32:58 -040059class ManifestValidateFilePaths(unittest.TestCase):
60 """Check _ValidateFilePaths helper.
61
62 This doesn't access a real filesystem.
63 """
64
65 def check_both(self, *args):
66 manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args)
67 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
68
69 def test_normal_path(self):
70 """Make sure good paths are accepted."""
71 self.check_both('foo', 'bar')
72 self.check_both('foo/bar', 'bar')
73 self.check_both('foo', 'bar/bar')
74 self.check_both('foo/bar', 'bar/bar')
75
76 def test_symlink_targets(self):
77 """Some extra checks for symlinks."""
78 def check(*args):
79 manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
80
81 # We allow symlinks to end in a slash since we allow them to point to dirs
82 # in general. Technically the slash isn't necessary.
83 check('foo/', 'bar')
Mike Frysingerae625412020-02-10 17:10:03 -050084 # We allow a single '.' to get a reference to the project itself.
85 check('.', 'bar')
Mike Frysinger04122b72019-07-31 23:32:58 -040086
87 def test_bad_paths(self):
88 """Make sure bad paths (src & dest) are rejected."""
89 PATHS = (
90 '..',
91 '../',
92 './',
93 'foo/',
94 './foo',
95 '../foo',
96 'foo/./bar',
97 'foo/../../bar',
98 '/foo',
99 './../foo',
100 '.git/foo',
101 # Check case folding.
102 '.GIT/foo',
103 'blah/.git/foo',
104 '.repo/foo',
105 '.repoconfig',
106 # Block ~ due to 8.3 filenames on Windows filesystems.
107 '~',
108 'foo~',
109 'blah/foo~',
110 # Block Unicode characters that get normalized out by filesystems.
111 u'foo\u200Cbar',
112 )
Mike Frysingerd9254592020-02-19 22:36:26 -0500113 # Make sure platforms that use path separators (e.g. Windows) are also
114 # rejected properly.
115 if os.path.sep != '/':
116 PATHS += tuple(x.replace('/', os.path.sep) for x in PATHS)
117
Mike Frysinger04122b72019-07-31 23:32:58 -0400118 for path in PATHS:
119 self.assertRaises(
120 error.ManifestInvalidPathError, self.check_both, path, 'a')
121 self.assertRaises(
122 error.ManifestInvalidPathError, self.check_both, 'a', path)
Mike Frysingerbb8ee7f2020-02-22 05:30:12 -0500123
124
125class ValueTests(unittest.TestCase):
126 """Check utility parsing code."""
127
128 def _get_node(self, text):
129 return xml.dom.minidom.parseString(text).firstChild
130
131 def test_bool_default(self):
132 """Check XmlBool default handling."""
133 node = self._get_node('<node/>')
134 self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
135 self.assertIsNone(manifest_xml.XmlBool(node, 'a', None))
136 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
137
138 node = self._get_node('<node a=""/>')
139 self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
140
141 def test_bool_invalid(self):
142 """Check XmlBool invalid handling."""
143 node = self._get_node('<node a="moo"/>')
144 self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
145
146 def test_bool_true(self):
147 """Check XmlBool true values."""
148 for value in ('yes', 'true', '1'):
149 node = self._get_node('<node a="%s"/>' % (value,))
150 self.assertTrue(manifest_xml.XmlBool(node, 'a'))
151
152 def test_bool_false(self):
153 """Check XmlBool false values."""
154 for value in ('no', 'false', '0'):
155 node = self._get_node('<node a="%s"/>' % (value,))
156 self.assertFalse(manifest_xml.XmlBool(node, 'a'))
157
158 def test_int_default(self):
159 """Check XmlInt default handling."""
160 node = self._get_node('<node/>')
161 self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
162 self.assertIsNone(manifest_xml.XmlInt(node, 'a', None))
163 self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123))
164
165 node = self._get_node('<node a=""/>')
166 self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
167
168 def test_int_good(self):
169 """Check XmlInt numeric handling."""
170 for value in (-1, 0, 1, 50000):
171 node = self._get_node('<node a="%s"/>' % (value,))
172 self.assertEqual(value, manifest_xml.XmlInt(node, 'a'))
173
174 def test_int_invalid(self):
175 """Check XmlInt invalid handling."""
176 with self.assertRaises(error.ManifestParseError):
177 node = self._get_node('<node a="xx"/>')
178 manifest_xml.XmlInt(node, 'a')
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400179
180
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500181class XmlManifestTests(ManifestParseTestCase):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400182 """Check manifest processing."""
183
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400184 def test_empty(self):
185 """Parse an 'empty' manifest file."""
186 manifest = self.getXmlManifest(
187 '<?xml version="1.0" encoding="UTF-8"?>'
188 '<manifest></manifest>')
189 self.assertEqual(manifest.remotes, {})
190 self.assertEqual(manifest.projects, [])
191
192 def test_link(self):
193 """Verify Link handling with new names."""
194 manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
195 with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp:
196 fp.write('<manifest></manifest>')
197 manifest.Link('foo.xml')
198 with open(self.manifest_file) as fp:
199 self.assertIn('<include name="foo.xml" />', fp.read())
200
201 def test_toxml_empty(self):
202 """Verify the ToXml() helper."""
203 manifest = self.getXmlManifest(
204 '<?xml version="1.0" encoding="UTF-8"?>'
205 '<manifest></manifest>')
206 self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>')
207
208 def test_todict_empty(self):
209 """Verify the ToDict() helper."""
210 manifest = self.getXmlManifest(
211 '<?xml version="1.0" encoding="UTF-8"?>'
212 '<manifest></manifest>')
213 self.assertEqual(manifest.ToDict(), {})
214
Mike Frysinger51e39d52020-12-04 05:32:06 -0500215 def test_repo_hooks(self):
216 """Check repo-hooks settings."""
217 manifest = self.getXmlManifest("""
218<manifest>
219 <remote name="test-remote" fetch="http://localhost" />
220 <default remote="test-remote" revision="refs/heads/main" />
221 <project name="repohooks" path="src/repohooks"/>
222 <repo-hooks in-project="repohooks" enabled-list="a, b"/>
223</manifest>
224""")
225 self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
226 self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
227
Raman Tenneti48b2d102021-01-11 12:18:47 -0800228 def test_unknown_tags(self):
229 """Check superproject settings."""
230 manifest = self.getXmlManifest("""
231<manifest>
232 <remote name="test-remote" fetch="http://localhost" />
233 <default remote="test-remote" revision="refs/heads/main" />
234 <superproject name="superproject"/>
235 <iankaz value="unknown (possible) future tags are ignored"/>
236 <x-custom-tag>X tags are always ignored</x-custom-tag>
237</manifest>
238""")
239 self.assertEqual(manifest.superproject['name'], 'superproject')
240 self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
241 self.assertEqual(
242 manifest.ToXml().toxml(),
243 '<?xml version="1.0" ?><manifest>' +
244 '<remote name="test-remote" fetch="http://localhost"/>' +
245 '<default remote="test-remote" revision="refs/heads/main"/>' +
246 '<superproject name="superproject"/>' +
247 '</manifest>')
248
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400249 def test_project_group(self):
250 """Check project group settings."""
251 manifest = self.getXmlManifest("""
252<manifest>
253 <remote name="test-remote" fetch="http://localhost" />
254 <default remote="test-remote" revision="refs/heads/main" />
255 <project name="test-name" path="test-path"/>
256 <project name="extras" path="path" groups="g1,g2,g1"/>
257</manifest>
258""")
259 self.assertEqual(len(manifest.projects), 2)
260 # Ordering isn't guaranteed.
261 result = {
262 manifest.projects[0].name: manifest.projects[0].groups,
263 manifest.projects[1].name: manifest.projects[1].groups,
264 }
265 project = manifest.projects[0]
266 self.assertCountEqual(
267 result['test-name'],
268 ['name:test-name', 'all', 'path:test-path'])
269 self.assertCountEqual(
270 result['extras'],
271 ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200272
Raman Tennetib5c5a5e2021-02-06 09:44:15 -0800273 def test_project_set_revision_id(self):
274 """Check setting of project's revisionId."""
275 manifest = self.getXmlManifest("""
276<manifest>
277 <remote name="default-remote" fetch="http://localhost" />
278 <default remote="default-remote" revision="refs/heads/main" />
279 <project name="test-name"/>
280</manifest>
281""")
282 self.assertEqual(len(manifest.projects), 1)
283 project = manifest.projects[0]
284 project.SetRevisionId('ABCDEF')
285 self.assertEqual(
286 manifest.ToXml().toxml(),
287 '<?xml version="1.0" ?><manifest>' +
288 '<remote name="default-remote" fetch="http://localhost"/>' +
289 '<default remote="default-remote" revision="refs/heads/main"/>' +
290 '<project name="test-name" revision="ABCDEF"/>' +
291 '</manifest>')
292
Fredrik de Groot352c93b2020-10-06 12:55:14 +0200293 def test_include_levels(self):
294 root_m = os.path.join(self.manifest_dir, 'root.xml')
295 with open(root_m, 'w') as fp:
296 fp.write("""
297<manifest>
298 <remote name="test-remote" fetch="http://localhost" />
299 <default remote="test-remote" revision="refs/heads/main" />
300 <include name="level1.xml" groups="level1-group" />
301 <project name="root-name1" path="root-path1" />
302 <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
303</manifest>
304""")
305 with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
306 fp.write("""
307<manifest>
308 <include name="level2.xml" groups="level2-group" />
309 <project name="level1-name1" path="level1-path1" />
310</manifest>
311""")
312 with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
313 fp.write("""
314<manifest>
315 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
316</manifest>
317""")
318 include_m = manifest_xml.XmlManifest(self.repodir, root_m)
319 for proj in include_m.projects:
320 if proj.name == 'root-name1':
321 # Check include group not set on root level proj.
322 self.assertNotIn('level1-group', proj.groups)
323 if proj.name == 'root-name2':
324 # Check root proj group not removed.
325 self.assertIn('r2g1', proj.groups)
326 if proj.name == 'level1-name1':
327 # Check level1 proj has inherited group level 1.
328 self.assertIn('level1-group', proj.groups)
329 if proj.name == 'level2-name1':
330 # Check level2 proj has inherited group levels 1 and 2.
331 self.assertIn('level1-group', proj.groups)
332 self.assertIn('level2-group', proj.groups)
333 # Check level2 proj group not removed.
334 self.assertIn('l2g1', proj.groups)
Mike Frysinger37ac3d62021-02-25 04:54:56 -0500335
336
337class SuperProjectTests(ManifestParseTestCase):
338 """Tests for <superproject>."""
339
340 def test_superproject(self):
341 """Check superproject settings."""
342 manifest = self.getXmlManifest("""
343<manifest>
344 <remote name="test-remote" fetch="http://localhost" />
345 <default remote="test-remote" revision="refs/heads/main" />
346 <superproject name="superproject"/>
347</manifest>
348""")
349 self.assertEqual(manifest.superproject['name'], 'superproject')
350 self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
351 self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
352 self.assertEqual(
353 manifest.ToXml().toxml(),
354 '<?xml version="1.0" ?><manifest>' +
355 '<remote name="test-remote" fetch="http://localhost"/>' +
356 '<default remote="test-remote" revision="refs/heads/main"/>' +
357 '<superproject name="superproject"/>' +
358 '</manifest>')
359
360 def test_remote(self):
361 """Check superproject settings with a remote."""
362 manifest = self.getXmlManifest("""
363<manifest>
364 <remote name="default-remote" fetch="http://localhost" />
365 <remote name="superproject-remote" fetch="http://localhost" />
366 <default remote="default-remote" revision="refs/heads/main" />
367 <superproject name="platform/superproject" remote="superproject-remote"/>
368</manifest>
369""")
370 self.assertEqual(manifest.superproject['name'], 'platform/superproject')
371 self.assertEqual(manifest.superproject['remote'].name, 'superproject-remote')
372 self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/platform/superproject')
373 self.assertEqual(
374 manifest.ToXml().toxml(),
375 '<?xml version="1.0" ?><manifest>' +
376 '<remote name="default-remote" fetch="http://localhost"/>' +
377 '<remote name="superproject-remote" fetch="http://localhost"/>' +
378 '<default remote="default-remote" revision="refs/heads/main"/>' +
379 '<superproject name="platform/superproject" remote="superproject-remote"/>' +
380 '</manifest>')
381
382 def test_defalut_remote(self):
383 """Check superproject settings with a default remote."""
384 manifest = self.getXmlManifest("""
385<manifest>
386 <remote name="default-remote" fetch="http://localhost" />
387 <default remote="default-remote" revision="refs/heads/main" />
388 <superproject name="superproject" remote="default-remote"/>
389</manifest>
390""")
391 self.assertEqual(manifest.superproject['name'], 'superproject')
392 self.assertEqual(manifest.superproject['remote'].name, 'default-remote')
393 self.assertEqual(
394 manifest.ToXml().toxml(),
395 '<?xml version="1.0" ?><manifest>' +
396 '<remote name="default-remote" fetch="http://localhost"/>' +
397 '<default remote="default-remote" revision="refs/heads/main"/>' +
398 '<superproject name="superproject"/>' +
399 '</manifest>')