mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-09-27 22:47:07 +03:00
Merge branch 'python-pillow:main' into main
This commit is contained in:
commit
747453764d
12
.github/workflows/docs.yml
vendored
12
.github/workflows/docs.yml
vendored
|
@ -37,16 +37,26 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: "3.x"
|
GHA_PYTHON_VERSION: "3.x"
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||||
|
|
2
.github/workflows/test-cygwin.yml
vendored
2
.github/workflows/test-cygwin.yml
vendored
|
@ -95,7 +95,7 @@ jobs:
|
||||||
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: pip cache
|
- name: pip cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
||||||
|
|
2
.github/workflows/test-windows.yml
vendored
2
.github/workflows/test-windows.yml
vendored
|
@ -89,7 +89,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: build-cache
|
id: build-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
|
|
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
|
@ -26,6 +26,9 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -65,17 +68,28 @@ jobs:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Install macOS dependencies
|
- name: Install macOS dependencies
|
||||||
if: startsWith(matrix.os, 'macOS')
|
if: startsWith(matrix.os, 'macOS')
|
||||||
|
|
|
@ -5,6 +5,12 @@ Changelog (Pillow)
|
||||||
10.3.0 (unreleased)
|
10.3.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Do not support using test-image-results to upload images after test failures #7739
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed ImageMath.ops to be static #7721
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Fix APNG info after seeking backwards more than twice #7701
|
- Fix APNG info after seeking backwards more than twice #7701
|
||||||
[esoma, radarhere]
|
[esoma, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PIL import PyAccess
|
from PIL import PyAccess
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/fli_overflow.fli"
|
TEST_FILE = "Tests/images/fli_overflow.fli"
|
||||||
|
|
||||||
|
|
||||||
def test_fli_overflow():
|
def test_fli_overflow() -> None:
|
||||||
# this should not crash with a malloc error or access violation
|
# this should not crash with a malloc error or access violation
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -12,31 +15,34 @@ max_iterations = 10000
|
||||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||||
|
|
||||||
|
|
||||||
def _get_mem_usage():
|
def _get_mem_usage() -> float:
|
||||||
from resource import RUSAGE_SELF, getpagesize, getrusage
|
from resource import RUSAGE_SELF, getpagesize, getrusage
|
||||||
|
|
||||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
return mem * getpagesize() / 1024 / 1024
|
return mem * getpagesize() / 1024 / 1024
|
||||||
|
|
||||||
|
|
||||||
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
|
def _test_leak(
|
||||||
|
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
|
||||||
|
) -> None:
|
||||||
mem_limit = None
|
mem_limit = None
|
||||||
for i in range(max_iterations):
|
for i in range(max_iterations):
|
||||||
fn(*args, **kwargs)
|
fn(*args)
|
||||||
mem = _get_mem_usage()
|
mem = _get_mem_usage()
|
||||||
if i < min_iterations:
|
if i < min_iterations:
|
||||||
mem_limit = mem + 1
|
mem_limit = mem + 1
|
||||||
continue
|
continue
|
||||||
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
||||||
|
assert mem_limit is not None
|
||||||
assert mem <= mem_limit, msg
|
assert mem <= mem_limit, msg
|
||||||
|
|
||||||
|
|
||||||
def test_leak_putdata():
|
def test_leak_putdata() -> None:
|
||||||
im = Image.new("RGB", (25, 25))
|
im = Image.new("RGB", (25, 25))
|
||||||
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
||||||
|
|
||||||
|
|
||||||
def test_leak_getlist():
|
def test_leak_getlist() -> None:
|
||||||
im = Image.new("P", (25, 25))
|
im = Image.new("P", (25, 25))
|
||||||
_test_leak(
|
_test_leak(
|
||||||
min_iterations,
|
min_iterations,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -19,7 +20,7 @@ pytestmark = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_leak_load():
|
def test_leak_load() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
@ -29,7 +30,7 @@ def test_leak_load():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_leak_save():
|
def test_leak_save() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_j2k_overflow(tmp_path):
|
def test_j2k_overflow(tmp_path: Path) -> None:
|
||||||
im = Image.new("RGBA", (1024, 131584))
|
im = Image.new("RGBA", (1024, 131584))
|
||||||
target = str(tmp_path / "temp.jpc")
|
target = str(tmp_path / "temp.jpc")
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
# version.
|
# version.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -110,14 +111,14 @@ standard_chrominance_qtable = (
|
||||||
[standard_l_qtable, standard_chrominance_qtable],
|
[standard_l_qtable, standard_chrominance_qtable],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_qtables_leak(qtables):
|
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
test_output = BytesIO()
|
test_output = BytesIO()
|
||||||
im.save(test_output, "JPEG", qtables=qtables)
|
im.save(test_output, "JPEG", qtables=qtables)
|
||||||
|
|
||||||
|
|
||||||
def test_exif_leak():
|
def test_exif_leak() -> None:
|
||||||
"""
|
"""
|
||||||
pre patch:
|
pre patch:
|
||||||
|
|
||||||
|
@ -180,7 +181,7 @@ def test_exif_leak():
|
||||||
im.save(test_output, "JPEG", exif=exif)
|
im.save(test_output, "JPEG", exif=exif)
|
||||||
|
|
||||||
|
|
||||||
def test_base_save():
|
def test_base_save() -> None:
|
||||||
"""
|
"""
|
||||||
base case:
|
base case:
|
||||||
MB
|
MB
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -15,6 +18,7 @@ from PIL import Image
|
||||||
# 2.7 and 3.2.
|
# 2.7 and 3.2.
|
||||||
|
|
||||||
|
|
||||||
|
numpy: ModuleType | None
|
||||||
try:
|
try:
|
||||||
import numpy
|
import numpy
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -27,23 +31,24 @@ XDIM = 48000
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
im = Image.new("L", (xdim, ydim), 0)
|
im = Image.new("L", (xdim, ydim), 0)
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
||||||
def test_size_greater_than_int():
|
def test_size_greater_than_int() -> None:
|
||||||
|
assert numpy is not None
|
||||||
arr = numpy.ndarray(shape=(16394, 16394))
|
arr = numpy.ndarray(shape=(16394, 16394))
|
||||||
Image.fromarray(arr)
|
Image.fromarray(arr)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -23,7 +25,7 @@ XDIM = 48000
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
dtype = np.uint8
|
dtype = np.uint8
|
||||||
a = np.zeros((xdim, ydim), dtype=dtype)
|
a = np.zeros((xdim, ydim), dtype=dtype)
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
@ -31,11 +33,11 @@ def _write_png(tmp_path, xdim, ydim):
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -6,7 +7,7 @@ from PIL import Image
|
||||||
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
||||||
|
|
||||||
|
|
||||||
def test_libtiff_segfault():
|
def test_libtiff_segfault() -> None:
|
||||||
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
||||||
libtiff >= 4.0.0
|
libtiff >= 4.0.0
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ 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():
|
def test_ignore_dos_text() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -23,7 +24,7 @@ def test_ignore_dos_text():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_text():
|
def test_dos_text() -> None:
|
||||||
try:
|
try:
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -35,7 +36,7 @@ def test_dos_text():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_total_memory():
|
def test_dos_total_memory() -> None:
|
||||||
im = Image.new("L", (1, 1))
|
im = Image.new("L", (1, 1))
|
||||||
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ def test_dos_total_memory():
|
||||||
try:
|
try:
|
||||||
im2 = Image.open(b)
|
im2 = Image.open(b)
|
||||||
except ValueError as msg:
|
except ValueError as msg:
|
||||||
assert "Too much memory" in msg
|
assert "Too much memory" in str(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
total_len = 0
|
total_len = 0
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_modules():
|
def test_wheel_modules() -> None:
|
||||||
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||||
|
|
||||||
# tkinter is not available in cibuildwheel installed CPython on Windows
|
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||||
|
@ -18,13 +19,13 @@ def test_wheel_modules():
|
||||||
assert set(features.get_supported_modules()) == expected_modules
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_codecs():
|
def test_wheel_codecs() -> None:
|
||||||
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
||||||
|
|
||||||
assert set(features.get_supported_codecs()) == expected_codecs
|
assert set(features.get_supported_codecs()) == expected_codecs
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_features():
|
def test_wheel_features() -> None:
|
||||||
expected_features = {
|
expected_features = {
|
||||||
"webp_anim",
|
"webp_anim",
|
||||||
"webp_mux",
|
"webp_mux",
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
def pytest_report_header(config):
|
|
||||||
|
def pytest_report_header(config: pytest.Config) -> str:
|
||||||
try:
|
try:
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
|
||||||
|
@ -13,7 +16,7 @@ def pytest_report_header(config):
|
||||||
return f"pytest_report_header failed: {e}"
|
return f"pytest_report_header failed: {e}"
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config: pytest.Config) -> None:
|
||||||
config.addinivalue_line(
|
config.addinivalue_line(
|
||||||
"markers",
|
"markers",
|
||||||
"pil_noop_mark: A conditional mark where nothing special happens",
|
"pil_noop_mark: A conditional mark where nothing special happens",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
172
Tests/helper.py
172
Tests/helper.py
|
@ -11,6 +11,7 @@ import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
@ -19,42 +20,31 @@ from PIL import Image, ImageMath, features
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
uploader = None
|
||||||
HAS_UPLOADER = False
|
|
||||||
|
|
||||||
if os.environ.get("SHOW_ERRORS"):
|
if os.environ.get("SHOW_ERRORS"):
|
||||||
# local img.show for errors.
|
uploader = "show"
|
||||||
HAS_UPLOADER = True
|
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
|
||||||
def upload(a, b):
|
|
||||||
a.show()
|
|
||||||
b.show()
|
|
||||||
|
|
||||||
elif "GITHUB_ACTIONS" in os.environ:
|
elif "GITHUB_ACTIONS" in os.environ:
|
||||||
HAS_UPLOADER = True
|
uploader = "github_actions"
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
|
||||||
def upload(a, b):
|
|
||||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
|
||||||
os.makedirs(dir_errors, exist_ok=True)
|
|
||||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
|
||||||
a.save(os.path.join(tmpdir, "a.png"))
|
|
||||||
b.save(os.path.join(tmpdir, "b.png"))
|
|
||||||
return tmpdir
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import test_image_results
|
|
||||||
|
|
||||||
HAS_UPLOADER = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_comparable(a, b):
|
def upload(a: Image.Image, b: Image.Image) -> str | None:
|
||||||
|
if uploader == "show":
|
||||||
|
# local img.show for errors.
|
||||||
|
a.show()
|
||||||
|
b.show()
|
||||||
|
elif uploader == "github_actions":
|
||||||
|
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||||
|
os.makedirs(dir_errors, exist_ok=True)
|
||||||
|
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||||
|
a.save(os.path.join(tmpdir, "a.png"))
|
||||||
|
b.save(os.path.join(tmpdir, "b.png"))
|
||||||
|
return tmpdir
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_comparable(
|
||||||
|
a: Image.Image, b: Image.Image
|
||||||
|
) -> tuple[Image.Image, Image.Image]:
|
||||||
new_a, new_b = a, b
|
new_a, new_b = a, b
|
||||||
if a.mode == "P":
|
if a.mode == "P":
|
||||||
new_a = Image.new("L", a.size)
|
new_a = Image.new("L", a.size)
|
||||||
|
@ -67,14 +57,18 @@ def convert_to_comparable(a, b):
|
||||||
return new_a, new_b
|
return new_a, new_b
|
||||||
|
|
||||||
|
|
||||||
def assert_deep_equal(a, b, msg=None):
|
def assert_deep_equal(
|
||||||
|
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
try:
|
try:
|
||||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||||
except Exception:
|
except Exception:
|
||||||
assert a == b, msg
|
assert a == b, msg
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, mode, size, msg=None):
|
def assert_image(
|
||||||
|
im: Image.Image, mode: str, size: tuple[int, int], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
assert im.mode == mode, (
|
assert im.mode == mode, (
|
||||||
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
||||||
|
@ -86,28 +80,32 @@ def assert_image(im, mode, size, msg=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal(a, b, msg=None):
|
def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
if a.tobytes() != b.tobytes():
|
if a.tobytes() != b.tobytes():
|
||||||
if HAS_UPLOADER:
|
try:
|
||||||
try:
|
url = upload(a, b)
|
||||||
url = test_image_results.upload(a, b)
|
if url:
|
||||||
logger.error("URL for test images: %s", url)
|
logger.error("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pytest.fail(msg or "got different content")
|
pytest.fail(msg or "got different content")
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
def assert_image_equal_tofile(
|
||||||
|
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_equal(a, img, msg)
|
assert_image_equal(a, img, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar(a, b, epsilon, msg=None):
|
def assert_image_similar(
|
||||||
|
a: Image.Image, b: Image.Image, epsilon: float, msg: str | None = None
|
||||||
|
) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
|
|
||||||
|
@ -125,55 +123,68 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if HAS_UPLOADER:
|
try:
|
||||||
try:
|
url = upload(a, b)
|
||||||
url = test_image_results.upload(a, b)
|
if url:
|
||||||
logger.exception("URL for test images: %s", url)
|
logger.exception("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
|
def assert_image_similar_tofile(
|
||||||
|
a: Image.Image,
|
||||||
|
filename: str,
|
||||||
|
epsilon: float,
|
||||||
|
msg: str | None = None,
|
||||||
|
mode: str | None = None,
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_similar(a, img, epsilon, msg)
|
assert_image_similar(a, img, epsilon, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_all_same(items, msg=None):
|
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) == len(items), msg
|
assert items.count(items[0]) == len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_not_all_same(items, msg=None):
|
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) != len(items), msg
|
assert items.count(items[0]) != len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_tuple_approx_equal(actuals, targets, threshold, msg):
|
def assert_tuple_approx_equal(
|
||||||
|
actuals: Sequence[int], targets: tuple[int, ...], threshold: int, msg: str
|
||||||
|
) -> None:
|
||||||
"""Tests if actuals has values within threshold from targets"""
|
"""Tests if actuals has values within threshold from targets"""
|
||||||
value = True
|
|
||||||
for i, target in enumerate(targets):
|
for i, target in enumerate(targets):
|
||||||
value *= target - threshold <= actuals[i] <= target + threshold
|
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||||
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature):
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature_version(feature, version_required, reason=None):
|
def skip_unless_feature_version(
|
||||||
|
feature: str, required: str, reason: str | None = None
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.skip(f"{feature} not available")
|
return pytest.mark.skip(f"{feature} not available")
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = f"{feature} is older than {version_required}"
|
reason = f"{feature} is older than {required}"
|
||||||
version_required = parse_version(version_required)
|
version_required = parse_version(required)
|
||||||
version_available = parse_version(features.version(feature))
|
version_available = parse_version(features.version(feature))
|
||||||
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def mark_if_feature_version(mark, feature, version_blacklist, reason=None):
|
def mark_if_feature_version(
|
||||||
|
mark: pytest.MarkDecorator,
|
||||||
|
feature: str,
|
||||||
|
version_blacklist: str,
|
||||||
|
reason: str | None = None,
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.pil_noop_mark()
|
return pytest.mark.pil_noop_mark()
|
||||||
if reason is None:
|
if reason is None:
|
||||||
|
@ -194,7 +205,7 @@ class PillowLeakTestCase:
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
||||||
def _get_mem_usage(self):
|
def _get_mem_usage(self) -> float:
|
||||||
"""
|
"""
|
||||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||||
between macOS and Linux rss reporting
|
between macOS and Linux rss reporting
|
||||||
|
@ -216,7 +227,7 @@ class PillowLeakTestCase:
|
||||||
# This is the maximum resident set size used (in kilobytes).
|
# This is the maximum resident set size used (in kilobytes).
|
||||||
return mem # Kb
|
return mem # Kb
|
||||||
|
|
||||||
def _test_leak(self, core):
|
def _test_leak(self, core: Callable[[], None]) -> None:
|
||||||
start_mem = self._get_mem_usage()
|
start_mem = self._get_mem_usage()
|
||||||
for cycle in range(self.iterations):
|
for cycle in range(self.iterations):
|
||||||
core()
|
core()
|
||||||
|
@ -228,17 +239,17 @@ class PillowLeakTestCase:
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data: bytes) -> Image.Image:
|
||||||
return Image.open(BytesIO(data))
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
def tostring(im, string_format, **options):
|
def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, string_format, **options)
|
im.save(out, string_format, **options)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def hopper(mode=None, cache={}):
|
def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image.Image:
|
||||||
if mode is None:
|
if mode is None:
|
||||||
# Always return fresh not-yet-loaded version of image.
|
# Always return fresh not-yet-loaded version of image.
|
||||||
# Operations on not-yet-loaded images is separate class of errors
|
# Operations on not-yet-loaded images is separate class of errors
|
||||||
|
@ -259,29 +270,31 @@ def hopper(mode=None, cache={}):
|
||||||
return im.copy()
|
return im.copy()
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
def djpeg_available() -> bool:
|
||||||
if shutil.which("djpeg"):
|
if shutil.which("djpeg"):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["djpeg", "-version"])
|
subprocess.check_call(["djpeg", "-version"])
|
||||||
return True
|
return True
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def cjpeg_available() -> bool:
|
||||||
if shutil.which("cjpeg"):
|
if shutil.which("cjpeg"):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["cjpeg", "-version"])
|
subprocess.check_call(["cjpeg", "-version"])
|
||||||
return True
|
return True
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
def netpbm_available() -> bool:
|
||||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||||
|
|
||||||
|
|
||||||
def magick_command():
|
def magick_command() -> list[str] | None:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
magickhome = os.environ.get("MAGICK_HOME")
|
magickhome = os.environ.get("MAGICK_HOME")
|
||||||
if magickhome:
|
if magickhome:
|
||||||
|
@ -298,47 +311,48 @@ def magick_command():
|
||||||
return imagemagick
|
return imagemagick
|
||||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||||
return graphicsmagick
|
return graphicsmagick
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def on_appveyor():
|
def on_appveyor() -> bool:
|
||||||
return "APPVEYOR" in os.environ
|
return "APPVEYOR" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_github_actions():
|
def on_github_actions() -> bool:
|
||||||
return "GITHUB_ACTIONS" in os.environ
|
return "GITHUB_ACTIONS" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_ci():
|
def on_ci() -> bool:
|
||||||
# GitHub Actions and AppVeyor have "CI"
|
# GitHub Actions and AppVeyor have "CI"
|
||||||
return "CI" in os.environ
|
return "CI" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def is_big_endian():
|
def is_big_endian() -> bool:
|
||||||
return sys.byteorder == "big"
|
return sys.byteorder == "big"
|
||||||
|
|
||||||
|
|
||||||
def is_ppc64le():
|
def is_ppc64le() -> bool:
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
return platform.machine() == "ppc64le"
|
return platform.machine() == "ppc64le"
|
||||||
|
|
||||||
|
|
||||||
def is_win32():
|
def is_win32() -> bool:
|
||||||
return sys.platform.startswith("win32")
|
return sys.platform.startswith("win32")
|
||||||
|
|
||||||
|
|
||||||
def is_pypy():
|
def is_pypy() -> bool:
|
||||||
return hasattr(sys, "pypy_translation_info")
|
return hasattr(sys, "pypy_translation_info")
|
||||||
|
|
||||||
|
|
||||||
def is_mingw():
|
def is_mingw() -> bool:
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class CachedProperty:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, instance, cls=None):
|
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||||
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -23,7 +23,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_font(data)
|
fuzzers.fuzz_font(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -32,7 +32,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -23,7 +23,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_image(data)
|
fuzzers.fuzz_image(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -32,7 +32,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||||
|
|
||||||
|
|
||||||
def enable_decompressionbomb_error():
|
def enable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||||
|
|
||||||
|
|
||||||
def disable_decompressionbomb_error():
|
def disable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
warnings.resetwarnings()
|
warnings.resetwarnings()
|
||||||
|
|
||||||
|
|
||||||
def fuzz_image(data):
|
def fuzz_image(data: bytes) -> None:
|
||||||
# This will fail on some images in the corpus, as we have many
|
# This will fail on some images in the corpus, as we have many
|
||||||
# invalid images in the test suite.
|
# invalid images in the test suite.
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
@ -25,7 +26,7 @@ def fuzz_image(data):
|
||||||
im.save(io.BytesIO(), "BMP")
|
im.save(io.BytesIO(), "BMP")
|
||||||
|
|
||||||
|
|
||||||
def fuzz_font(data):
|
def fuzz_font(data: bytes) -> None:
|
||||||
wrapper = io.BytesIO(data)
|
wrapper = io.BytesIO(data)
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(wrapper)
|
font = ImageFont.truetype(wrapper)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ if features.check("libjpeg_turbo"):
|
||||||
"path",
|
"path",
|
||||||
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
||||||
)
|
)
|
||||||
def test_fuzz_images(path):
|
def test_fuzz_images(path: str) -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
@ -54,7 +55,7 @@ def test_fuzz_images(path):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||||
)
|
)
|
||||||
def test_fuzz_fonts(path):
|
def test_fuzz_fonts(path: str) -> None:
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import _binary
|
from PIL import _binary
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image, ImageFilter
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from array import array
|
from array import array
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import _deprecate
|
from PIL import _deprecate
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageSequence, PngImagePlugin
|
from PIL import Image, ImageSequence, PngImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import BufrStubImagePlugin, Image
|
from PIL import BufrStubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import CurImagePlugin, Image
|
from PIL import CurImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Test DdsImagePlugin"""
|
"""Test DdsImagePlugin"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import FtexImagePlugin, Image
|
from PIL import FtexImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GbrImagePlugin, Image
|
from PIL import GbrImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GdImageFile, UnidentifiedImageError
|
from PIL import GdImageFile, UnidentifiedImageError
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import GimpGradientFile, ImagePalette
|
from PIL import GimpGradientFile, ImagePalette
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL.GimpPaletteFile import GimpPaletteFile
|
from PIL.GimpPaletteFile import GimpPaletteFile
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GribStubImagePlugin, Image
|
from PIL import GribStubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Hdf5StubImagePlugin, Image
|
from PIL import Hdf5StubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import filecmp
|
import filecmp
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, McIdasImagePlugin
|
from PIL import Image, McIdasImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImagePalette
|
from PIL import Image, ImagePalette
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PcxImagePlugin
|
from PIL import Image, ImageFile, PcxImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, PixarImagePlugin
|
from PIL import Image, PixarImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, QoiImagePlugin
|
from PIL import Image, QoiImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, SgiImagePlugin
|
from PIL import Image, SgiImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import WalImageFile
|
from PIL import WalImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, WmfImagePlugin
|
from PIL import Image, WmfImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, XpmImagePlugin
|
from PIL import Image, XpmImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, XVThumbImagePlugin
|
from PIL import Image, XVThumbImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import BdfFontFile, FontFile
|
from PIL import BdfFontFile, FontFile
|
||||||
|
@ -6,7 +7,7 @@ from PIL import BdfFontFile, FontFile
|
||||||
filename = "Tests/images/courB08.bdf"
|
filename = "Tests/images/courB08.bdf"
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity() -> None:
|
||||||
with open(filename, "rb") as test_file:
|
with open(filename, "rb") as test_file:
|
||||||
font = BdfFontFile.BdfFontFile(test_file)
|
font = BdfFontFile.BdfFontFile(test_file)
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ def test_sanity():
|
||||||
assert len([_f for _f in font.glyph if _f]) == 190
|
assert len([_f for _f in font.glyph if _f]) == 190
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file() -> None:
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
BdfFontFile.BdfFontFile(fp)
|
BdfFontFile.BdfFontFile(fp)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
@ -7,7 +8,7 @@ from .helper import skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
class TestFontCrash:
|
class TestFontCrash:
|
||||||
def _fuzz_font(self, font):
|
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||||
# from fuzzers.fuzz_font
|
# from fuzzers.fuzz_font
|
||||||
font.getbbox("ABC")
|
font.getbbox("ABC")
|
||||||
font.getmask("test text")
|
font.getmask("test text")
|
||||||
|
@ -17,7 +18,7 @@ class TestFontCrash:
|
||||||
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
def test_segfault(self):
|
def test_segfault(self) -> None:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
|
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
|
||||||
self._fuzz_font(font)
|
self._fuzz_font(font)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
from .helper import PillowLeakTestCase, skip_unless_feature
|
from .helper import PillowLeakTestCase, skip_unless_feature
|
||||||
|
@ -9,7 +10,7 @@ class TestTTypeFontLeak(PillowLeakTestCase):
|
||||||
iterations = 10
|
iterations = 10
|
||||||
mem_limit = 4096 # k
|
mem_limit = 4096 # k
|
||||||
|
|
||||||
def _test_font(self, font):
|
def _test_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||||
im = Image.new("RGB", (255, 255), "white")
|
im = Image.new("RGB", (255, 255), "white")
|
||||||
draw = ImageDraw.ImageDraw(im)
|
draw = ImageDraw.ImageDraw(im)
|
||||||
self._test_leak(
|
self._test_leak(
|
||||||
|
@ -19,7 +20,7 @@ class TestTTypeFontLeak(PillowLeakTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
def test_leak(self):
|
def test_leak(self) -> None:
|
||||||
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
||||||
self._test_font(ttype)
|
self._test_font(ttype)
|
||||||
|
|
||||||
|
@ -29,6 +30,6 @@ class TestDefaultFontLeak(TestTTypeFontLeak):
|
||||||
iterations = 100
|
iterations = 100
|
||||||
mem_limit = 1024 # k
|
mem_limit = 1024 # k
|
||||||
|
|
||||||
def test_leak(self):
|
def test_leak(self) -> None:
|
||||||
default_font = ImageFont.load_default()
|
default_font = ImageFont.load_default()
|
||||||
self._test_font(default_font)
|
self._test_font(default_font)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -19,7 +21,7 @@ message = "hello, world"
|
||||||
pytestmark = skip_unless_feature("zlib")
|
pytestmark = skip_unless_feature("zlib")
|
||||||
|
|
||||||
|
|
||||||
def save_font(request, tmp_path):
|
def save_font(request: pytest.FixtureRequest, tmp_path: Path) -> str:
|
||||||
with open(fontname, "rb") as test_file:
|
with open(fontname, "rb") as test_file:
|
||||||
font = PcfFontFile.PcfFontFile(test_file)
|
font = PcfFontFile.PcfFontFile(test_file)
|
||||||
assert isinstance(font, FontFile.FontFile)
|
assert isinstance(font, FontFile.FontFile)
|
||||||
|
@ -28,7 +30,7 @@ def save_font(request, tmp_path):
|
||||||
|
|
||||||
tempname = str(tmp_path / "temp.pil")
|
tempname = str(tmp_path / "temp.pil")
|
||||||
|
|
||||||
def delete_tempfile():
|
def delete_tempfile() -> None:
|
||||||
try:
|
try:
|
||||||
os.remove(tempname[:-4] + ".pbm")
|
os.remove(tempname[:-4] + ".pbm")
|
||||||
except OSError:
|
except OSError:
|
||||||
|
@ -46,11 +48,11 @@ def save_font(request, tmp_path):
|
||||||
return tempname
|
return tempname
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(request, tmp_path):
|
def test_sanity(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
save_font(request, tmp_path)
|
save_font(request, tmp_path)
|
||||||
|
|
||||||
|
|
||||||
def test_less_than_256_characters():
|
def test_less_than_256_characters() -> None:
|
||||||
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
|
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
|
||||||
font = PcfFontFile.PcfFontFile(test_file)
|
font = PcfFontFile.PcfFontFile(test_file)
|
||||||
assert isinstance(font, FontFile.FontFile)
|
assert isinstance(font, FontFile.FontFile)
|
||||||
|
@ -58,13 +60,13 @@ def test_less_than_256_characters():
|
||||||
assert len([_f for _f in font.glyph if _f]) == 127
|
assert len([_f for _f in font.glyph if _f]) == 127
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file() -> None:
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
PcfFontFile.PcfFontFile(fp)
|
PcfFontFile.PcfFontFile(fp)
|
||||||
|
|
||||||
|
|
||||||
def test_draw(request, tmp_path):
|
def test_draw(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
im = Image.new("L", (130, 30), "white")
|
im = Image.new("L", (130, 30), "white")
|
||||||
|
@ -73,7 +75,7 @@ def test_draw(request, tmp_path):
|
||||||
assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0)
|
assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0)
|
||||||
|
|
||||||
|
|
||||||
def test_textsize(request, tmp_path):
|
def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
for i in range(255):
|
for i in range(255):
|
||||||
|
@ -89,7 +91,9 @@ def test_textsize(request, tmp_path):
|
||||||
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
|
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
|
||||||
|
|
||||||
|
|
||||||
def _test_high_characters(request, tmp_path, message):
|
def _test_high_characters(
|
||||||
|
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes
|
||||||
|
) -> None:
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
im = Image.new("L", (750, 30), "white")
|
im = Image.new("L", (750, 30), "white")
|
||||||
|
@ -98,7 +102,7 @@ def _test_high_characters(request, tmp_path, message):
|
||||||
assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0)
|
assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0)
|
||||||
|
|
||||||
|
|
||||||
def test_high_characters(request, tmp_path):
|
def test_high_characters(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
message = "".join(chr(i + 1) for i in range(140, 232))
|
message = "".join(chr(i + 1) for i in range(140, 232))
|
||||||
_test_high_characters(request, tmp_path, message)
|
_test_high_characters(request, tmp_path, message)
|
||||||
# accept bytes instances.
|
# accept bytes instances.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -13,7 +16,14 @@ from .helper import (
|
||||||
|
|
||||||
fontname = "Tests/fonts/ter-x20b.pcf"
|
fontname = "Tests/fonts/ter-x20b.pcf"
|
||||||
|
|
||||||
charsets = {
|
|
||||||
|
class Charset(TypedDict):
|
||||||
|
glyph_count: int
|
||||||
|
message: str
|
||||||
|
image1: str
|
||||||
|
|
||||||
|
|
||||||
|
charsets: dict[str, Charset] = {
|
||||||
"iso8859-1": {
|
"iso8859-1": {
|
||||||
"glyph_count": 223,
|
"glyph_count": 223,
|
||||||
"message": "hello, world",
|
"message": "hello, world",
|
||||||
|
@ -35,7 +45,7 @@ charsets = {
|
||||||
pytestmark = skip_unless_feature("zlib")
|
pytestmark = skip_unless_feature("zlib")
|
||||||
|
|
||||||
|
|
||||||
def save_font(request, tmp_path, encoding):
|
def save_font(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> str:
|
||||||
with open(fontname, "rb") as test_file:
|
with open(fontname, "rb") as test_file:
|
||||||
font = PcfFontFile.PcfFontFile(test_file, encoding)
|
font = PcfFontFile.PcfFontFile(test_file, encoding)
|
||||||
assert isinstance(font, FontFile.FontFile)
|
assert isinstance(font, FontFile.FontFile)
|
||||||
|
@ -44,7 +54,7 @@ def save_font(request, tmp_path, encoding):
|
||||||
|
|
||||||
tempname = str(tmp_path / "temp.pil")
|
tempname = str(tmp_path / "temp.pil")
|
||||||
|
|
||||||
def delete_tempfile():
|
def delete_tempfile() -> None:
|
||||||
try:
|
try:
|
||||||
os.remove(tempname[:-4] + ".pbm")
|
os.remove(tempname[:-4] + ".pbm")
|
||||||
except OSError:
|
except OSError:
|
||||||
|
@ -63,12 +73,12 @@ def save_font(request, tmp_path, encoding):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
def test_sanity(request, tmp_path, encoding):
|
def test_sanity(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> None:
|
||||||
save_font(request, tmp_path, encoding)
|
save_font(request, tmp_path, encoding)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
def test_draw(request, tmp_path, encoding):
|
def test_draw(request: pytest.FixtureRequest, tmp_path: Path, encoding: str) -> None:
|
||||||
tempname = save_font(request, tmp_path, encoding)
|
tempname = save_font(request, tmp_path, encoding)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
im = Image.new("L", (150, 30), "white")
|
im = Image.new("L", (150, 30), "white")
|
||||||
|
@ -79,7 +89,9 @@ def test_draw(request, tmp_path, encoding):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
def test_textsize(request, tmp_path, encoding):
|
def test_textsize(
|
||||||
|
request: pytest.FixtureRequest, tmp_path: Path, encoding: str
|
||||||
|
) -> None:
|
||||||
tempname = save_font(request, tmp_path, encoding)
|
tempname = save_font(request, tmp_path, encoding)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
for i in range(255):
|
for i in range(255):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import FontFile
|
from PIL import FontFile
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user