diff options
author | Mike Frysinger <vapier@google.com> | 2020-02-19 22:36:26 -0500 |
---|---|---|
committer | David Pursehouse <dpursehouse@collab.net> | 2020-02-21 05:17:05 +0000 |
commit | d9254599f9bb47632313ecb90c5f281ceca5da3a (patch) | |
tree | 5c1bdb0815f5a027f0de194b488ba254719c7c11 | |
parent | 746e7f664e306e823a40cd95a127516aa522ed8f (diff) | |
download | git-repo-d9254599f9bb47632313ecb90c5f281ceca5da3a.tar.gz |
manifest/tests: get them passing under Windows
We also need to check more things in the manifest/project handlers,
and use platform_utils in a few places to address Windows behavior.
Drop Python 2.7 from Windows testing as it definitely doesn't work
and we won't be fixing it.
Change-Id: I83d00ee9f1612312bb3f7147cb9535fc61268245
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256113
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Jonathan Nieder <jrn@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
-rw-r--r-- | .github/workflows/test-ci.yml | 3 | ||||
-rw-r--r-- | manifest_xml.py | 17 | ||||
-rw-r--r-- | project.py | 7 | ||||
-rw-r--r-- | tests/test_manifest_xml.py | 6 | ||||
-rw-r--r-- | tests/test_project.py | 28 |
5 files changed, 45 insertions, 16 deletions
diff --git a/.github/workflows/test-ci.yml b/.github/workflows/test-ci.yml index 93061814..c33002dc 100644 --- a/.github/workflows/test-ci.yml +++ b/.github/workflows/test-ci.yml | |||
@@ -15,6 +15,9 @@ jobs: | |||
15 | matrix: | 15 | matrix: |
16 | os: [ubuntu-latest, macos-latest, windows-latest] | 16 | os: [ubuntu-latest, macos-latest, windows-latest] |
17 | python-version: [2.7, 3.6, 3.7, 3.8] | 17 | python-version: [2.7, 3.6, 3.7, 3.8] |
18 | exclude: | ||
19 | - os: windows-latest | ||
20 | python-version: 2.7 | ||
18 | runs-on: ${{ matrix.os }} | 21 | runs-on: ${{ matrix.os }} |
19 | 22 | ||
20 | steps: | 23 | steps: |
diff --git a/manifest_xml.py b/manifest_xml.py index 41628003..fe0735a8 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -1010,19 +1010,30 @@ class XmlManifest(object): | |||
1010 | # Assume paths might be used on case-insensitive filesystems. | 1010 | # Assume paths might be used on case-insensitive filesystems. |
1011 | path = path.lower() | 1011 | path = path.lower() |
1012 | 1012 | ||
1013 | # Split up the path by its components. We can't use os.path.sep exclusively | ||
1014 | # as some platforms (like Windows) will convert / to \ and that bypasses all | ||
1015 | # our constructed logic here. Especially since manifest authors only use | ||
1016 | # / in their paths. | ||
1017 | resep = re.compile(r'[/%s]' % re.escape(os.path.sep)) | ||
1018 | parts = resep.split(path) | ||
1019 | |||
1013 | # Some people use src="." to create stable links to projects. Lets allow | 1020 | # Some people use src="." to create stable links to projects. Lets allow |
1014 | # that but reject all other uses of "." to keep things simple. | 1021 | # that but reject all other uses of "." to keep things simple. |
1015 | parts = path.split(os.path.sep) | ||
1016 | if parts != ['.']: | 1022 | if parts != ['.']: |
1017 | for part in set(parts): | 1023 | for part in set(parts): |
1018 | if part in {'.', '..', '.git'} or part.startswith('.repo'): | 1024 | if part in {'.', '..', '.git'} or part.startswith('.repo'): |
1019 | return 'bad component: %s' % (part,) | 1025 | return 'bad component: %s' % (part,) |
1020 | 1026 | ||
1021 | if not symlink and path.endswith(os.path.sep): | 1027 | if not symlink and resep.match(path[-1]): |
1022 | return 'dirs not allowed' | 1028 | return 'dirs not allowed' |
1023 | 1029 | ||
1030 | # NB: The two abspath checks here are to handle platforms with multiple | ||
1031 | # filesystem path styles (e.g. Windows). | ||
1024 | norm = os.path.normpath(path) | 1032 | norm = os.path.normpath(path) |
1025 | if norm == '..' or norm.startswith('../') or norm.startswith(os.path.sep): | 1033 | if (norm == '..' or |
1034 | (len(norm) >= 3 and norm.startswith('..') and resep.match(norm[0])) or | ||
1035 | os.path.isabs(norm) or | ||
1036 | norm.startswith('/')): | ||
1026 | return 'path cannot be outside' | 1037 | return 'path cannot be outside' |
1027 | 1038 | ||
1028 | @classmethod | 1039 | @classmethod |
@@ -275,7 +275,12 @@ def _SafeExpandPath(base, subpath, skipfinal=False): | |||
275 | NB: We rely on a number of paths already being filtered out while parsing the | 275 | NB: We rely on a number of paths already being filtered out while parsing the |
276 | manifest. See the validation logic in manifest_xml.py for more details. | 276 | manifest. See the validation logic in manifest_xml.py for more details. |
277 | """ | 277 | """ |
278 | components = subpath.split(os.path.sep) | 278 | # Split up the path by its components. We can't use os.path.sep exclusively |
279 | # as some platforms (like Windows) will convert / to \ and that bypasses all | ||
280 | # our constructed logic here. Especially since manifest authors only use | ||
281 | # / in their paths. | ||
282 | resep = re.compile(r'[/%s]' % re.escape(os.path.sep)) | ||
283 | components = resep.split(subpath) | ||
279 | if skipfinal: | 284 | if skipfinal: |
280 | # Whether the caller handles the final component itself. | 285 | # Whether the caller handles the final component itself. |
281 | finalpart = components.pop() | 286 | finalpart = components.pop() |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index b6ec5b86..1cb72971 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
@@ -18,6 +18,7 @@ | |||
18 | 18 | ||
19 | from __future__ import print_function | 19 | from __future__ import print_function |
20 | 20 | ||
21 | import os | ||
21 | import unittest | 22 | import unittest |
22 | 23 | ||
23 | import error | 24 | import error |
@@ -78,6 +79,11 @@ class ManifestValidateFilePaths(unittest.TestCase): | |||
78 | # Block Unicode characters that get normalized out by filesystems. | 79 | # Block Unicode characters that get normalized out by filesystems. |
79 | u'foo\u200Cbar', | 80 | u'foo\u200Cbar', |
80 | ) | 81 | ) |
82 | # Make sure platforms that use path separators (e.g. Windows) are also | ||
83 | # rejected properly. | ||
84 | if os.path.sep != '/': | ||
85 | PATHS += tuple(x.replace('/', os.path.sep) for x in PATHS) | ||
86 | |||
81 | for path in PATHS: | 87 | for path in PATHS: |
82 | self.assertRaises( | 88 | self.assertRaises( |
83 | error.ManifestInvalidPathError, self.check_both, path, 'a') | 89 | error.ManifestInvalidPathError, self.check_both, path, 'a') |
diff --git a/tests/test_project.py b/tests/test_project.py index b416386e..67574cb8 100644 --- a/tests/test_project.py +++ b/tests/test_project.py | |||
@@ -27,6 +27,7 @@ import unittest | |||
27 | 27 | ||
28 | import error | 28 | import error |
29 | import git_config | 29 | import git_config |
30 | import platform_utils | ||
30 | import project | 31 | import project |
31 | 32 | ||
32 | 33 | ||
@@ -40,7 +41,7 @@ def TempGitTree(): | |||
40 | subprocess.check_call(['git', 'init'], cwd=tempdir) | 41 | subprocess.check_call(['git', 'init'], cwd=tempdir) |
41 | yield tempdir | 42 | yield tempdir |
42 | finally: | 43 | finally: |
43 | shutil.rmtree(tempdir) | 44 | platform_utils.rmtree(tempdir) |
44 | 45 | ||
45 | 46 | ||
46 | class RepoHookShebang(unittest.TestCase): | 47 | class RepoHookShebang(unittest.TestCase): |
@@ -243,17 +244,19 @@ class CopyFile(CopyLinkTestCase): | |||
243 | src = os.path.join(self.worktree, 'foo.txt') | 244 | src = os.path.join(self.worktree, 'foo.txt') |
244 | sym = os.path.join(self.worktree, 'sym') | 245 | sym = os.path.join(self.worktree, 'sym') |
245 | self.touch(src) | 246 | self.touch(src) |
246 | os.symlink('foo.txt', sym) | 247 | platform_utils.symlink('foo.txt', sym) |
247 | self.assertExists(sym) | 248 | self.assertExists(sym) |
248 | cf = self.CopyFile('sym', 'foo') | 249 | cf = self.CopyFile('sym', 'foo') |
249 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | 250 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) |
250 | 251 | ||
251 | def test_src_block_symlink_traversal(self): | 252 | def test_src_block_symlink_traversal(self): |
252 | """Do not allow reading through a symlink dir.""" | 253 | """Do not allow reading through a symlink dir.""" |
253 | src = os.path.join(self.worktree, 'bar', 'passwd') | 254 | realfile = os.path.join(self.tempdir, 'file.txt') |
254 | os.symlink('/etc', os.path.join(self.worktree, 'bar')) | 255 | self.touch(realfile) |
256 | src = os.path.join(self.worktree, 'bar', 'file.txt') | ||
257 | platform_utils.symlink(self.tempdir, os.path.join(self.worktree, 'bar')) | ||
255 | self.assertExists(src) | 258 | self.assertExists(src) |
256 | cf = self.CopyFile('bar/foo.txt', 'foo') | 259 | cf = self.CopyFile('bar/file.txt', 'foo') |
257 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | 260 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) |
258 | 261 | ||
259 | def test_src_block_copy_from_dir(self): | 262 | def test_src_block_copy_from_dir(self): |
@@ -267,7 +270,7 @@ class CopyFile(CopyLinkTestCase): | |||
267 | """Do not allow writing to a symlink.""" | 270 | """Do not allow writing to a symlink.""" |
268 | src = os.path.join(self.worktree, 'foo.txt') | 271 | src = os.path.join(self.worktree, 'foo.txt') |
269 | self.touch(src) | 272 | self.touch(src) |
270 | os.symlink('dest', os.path.join(self.topdir, 'sym')) | 273 | platform_utils.symlink('dest', os.path.join(self.topdir, 'sym')) |
271 | cf = self.CopyFile('foo.txt', 'sym') | 274 | cf = self.CopyFile('foo.txt', 'sym') |
272 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | 275 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) |
273 | 276 | ||
@@ -275,7 +278,8 @@ class CopyFile(CopyLinkTestCase): | |||
275 | """Do not allow writing through a symlink dir.""" | 278 | """Do not allow writing through a symlink dir.""" |
276 | src = os.path.join(self.worktree, 'foo.txt') | 279 | src = os.path.join(self.worktree, 'foo.txt') |
277 | self.touch(src) | 280 | self.touch(src) |
278 | os.symlink('/tmp', os.path.join(self.topdir, 'sym')) | 281 | platform_utils.symlink(tempfile.gettempdir(), |
282 | os.path.join(self.topdir, 'sym')) | ||
279 | cf = self.CopyFile('foo.txt', 'sym/foo.txt') | 283 | cf = self.CopyFile('foo.txt', 'sym/foo.txt') |
280 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) | 284 | self.assertRaises(error.ManifestInvalidPathError, cf._Copy) |
281 | 285 | ||
@@ -303,7 +307,7 @@ class LinkFile(CopyLinkTestCase): | |||
303 | dest = os.path.join(self.topdir, 'foo') | 307 | dest = os.path.join(self.topdir, 'foo') |
304 | self.assertExists(dest) | 308 | self.assertExists(dest) |
305 | self.assertTrue(os.path.islink(dest)) | 309 | self.assertTrue(os.path.islink(dest)) |
306 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | 310 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) |
307 | 311 | ||
308 | def test_src_subdir(self): | 312 | def test_src_subdir(self): |
309 | """Link to a file in a subdir of a project.""" | 313 | """Link to a file in a subdir of a project.""" |
@@ -320,7 +324,7 @@ class LinkFile(CopyLinkTestCase): | |||
320 | lf = self.LinkFile('.', 'foo/bar') | 324 | lf = self.LinkFile('.', 'foo/bar') |
321 | lf._Link() | 325 | lf._Link() |
322 | self.assertExists(dest) | 326 | self.assertExists(dest) |
323 | self.assertEqual('../git-project', os.readlink(dest)) | 327 | self.assertEqual(os.path.join('..', 'git-project'), os.readlink(dest)) |
324 | 328 | ||
325 | def test_dest_subdir(self): | 329 | def test_dest_subdir(self): |
326 | """Link a file to a subdir of a checkout.""" | 330 | """Link a file to a subdir of a checkout.""" |
@@ -354,10 +358,10 @@ class LinkFile(CopyLinkTestCase): | |||
354 | self.touch(src) | 358 | self.touch(src) |
355 | lf = self.LinkFile('foo.txt', 'sym') | 359 | lf = self.LinkFile('foo.txt', 'sym') |
356 | lf._Link() | 360 | lf._Link() |
357 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | 361 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) |
358 | 362 | ||
359 | # Point the symlink somewhere else. | 363 | # Point the symlink somewhere else. |
360 | os.unlink(dest) | 364 | os.unlink(dest) |
361 | os.symlink('/', dest) | 365 | platform_utils.symlink(self.tempdir, dest) |
362 | lf._Link() | 366 | lf._Link() |
363 | self.assertEqual('git-project/foo.txt', os.readlink(dest)) | 367 | self.assertEqual(os.path.join('git-project', 'foo.txt'), os.readlink(dest)) |