test_manifest_xml.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # -*- coding:utf-8 -*-
  2. #
  3. # Copyright (C) 2019 The Android Open Source Project
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Unittests for the manifest_xml.py module."""
  17. from __future__ import print_function
  18. import os
  19. import shutil
  20. import tempfile
  21. import unittest
  22. import xml.dom.minidom
  23. import error
  24. import manifest_xml
  25. class ManifestValidateFilePaths(unittest.TestCase):
  26. """Check _ValidateFilePaths helper.
  27. This doesn't access a real filesystem.
  28. """
  29. def check_both(self, *args):
  30. manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args)
  31. manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
  32. def test_normal_path(self):
  33. """Make sure good paths are accepted."""
  34. self.check_both('foo', 'bar')
  35. self.check_both('foo/bar', 'bar')
  36. self.check_both('foo', 'bar/bar')
  37. self.check_both('foo/bar', 'bar/bar')
  38. def test_symlink_targets(self):
  39. """Some extra checks for symlinks."""
  40. def check(*args):
  41. manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args)
  42. # We allow symlinks to end in a slash since we allow them to point to dirs
  43. # in general. Technically the slash isn't necessary.
  44. check('foo/', 'bar')
  45. # We allow a single '.' to get a reference to the project itself.
  46. check('.', 'bar')
  47. def test_bad_paths(self):
  48. """Make sure bad paths (src & dest) are rejected."""
  49. PATHS = (
  50. '..',
  51. '../',
  52. './',
  53. 'foo/',
  54. './foo',
  55. '../foo',
  56. 'foo/./bar',
  57. 'foo/../../bar',
  58. '/foo',
  59. './../foo',
  60. '.git/foo',
  61. # Check case folding.
  62. '.GIT/foo',
  63. 'blah/.git/foo',
  64. '.repo/foo',
  65. '.repoconfig',
  66. # Block ~ due to 8.3 filenames on Windows filesystems.
  67. '~',
  68. 'foo~',
  69. 'blah/foo~',
  70. # Block Unicode characters that get normalized out by filesystems.
  71. u'foo\u200Cbar',
  72. )
  73. # Make sure platforms that use path separators (e.g. Windows) are also
  74. # rejected properly.
  75. if os.path.sep != '/':
  76. PATHS += tuple(x.replace('/', os.path.sep) for x in PATHS)
  77. for path in PATHS:
  78. self.assertRaises(
  79. error.ManifestInvalidPathError, self.check_both, path, 'a')
  80. self.assertRaises(
  81. error.ManifestInvalidPathError, self.check_both, 'a', path)
  82. class ValueTests(unittest.TestCase):
  83. """Check utility parsing code."""
  84. def _get_node(self, text):
  85. return xml.dom.minidom.parseString(text).firstChild
  86. def test_bool_default(self):
  87. """Check XmlBool default handling."""
  88. node = self._get_node('<node/>')
  89. self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
  90. self.assertIsNone(manifest_xml.XmlBool(node, 'a', None))
  91. self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
  92. node = self._get_node('<node a=""/>')
  93. self.assertIsNone(manifest_xml.XmlBool(node, 'a'))
  94. def test_bool_invalid(self):
  95. """Check XmlBool invalid handling."""
  96. node = self._get_node('<node a="moo"/>')
  97. self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123))
  98. def test_bool_true(self):
  99. """Check XmlBool true values."""
  100. for value in ('yes', 'true', '1'):
  101. node = self._get_node('<node a="%s"/>' % (value,))
  102. self.assertTrue(manifest_xml.XmlBool(node, 'a'))
  103. def test_bool_false(self):
  104. """Check XmlBool false values."""
  105. for value in ('no', 'false', '0'):
  106. node = self._get_node('<node a="%s"/>' % (value,))
  107. self.assertFalse(manifest_xml.XmlBool(node, 'a'))
  108. def test_int_default(self):
  109. """Check XmlInt default handling."""
  110. node = self._get_node('<node/>')
  111. self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
  112. self.assertIsNone(manifest_xml.XmlInt(node, 'a', None))
  113. self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123))
  114. node = self._get_node('<node a=""/>')
  115. self.assertIsNone(manifest_xml.XmlInt(node, 'a'))
  116. def test_int_good(self):
  117. """Check XmlInt numeric handling."""
  118. for value in (-1, 0, 1, 50000):
  119. node = self._get_node('<node a="%s"/>' % (value,))
  120. self.assertEqual(value, manifest_xml.XmlInt(node, 'a'))
  121. def test_int_invalid(self):
  122. """Check XmlInt invalid handling."""
  123. with self.assertRaises(error.ManifestParseError):
  124. node = self._get_node('<node a="xx"/>')
  125. manifest_xml.XmlInt(node, 'a')
  126. class XmlManifestTests(unittest.TestCase):
  127. """Check manifest processing."""
  128. def setUp(self):
  129. self.tempdir = tempfile.mkdtemp(prefix='repo_tests')
  130. self.repodir = os.path.join(self.tempdir, '.repo')
  131. self.manifest_dir = os.path.join(self.repodir, 'manifests')
  132. self.manifest_file = os.path.join(
  133. self.repodir, manifest_xml.MANIFEST_FILE_NAME)
  134. self.local_manifest_dir = os.path.join(
  135. self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME)
  136. os.mkdir(self.repodir)
  137. os.mkdir(self.manifest_dir)
  138. # The manifest parsing really wants a git repo currently.
  139. gitdir = os.path.join(self.repodir, 'manifests.git')
  140. os.mkdir(gitdir)
  141. with open(os.path.join(gitdir, 'config'), 'w') as fp:
  142. fp.write("""[remote "origin"]
  143. url = https://localhost:0/manifest
  144. """)
  145. def tearDown(self):
  146. shutil.rmtree(self.tempdir, ignore_errors=True)
  147. def getXmlManifest(self, data):
  148. """Helper to initialize a manifest for testing."""
  149. with open(self.manifest_file, 'w') as fp:
  150. fp.write(data)
  151. return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
  152. def test_empty(self):
  153. """Parse an 'empty' manifest file."""
  154. manifest = self.getXmlManifest(
  155. '<?xml version="1.0" encoding="UTF-8"?>'
  156. '<manifest></manifest>')
  157. self.assertEqual(manifest.remotes, {})
  158. self.assertEqual(manifest.projects, [])
  159. def test_link(self):
  160. """Verify Link handling with new names."""
  161. manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
  162. with open(os.path.join(self.manifest_dir, 'foo.xml'), 'w') as fp:
  163. fp.write('<manifest></manifest>')
  164. manifest.Link('foo.xml')
  165. with open(self.manifest_file) as fp:
  166. self.assertIn('<include name="foo.xml" />', fp.read())
  167. def test_toxml_empty(self):
  168. """Verify the ToXml() helper."""
  169. manifest = self.getXmlManifest(
  170. '<?xml version="1.0" encoding="UTF-8"?>'
  171. '<manifest></manifest>')
  172. self.assertEqual(manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>')
  173. def test_todict_empty(self):
  174. """Verify the ToDict() helper."""
  175. manifest = self.getXmlManifest(
  176. '<?xml version="1.0" encoding="UTF-8"?>'
  177. '<manifest></manifest>')
  178. self.assertEqual(manifest.ToDict(), {})
  179. def test_project_group(self):
  180. """Check project group settings."""
  181. manifest = self.getXmlManifest("""
  182. <manifest>
  183. <remote name="test-remote" fetch="http://localhost" />
  184. <default remote="test-remote" revision="refs/heads/main" />
  185. <project name="test-name" path="test-path"/>
  186. <project name="extras" path="path" groups="g1,g2,g1"/>
  187. </manifest>
  188. """)
  189. self.assertEqual(len(manifest.projects), 2)
  190. # Ordering isn't guaranteed.
  191. result = {
  192. manifest.projects[0].name: manifest.projects[0].groups,
  193. manifest.projects[1].name: manifest.projects[1].groups,
  194. }
  195. project = manifest.projects[0]
  196. self.assertCountEqual(
  197. result['test-name'],
  198. ['name:test-name', 'all', 'path:test-path'])
  199. self.assertCountEqual(
  200. result['extras'],
  201. ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path'])
  202. def test_include_levels(self):
  203. root_m = os.path.join(self.manifest_dir, 'root.xml')
  204. with open(root_m, 'w') as fp:
  205. fp.write("""
  206. <manifest>
  207. <remote name="test-remote" fetch="http://localhost" />
  208. <default remote="test-remote" revision="refs/heads/main" />
  209. <include name="level1.xml" groups="level1-group" />
  210. <project name="root-name1" path="root-path1" />
  211. <project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
  212. </manifest>
  213. """)
  214. with open(os.path.join(self.manifest_dir, 'level1.xml'), 'w') as fp:
  215. fp.write("""
  216. <manifest>
  217. <include name="level2.xml" groups="level2-group" />
  218. <project name="level1-name1" path="level1-path1" />
  219. </manifest>
  220. """)
  221. with open(os.path.join(self.manifest_dir, 'level2.xml'), 'w') as fp:
  222. fp.write("""
  223. <manifest>
  224. <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
  225. </manifest>
  226. """)
  227. include_m = manifest_xml.XmlManifest(self.repodir, root_m)
  228. for proj in include_m.projects:
  229. if proj.name == 'root-name1':
  230. # Check include group not set on root level proj.
  231. self.assertNotIn('level1-group', proj.groups)
  232. if proj.name == 'root-name2':
  233. # Check root proj group not removed.
  234. self.assertIn('r2g1', proj.groups)
  235. if proj.name == 'level1-name1':
  236. # Check level1 proj has inherited group level 1.
  237. self.assertIn('level1-group', proj.groups)
  238. if proj.name == 'level2-name1':
  239. # Check level2 proj has inherited group levels 1 and 2.
  240. self.assertIn('level1-group', proj.groups)
  241. self.assertIn('level2-group', proj.groups)
  242. # Check level2 proj group not removed.
  243. self.assertIn('l2g1', proj.groups)