Use monkeypatch (#9406)

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
This commit is contained in:
Andrew Murray 2026-01-28 08:43:14 +11:00 committed by GitHub
parent 6a5c588c5f
commit 29ff5fcb55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 69 additions and 84 deletions

View File

@ -119,36 +119,36 @@ def test_large_count(tmp_path: Path) -> None:
_roundtrip(tmp_path, im)
def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) -> None:
_last = ImageFile.MAXBLOCK
ImageFile.MAXBLOCK = size
try:
_roundtrip(tmp_path, im)
finally:
ImageFile.MAXBLOCK = _last
def _test_buffer_overflow(
tmp_path: Path, im: Image.Image, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(ImageFile, "MAXBLOCK", 1024)
_roundtrip(tmp_path, im)
def test_break_in_count_overflow(tmp_path: Path) -> None:
def test_break_in_count_overflow(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
im = Image.new("L", (256, 5))
px = im.load()
assert px is not None
for y in range(4):
for x in range(256):
px[x, y] = x % 128
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)
def test_break_one_in_loop(tmp_path: Path) -> None:
def test_break_one_in_loop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("L", (256, 5))
px = im.load()
assert px is not None
for y in range(5):
for x in range(256):
px[x, y] = x % 128
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)
def test_break_many_in_loop(tmp_path: Path) -> None:
def test_break_many_in_loop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("L", (256, 5))
px = im.load()
assert px is not None
@ -157,10 +157,10 @@ def test_break_many_in_loop(tmp_path: Path) -> None:
px[x, y] = x % 128
for x in range(8):
px[x, 4] = 16
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)
def test_break_one_at_end(tmp_path: Path) -> None:
def test_break_one_at_end(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("L", (256, 5))
px = im.load()
assert px is not None
@ -168,10 +168,10 @@ def test_break_one_at_end(tmp_path: Path) -> None:
for x in range(256):
px[x, y] = x % 128
px[0, 3] = 128 + 64
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)
def test_break_many_at_end(tmp_path: Path) -> None:
def test_break_many_at_end(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("L", (256, 5))
px = im.load()
assert px is not None
@ -181,10 +181,10 @@ def test_break_many_at_end(tmp_path: Path) -> None:
for x in range(4):
px[x * 2, 3] = 128 + 64
px[x + 256 - 4, 3] = 0
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)
def test_break_padding(tmp_path: Path) -> None:
def test_break_padding(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("L", (257, 5))
px = im.load()
assert px is not None
@ -193,4 +193,4 @@ def test_break_padding(tmp_path: Path) -> None:
px[x, y] = x % 128
for x in range(5):
px[x, 3] = 0
_test_buffer_overflow(tmp_path, im)
_test_buffer_overflow(tmp_path, im, monkeypatch)

View File

@ -654,21 +654,17 @@ class TestFilePng:
with pytest.raises(SyntaxError, match="Unknown compression method"):
PngImagePlugin.PngImageFile("Tests/images/unknown_compression_method.png")
def test_padded_idat(self) -> None:
def test_padded_idat(self, monkeypatch: pytest.MonkeyPatch) -> None:
# This image has been manually hexedited
# so that the IDAT chunk has padding at the end
# Set MAXBLOCK to the length of the actual data
# so that the decoder finishes reading before the chunk ends
MAXBLOCK = ImageFile.MAXBLOCK
ImageFile.MAXBLOCK = 45
ImageFile.LOAD_TRUNCATED_IMAGES = True
monkeypatch.setattr(ImageFile, "MAXBLOCK", 45)
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open("Tests/images/padded_idat.png") as im:
im.load()
ImageFile.MAXBLOCK = MAXBLOCK
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
@pytest.mark.parametrize(

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import pytest
from PIL import Image, ImageDraw, ImageFont, _util
from .helper import PillowLeakTestCase, features, skip_unless_feature
@ -7,11 +9,7 @@ from .helper import PillowLeakTestCase, features, skip_unless_feature
original_core = ImageFont.core
class TestTTypeFontLeak(PillowLeakTestCase):
# fails at iteration 3 in main
iterations = 10
mem_limit = 4096 # k
class TestFontLeak(PillowLeakTestCase):
def _test_font(self, font: ImageFont.FreeTypeFont | ImageFont.ImageFont) -> None:
im = Image.new("RGB", (255, 255), "white")
draw = ImageDraw.ImageDraw(im)
@ -21,23 +19,29 @@ class TestTTypeFontLeak(PillowLeakTestCase):
)
)
class TestTTypeFontLeak(TestFontLeak):
# fails at iteration 3 in main
iterations = 10
mem_limit = 4096 # k
@skip_unless_feature("freetype2")
def test_leak(self) -> None:
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
self._test_font(ttype)
class TestDefaultFontLeak(TestTTypeFontLeak):
class TestDefaultFontLeak(TestFontLeak):
# fails at iteration 37 in main
iterations = 100
mem_limit = 1024 # k
def test_leak(self) -> None:
def test_leak(self, monkeypatch: pytest.MonkeyPatch) -> None:
if features.check_module("freetype2"):
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
try:
default_font = ImageFont.load_default()
finally:
ImageFont.core = original_core
monkeypatch.setattr(
ImageFont,
"core",
_util.DeferredError(ImportError("Disabled for testing")),
)
default_font = ImageFont.load_default()
self._test_font(default_font)

