Merge branch 'main' into context_manager

This commit is contained in:
Andrew Murray 2025-01-27 21:31:34 +11:00 committed by GitHub
commit a8c88c213c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 169 additions and 209 deletions

View File

@ -35,10 +35,6 @@ jobs:
matrix: matrix:
os: ["ubuntu-latest"] os: ["ubuntu-latest"]
docker: [ docker: [
# Run slower jobs first to give them a headstart and reduce waiting time
ubuntu-24.04-noble-ppc64le,
ubuntu-24.04-noble-s390x,
# Then run the remainder
alpine, alpine,
amazon-2-amd64, amazon-2-amd64,
amazon-2023-amd64, amazon-2023-amd64,
@ -56,9 +52,13 @@ jobs:
dockerTag: [main] dockerTag: [main]
include: include:
- docker: "ubuntu-24.04-noble-ppc64le" - docker: "ubuntu-24.04-noble-ppc64le"
os: "ubuntu-22.04"
qemu-arch: "ppc64le" qemu-arch: "ppc64le"
dockerTag: main
- docker: "ubuntu-24.04-noble-s390x" - docker: "ubuntu-24.04-noble-s390x"
os: "ubuntu-22.04"
qemu-arch: "s390x" qemu-arch: "s390x"
dockerTag: main
- docker: "ubuntu-24.04-noble-arm64v8" - docker: "ubuntu-24.04-noble-arm64v8"
os: "ubuntu-24.04-arm" os: "ubuntu-24.04-arm"
dockerTag: main dockerTag: main

View File

@ -38,11 +38,11 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds # Package versions for fresh source builds
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=10.1.0 HARFBUZZ_VERSION=10.2.0
LIBPNG_VERSION=1.6.45 LIBPNG_VERSION=1.6.46
JPEGTURBO_VERSION=3.1.0 JPEGTURBO_VERSION=3.1.0
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.3 XZ_VERSION=5.6.4
TIFF_VERSION=4.6.0 TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16 LCMS2_VERSION=2.16
ZLIB_NG_VERSION=2.2.3 ZLIB_NG_VERSION=2.2.3

View File

@ -3,19 +3,18 @@ from __future__ import annotations
import zlib import zlib
from io import BytesIO from io import BytesIO
import pytest
from PIL import Image, ImageFile, PngImagePlugin from PIL import Image, ImageFile, PngImagePlugin
TEST_FILE = "Tests/images/png_decompression_dos.png" TEST_FILE = "Tests/images/png_decompression_dos.png"
def test_ignore_dos_text() -> None: def test_ignore_dos_text(monkeypatch: pytest.MonkeyPatch) -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try: with Image.open(TEST_FILE) as im:
im = Image.open(TEST_FILE)
im.load() im.load()
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values(): for s in im.text.values():

View File

@ -12,19 +12,16 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS
class TestDecompressionBomb: class TestDecompressionBomb:
def teardown_method(self) -> None:
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def test_no_warning_small_file(self) -> None: def test_no_warning_small_file(self) -> None:
# Implicit assert: no warning. # Implicit assert: no warning.
# A warning would cause a failure. # A warning would cause a failure.
with Image.open(TEST_FILE): with Image.open(TEST_FILE):
pass pass
def test_no_warning_no_limit(self) -> None: def test_no_warning_no_limit(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Arrange # Arrange
# Turn limit off # Turn limit off
Image.MAX_IMAGE_PIXELS = None monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None)
assert Image.MAX_IMAGE_PIXELS is None assert Image.MAX_IMAGE_PIXELS is None
# Act / Assert # Act / Assert
@ -33,18 +30,18 @@ class TestDecompressionBomb:
with Image.open(TEST_FILE): with Image.open(TEST_FILE):
pass pass
def test_warning(self) -> None: def test_warning(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Set limit to trigger warning on the test file # Set limit to trigger warning on the test file
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 128 * 128 - 1)
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
with pytest.warns(Image.DecompressionBombWarning): with pytest.warns(Image.DecompressionBombWarning):
with Image.open(TEST_FILE): with Image.open(TEST_FILE):
pass pass
def test_exception(self) -> None: def test_exception(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Set limit to trigger exception on the test file # Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 64 * 128 - 1)
assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1 assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
@ -66,9 +63,9 @@ class TestDecompressionBomb:
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
im.seek(1) im.seek(1)
def test_exception_gif_zero_width(self) -> None: def test_exception_gif_zero_width(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Set limit to trigger exception on the test file # Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 4 * 64 * 128 monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", 4 * 64 * 128)
assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128 assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):

View File

@ -39,9 +39,8 @@ def test_sanity() -> None:
assert im.is_animated assert im.is_animated
def test_prefix_chunk() -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
with Image.open(animated_test_file_with_prefix_chunk) as im: with Image.open(animated_test_file_with_prefix_chunk) as im:
assert isinstance(im, FliImagePlugin.FliImageFile) assert isinstance(im, FliImagePlugin.FliImageFile)
@ -56,8 +55,6 @@ def test_prefix_chunk() -> None:
assert palette[3:6] == [255, 255, 255] assert palette[3:6] == [255, 255, 255]
assert palette[381:384] == [204, 204, 12] assert palette[381:384] == [204, 204, 12]
assert palette[765:] == [252, 0, 0] assert palette[765:] == [252, 0, 0]
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")

View File

@ -113,7 +113,7 @@ def test_palette_not_needed_for_second_frame() -> None:
assert_image_similar(im, hopper("L").convert("RGB"), 8) assert_image_similar(im, hopper("L").convert("RGB"), 8)
def test_strategy() -> None: def test_strategy(monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB") expected_rgb_always = im.convert("RGB")
@ -123,8 +123,9 @@ def test_strategy() -> None:
im.seek(1) im.seek(1)
expected_different = im.convert("RGB") expected_different = im.convert("RGB")
try: monkeypatch.setattr(
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS GifImagePlugin, "LOADING_STRATEGY", GifImagePlugin.LoadingStrategy.RGB_ALWAYS
)
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert_image_equal(im, expected_rgb_always) assert_image_equal(im, expected_rgb_always)
@ -133,8 +134,10 @@ def test_strategy() -> None:
assert im.mode == "RGBA" assert im.mode == "RGBA"
assert_image_equal(im, expected_rgb_always_rgba) assert_image_equal(im, expected_rgb_always_rgba)
GifImagePlugin.LOADING_STRATEGY = ( monkeypatch.setattr(
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY GifImagePlugin,
"LOADING_STRATEGY",
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
) )
# Stay in P mode with only a global palette # Stay in P mode with only a global palette
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
@ -150,8 +153,6 @@ def test_strategy() -> None:
im.seek(1) im.seek(1)
assert im.mode == "RGB" assert im.mode == "RGB"
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_optimize() -> None: def test_optimize() -> None:
@ -583,17 +584,15 @@ def test_dispose_background_transparency() -> None:
def test_transparent_dispose( def test_transparent_dispose(
loading_strategy: GifImagePlugin.LoadingStrategy, loading_strategy: GifImagePlugin.LoadingStrategy,
expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]], expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]],
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy)
try:
with Image.open("Tests/images/transparent_dispose.gif") as img: with Image.open("Tests/images/transparent_dispose.gif") as img:
for frame in range(3): for frame in range(3):
img.seek(frame) img.seek(frame)
for x in range(3): for x in range(3):
color = img.getpixel((x, 0)) color = img.getpixel((x, 0))
assert color == expected_colors[frame][x] assert color == expected_colors[frame][x]
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_dispose_previous() -> None: def test_dispose_previous() -> None:
@ -1445,10 +1444,11 @@ def test_lzw_bits() -> None:
), ),
) )
def test_extents( def test_extents(
test_file: str, loading_strategy: GifImagePlugin.LoadingStrategy test_file: str,
loading_strategy: GifImagePlugin.LoadingStrategy,
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy monkeypatch.setattr(GifImagePlugin, "LOADING_STRATEGY", loading_strategy)
try:
with Image.open("Tests/images/" + test_file) as im: with Image.open("Tests/images/" + test_file) as im:
assert isinstance(im, GifImagePlugin.GifImageFile) assert isinstance(im, GifImagePlugin.GifImageFile)
assert im.size == (100, 100) assert im.size == (100, 100)
@ -1462,8 +1462,6 @@ def test_extents(
im.load() im.load()
assert im.im.size == (150, 150) assert im.im.size == (150, 150)
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_missing_background() -> None: def test_missing_background() -> None:

View File

@ -250,15 +250,14 @@ def test_draw_reloaded(tmp_path: Path) -> None:
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")
def test_truncated_mask() -> None: def test_truncated_mask(monkeypatch: pytest.MonkeyPatch) -> None:
# 1 bpp # 1 bpp
with open("Tests/images/hopper_mask.ico", "rb") as fp: with open("Tests/images/hopper_mask.ico", "rb") as fp:
data = fp.read() data = fp.read()
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
data = data[:-3] data = data[:-3]
try:
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
assert im.mode == "1" assert im.mode == "1"
@ -271,5 +270,3 @@ def test_truncated_mask() -> None:
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
assert im.mode == "RGB" assert im.mode == "RGB"
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False

View File

@ -550,12 +550,13 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_truncated_jpeg_should_read_all_the_data(self) -> None: def test_truncated_jpeg_should_read_all_the_data(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
filename = "Tests/images/truncated_jpeg.jpg" filename = "Tests/images/truncated_jpeg.jpg"
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open(filename) as im: with Image.open(filename) as im:
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert im.getbbox() is not None assert im.getbbox() is not None
def test_truncated_jpeg_throws_oserror(self) -> None: def test_truncated_jpeg_throws_oserror(self) -> None:
@ -1054,7 +1055,7 @@ class TestFileJpeg:
im.save(f, xmp=b"1" * 65505) im.save(f, xmp=b"1" * 65505)
@pytest.mark.timeout(timeout=1) @pytest.mark.timeout(timeout=1)
def test_eof(self) -> None: def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Even though this decoder never says that it is finished # Even though this decoder never says that it is finished
# the image should still end when there is no new data # the image should still end when there is no new data
class InfiniteMockPyDecoder(ImageFile.PyDecoder): class InfiniteMockPyDecoder(ImageFile.PyDecoder):
@ -1069,9 +1070,8 @@ class TestFileJpeg:
im.tile = [ im.tile = [
ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)), ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
] ]
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_separate_tables(self) -> None: def test_separate_tables(self) -> None:
im = hopper() im = hopper()

View File

@ -182,14 +182,11 @@ def test_load_dpi() -> None:
assert "dpi" not in im.info assert "dpi" not in im.info
def test_restricted_icc_profile() -> None: def test_restricted_icc_profile(monkeypatch: pytest.MonkeyPatch) -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
# JPEG2000 image with a restricted ICC profile and a known colorspace # JPEG2000 image with a restricted ICC profile and a known colorspace
with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im: with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.skipif( @pytest.mark.skipif(

View File

@ -1185,13 +1185,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(im.tag_v2[STRIPOFFSETS]) > 1 assert len(im.tag_v2[STRIPOFFSETS]) > 1
@pytest.mark.parametrize("argument", (True, False)) @pytest.mark.parametrize("argument", (True, False))
def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None: def test_save_single_strip(
self, argument: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
im = hopper("RGB").resize((256, 256)) im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
if not argument: if not argument:
TiffImagePlugin.STRIP_SIZE = 2**18 monkeypatch.setattr(TiffImagePlugin, "STRIP_SIZE", 2**18)
try:
arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"} arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
if argument: if argument:
arguments["strip_size"] = 2**18 arguments["strip_size"] = 2**18
@ -1200,8 +1201,6 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im: with Image.open(out) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert len(im.tag_v2[STRIPOFFSETS]) == 1 assert len(im.tag_v2[STRIPOFFSETS]) == 1
finally:
TiffImagePlugin.STRIP_SIZE = 65536
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
def test_save_zero(self, compression: str | None, tmp_path: Path) -> None: def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:

View File

@ -378,7 +378,7 @@ class TestFilePng:
with pytest.raises((OSError, SyntaxError)): with pytest.raises((OSError, SyntaxError)):
im.verify() im.verify()
def test_verify_ignores_crc_error(self) -> None: def test_verify_ignores_crc_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
# check ignores crc errors in ancillary chunks # check ignores crc errors in ancillary chunks
chunk_data = chunk(b"tEXt", b"spam") chunk_data = chunk(b"tEXt", b"spam")
@ -388,24 +388,20 @@ class TestFilePng:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
PngImagePlugin.PngImageFile(BytesIO(image_data)) PngImagePlugin.PngImageFile(BytesIO(image_data))
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
im = load(image_data) im = load(image_data)
assert im is not None assert im is not None
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None: def test_verify_not_ignores_crc_error_in_required_chunk(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
# check does not ignore crc errors in required chunks # check does not ignore crc errors in required chunks
image_data = MAGIC + IHDR[:-1] + b"q" + TAIL image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
PngImagePlugin.PngImageFile(BytesIO(image_data)) PngImagePlugin.PngImageFile(BytesIO(image_data))
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_roundtrip_dpi(self) -> None: def test_roundtrip_dpi(self) -> None:
# Check dpi roundtripping # Check dpi roundtripping
@ -616,7 +612,7 @@ class TestFilePng:
(b"prIV", b"VALUE3", True), (b"prIV", b"VALUE3", True),
] ]
def test_textual_chunks_after_idat(self) -> None: def test_textual_chunks_after_idat(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/hopper.png") as im: with Image.open("Tests/images/hopper.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
assert "comment" in im.text assert "comment" in im.text
@ -632,6 +628,11 @@ class TestFilePng:
with pytest.raises(OSError): with pytest.raises(OSError):
assert isinstance(im.text, dict) assert isinstance(im.text, dict)
# Raises an EOFError in load_end
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
# Raises a UnicodeDecodeError in load_end # Raises a UnicodeDecodeError in load_end
with Image.open("Tests/images/truncated_image.png") as im: with Image.open("Tests/images/truncated_image.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile) assert isinstance(im, PngImagePlugin.PngImageFile)
@ -639,14 +640,8 @@ class TestFilePng:
# The file is truncated # The file is truncated
with pytest.raises(OSError): with pytest.raises(OSError):
im.text im.text
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
assert isinstance(im.text, dict) assert isinstance(im.text, dict)
ImageFile.LOAD_TRUNCATED_IMAGES = False
# Raises an EOFError in load_end
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
def test_unknown_compression_method(self) -> None: def test_unknown_compression_method(self) -> None:
with pytest.raises(SyntaxError, match="Unknown compression method"): with pytest.raises(SyntaxError, match="Unknown compression method"):
@ -672,15 +667,16 @@ class TestFilePng:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT") "cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
) )
def test_truncated_chunks(self, cid: bytes) -> None: def test_truncated_chunks(
self, cid: bytes, monkeypatch: pytest.MonkeyPatch
) -> None:
fp = BytesIO() fp = BytesIO()
with PngImagePlugin.PngStream(fp) as png: with PngImagePlugin.PngStream(fp) as png:
with pytest.raises(ValueError): with pytest.raises(ValueError):
png.call(cid, 0, 0) png.call(cid, 0, 0)
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
png.call(cid, 0, 0) png.call(cid, 0, 0)
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.parametrize("save_all", (True, False)) @pytest.mark.parametrize("save_all", (True, False))
def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None: def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None:
@ -826,17 +822,14 @@ class TestFilePng:
with Image.open(mystdout) as reloaded: with Image.open(mystdout) as reloaded:
assert_image_equal_tofile(reloaded, TEST_PNG_FILE) assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
def test_truncated_end_chunk(self) -> None: def test_truncated_end_chunk(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/truncated_end_chunk.png") as im: with Image.open("Tests/images/truncated_end_chunk.png") as im:
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
with Image.open("Tests/images/truncated_end_chunk.png") as im: with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png") assert_image_equal_tofile(im, "Tests/images/hopper.png")
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@ -845,11 +838,11 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
mem_limit = 2 * 1024 # max increase in K mem_limit = 2 * 1024 # max increase in K
iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs
def test_leak_load(self) -> None: def test_leak_load(self, monkeypatch: pytest.MonkeyPatch) -> None:
with open("Tests/images/hopper.png", "rb") as f: with open("Tests/images/hopper.png", "rb") as f:
DATA = BytesIO(f.read(16 * 1024)) DATA = BytesIO(f.read(16 * 1024))
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open(DATA) as im: with Image.open(DATA) as im:
im.load() im.load()
@ -857,7 +850,4 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
with Image.open(DATA) as im: with Image.open(DATA) as im:
im.load() im.load()
try:
self._test_leak(core) self._test_leak(core)
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False

View File

@ -979,11 +979,10 @@ class TestFileTiff:
@pytest.mark.timeout(6) @pytest.mark.timeout(6)
@pytest.mark.filterwarnings("ignore:Truncated File Read") @pytest.mark.filterwarnings("ignore:Truncated File Read")
def test_timeout(self) -> None: def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/timeout-6646305047838720") as im: with Image.open("Tests/images/timeout-6646305047838720") as im:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_file", "test_file",

View File

@ -28,9 +28,9 @@ except ImportError:
class TestUnsupportedWebp: class TestUnsupportedWebp:
def test_unsupported(self) -> None: def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
if HAVE_WEBP: if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = False monkeypatch.setattr(WebPImagePlugin, "SUPPORTED", False)
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
@ -38,9 +38,6 @@ class TestUnsupportedWebp:
with Image.open(file_path): with Image.open(file_path):
pass pass
if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True
@skip_unless_feature("webp") @skip_unless_feature("webp")
class TestFileWebp: class TestFileWebp:

View File

@ -191,13 +191,10 @@ class TestImageFile:
im.load() im.load()
@skip_unless_feature("zlib") @skip_unless_feature("zlib")
def test_truncated_without_errors(self) -> None: def test_truncated_without_errors(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/truncated_image.png") as im: with Image.open("Tests/images/truncated_image.png") as im:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
im.load() im.load()
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
@skip_unless_feature("zlib") @skip_unless_feature("zlib")
def test_broken_datastream_with_errors(self) -> None: def test_broken_datastream_with_errors(self) -> None:
@ -206,13 +203,12 @@ class TestImageFile:
im.load() im.load()
@skip_unless_feature("zlib") @skip_unless_feature("zlib")
def test_broken_datastream_without_errors(self) -> None: def test_broken_datastream_without_errors(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
with Image.open("Tests/images/broken_data_stream.png") as im: with Image.open("Tests/images/broken_data_stream.png") as im:
ImageFile.LOAD_TRUNCATED_IMAGES = True monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
try:
im.load() im.load()
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
class MockPyDecoder(ImageFile.PyDecoder): class MockPyDecoder(ImageFile.PyDecoder):

View File

@ -7,36 +7,30 @@ import pytest
from PIL import Image from PIL import Image
def test_overflow() -> None: def test_overflow(monkeypatch: pytest.MonkeyPatch) -> None:
# There is the potential to overflow comparisons in map.c # There is the potential to overflow comparisons in map.c
# if there are > SIZE_MAX bytes in the image or if # if there are > SIZE_MAX bytes in the image or if
# the file encodes an offset that makes # the file encodes an offset that makes
# (offset + size(bytes)) > SIZE_MAX # (offset + size(bytes)) > SIZE_MAX
# Note that this image triggers the decompression bomb warning: # Note that this image triggers the decompression bomb warning:
max_pixels = Image.MAX_IMAGE_PIXELS monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None)
Image.MAX_IMAGE_PIXELS = None
# This image hits the offset test. # This image hits the offset test.
with Image.open("Tests/images/l2rgb_read.bmp") as im: with Image.open("Tests/images/l2rgb_read.bmp") as im:
with pytest.raises((ValueError, MemoryError, OSError)): with pytest.raises((ValueError, MemoryError, OSError)):
im.load() im.load()
Image.MAX_IMAGE_PIXELS = max_pixels
def test_tobytes(monkeypatch: pytest.MonkeyPatch) -> None:
def test_tobytes() -> None:
# Note that this image triggers the decompression bomb warning: # Note that this image triggers the decompression bomb warning:
max_pixels = Image.MAX_IMAGE_PIXELS monkeypatch.setattr(Image, "MAX_IMAGE_PIXELS", None)
Image.MAX_IMAGE_PIXELS = None
# Previously raised an access violation on Windows # Previously raised an access violation on Windows
with Image.open("Tests/images/l2rgb_read.bmp") as im: with Image.open("Tests/images/l2rgb_read.bmp") as im:
with pytest.raises((ValueError, MemoryError, OSError)): with pytest.raises((ValueError, MemoryError, OSError)):
im.tobytes() im.tobytes()
Image.MAX_IMAGE_PIXELS = max_pixels
@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
def test_ysize() -> None: def test_ysize() -> None:

View File

@ -2,7 +2,7 @@
# install libimagequant # install libimagequant
archive_name=libimagequant archive_name=libimagequant
archive_version=4.3.3 archive_version=4.3.4
archive=$archive_name-$archive_version archive=$archive_name-$archive_version

View File

@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.3.3** * Pillow has been tested with libimagequant **2.6-4.3.4**
* Libimagequant is licensed GPLv3, which is more restrictive than * Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled. with libimagequant support enabled.

View File

@ -113,14 +113,14 @@ V = {
"BROTLI": "1.1.0", "BROTLI": "1.1.0",
"FREETYPE": "2.13.3", "FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16", "FRIBIDI": "1.0.16",
"HARFBUZZ": "10.1.0", "HARFBUZZ": "10.2.0",
"JPEGTURBO": "3.1.0", "JPEGTURBO": "3.1.0",
"LCMS2": "2.16", "LCMS2": "2.16",
"LIBPNG": "1.6.45", "LIBPNG": "1.6.46",
"LIBWEBP": "1.5.0", "LIBWEBP": "1.5.0",
"OPENJPEG": "2.5.3", "OPENJPEG": "2.5.3",
"TIFF": "4.6.0", "TIFF": "4.6.0",
"XZ": "5.6.3", "XZ": "5.6.4",
"ZLIBNG": "2.2.3", "ZLIBNG": "2.2.3",
} }
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])