1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
https://github.com/pycompression/python-zlib-ng/commit/10d2ffd6e97dca906da84d59c5b39fad915b5262
From 10d2ffd6e97dca906da84d59c5b39fad915b5262 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?=
<16805946+edgarrmondragon@users.noreply.github.com>
Date: Fri, 3 Jul 2026 01:02:25 -0600
Subject: [PATCH] Fix a crash when calling ``copy()`` on a flushed compress
object on Python 3.15 (#80)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Fix a crash when calling `copy()` on a flushed compress object on Python 3.15
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
* test: Address Pytest 9.1 warnings
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
* chore: Allow usage of ` tox -e 3.15 -- <some pytest params>`
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
* ci: Add Python 3.15 and 3.15t to CI matrix
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
* ci: Replace `macos-13` runner with `macos-15-intel`
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
---------
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
---
src/zlib_ng/zlib_ngmodule.c | 3 ++-
tests/test_compat.py | 24 ++++++++++++------------
tests/test_gzip_ng.py | 2 +-
tests/test_gzip_ng_threaded.py | 4 ++--
tox.ini | 2 +-
7 files changed, 28 insertions(+), 20 deletions(-)
index ea71d53..c8c2a32 100644
--- a/src/zlib_ng/zlib_ngmodule.c
+++ b/src/zlib_ng/zlib_ngmodule.c
@@ -801,7 +801,8 @@ zlib_Compress_copy(compobject *self, PyObject *Py_UNUSED(ignored))
if (!self->is_initialised) {
PyErr_SetString(PyExc_ValueError, "Cannot copy flushed objects.");
- goto error;
+ Py_DECREF(return_value);
+ return NULL;
}
/* Copy the zstream state
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 12dda97..871bf84 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -64,21 +64,21 @@ def limited_zlib_tests(strategies=ZLIB_STRATEGIES):
@pytest.mark.parametrize(["data_size", "value"],
- itertools.product(DATA_SIZES, SEEDS))
+ list(itertools.product(DATA_SIZES, SEEDS)))
def test_crc32(data_size, value):
data = DATA[:data_size]
assert zlib.crc32(data, value) == zlib_ng.crc32(data, value)
@pytest.mark.parametrize(["data_size", "value"],
- itertools.product(DATA_SIZES, SEEDS))
+ list(itertools.product(DATA_SIZES, SEEDS)))
def test_adler32(data_size, value):
data = DATA[:data_size]
assert zlib.adler32(data, value) == zlib_ng.adler32(data, value)
@pytest.mark.parametrize(["data_size", "level", "wbits"],
- itertools.product(DATA_SIZES, range(10), WBITS_RANGE))
+ list(itertools.product(DATA_SIZES, range(10), WBITS_RANGE)))
def test_compress(data_size, level, wbits):
data = DATA[:data_size]
compressed = zlib_ng.compress(data, level=level, wbits=wbits)
@@ -87,7 +87,7 @@ def test_compress(data_size, level, wbits):
@pytest.mark.parametrize(["data_size", "level"],
- itertools.product(DATA_SIZES, range(10)))
+ list(itertools.product(DATA_SIZES, range(10))))
def test_decompress_zlib(data_size, level):
data = DATA[:data_size]
compressed = zlib.compress(data, level=level)
@@ -96,7 +96,7 @@ def test_decompress_zlib(data_size, level):
@pytest.mark.parametrize(["data_size", "level", "wbits", "memLevel", "strategy"],
- limited_zlib_tests(ZLIB_STRATEGIES))
+ list(limited_zlib_tests(ZLIB_STRATEGIES)))
def test_decompress_wbits(data_size, level, wbits, memLevel, strategy):
data = DATA[:data_size]
compressobj = zlib.compressobj(level=level, wbits=wbits, memLevel=memLevel,
@@ -107,7 +107,7 @@ def test_decompress_wbits(data_size, level, wbits, memLevel, strategy):
@pytest.mark.parametrize(["data_size", "level", "wbits"],
- itertools.product([128 * 1024], range(10), WBITS_RANGE),)
+ list(itertools.product([128 * 1024], range(10), WBITS_RANGE),))
def test_decompress_zlib_ng(data_size, level, wbits):
data = DATA[:data_size]
compressed = zlib_ng.compress(data, level=level, wbits=wbits)
@@ -116,7 +116,7 @@ def test_decompress_zlib_ng(data_size, level, wbits):
@pytest.mark.parametrize(["data_size", "level", "wbits", "memLevel", "strategy"],
- limited_zlib_tests(ZLIBNG_STRATEGIES))
+ list(limited_zlib_tests(ZLIBNG_STRATEGIES)))
def test_compress_compressobj(data_size, level, wbits, memLevel, strategy):
data = DATA[:data_size]
compressobj = zlib_ng.compressobj(level=level,
@@ -129,7 +129,7 @@ def test_compress_compressobj(data_size, level, wbits, memLevel, strategy):
@pytest.mark.parametrize(["data_size", "level", "wbits", "memLevel", "strategy"],
- limited_zlib_tests(ZLIB_STRATEGIES))
+ list(limited_zlib_tests(ZLIB_STRATEGIES)))
def test_decompress_decompressobj(data_size, level, wbits, memLevel, strategy):
data = DATA[:data_size]
compressobj = zlib.compressobj(level=level, wbits=wbits, memLevel=memLevel,
@@ -151,7 +151,7 @@ def test_decompressobj_unconsumed_tail():
@pytest.mark.parametrize(["data_size", "level"],
- itertools.product(DATA_SIZES, range(10)))
+ list(itertools.product(DATA_SIZES, range(10))))
def test_gzip_ng_compress(data_size, level):
data = DATA[:data_size]
compressed = gzip_ng.compress(data, compresslevel=level)
@@ -159,7 +159,7 @@ def test_gzip_ng_compress(data_size, level):
@pytest.mark.parametrize(["data_size", "level"],
- itertools.product(DATA_SIZES, range(10)))
+ list(itertools.product(DATA_SIZES, range(10))))
def test_decompress_gzip(data_size, level):
data = DATA[:data_size]
compressed = gzip.compress(data, compresslevel=level)
@@ -168,7 +168,7 @@ def test_decompress_gzip(data_size, level):
@pytest.mark.parametrize(["data_size", "level"],
- itertools.product(DATA_SIZES, range(10)))
+ list(itertools.product(DATA_SIZES, range(10))))
def test_decompress_gzip_ng(data_size, level):
data = DATA[:data_size]
compressed = gzip_ng.compress(data, compresslevel=level)
@@ -177,7 +177,7 @@ def test_decompress_gzip_ng(data_size, level):
@pytest.mark.parametrize(["unused_size", "wbits"],
- itertools.product([26], [-15, 15, 31]))
+ list(itertools.product([26], [-15, 15, 31])))
def test_unused_data(unused_size, wbits):
unused_data = b"abcdefghijklmnopqrstuvwxyz"[:unused_size]
compressor = zlib.compressobj(wbits=wbits)
diff --git a/tests/test_gzip_ng.py b/tests/test_gzip_ng.py
index abfd283..6c9d3c6 100644
--- a/tests/test_gzip_ng.py
+++ b/tests/test_gzip_ng.py
@@ -65,7 +65,7 @@ def test_GzipNGFile_read_truncated():
"reached")
-@pytest.mark.parametrize("level", range(1, 10))
+@pytest.mark.parametrize("level", list(range(1, 10)))
def test_decompress_stdin_stdout(capsysbinary, level):
"""Test if the command line can decompress data that has been compressed
by gzip at all levels."""
diff --git a/tests/test_gzip_ng_threaded.py b/tests/test_gzip_ng_threaded.py
index b976419..a587193 100644
--- a/tests/test_gzip_ng_threaded.py
+++ b/tests/test_gzip_ng_threaded.py
@@ -31,7 +31,7 @@ def test_threaded_read():
@pytest.mark.parametrize(["mode", "threads"],
- itertools.product(["wb", "wt"], [1, 3, -1]))
+ list(itertools.product(["wb", "wt"], [1, 3, -1])))
def test_threaded_write(mode, threads):
with tempfile.NamedTemporaryFile("wb", delete=False) as tmp:
# Use a small block size to simulate many writes.
@@ -216,7 +216,7 @@ def test_threaded_writer_does_not_close_stream():
@pytest.mark.timeout(5)
@pytest.mark.parametrize(
- ["mode", "threads"], itertools.product(["rb", "wb"], [1, 2]))
+ ["mode", "threads"], list(itertools.product(["rb", "wb"], [1, 2])))
def test_threaded_program_can_exit_on_error(tmp_path, mode, threads):
program = tmp_path / "no_context_manager.py"
test_file = tmp_path / "output.gz"
diff --git a/tox.ini b/tox.ini
index 14409e4..dfefd90 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,7 +15,7 @@ setenv=
PYTHONDEVMODE=1
commands =
# Create HTML coverage report for humans and xml coverage report for external services.
- coverage run --branch --source=zlib_ng -m pytest tests
+ coverage run --branch --source=zlib_ng -m pytest {posargs:tests}
# Ignore errors during report generation. Pypy does not generate proper coverage reports.
coverage html -i
coverage xml -i
|