Skip to content

Commit e03f55a

Browse files
committed
Add a pinned test for max_length=0 calls with output pending
Fuzzing the zstd tests with Atheris found that ZstdDecompressor raises ZstdError on the sixteenth consecutive decompress(b'', 0) call when a stream is buffered and its output is pending: the C loop calls ZSTD_decompressStream with an empty output buffer, and libzstd gives up after sixteen calls that make no progress. LZMADecompressor fails the same shape on the fourth call, a variant of gh-160000. test_repeated_max_length_zero only exercises a fresh decompressor, so this adds a companion with a whole stream fed first. It is an expectedFailure for lzma and zstd; bz2 and zlib pass.
1 parent e795310 commit e03f55a

1 file changed

Lines changed: 43 additions & 3 deletions

File tree

‎Lib/test/test_compression_property.py‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,26 @@ def _check_repeated_max_length_zero(self, data, count):
380380
def test_repeated_max_length_zero(self, data, count):
381381
self._check_repeated_max_length_zero(data, count)
382382

383+
def _check_max_length_zero_with_pending_output(self, data, count):
384+
# The same, but with a whole stream buffered and its output pending.
385+
decomp = self.backend.new_decompressor()
386+
self.assertEqual(decomp.decompress(self.backend.compress(data), 0),
387+
b"")
388+
for _ in range(count):
389+
if decomp.eof:
390+
break
391+
self.assertEqual(decomp.decompress(b"", 0), b"")
392+
if not decomp.eof:
393+
self.assertEqual(decomp.decompress(b"", -1), data)
394+
self.assertTrue(decomp.eof)
395+
396+
@hypothesis.given(data=data_strategy, count=st.integers(1, 20))
397+
@hypothesis.example(data=b"", count=1)
398+
@hypothesis.example(data=b"\x00", count=16)
399+
@hypothesis.example(data=b"spam" * 10, count=20)
400+
def test_max_length_zero_with_pending_output(self, data, count):
401+
self._check_max_length_zero_with_pending_output(data, count)
402+
383403
@hypothesis.given(data=data_strategy, cut=st.integers(0, 10**6))
384404
@hypothesis.example(data=b"", cut=0)
385405
@hypothesis.example(data=b"", cut=10**6)
@@ -502,8 +522,10 @@ def test_needs_input_means_drained(self, data, sizes, max_lengths):
502522
# liblzma reports an error when two consecutive calls can make no progress,
503523
# which lzma.LZMADecompressor lets through as LZMAError("Insufficient buffer
504524
# space") for two max_length=0 calls in a row with nothing buffered
505-
# (gh-160000). The broad lzma tests avoid max_length=0 so that they keep
506-
# checking everything else; test_repeated_max_length_zero pins the bug.
525+
# (gh-160000), and on the fourth such call when output is pending. The
526+
# broad lzma tests avoid max_length=0 so that they keep checking everything
527+
# else; test_repeated_max_length_zero and
528+
# test_max_length_zero_with_pending_output pin the bug.
507529
lzma_max_length_strategy = st.integers(1, 64) | st.just(-1)
508530
lzma_max_lengths_strategy = st.lists(lzma_max_length_strategy, max_size=16)
509531

@@ -542,6 +564,23 @@ def test_needs_input_means_drained(self, data, sizes, max_lengths):
542564
def test_repeated_max_length_zero(self, data, count):
543565
self._check_repeated_max_length_zero(data, count)
544566

567+
@unittest.expectedFailure
568+
@hypothesis.given(data=data_strategy, count=st.integers(1, 20))
569+
@hypothesis.example(data=b"\x00", count=16)
570+
def test_max_length_zero_with_pending_output(self, data, count):
571+
self._check_max_length_zero_with_pending_output(data, count)
572+
573+
574+
class _ZstdMaxLengthZeroBug:
575+
# libzstd refuses the 16th consecutive call that makes no progress, and
576+
# ZstdDecompressor calls it even for max_length=0, so sixteen such calls
577+
# with output pending raise ZstdError.
578+
@unittest.expectedFailure
579+
@hypothesis.given(data=data_strategy, count=st.integers(1, 20))
580+
@hypothesis.example(data=b"\x00", count=16)
581+
def test_max_length_zero_with_pending_output(self, data, count):
582+
self._check_max_length_zero_with_pending_output(data, count)
583+
545584

546585
class LZMADecompressorMachine(_DecompressorMachine):
547586
# See lzma_max_length_strategy above.
@@ -581,7 +620,8 @@ class RawDeflateDecompressorTests(_ZlibNeedsInputBug, _DecompressorTests,
581620

582621

583622
@support.requires_zstd()
584-
class ZstdDecompressorTests(_DecompressorTests, unittest.TestCase):
623+
class ZstdDecompressorTests(_ZstdMaxLengthZeroBug, _DecompressorTests,
624+
unittest.TestCase):
585625
backend = _zstd_backend()
586626

587627

0 commit comments

Comments
 (0)