Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Tests/check_imaging_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ def _get_mem_usage() -> float:


def _test_leak(
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
min_iterations: int,
max_iterations: int,
fn: Callable[..., Image.Image | None],
*args: Any,
) -> None:
mem_limit = None
for i in range(max_iterations):
Expand Down
3 changes: 3 additions & 0 deletions Tests/check_png_dos.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def test_ignore_dos_text() -> None:
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False

assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"

Expand All @@ -32,6 +33,7 @@ def test_dos_text() -> None:
assert msg, "Decompressed Data Too Large"
return

assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"

Expand All @@ -57,6 +59,7 @@ def test_dos_total_memory() -> None:
return

total_len = 0
assert isinstance(im2, PngImagePlugin.PngImageFile)
for txt in im2.text.values():
total_len += len(txt)
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
2 changes: 1 addition & 1 deletion Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def is_mingw() -> bool:


class CachedProperty:
def __init__(self, func: Callable[[Any], None]) -> None:
def __init__(self, func: Callable[[Any], Any]) -> None:
self.func = func

def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
Expand Down
8 changes: 5 additions & 3 deletions Tests/test_file_container.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Literal

import pytest

from PIL import ContainerIO, Image
Expand All @@ -22,14 +24,14 @@ def test_isatty() -> None:


@pytest.mark.parametrize(
"mode, expected_value",
"mode, expected_position",
(
(0, 33),
(1, 66),
(2, 100),
),
)
def test_seek_mode(mode: int, expected_value: int) -> None:
def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
# Arrange
with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
Expand All @@ -39,7 +41,7 @@ def test_seek_mode(mode: int, expected_value: int) -> None:
container.seek(33, mode)

# Assert
assert container.tell() == expected_value
assert container.tell() == expected_position


@pytest.mark.parametrize("bytesmode", (True, False))
Expand Down
43 changes: 25 additions & 18 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
from typing import Any, cast

import pytest

Expand Down Expand Up @@ -45,14 +45,20 @@

@skip_unless_feature("jpg")
class TestFileJpeg:
def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image:
def roundtrip_with_bytes(
self, im: Image.Image, **options: Any
) -> tuple[JpegImagePlugin.JpegImageFile, int]:
out = BytesIO()
im.save(out, "JPEG", **options)
test_bytes = out.tell()
out.seek(0)
im = Image.open(out)
im.bytes = test_bytes # for testing only
return im
reloaded = cast(JpegImagePlugin.JpegImageFile, Image.open(out))
return reloaded, test_bytes

def roundtrip(
self, im: Image.Image, **options: Any
) -> JpegImagePlugin.JpegImageFile:
return self.roundtrip_with_bytes(im, **options)[0]

