summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2020-02-19 22:36:26 -0500
committerDavid Pursehouse <dpursehouse@collab.net>2020-02-21 05:17:05 +0000
commitd9254599f9bb47632313ecb90c5f281ceca5da3a (patch)
tree5c1bdb0815f5a027f0de194b488ba254719c7c11
parent746e7f664e306e823a40cd95a127516aa522ed8f (diff)
downloadgit-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.yml3
-rw-r--r--manifest_xml.py17
-rw-r--r--project.py7
-rw-r--r--tests/test_manifest_xml.py6
-rw-r--r--tests/test_project.py28
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
diff --git a/project.py b/project.py
index 2112ee32..99ef238f 100644
--- a/project.py
+++ b/project.py
@@ -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
19from __future__ import print_function 19from __future__ import print_function
20 20
21import os
21import unittest 22import unittest
22 23
23import error 24import 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
28import error 28import error
29import git_config 29import git_config
30import platform_utils
30import project 31import 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
46class RepoHookShebang(unittest.TestCase): 47class 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))