test_manifest_xml.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # 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. """Unittests for the manifest_xml.py module."""
  15. import os
  16. import shutil
  17. import tempfile
  18. import unittest
  19. import xml.dom.minidom
  20. import error
  21. import manifest_xml
  22. class ManifestValidateFilePaths(unittest.TestCase):
  23. """Check _ValidateFilePaths helper.
  24. This doesn't access a real filesystem.
  25. """
  26. def check_both(self, *args):
  27. manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args)
  28. manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
  29. def test_normal_path(self):
  30. """Make sure good paths are accepted."""
  31. self.check_both('foo', 'bar')
  32. self.check_both('foo/bar', 'bar')
  33. self.check_both('foo', 'bar/bar')
  34. self.check_both('foo/bar', 'bar/bar')
  35. def test_symlink_targets(self):
  36. """Some extra checks for symlinks."""
  37. def check(*args):
  38. manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
  39. # We allow symlinks to end in a slash since we allow them to point to dirs
  40. # in general. Technically the slash isn't necessary.
  41. check('foo/', 'bar')
  42. # We allow a single '.' to get a reference to the project itself.
  43. check('.', 'bar')
  44. def test_bad_paths(self):
  45. """Make sure bad paths (src & dest) are rejected."""
  46. PATHS = (
  47. '..',
  48. '../',
  49. './',
  50. 'foo/',
  51. './foo',
  52. '../foo',
  53. 'foo/./bar',
  54. 'foo/../../bar',
  55. '/foo',
  56. './../foo',
  57. '.git/foo',
  58. # Check case folding.
  59. '.GIT/foo',
  60. 'blah/.git/foo',
  61. '.repo/foo',
  62. '.repoconfig',
  63. # Block ~ due to 8.3 filenames on Windows filesystems.
  64. '~',
  65. 'foo~',
  66. 'blah/foo~',
  67. # Block Unicode characters that get normalized out by filesystems.
  68. u'foo\u200Cbar',
  69. )
  70. # Make sure platforms that use path separators (e.g. Windows) are also
  71. # rejected properly.
  72. if os.path.sep != '/':
  73. PATHS += tuple(x.replace('/', os.path.sep) for x in PATHS)
  74. for path in PATHS:
  75. self.assertRaises(
  76. error.ManifestInvalidPathError, self.check_both, path, 'a')
  77. self.assertRaises(
  78. error.ManifestInvalidPathError, self.check_both, 'a', path)
  79. class ValueTests(unittest.TestCase):
  80. """Check utility parsing code."""
  81. def _get_node(self, text):
  82. return xml.dom.minidom.parseString(text).firstChild
  83. def test_bool_default(self):
  84. """Check XmlBool default handling."""
  85. node = self._get_node('<node/>')
  86. self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
  87. self.assertIsNone(manifest_xml.XmlBool(node, 'a', None))
  88. self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
  89. node = self._get_node('<node a=""/>')
  90. self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
  91. def test_bool_invalid(self):
  92. """Check XmlBool invalid handling."""
  93. node = self._get_node('<node a="moo"/>')
  94. self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
  95. def test_bool_true(self):
  96. """Check XmlBool true values."""
  97. for value in ('yes', 'true', '1'):
  98. node = self._get_node('<node a="%s"/>' % (value,))
  99. self.assertTrue(manifest_xml.XmlBool(node, 'a'))
  100. def test_bool_false(self):
  101. """Check XmlBool false values."""
  102. for value in ('no', 'false', '0'):
  103. node = self._get_node('<node a="%s"/>' % (value,))
  104. self.assertFalse(manifest_xml.XmlBool(node, 'a'))
  105. def test_int_default(self):
  106. """Check XmlInt default handling."""
  107. node = self._get_node('<node/>')
  108. self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
  109. self.assertIsNone(manifest_xml.XmlInt(node, 'a', None))
  110. self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123))
  111. node = self._get_node('<node a=""/>')
  112. self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
  113. def test_int_good(self):
  114. """Check XmlInt numeric handling."""
  115. for value in (-1, 0, 1, 50000):
  116. node = self._get_node('<node a="%s"/>' % (value,))
  117. self.assertEqual(value, manifest_xml.XmlInt(node, 'a'))
  118. def test_int_invalid(self):
  119. """Check XmlInt invalid handling."""
  120. with self.assertRaises(error.ManifestParseError):
  121. node = self._get_node('<node a="xx"/>')
  122. manifest_xml.XmlInt(node, 'a')
  123. class XmlManifestTests(unittest.TestCase):
  124. """Check manifest processing."""
  125. def setUp(self):
  126. self.tempdir = tempfile.mkdtemp(prefix='repo_tests')
  127. self.repodir = os.path.join(self.tempdir, '.repo')
  128. self.manifest_dir = os.path.join(self.repodir, 'manifests')
  129. self.manifest_file = os.path.join(
  130. self.repodir, manifest_xml.MANIFEST_FILE_NAME)
  131. self.local_manifest_dir = os.path.join(
  132. self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME)
  133. os.mkdir(self.repodir)
  134. os.mkdir(self.manifest_dir)
  135. # The manifest parsing really wants a git repo currently.
  136. gitdir = os.path.join(self.repodir, 'manifests.git')
  137. os.mkdir(gitdir)
  138. with open(os.path.join(gitdir, 'config'), 'w') as fp:
  139. fp.write("""[remote "origin"]
  140. url = https://localhost:0/manifest
  141. """)
  142. def tearDown(self):
  143. shutil.rmtree(self.tempdir, ignore_errors=True)
  144. def getXmlManifest(self, data):
  145. """Helper to initialize a manifest for testing."""
  146. with open(self.manifest_file, 'w') as fp:
  147. fp.write(data)
  148. return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
  149. def test_empty(self):
  150. """Parse an 'empty' manifest file."""
  151. manifest = self.getXmlManifest(
  152. '<?xml version="1.0" encoding="UTF-8"?>'
  153. '<manifest></manifest>')
  154. self.assertEqual(manifest.remotes, {})
  155. self.assertEqual(manifest.projects, [])
  156. def test_link(self):
  157. """Verify Link handling with new names."""
  158. manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
  159. with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp:
  160. fp.write('<manifest></manifest>')
  161. manifest.Link('foo.xml')
  162. with open(self.manifest_file) as fp:
  163. self.assertIn('<include name="foo.xml" />', fp.read())
  164. def test_toxml_empty(self):
  165. """Verify the ToXml() helper."""
  166. manifest = self.getXmlManifest(
  167. '<?xml version="1.0" encoding="UTF-8"?>'
  168. '<manifest></manifest>')
  169. self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>')
  170. def test_todict_empty(self):
  171. """Verify the ToDict() helper."""
  172. manifest = self.getXmlManifest(
  173. '<?xml version="1.0" encoding="UTF-8"?>'
  174. '<manifest></manifest>')
  175. self.assertEqual(manifest.ToDict(), {})
  176. def test_repo_hooks(self):
  177. """Check repo-hooks settings."""
  178. manifest = self.getXmlManifest("""
  179. <manifest>
  180. <remote name="test-remote" fetch="http://localhost" />
  181. <default remote="test-remote" revision="refs/heads/main" />
  182. <project name="repohooks" path="src/repohooks"/>
  183. <repo-hooks in-project="repohooks" enabled-list="a, b"/>
  184. </manifest>
  185. """)
  186. self.assertEqual(manifest.repo_hooks_project.name, 'repohooks')
  187. self.assertEqual(manifest.repo_hooks_project.enabled_repo_hooks, ['a', 'b'])
  188. def test_superproject(self):
  189. """Check superproject settings."""
  190. manifest = self.getXmlManifest("""
  191. <manifest>
  192. <remote name="test-remote" fetch="http://localhost" />
  193. <default remote="test-remote" revision="refs/heads/main" />
  194. <superproject name="superproject"/>
  195. </manifest>
  196. """)
  197. self.assertEqual(manifest.superproject['name'], 'superproject')
  198. self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
  199. self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/superproject')
  200. self.assertEqual(
  201. manifest.ToXml().toxml(),
  202. '<?xml version="1.0" ?><manifest>' +
  203. '<remote name="test-remote" fetch="http://localhost"/>' +
  204. '<default remote="test-remote" revision="refs/heads/main"/>' +
  205. '<superproject name="superproject"/>' +
  206. '</manifest>')
  207. def test_superproject_with_remote(self):
  208. """Check superproject settings."""
  209. manifest = self.getXmlManifest("""
  210. <manifest>
  211. <remote name="default-remote" fetch="http://localhost" />
  212. <remote name="superproject-remote" fetch="http://localhost" />
  213. <default remote="default-remote" revision="refs/heads/main" />
  214. <superproject name="platform/superproject" remote="superproject-remote"/>
  215. </manifest>
  216. """)
  217. self.assertEqual(manifest.superproject['name'], 'platform/superproject')
  218. self.assertEqual(manifest.superproject['remote'].name, 'superproject-remote')
  219. self.assertEqual(manifest.superproject['remote'].url, 'http://localhost/platform/superproject')
  220. self.assertEqual(
  221. manifest.ToXml().toxml(),
  222. '<?xml version="1.0" ?><manifest>' +
  223. '<remote name="default-remote" fetch="http://localhost"/>' +
  224. '<remote name="superproject-remote" fetch="http://localhost"/>' +
  225. '<default remote="default-remote" revision="refs/heads/main"/>' +
  226. '<superproject name="platform/superproject" remote="superproject-remote"/>' +
  227. '</manifest>')
  228. def test_superproject_with_defalut_remote(self):
  229. """Check superproject settings."""
  230. manifest = self.getXmlManifest("""
  231. <manifest>
  232. <remote name="default-remote" fetch="http://localhost" />
  233. <default remote="default-remote" revision="refs/heads/main" />
  234. <superproject name="superproject" remote="default-remote"/>
  235. </manifest>
  236. """)
  237. self.assertEqual(manifest.superproject['name'], 'superproject')
  238. self.assertEqual(manifest.superproject['remote'].name, 'default-remote')
  239. self.assertEqual(
  240. manifest.ToXml().toxml(),
  241. '<?xml version="1.0" ?><manifest>' +
  242. '<remote name="default-remote" fetch="http://localhost"/>' +
  243. '<default remote="default-remote" revision="refs/heads/main"/>' +
  244. '<superproject name="superproject"/>' +
  245. '</manifest>')
  246. def test_unknown_tags(self):
  247. """Check superproject settings."""
  248. manifest = self.getXmlManifest("""
  249. <manifest>
  250. <remote name="test-remote" fetch="http://localhost" />
  251. <default remote="test-remote" revision="refs/heads/main" />
  252. <superproject name="superproject"/>
  253. <iankaz value="unknown (possible) future tags are ignored"/>
  254. <x-custom-tag>X tags are always ignored</x-custom-tag>
  255. </manifest>
  256. """)
  257. self.assertEqual(manifest.superproject['name'], 'superproject')
  258. self.assertEqual(manifest.superproject['remote'].name, 'test-remote')
  259. self.assertEqual(
  260. manifest.ToXml().toxml(),
  261. '<?xml version="1.0" ?><manifest>' +
  262. '<remote name="test-remote" fetch="http://localhost"/>' +
  263. '<default remote="test-remote" revision="refs/heads/main"/>' +
  264. '<superproject name="superproject"/>' +
  265. '</manifest>')
  266. def test_project_group(self):
  267. """Check project group settings."""
  268. manifest = self.getXmlManifest("""
  269. <manifest>
  270. <remote name="test-remote" fetch="http://localhost" />
  271. <default remote="test-remote" revision="refs/heads/main" />
  272. <project name="test-name" path="test-path"/>
  273. <project name="extras" path="path" groups="g1,g2,g1"/>
  274. </manifest>
  275. """)
  276. self.assertEqual(len(manifest.projects), 2)
  277. # Ordering isn't guaranteed.
  278. result = {
  279. manifest.projects[0].name: manifest.projects[0].groups,
  280. manifest.projects[1].name: manifest.projects[1].groups,
  281. }
  282. project = manifest.projects[0]
  283. self.assertCountEqual(
  284. result['test-name'],
  285. ['name:test-name', 'all', 'path:test-path'])
  286. self.assertCountEqual(
  287. result['extras'],
  288. ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
  289. def test_include_levels(self):
  290. root_m = os.path.join(self.manifest_dir, 'root.xml')
  291. with open(root_m, 'w') as fp:
  292. fp.write("""
  293. <manifest>
  294. <remote name="test-remote" fetch="http://localhost" />
  295. <default remote="test-remote" revision="refs/heads/main" />
  296. <include name="level1.xml" groups="level1-group" />
  297. <project name="root-name1" path="root-path1" />
  298. <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
  299. </manifest>
  300. """)
  301. with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
  302. fp.write("""
  303. <manifest>
  304. <include name="level2.xml" groups="level2-group" />
  305. <project name="level1-name1" path="level1-path1" />
  306. </manifest>
  307. """)
  308. with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
  309. fp.write("""
  310. <manifest>
  311. <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
  312. </manifest>
  313. """)
  314. include_m = manifest_xml.XmlManifest(self.repodir, root_m)
  315. for proj in include_m.projects:
  316. if proj.name == 'root-name1':
  317. # Check include group not set on root level proj.
  318. self.assertNotIn('level1-group', proj.groups)
  319. if proj.name == 'root-name2':
  320. # Check root proj group not removed.
  321. self.assertIn('r2g1', proj.groups)
  322. if proj.name == 'level1-name1':
  323. # Check level1 proj has inherited group level 1.
  324. self.assertIn('level1-group', proj.groups)
  325. if proj.name == 'level2-name1':
  326. # Check level2 proj has inherited group levels 1 and 2.
  327. self.assertIn('level1-group', proj.groups)
  328. self.assertIn('level2-group', proj.groups)
  329. # Check level2 proj group not removed.
  330. self.assertIn('l2g1', proj.groups)