def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image:
"""Generates a very hard to compress file
Expand Down Expand Up @@ -246,13 +252,13 @@ def test_large_icc_meta(self, tmp_path: Path) -> None:
im.save(f, progressive=True, quality=94, exif=b" " * 43668)

def test_optimize(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), optimize=0)
im3 = self.roundtrip(hopper(), optimize=1)
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), optimize=0)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), optimize=1)
assert_image_equal(im1, im2)
assert_image_equal(im1, im3)
assert im1.bytes >= im2.bytes
assert im1.bytes >= im3.bytes
assert im1_bytes >= im2_bytes
assert im1_bytes >= im3_bytes

def test_optimize_large_buffer(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/148
Expand All @@ -262,15 +268,15 @@ def test_optimize_large_buffer(self, tmp_path: Path) -> None:
im.save(f, format="JPEG", optimize=True)

def test_progressive(self) -> None:
im1 = self.roundtrip(hopper())
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2 = self.roundtrip(hopper(), progressive=False)
im3 = self.roundtrip(hopper(), progressive=True)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), progressive=True)
assert not im1.info.get("progressive")
assert not im2.info.get("progressive")
assert im3.info.get("progressive")

assert_image_equal(im1, im3)
assert im1.bytes >= im3.bytes
assert im1_bytes >= im3_bytes

def test_progressive_large_buffer(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
Expand Down Expand Up @@ -341,6 +347,7 @@ def test_empty_exif_gps(self) -> None:
assert exif.get_ifd(0x8825) == {}

transposed = ImageOps.exif_transpose(im)
assert transposed is not None
exif = transposed.getexif()
assert exif.get_ifd(0x8825) == {}

Expand Down Expand Up @@ -419,14 +426,14 @@ def test_progressive_compat(self) -> None:
assert im3.info.get("progression")

def test_quality(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), quality=50)
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), quality=50)
assert_image(im1, im2.mode, im2.size)
assert im1.bytes >= im2.bytes
assert im1_bytes >= im2_bytes

im3 = self.roundtrip(hopper(), quality=0)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), quality=0)
assert_image(im1, im3.mode, im3.size)
assert im2.bytes > im3.bytes
assert im2_bytes > im3_bytes

def test_smooth(self) -> None:
im1 = self.roundtrip(hopper())
Expand Down
7 changes: 4 additions & 3 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "JPEG2000", **options)
test_bytes = out.tell()
out.seek(0)
with Image.open(out) as im:
im.bytes = test_bytes # for testing only
im.load()
return im

Expand Down Expand Up @@ -77,7 +75,9 @@ def test_invalid_file() -> None:
def test_bytesio() -> None:
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = BytesIO(f.read())
assert_image_similar_tofile(test_card, data, 1.0e-3)
with Image.open(data) as im:
im.load()
assert_image_similar(im, test_card, 1.0e-3)


# These two test pre-written JPEG 2000 files that were not written with
Expand Down Expand Up @@ -340,6 +340,7 @@ def test_parser_feed() -> None:
p.feed(data)

# Assert
assert p.image is not None
assert p.image.size == (640, 480)


Expand Down
8 changes: 6 additions & 2 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

@skip_unless_feature("libtiff")
class LibTiffTestCase:
def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None:
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit
assert im.mode == "1"
Expand Down Expand Up @@ -524,7 +524,8 @@ def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
im.save(out, compression=compression)

def test_fp_leak(self) -> None:
im = Image.open("Tests/images/hopper_g4_500.tif")
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
assert im is not None
fn = im.fp.fileno()

os.fstat(fn)
Expand Down Expand Up @@ -716,6 +717,7 @@ def test_fd_duplication(self, tmp_path: Path) -> None:
f.write(src.read())

im = Image.open(tmpfile)
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.n_frames
im.close()
# Should not raise PermissionError.
Expand Down Expand Up @@ -1097,6 +1099,7 @@ def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:

with Image.open(out) as im:
# Assert that there are multiple strips
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert len(im.tag_v2[STRIPOFFSETS]) > 1

@pytest.mark.parametrize("argument", (True, False))
Expand All @@ -1113,6 +1116,7 @@ def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
im.save(out, **arguments)

with Image.open(out) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert len(im.tag_v2[STRIPOFFSETS]) == 1
finally:
TiffImagePlugin.STRIP_SIZE = 65536
Expand Down
11 changes: 4 additions & 7 deletions Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import warnings
from io import BytesIO
from typing import Any
from typing import Any, cast

import pytest

from PIL import Image
from PIL import Image, MpoImagePlugin

from .helper import (
assert_image_equal,
Expand All @@ -20,14 +20,11 @@
pytestmark = skip_unless_feature("jpg")


def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile:
out = BytesIO()
im.save(out, "MPO", **options)
test_bytes = out.tell()
out.seek(0)
im = Image.open(out)
im.bytes = test_bytes # for testing only
return im
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))


@pytest.mark.parametrize("test_file", test_files)
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
from typing import Any, cast

import pytest

Expand Down Expand Up @@ -59,11 +59,11 @@ def load(data: bytes) -> Image.Image:
return Image.open(BytesIO(data))


def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile:
out = BytesIO()
im.save(out, "PNG", **options)
out.seek(0)
return Image.open(out)
return cast(PngImagePlugin.PngImageFile, Image.open(out))


@skip_unless_feature("zlib")
Expand Down
7 changes: 4 additions & 3 deletions Tests/test_file_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from PIL import Image, ImageSequence, SpiderImagePlugin

from .helper import assert_image_equal_tofile, hopper, is_pypy
from .helper import assert_image_equal, hopper, is_pypy

TEST_FILE = "Tests/images/hopper.spider"

Expand Down Expand Up @@ -152,12 +152,13 @@ def test_nonstack_dos() -> None:
assert i <= 1, "Non-stack DOS file test failed"


# for issue #4093
# for issue #4093s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, thanks. I've created #7855

def test_odd_size() -> None:
data = BytesIO()
width = 100
im = Image.new("F", (width, 64))
im.save(data, format="SPIDER")

data.seek(0)
assert_image_equal_tofile(im, data)
with Image.open(data) as im2:
assert_image_equal(im, im2)
1 change: 1 addition & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ def test_rowsperstrip(self, tmp_path: Path) -> None:
im.save(outfile, tiffinfo={278: 256})

with Image.open(outfile) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[278] == 256

def test_strip_raw(self) -> None:
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ def test_width_height(self) -> None:
assert im.height == 2

with pytest.raises(AttributeError):
im.size = (3, 4)
im.size = (3, 4) # type: ignore[misc]

def test_set_mode(self) -> None:
im = Image.new("RGB", (1, 1))

with pytest.raises(AttributeError):
im.mode = "P"
im.mode = "P" # type: ignore[misc]

def test_invalid_image(self) -> None:
im = io.BytesIO(b"")
Expand Down
1 change: 1 addition & 0 deletions Tests/test_image_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
cffi: ModuleType | None
if os.environ.get("PYTHONOPTIMIZE") == "2":
cffi = None
else:
Expand Down
Loading