|
33 | 33 | with_source_date_epoch, without_source_date_epoch, |
34 | 34 | ) |
35 | 35 | from test.support.import_helper import ensure_lazy_imports |
36 | | -from test.support.warnings_helper import check_no_resource_warning |
| 36 | +from test.support.warnings_helper import ( |
| 37 | + check_no_resource_warning, ignore_warnings, |
| 38 | +) |
37 | 39 |
|
38 | 40 |
|
39 | 41 | TESTFN2 = TESTFN + "2" |
@@ -4916,6 +4918,75 @@ class ZstdBoundedDecompressTests(AbstractBoundedDecompressTests, |
4916 | 4918 | compression = zipfile.ZIP_ZSTANDARD |
4917 | 4919 |
|
4918 | 4920 |
|
| 4921 | +class MonkeypatchedDecompressorTests(unittest.TestCase): |
| 4922 | + # Some third-party projects monkey-patch _get_decompressor() to add |
| 4923 | + # additional compression schemes. This can break at any time as the |
| 4924 | + # internal compressor objects change. |
| 4925 | + # To protect users, we try to keep this case working. |
| 4926 | + # See also: GH-156002 and GH-113767. |
| 4927 | + COMPRESSION = 99 |
| 4928 | + |
| 4929 | + class Compressor: |
| 4930 | + """Compressor with only the original BZ2Compressor API""" |
| 4931 | + def compress(self, data): |
| 4932 | + return data.swapcase() |
| 4933 | + |
| 4934 | + def flush(self): |
| 4935 | + return b'' |
| 4936 | + |
| 4937 | + class Decompressor: |
| 4938 | + """Decompressor with only the 3.3+ BZ2Decompressor API""" |
| 4939 | + eof = False |
| 4940 | + |
| 4941 | + def decompress(self, data): |
| 4942 | + return data.swapcase() |
| 4943 | + |
| 4944 | + def setUp(self): |
| 4945 | + orig_check_compression = zipfile._check_compression |
| 4946 | + orig_get_compressor = zipfile._get_compressor |
| 4947 | + orig_get_decompressor = zipfile._get_decompressor |
| 4948 | + |
| 4949 | + def check_compression(compression): |
| 4950 | + if compression != self.COMPRESSION: |
| 4951 | + orig_check_compression(compression) |
| 4952 | + |
| 4953 | + def get_compressor(compress_type, compresslevel=None): |
| 4954 | + if compress_type == self.COMPRESSION: |
| 4955 | + return self.Compressor() |
| 4956 | + return orig_get_compressor(compress_type, compresslevel) |
| 4957 | + |
| 4958 | + def get_decompressor(compress_type): |
| 4959 | + if compress_type == self.COMPRESSION: |
| 4960 | + return self.Decompressor() |
| 4961 | + return orig_get_decompressor(compress_type) |
| 4962 | + |
| 4963 | + self.enterContext(mock.patch.object( |
| 4964 | + zipfile, '_check_compression', check_compression)) |
| 4965 | + self.enterContext(mock.patch.object( |
| 4966 | + zipfile, '_get_compressor', get_compressor)) |
| 4967 | + self.enterContext(mock.patch.object( |
| 4968 | + zipfile, '_get_decompressor', get_decompressor)) |
| 4969 | + |
| 4970 | + def test_roundtrip_monkeypatched_decompressor(self): |
| 4971 | + data = bytes(range(256)) * 8 |
| 4972 | + buf = io.BytesIO() |
| 4973 | + with zipfile.ZipFile(buf, "w", compression=self.COMPRESSION) as zf: |
| 4974 | + zf.writestr("member", data) |
| 4975 | + self.assertIn(data.swapcase(), buf.getvalue()) |
| 4976 | + with (ignore_warnings(category=DeprecationWarning, |
| 4977 | + message='.*two arguments.*'), |
| 4978 | + zipfile.ZipFile(io.BytesIO(buf.getvalue())) as zf): |
| 4979 | + self.assertEqual(zf.read("member"), data) |
| 4980 | + with zf.open("member") as f: |
| 4981 | + self.assertEqual(f.read(100), data[:100]) |
| 4982 | + self.assertEqual(f.read1(100), data[100:200]) |
| 4983 | + f.seek(-100, os.SEEK_END) |
| 4984 | + self.assertEqual(f.read(), data[-100:]) |
| 4985 | + # Rewinding past the read buffer re-creates the decompressor. |
| 4986 | + f.seek(0) |
| 4987 | + self.assertEqual(f.read(), data) |
| 4988 | + |
| 4989 | + |
4919 | 4990 | class AbstractBadCrcTests: |
4920 | 4991 | def test_testzip_with_bad_crc(self): |
4921 | 4992 | """Tests that files with bad CRCs return their name from testzip.""" |
|
0 commit comments