import os from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath import platform import tempfile import unittest from unittest import mock from blender_asset_tracer.bpathlib import BlendPath, make_absolute, strip_root class BlendPathTest(unittest.TestCase): def test_string_path(self): p = BlendPath(PurePosixPath("//some/file.blend")) self.assertEqual("//some/file.blend", str(PurePosixPath("//some/file.blend"))) self.assertEqual(b"//some/file.blend", p) p = BlendPath(Path(r"C:\some\file.blend")) self.assertEqual(b"C:/some/file.blend", p) def test_invalid_type(self): with self.assertRaises(TypeError): BlendPath("//some/file.blend") with self.assertRaises(TypeError): BlendPath(47) with self.assertRaises(TypeError): BlendPath(None) def test_repr(self): p = BlendPath(b"//some/file.blend") self.assertEqual("BlendPath(b'//some/file.blend')", repr(p)) p = BlendPath(PurePosixPath("//some/file.blend")) self.assertEqual("BlendPath(b'//some/file.blend')", repr(p)) def test_to_path(self): self.assertEqual( PurePath("/some/file.blend"), BlendPath(b"/some/file.blend").to_path() ) self.assertEqual( PurePath("C:/some/file.blend"), BlendPath(b"C:/some/file.blend").to_path() ) self.assertEqual( PurePath("C:/some/file.blend"), BlendPath(rb"C:\some\file.blend").to_path() ) with mock.patch("sys.getfilesystemencoding") as mock_getfse: mock_getfse.return_value = "latin1" # \xe9 is Latin-1 for é, and BlendPath should revert to using the # (mocked) filesystem encoding when decoding as UTF-8 fails. self.assertEqual( PurePath("C:/some/filé.blend"), BlendPath(b"C:\\some\\fil\xe9.blend").to_path(), ) with self.assertRaises(ValueError): BlendPath(b"//relative/path.jpg").to_path() def test_is_absolute(self): self.assertFalse(BlendPath(b"//some/file.blend").is_absolute()) self.assertTrue(BlendPath(b"/some/file.blend").is_absolute()) self.assertTrue(BlendPath(b"C:/some/file.blend").is_absolute()) self.assertTrue(BlendPath(b"C:\\some\\file.blend").is_absolute()) self.assertFalse(BlendPath(b"some/file.blend").is_absolute()) def test_is_blendfile_relative(self): self.assertTrue(BlendPath(b"//some/file.blend").is_blendfile_relative()) self.assertFalse(BlendPath(b"/some/file.blend").is_blendfile_relative()) self.assertFalse(BlendPath(b"C:/some/file.blend").is_blendfile_relative()) self.assertFalse(BlendPath(b"some/file.blend").is_blendfile_relative()) def test_make_absolute(self): self.assertEqual( b"/root/to/some/file.blend", BlendPath(b"//some/file.blend").absolute(b"/root/to"), ) self.assertEqual( b"/root/to/some/file.blend", BlendPath(b"some/file.blend").absolute(b"/root/to"), ) self.assertEqual( b"/root/to/../some/file.blend", BlendPath(b"../some/file.blend").absolute(b"/root/to"), ) self.assertEqual( b"/shared/some/file.blend", BlendPath(b"/shared/some/file.blend").absolute(b"/root/to"), ) def test_slash(self): self.assertEqual( b"/root/and/parent.blend", BlendPath(b"/root/and") / b"parent.blend" ) with self.assertRaises(ValueError): BlendPath(b"/root/and") / b"/parent.blend" self.assertEqual( b"/root/and/parent.blend", b"/root/and" / BlendPath(b"parent.blend") ) with self.assertRaises(ValueError): b"/root/and" / BlendPath(b"/parent.blend") # On Windows+Python 3.5.4 this resulted in b'//root//parent.blend', # but only if the root is a single term (so not b'//root/and/'). self.assertEqual( BlendPath(b"//root/parent.blend"), BlendPath(b"//root/") / b"parent.blend" ) @unittest.skipIf( platform.system() == "Windows", "POSIX paths cannot be used on Windows" ) def test_mkrelative_posix(self): self.assertEqual( b"//asset.png", BlendPath.mkrelative( Path("/path/to/asset.png"), PurePosixPath("/path/to/bfile.blend"), ), ) self.assertEqual( b"//to/asset.png", BlendPath.mkrelative( Path("/path/to/asset.png"), PurePosixPath("/path/bfile.blend"), ), ) self.assertEqual( b"//../of/asset.png", BlendPath.mkrelative( Path("/path/of/asset.png"), PurePosixPath("/path/to/bfile.blend"), ), ) self.assertEqual( b"//../../path/of/asset.png", BlendPath.mkrelative( Path("/path/of/asset.png"), PurePosixPath("/some/weird/bfile.blend"), ), ) self.assertEqual( b"//very/very/very/very/very/deep/asset.png", BlendPath.mkrelative( Path("/path/to/very/very/very/very/very/deep/asset.png"), PurePosixPath("/path/to/bfile.blend"), ), ) self.assertEqual( b"//../../../../../../../../shallow/asset.png", BlendPath.mkrelative( Path("/shallow/asset.png"), PurePosixPath("/path/to/very/very/very/very/very/deep/bfile.blend"), ), ) @unittest.skipIf( platform.system() != "Windows", "Windows paths cannot be used on POSIX" ) def test_mkrelative_windows(self): self.assertEqual( b"//asset.png", BlendPath.mkrelative( Path("Q:/path/to/asset.png"), PureWindowsPath("Q:/path/to/bfile.blend"), ), ) self.assertEqual( b"//to/asset.png", BlendPath.mkrelative( Path("Q:/path/to/asset.png"), PureWindowsPath("Q:/path/bfile.blend"), ), ) self.assertEqual( b"//../of/asset.png", BlendPath.mkrelative( Path("Q:/path/of/asset.png"), PureWindowsPath("Q:/path/to/bfile.blend"), ), ) self.assertEqual( b"//../../path/of/asset.png", BlendPath.mkrelative( Path("Q:/path/of/asset.png"), PureWindowsPath("Q:/some/weird/bfile.blend"), ), ) self.assertEqual( b"//very/very/very/very/very/deep/asset.png", BlendPath.mkrelative( Path("Q:/path/to/very/very/very/very/very/deep/asset.png"), PureWindowsPath("Q:/path/to/bfile.blend"), ), ) self.assertEqual( b"//../../../../../../../../shallow/asset.png", BlendPath.mkrelative( Path("Q:/shallow/asset.png"), PureWindowsPath("Q:/path/to/very/very/very/very/very/deep/bfile.blend"), ), ) self.assertEqual( b"D:/path/to/asset.png", BlendPath.mkrelative( Path("D:/path/to/asset.png"), PureWindowsPath("Q:/path/to/bfile.blend"), ), ) class MakeAbsoluteTest(unittest.TestCase): def test_relative(self): my_dir = Path(__file__).absolute().parent cwd = os.getcwd() try: os.chdir(my_dir) self.assertEqual( my_dir / "blendfiles/Cube.btx", make_absolute(Path("blendfiles/Cube.btx")), ) except Exception: os.chdir(cwd) raise @unittest.skipIf(platform.system() != "Windows", "This test uses drive letters") def test_relative_drive(self): cwd = os.getcwd() my_drive = Path(f"{Path(cwd).drive}/") self.assertEqual( my_drive / "blendfiles/Cube.btx", make_absolute(Path("/blendfiles/Cube.btx")), ) def test_drive_letters(self): """PureWindowsPath should be accepted and work well on POSIX systems too.""" in_path = PureWindowsPath("R:/wrongroot/oops/../../path/to/a/file") expect_path = Path("R:/path/to/a/file") self.assertNotEqual( expect_path, in_path, "pathlib should not automatically resolve ../" ) self.assertEqual(expect_path, make_absolute(in_path)) @unittest.skipIf(platform.system() == "Windows", "This test ignores drive letters") def test_dotdot_dotdot_posix(self): in_path = Path("/wrongroot/oops/../../path/to/a/file") expect_path = Path("/path/to/a/file") self.assertNotEqual( expect_path, in_path, "pathlib should not automatically resolve ../" ) self.assertEqual(expect_path, make_absolute(in_path)) @unittest.skipIf(platform.system() != "Windows", "This test uses drive letters") def test_dotdot_dotdot_windows(self): in_path = Path("Q:/wrongroot/oops/../../path/to/a/file") expect_path = Path("Q:/path/to/a/file") self.assertNotEqual( expect_path, in_path, "pathlib should not automatically resolve ../" ) self.assertEqual(expect_path, make_absolute(in_path)) @unittest.skipIf(platform.system() == "Windows", "This test ignores drive letters") def test_way_too_many_dotdot_posix(self): in_path = Path("/webroot/../../../../../etc/passwd") expect_path = Path("/etc/passwd") self.assertEqual(expect_path, make_absolute(in_path)) @unittest.skipIf(platform.system() != "Windows", "This test uses drive letters") def test_way_too_many_dotdot_windows(self): in_path = Path("G:/webroot/../../../../../etc/passwd") expect_path = Path("G:/etc/passwd") self.assertEqual(expect_path, make_absolute(in_path)) @unittest.skipIf( platform.system() == "Windows", "Symlinks on Windows require Administrator rights", ) def test_symlinks(self): with tempfile.TemporaryDirectory(suffix="-bat-symlink-test") as tmpdir_str: tmpdir = Path(tmpdir_str) orig_path = tmpdir / "some_file.txt" with orig_path.open("w") as outfile: outfile.write("this file exists now") symlink = tmpdir / "subdir" / "linked.txt" symlink.parent.mkdir() symlink.symlink_to(orig_path) self.assertEqual( symlink, make_absolute(symlink), "Symlinks should not be resolved" ) @unittest.skipIf( platform.system() != "Windows", "Drive letters mapped to network share can only be tested on Windows", ) @unittest.skip( "Mapped drive letter testing should be mocked, but that is hard to do" ) def test_mapped_drive_letters(self): pass def test_path_types(self): platorm_path = type(PurePath()) self.assertIsInstance( make_absolute(PureWindowsPath("/some/path")), platorm_path ) self.assertIsInstance(make_absolute(PurePosixPath("/some/path")), platorm_path) class StripRootTest(unittest.TestCase): def test_windows_paths(self): self.assertEqual(PurePosixPath(), strip_root(PureWindowsPath())) self.assertEqual( PurePosixPath("C/Program Files/Blender"), strip_root(PureWindowsPath("C:/Program Files/Blender")), ) self.assertEqual( PurePosixPath("C/Program Files/Blender"), strip_root(PureWindowsPath("C:\\Program Files\\Blender")), ) self.assertEqual( PurePosixPath("C/Program Files/Blender"), strip_root(PureWindowsPath("C\\Program Files\\Blender")), ) def test_posix_paths(self): self.assertEqual(PurePosixPath(), strip_root(PurePosixPath())) self.assertEqual( PurePosixPath("C/path/to/blender"), strip_root(PurePosixPath("C:/path/to/blender")), ) self.assertEqual( PurePosixPath("C/path/to/blender"), strip_root(PurePosixPath("C/path/to/blender")), ) self.assertEqual( PurePosixPath("C/path/to/blender"), strip_root(PurePosixPath("/C/path/to/blender")), )