Mike Frysinger | 04122b7 | 2019-07-31 23:32:58 -0400 | [diff] [blame] | 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 | |
| 17 | """Unittests for the manifest_xml.py module.""" |
| 18 | |
| 19 | from __future__ import print_function |
| 20 | |
Mike Frysinger | d925459 | 2020-02-19 22:36:26 -0500 | [diff] [blame] | 21 | import os |
Mike Frysinger | 8c1e9cb | 2020-09-06 14:53:18 -0400 | [diff] [blame^] | 22 | import shutil |
| 23 | import tempfile |
Mike Frysinger | 04122b7 | 2019-07-31 23:32:58 -0400 | [diff] [blame] | 24 | import unittest |
Mike Frysinger | bb8ee7f | 2020-02-22 05:30:12 -0500 | [diff] [blame] | 25 | import xml.dom.minidom |
Mike Frysinger | 04122b7 | 2019-07-31 23:32:58 -0400 | [diff] [blame] | 26 | |
| 27 | import error |
| 28 | import manifest_xml |
| 29 | |
| 30 | |
| 31 | class ManifestValidateFilePaths(unittest.TestCase): |
| 32 | """Check _ValidateFilePaths helper. |
| 33 | |
| 34 | This doesn't access a real filesystem. |
| 35 | """ |
| 36 | |
| 37 | def check_both(self, *args): |
| 38 | manifest_xml.XmlManifest._ValidateFilePaths('copyfile', *args) |
| 39 | manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args) |
| 40 | |
| 41 | def test_normal_path(self): |
| 42 | """Make sure good paths are accepted.""" |
| 43 | self.check_both('foo', 'bar') |
| 44 | self.check_both('foo/bar', 'bar') |
| 45 | self.check_both('foo', 'bar/bar') |
| 46 | self.check_both('foo/bar', 'bar/bar') |
| 47 | |
| 48 | def test_symlink_targets(self): |
| 49 | """Some extra checks for symlinks.""" |
| 50 | def check(*args): |
| 51 | manifest_xml.XmlManifest._ValidateFilePaths('linkfile', *args) |
| 52 | |
| 53 | # We allow symlinks to end in a slash since we allow them to point to dirs |
| 54 | # in general. Technically the slash isn't necessary. |
| 55 | check('foo/', 'bar') |
Mike Frysinger | ae62541 | 2020-02-10 17:10:03 -0500 | [diff] [blame] | 56 | # We allow a single '.' to get a reference to the project itself. |
| 57 | check('.', 'bar') |
Mike Frysinger | 04122b7 | 2019-07-31 23:32:58 -0400 | [diff] [blame] | 58 | |
| 59 | def test_bad_paths(self): |
| 60 | """Make sure bad paths (src & dest) are rejected.""" |
| 61 | PATHS = ( |
| 62 | '..', |
| 63 | '../', |
| 64 | './', |
| 65 | 'foo/', |
| 66 | './foo', |
| 67 | '../foo', |
| 68 | 'foo/./bar', |
| 69 | 'foo/../../bar', |
| 70 | '/foo', |
| 71 | './../foo', |
| 72 | '.git/foo', |
| 73 | # Check case folding. |
| 74 | '.GIT/foo', |
| 75 | 'blah/.git/foo', |
| 76 | '.repo/foo', |
| 77 | '.repoconfig', |
| 78 | # Block ~ due to 8.3 filenames on Windows filesystems. |
| 79 | '~', |
| 80 | 'foo~', |
| 81 | 'blah/foo~', |
| 82 | # Block Unicode characters that get normalized out by filesystems. |
| 83 | u'foo\u200Cbar', |
| 84 | ) |
Mike Frysinger | d925459 | 2020-02-19 22:36:26 -0500 | [diff] [blame] | 85 | # Make sure platforms that use path separators (e.g. Windows) are also |
| 86 | # rejected properly. |
| 87 | if os.path.sep != '/': |
| 88 | PATHS += tuple(x.replace('/', os.path.sep) for x in PATHS) |
| 89 | |
Mike Frysinger | 04122b7 | 2019-07-31 23:32:58 -0400 | [diff] [blame] | 90 | for path in PATHS: |
| 91 | self.assertRaises( |
| 92 | error.ManifestInvalidPathError, self.check_both, path, 'a') |
| 93 | self.assertRaises( |
| 94 | error.ManifestInvalidPathError, self.check_both, 'a', path) |
Mike Frysinger | bb8ee7f | 2020-02-22 05:30:12 -0500 | [diff] [blame] | 95 | |
| 96 | |
| 97 | class ValueTests(unittest.TestCase): |
| 98 | """Check utility parsing code.""" |
| 99 | |
| 100 | def _get_node(self, text): |
| 101 | return xml.dom.minidom.parseString(text).firstChild |
| 102 | |
| 103 | def test_bool_default(self): |
| 104 | """Check XmlBool default handling.""" |
| 105 | node = self._get_node('<node/>') |
| 106 | self.assertIsNone(manifest_xml.XmlBool(node, 'a')) |
| 107 | self.assertIsNone(manifest_xml.XmlBool(node, 'a', None)) |
| 108 | self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) |
| 109 | |
| 110 | node = self._get_node('<node a=""/>') |
| 111 | self.assertIsNone(manifest_xml.XmlBool(node, 'a')) |
| 112 | |
| 113 | def test_bool_invalid(self): |
| 114 | """Check XmlBool invalid handling.""" |
| 115 | node = self._get_node('<node a="moo"/>') |
| 116 | self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) |
| 117 | |
| 118 | def test_bool_true(self): |
| 119 | """Check XmlBool true values.""" |
| 120 | for value in ('yes', 'true', '1'): |
| 121 | node = self._get_node('<node a="%s"/>' % (value,)) |
| 122 | self.assertTrue(manifest_xml.XmlBool(node, 'a')) |
| 123 | |
| 124 | def test_bool_false(self): |
| 125 | """Check XmlBool false values.""" |
| 126 | for value in ('no', 'false', '0'): |
| 127 | node = self._get_node('<node a="%s"/>' % (value,)) |
| 128 | self.assertFalse(manifest_xml.XmlBool(node, 'a')) |
| 129 | |
| 130 | def test_int_default(self): |
| 131 | """Check XmlInt default handling.""" |
| 132 | node = self._get_node('<node/>') |
| 133 | self.assertIsNone(manifest_xml.XmlInt(node, 'a')) |
| 134 | self.assertIsNone(manifest_xml.XmlInt(node, 'a', None)) |
| 135 | self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123)) |
| 136 | |
| 137 | node = self._get_node('<node a=""/>') |
| 138 | self.assertIsNone(manifest_xml.XmlInt(node, 'a')) |
| 139 | |
| 140 | def test_int_good(self): |
| 141 | """Check XmlInt numeric handling.""" |
| 142 | for value in (-1, 0, 1, 50000): |
| 143 | node = self._get_node('<node a="%s"/>' % (value,)) |
| 144 | self.assertEqual(value, manifest_xml.XmlInt(node, 'a')) |
| 145 | |
| 146 | def test_int_invalid(self): |
| 147 | """Check XmlInt invalid handling.""" |
| 148 | with self.assertRaises(error.ManifestParseError): |
| 149 | node = self._get_node('<node a="xx"/>') |
| 150 | manifest_xml.XmlInt(node, 'a') |
Mike Frysinger | 8c1e9cb | 2020-09-06 14:53:18 -0400 | [diff] [blame^] | 151 | |
| 152 | |
| 153 | class XmlManifestTests(unittest.TestCase): |
| 154 | """Check manifest processing.""" |
| 155 | |
| 156 | def setUp(self): |
| 157 | self.tempdir = tempfile.mkdtemp(prefix='repo_tests') |
| 158 | self.repodir = os.path.join(self.tempdir, '.repo') |
| 159 | self.manifest_dir = os.path.join(self.repodir, 'manifests') |
| 160 | self.manifest_file = os.path.join( |
| 161 | self.repodir, manifest_xml.MANIFEST_FILE_NAME) |
| 162 | self.local_manifest_dir = os.path.join( |
| 163 | self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME) |
| 164 | os.mkdir(self.repodir) |
| 165 | os.mkdir(self.manifest_dir) |
| 166 | |
| 167 | # The manifest parsing really wants a git repo currently. |
| 168 | gitdir = os.path.join(self.repodir, 'manifests.git') |
| 169 | os.mkdir(gitdir) |
| 170 | with open(os.path.join(gitdir, 'config'), 'w') as fp: |
| 171 | fp.write("""[remote "origin"] |
| 172 | url = https://localhost:0/manifest |
| 173 | """) |
| 174 | |
| 175 | def tearDown(self): |
| 176 | shutil.rmtree(self.tempdir, ignore_errors=True) |
| 177 | |
| 178 | def getXmlManifest(self, data): |
| 179 | """Helper to initialize a manifest for testing.""" |
| 180 | with open(self.manifest_file, 'w') as fp: |
| 181 | fp.write(data) |
| 182 | return manifest_xml.XmlManifest(self.repodir, self.manifest_file) |
| 183 | |
| 184 | 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 | |
| 215 | def test_project_group(self): |
| 216 | """Check project group 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="test-name" path="test-path"/> |
| 222 | <project name="extras" path="path" groups="g1,g2,g1"/> |
| 223 | </manifest> |
| 224 | """) |
| 225 | self.assertEqual(len(manifest.projects), 2) |
| 226 | # Ordering isn't guaranteed. |
| 227 | result = { |
| 228 | manifest.projects[0].name: manifest.projects[0].groups, |
| 229 | manifest.projects[1].name: manifest.projects[1].groups, |
| 230 | } |
| 231 | project = manifest.projects[0] |
| 232 | self.assertCountEqual( |
| 233 | result['test-name'], |
| 234 | ['name:test-name', 'all', 'path:test-path']) |
| 235 | self.assertCountEqual( |
| 236 | result['extras'], |
| 237 | ['g1', 'g2', 'g1', 'name:extras', 'all', 'path:path']) |