View File

@ -456,9 +456,11 @@ class TestImage:
# Assert
assert len(Image.ID) == id_length
def test_registered_extensions_uninitialized(self) -> None:
def test_registered_extensions_uninitialized(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
# Arrange
Image._initialized = 0
monkeypatch.setattr(Image, "_initialized", 0)
# Act
Image.registered_extensions()

View File

@ -1485,21 +1485,15 @@ def test_stroke_multiline() -> None:
@skip_unless_feature("freetype2")
def test_setting_default_font() -> None:
# Arrange
def test_setting_default_font(monkeypatch: pytest.MonkeyPatch) -> None:
im = Image.new("RGB", (100, 250))
draw = ImageDraw.Draw(im)
assert isinstance(draw.getfont(), ImageFont.load_default().__class__)
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
# Act
ImageDraw.ImageDraw.font = font
# Assert
try:
assert draw.getfont() == font
finally:
ImageDraw.ImageDraw.font = None
assert isinstance(draw.getfont(), ImageFont.load_default().__class__)
monkeypatch.setattr(ImageDraw.ImageDraw, "font", font)
assert draw.getfont() == font
def test_default_font_size() -> None:

View File

@ -31,7 +31,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
class TestImageFile:
def test_parser(self) -> None:
def test_parser(self, monkeypatch: pytest.MonkeyPatch) -> None:
def roundtrip(format: str) -> tuple[Image.Image, Image.Image]:
im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
if format in ("MSP", "XBM"):
@ -55,12 +55,9 @@ class TestImageFile:
assert_image_equal(*roundtrip("IM"))
assert_image_equal(*roundtrip("MSP"))
if features.check("zlib"):
try:
# force multiple blocks in PNG driver
ImageFile.MAXBLOCK = 8192
assert_image_equal(*roundtrip("PNG"))
finally:
ImageFile.MAXBLOCK = MAXBLOCK
# force multiple blocks in PNG driver
monkeypatch.setattr(ImageFile, "MAXBLOCK", 8192)
assert_image_equal(*roundtrip("PNG"))
assert_image_equal(*roundtrip("PPM"))
assert_image_equal(*roundtrip("TIFF"))
assert_image_equal(*roundtrip("XBM"))
@ -120,14 +117,11 @@ class TestImageFile:
assert (128, 128) == p.image.size
@skip_unless_feature("zlib")
def test_safeblock(self) -> None:
def test_safeblock(self, monkeypatch: pytest.MonkeyPatch) -> None:
im1 = hopper()
try:
ImageFile.SAFEBLOCK = 1
im2 = fromstring(tostring(im1, "PNG"))
finally:
ImageFile.SAFEBLOCK = SAFEBLOCK
monkeypatch.setattr(ImageFile, "SAFEBLOCK", 1)
im2 = fromstring(tostring(im1, "PNG"))
assert_image_equal(im1, im2)

View File

@ -38,20 +38,18 @@ def test_invalid_mode() -> None:
font._load_pilfont_data(fp, im)
def test_without_freetype() -> None:
original_core = ImageFont.core
def test_without_freetype(monkeypatch: pytest.MonkeyPatch) -> None:
if features.check_module("freetype2"):
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
try:
with pytest.raises(ImportError):
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
monkeypatch.setattr(
ImageFont, "core", _util.DeferredError(ImportError("Disabled for testing"))
)
with pytest.raises(ImportError):
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
assert isinstance(ImageFont.load_default(), ImageFont.ImageFont)
assert isinstance(ImageFont.load_default(), ImageFont.ImageFont)
with pytest.raises(ImportError):
ImageFont.load_default(size=14)
finally:
ImageFont.core = original_core
with pytest.raises(ImportError):
ImageFont.load_default(size=14)
@pytest.mark.parametrize("font", fonts)

View File

@ -5,6 +5,8 @@ import sys
from io import BytesIO
from pathlib import Path
import pytest
from PIL import Image, PSDraw
@ -47,21 +49,16 @@ def test_draw_postscript(tmp_path: Path) -> None:
assert os.path.getsize(tempfile) > 0
def test_stdout() -> None:
def test_stdout(monkeypatch: pytest.MonkeyPatch) -> None:
# Temporarily redirect stdout
old_stdout = sys.stdout
class MyStdOut:
buffer = BytesIO()
mystdout = MyStdOut()
sys.stdout = mystdout
monkeypatch.setattr(sys, "stdout", mystdout)
ps = PSDraw.PSDraw()
_create_document(ps)
# Reset stdout
sys.stdout = old_stdout
assert mystdout.buffer.getvalue() != b""