codekingpro/portable-devtools
115k
1import importlib2import os3import unittest4 5import importlib_resources as resources6 7from . import util8from .compat.py39 import warnings_helper9 10 11class StringAnchorMixin:12 anchor01 = 'data01'13 anchor02 = 'data02'14 15 16class ModuleAnchorMixin:17 @property18 def anchor01(self):19 return importlib.import_module('data01')20 21 @property22 def anchor02(self):23 return importlib.import_module('data02')24 25 26class FunctionalAPIBase:27 def setUp(self):28 super().setUp()29 self.load_fixture('data02')30 31 def _gen_resourcetxt_path_parts(self):32 """Yield various names of a text file in anchor02, each in a subTest"""33 for path_parts in (34 ('subdirectory', 'subsubdir', 'resource.txt'),35 ('subdirectory/subsubdir/resource.txt',),36 ('subdirectory/subsubdir', 'resource.txt'),37 ):38 with self.subTest(path_parts=path_parts):39 yield path_parts40 41 @staticmethod42 def remove_utf16_bom(string):43 """Remove an architecture-specific UTF-16 BOM prefix when present.44 45 Some platforms surface UTF-16 BOM bytes as escaped text when the46 fixture is intentionally decoded as UTF-8 with ``errors='backslashreplace'``.47 Strip that prefix so assertions validate content consistently."""48 for bom in ('\\xff\\xfe', '\\xfe\\xff', '\ufeff'):49 if string.startswith(bom):50 return string[len(bom) :]51 return string52 53 def test_read_text(self):54 assert (55 resources.read_text(self.anchor01, 'utf-8.file') == 'Hello, UTF-8 world!\n'56 )57 assert (58 resources.read_text(59 self.anchor02,60 'subdirectory',61 'subsubdir',62 'resource.txt',63 encoding='utf-8',64 )65 == 'a resource'66 )67 for path_parts in self._gen_resourcetxt_path_parts():68 assert (69 resources.read_text(70 self.anchor02,71 *path_parts,72 encoding='utf-8',73 )74 == 'a resource'75 )76 # Use generic OSError, since e.g. attempting to read a directory can77 # fail with PermissionError rather than IsADirectoryError78 with self.assertRaises(OSError):79 resources.read_text(self.anchor01)80 with self.assertRaises((OSError, resources.abc.TraversalError)):81 resources.read_text(self.anchor01, 'no-such-file')82 with self.assertRaises(UnicodeDecodeError):83 resources.read_text(self.anchor01, 'utf-16.file')84 assert (85 resources.read_text(86 self.anchor01,87 'binary.file',88 encoding='latin1',89 )90 == '\x00\x01\x02\x03'91 )92 assert self.remove_utf16_bom(93 resources.read_text(94 self.anchor01,95 'utf-16.file',96 errors='backslashreplace',97 ),98 ) == 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(99 errors='backslashreplace',100 )101 102 def test_read_binary(self):103 assert (104 resources.read_binary(self.anchor01, 'utf-8.file')105 == b'Hello, UTF-8 world!\n'106 )107 for path_parts in self._gen_resourcetxt_path_parts():108 assert resources.read_binary(self.anchor02, *path_parts) == b'a resource'109 110 def test_open_text(self):111 with resources.open_text(self.anchor01, 'utf-8.file') as f:112 assert f.read() == 'Hello, UTF-8 world!\n'113 for path_parts in self._gen_resourcetxt_path_parts():114 with resources.open_text(115 self.anchor02,116 *path_parts,117 encoding='utf-8',118 ) as f:119 assert f.read() == 'a resource'120 # Use generic OSError, since e.g. attempting to read a directory can121 # fail with PermissionError rather than IsADirectoryError122 with self.assertRaises(OSError):123 resources.open_text(self.anchor01)124 with self.assertRaises((OSError, resources.abc.TraversalError)):125 resources.open_text(self.anchor01, 'no-such-file')126 with resources.open_text(self.anchor01, 'utf-16.file') as f:127 with self.assertRaises(UnicodeDecodeError):128 f.read()129 with resources.open_text(130 self.anchor01,131 'binary.file',132 encoding='latin1',133 ) as f:134 assert f.read() == '\x00\x01\x02\x03'135 with resources.open_text(136 self.anchor01,137 'utf-16.file',138 errors='backslashreplace',139 ) as f:140 assert self.remove_utf16_bom(f.read()) == 'Hello, UTF-16 world!\n'.encode(141 'utf-16-le'142 ).decode(143 errors='backslashreplace',144 )145 146 def test_open_binary(self):147 with resources.open_binary(self.anchor01, 'utf-8.file') as f:148 assert f.read() == b'Hello, UTF-8 world!\n'149 for path_parts in self._gen_resourcetxt_path_parts():150 with resources.open_binary(151 self.anchor02,152 *path_parts,153 ) as f:154 assert f.read() == b'a resource'155 156 def test_path(self):157 with resources.path(self.anchor01, 'utf-8.file') as path:158 with open(str(path), encoding='utf-8') as f:159 assert f.read() == 'Hello, UTF-8 world!\n'160 with resources.path(self.anchor01) as path:161 with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f:162 assert f.read() == 'Hello, UTF-8 world!\n'163 164 def test_is_resource(self):165 is_resource = resources.is_resource166 assert is_resource(self.anchor01, 'utf-8.file')167 assert not is_resource(self.anchor01, 'no_such_file')168 assert not is_resource(self.anchor01)169 assert not is_resource(self.anchor01, 'subdirectory')170 for path_parts in self._gen_resourcetxt_path_parts():171 assert is_resource(self.anchor02, *path_parts)172 173 def test_contents(self):174 with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):175 c = resources.contents(self.anchor01)176 assert set(c) >= {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}177 with (178 self.assertRaises(OSError),179 warnings_helper.check_warnings((180 ".*contents.*",181 DeprecationWarning,182 )),183 ):184 list(resources.contents(self.anchor01, 'utf-8.file'))185 186 for path_parts in self._gen_resourcetxt_path_parts():187 with (188 self.assertRaises((OSError, resources.abc.TraversalError)),189 warnings_helper.check_warnings((190 ".*contents.*",191 DeprecationWarning,192 )),193 ):194 list(resources.contents(self.anchor01, *path_parts))195 with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):196 c = resources.contents(self.anchor01, 'subdirectory')197 assert set(c) >= {'binary.file'}198 199 @warnings_helper.ignore_warnings(category=DeprecationWarning)200 def test_common_errors(self):201 for func in (202 resources.read_text,203 resources.read_binary,204 resources.open_text,205 resources.open_binary,206 resources.path,207 resources.is_resource,208 resources.contents,209 ):210 with self.subTest(func=func):211 # Rejecting None anchor212 with self.assertRaises(TypeError):213 func(None)214 # Rejecting invalid anchor type215 with self.assertRaises((TypeError, AttributeError)):216 func(1234)217 # Unknown module218 with self.assertRaises(ModuleNotFoundError):219 func('$missing module$')220 221 def test_text_errors(self):222 for func in (223 resources.read_text,224 resources.open_text,225 ):226 with self.subTest(func=func):227 # Multiple path arguments need explicit encoding argument.228 with self.assertRaises(TypeError):229 func(230 self.anchor02,231 'subdirectory',232 'subsubdir',233 'resource.txt',234 )235 236 237class FunctionalAPITest_StringAnchor_Disk(238 StringAnchorMixin,239 FunctionalAPIBase,240 util.DiskSetup,241 unittest.TestCase,242):243 pass244 245 246class FunctionalAPITest_ModuleAnchor_Disk(247 ModuleAnchorMixin,248 FunctionalAPIBase,249 util.DiskSetup,250 unittest.TestCase,251):252 pass253 254 255class FunctionalAPITest_StringAnchor_Memory(256 StringAnchorMixin,257 FunctionalAPIBase,258 util.MemorySetup,259 unittest.TestCase,260):261 pass262 