diff --git a/tools/asset_pipeline/setup_state.py b/tools/asset_pipeline/setup_state.py index 28f3ed71..5824e97d 100644 --- a/tools/asset_pipeline/setup_state.py +++ b/tools/asset_pipeline/setup_state.py @@ -57,6 +57,10 @@ def sha(path): def receipt(root, paths): + # Windows short names and directory junctions must use the same spelling + # on both sides of relative_to; also reject outputs outside the root. + root = root.resolve() + paths = (p.resolve() for p in paths) return {p.relative_to(root).as_posix(): {'size': p.stat().st_size, 'sha256': sha(p)} for p in sorted(set(paths)) if p.is_file()} diff --git a/tools/asset_pipeline/test_setup_recovery.py b/tools/asset_pipeline/test_setup_recovery.py index 135cc369..3acc3899 100644 --- a/tools/asset_pipeline/test_setup_recovery.py +++ b/tools/asset_pipeline/test_setup_recovery.py @@ -14,6 +14,25 @@ from .setup_state import source_directory, setup_lock, receipt, valid_receipt class SetupRecovery(unittest.TestCase): + @unittest.skipUnless(os.name == 'nt', 'Windows directory aliases') + def test_receipts_accept_windows_aliases_but_reject_outside_files(self): + import _winapi + with tempfile.TemporaryDirectory() as temp: + root = Path(temp).resolve() + target = root/'long installation directory';target.mkdir() + alias = root/'alias' + _winapi.CreateJunction(str(target),str(alias)) + try: + payload = target/'map.skate';payload.write_bytes(b'map') + expected = receipt(target,[payload]) + self.assertEqual(receipt(alias,[payload]),expected) + self.assertEqual(receipt(target,[alias/'map.skate']),expected) + self.assertTrue(valid_receipt(alias,expected)) + outside = root/'outside';outside.write_bytes(b'outside') + with self.assertRaises(ValueError):receipt(alias,[outside]) + finally: + alias.rmdir() # Remove the junction, never its target. + def test_xex_refresh_failure_never_publishes_core_or_edits_user_data(self): with tempfile.TemporaryDirectory() as temp: base, old, source, current, marker = test_versions.AssetVersions().fixture(Path(temp))