Removed PillowTestCase helper class

This commit is contained in:
Andrew Murray 2020-03-28 12:51:28 +11:00
parent a8637449b9
commit b602f365ae
22 changed files with 392 additions and 461 deletions

View File

@ -1,9 +1,8 @@
import time import time
import unittest
from PIL import PyAccess from PIL import PyAccess
from .helper import PillowTestCase, hopper from .helper import hopper
# Not running this test by default. No DOS against Travis CI. # Not running this test by default. No DOS against Travis CI.
@ -41,22 +40,17 @@ def timer(func, label, *args):
) )
class BenchCffiAccess(PillowTestCase): def test_direct():
def test_direct(self): im = hopper()
im = hopper() im.load()
im.load() # im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) caccess = im.im.pixel_access(False)
caccess = im.im.pixel_access(False) access = PyAccess.new(im, False)
access = PyAccess.new(im, False)
assert caccess[(0, 0)] == access[(0, 0)] assert caccess[(0, 0)] == access[(0, 0)]
print("Size: %sx%s" % im.size) print("Size: %sx%s" % im.size)
timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_get, "PyAccess - get", im.size, access)
timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access)
timer(iterate_get, "C-api - get", im.size, caccess) timer(iterate_get, "C-api - get", im.size, caccess)
timer(iterate_set, "C-api - set", im.size, caccess) timer(iterate_set, "C-api - set", im.size, caccess)
if __name__ == "__main__":
unittest.main()

View File

@ -1,19 +1,10 @@
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
TEST_FILE = "Tests/images/fli_overflow.fli" TEST_FILE = "Tests/images/fli_overflow.fli"
class TestFliOverflow(PillowTestCase): def test_fli_overflow():
def test_fli_overflow(self):
# 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()
if __name__ == "__main__":
unittest.main()

View File

@ -1,46 +1,44 @@
#!/usr/bin/env python #!/usr/bin/env python
import unittest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, is_win32 from .helper import is_win32
min_iterations = 100 min_iterations = 100
max_iterations = 10000 max_iterations = 10000
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
@unittest.skipIf(is_win32(), "requires Unix or macOS")
class TestImagingLeaks(PillowTestCase):
def _get_mem_usage(self):
from resource import getpagesize, getrusage, RUSAGE_SELF
mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024
def _test_leak(self, min_iterations, max_iterations, fn, *args, **kwargs):
mem_limit = None
for i in range(max_iterations):
fn(*args, **kwargs)
mem = self._get_mem_usage()
if i < min_iterations:
mem_limit = mem + 1
continue
msg = "memory usage limit exceeded after %d iterations" % (i + 1)
assert mem <= mem_limit, msg
def test_leak_putdata(self):
im = Image.new("RGB", (25, 25))
self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
def test_leak_getlist(self):
im = Image.new("P", (25, 25))
self._test_leak(
min_iterations,
max_iterations,
# Pass a new list at each iteration.
lambda: im.point(range(256)),
)
if __name__ == "__main__": def _get_mem_usage():
unittest.main() from resource import getpagesize, getrusage, RUSAGE_SELF
mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
mem_limit = None
for i in range(max_iterations):
fn(*args, **kwargs)
mem = _get_mem_usage()
if i < min_iterations:
mem_limit = mem + 1
continue
msg = "memory usage limit exceeded after %d iterations" % (i + 1)
assert mem <= mem_limit, msg
def test_leak_putdata():
im = Image.new("RGB", (25, 25))
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
def test_leak_getlist():
im = Image.new("P", (25, 25))
_test_leak(
min_iterations,
max_iterations,
# Pass a new list at each iteration.
lambda: im.point(range(256)),
)

View File

@ -1,9 +1,9 @@
import unittest
from io import BytesIO from io import BytesIO
import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, is_win32, skip_unless_feature from .helper import is_win32, skip_unless_feature
# Limits for testing the leak # Limits for testing the leak
mem_limit = 1024 * 1048576 mem_limit = 1024 * 1048576
@ -11,32 +11,31 @@ stack_size = 8 * 1048576
iterations = int((mem_limit / stack_size) * 2) iterations = int((mem_limit / stack_size) * 2)
test_file = "Tests/images/rgb_trns_ycbc.jp2" test_file = "Tests/images/rgb_trns_ycbc.jp2"
pytestmark = [
@unittest.skipIf(is_win32(), "requires Unix or macOS") pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
@skip_unless_feature("jpg_2000") skip_unless_feature("jpg_2000"),
class TestJpegLeaks(PillowTestCase): ]
def test_leak_load(self):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
def test_leak_save(self):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
test_output = BytesIO()
im.save(test_output, "JPEG2000")
test_output.seek(0)
test_output.read()
if __name__ == "__main__": def test_leak_load():
unittest.main() from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
def test_leak_save():
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations):
with Image.open(test_file) as im:
im.load()
test_output = BytesIO()
im.save(test_output, "JPEG2000")
test_output.seek(0)
test_output.read()

View File

@ -1,18 +1,9 @@
import unittest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
def test_j2k_overflow(tmp_path):
class TestJ2kEncodeOverflow(PillowTestCase): im = Image.new("RGBA", (1024, 131584))
def test_j2k_overflow(self): target = str(tmp_path / "temp.jpc")
with pytest.raises(IOError):
im = Image.new("RGBA", (1024, 131584)) im.save(target)
target = self.tempfile("temp.jpc")
with self.assertRaises(IOError):
im.save(target)
if __name__ == "__main__":
unittest.main()

View File

@ -1,7 +1,8 @@
import unittest
from io import BytesIO from io import BytesIO
from .helper import PillowTestCase, hopper, is_win32 import pytest
from .helper import hopper, is_win32
iterations = 5000 iterations = 5000
@ -15,10 +16,9 @@ valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
""" """
@unittest.skipIf(is_win32(), "requires Unix or macOS") pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
class TestJpegLeaks(PillowTestCase):
""" """
pre patch: pre patch:
MB MB
@ -74,49 +74,51 @@ post-patch:
""" """
def test_qtables_leak(self):
im = hopper("RGB")
standard_l_qtable = [ def test_qtables_leak():
int(s) im = hopper("RGB")
for s in """
16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
""".split(
None
)
]
standard_chrominance_qtable = [ standard_l_qtable = [
int(s) int(s)
for s in """ for s in """
17 18 24 47 99 99 99 99 16 11 10 16 24 40 51 61
18 21 26 66 99 99 99 99 12 12 14 19 26 58 60 55
24 26 56 99 99 99 99 99 14 13 16 24 40 57 69 56
47 66 99 99 99 99 99 99 14 17 22 29 51 87 80 62
99 99 99 99 99 99 99 99 18 22 37 56 68 109 103 77
99 99 99 99 99 99 99 99 24 35 55 64 81 104 113 92
99 99 99 99 99 99 99 99 49 64 78 87 103 121 120 101
99 99 99 99 99 99 99 99 72 92 95 98 112 100 103 99
""".split( """.split(
None None
) )
] ]
qtables = [standard_l_qtable, standard_chrominance_qtable] standard_chrominance_qtable = [
int(s)
for s in """
17 18 24 47 99 99 99 99
18 21 26 66 99 99 99 99
24 26 56 99 99 99 99 99
47 66 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99
""".split(
None
)
]
for _ in range(iterations): qtables = [standard_l_qtable, standard_chrominance_qtable]
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak(self): for _ in range(iterations):
""" test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak():
"""
pre patch: pre patch:
MB MB
@ -171,15 +173,16 @@ post patch:
0 11.33 0 11.33
""" """
im = hopper("RGB") im = hopper("RGB")
exif = b"12345678" * 4096 exif = b"12345678" * 4096
for _ in range(iterations): for _ in range(iterations):
test_output = BytesIO() test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif) im.save(test_output, "JPEG", exif=exif)
def test_base_save(self):
""" def test_base_save():
"""
base case: base case:
MB MB
20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
@ -205,12 +208,8 @@ base case:
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 7.882 0 7.882
""" """
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") im.save(test_output, "JPEG")
if __name__ == "__main__":
unittest.main()

View File

@ -1,10 +1,8 @@
import sys import sys
import unittest
import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #
# It requires > 2gb memory for the >2 gigapixel image generated in the # It requires > 2gb memory for the >2 gigapixel image generated in the
@ -24,26 +22,26 @@ YDIM = 32769
XDIM = 48000 XDIM = 48000
@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
class LargeMemoryTest(PillowTestCase):
def _write_png(self, xdim, ydim):
f = self.tempfile("temp.png")
im = Image.new("L", (xdim, ydim), 0)
im.save(f)
def test_large(self):
""" succeeded prepatch"""
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
self._write_png(XDIM, XDIM)
@unittest.skipIf(numpy is None, "Numpy is not installed")
def test_size_greater_than_int(self):
arr = numpy.ndarray(shape=(16394, 16394))
Image.fromarray(arr)
if __name__ == "__main__": def _write_png(tmp_path, xdim, ydim):
unittest.main() f = str(tmp_path / "temp.png")
im = Image.new("L", (xdim, ydim), 0)
im.save(f)
def test_large(tmp_path):
""" succeeded prepatch"""
_write_png(tmp_path, XDIM, YDIM)
def test_2gpx(tmp_path):
"""failed prepatch"""
_write_png(tmp_path, XDIM, XDIM)
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
def test_size_greater_than_int():
arr = numpy.ndarray(shape=(16394, 16394))
Image.fromarray(arr)

View File

@ -1,10 +1,8 @@
import sys import sys
import unittest
import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #
# It requires > 2gb memory for the >2 gigapixel image generated in the # It requires > 2gb memory for the >2 gigapixel image generated in the
@ -14,32 +12,28 @@ from .helper import PillowTestCase
# Raspberry Pis). # Raspberry Pis).
try: np = pytest.importorskip("numpy", reason="NumPy not installed")
import numpy as np
except ImportError:
raise unittest.SkipTest("numpy not installed")
YDIM = 32769 YDIM = 32769
XDIM = 48000 XDIM = 48000
@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
class LargeMemoryNumpyTest(PillowTestCase):
def _write_png(self, xdim, ydim):
dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype)
f = self.tempfile("temp.png")
im = Image.fromarray(a, "L")
im.save(f)
def test_large(self):
""" succeeded prepatch"""
self._write_png(XDIM, YDIM)
def test_2gpx(self):
"""failed prepatch"""
self._write_png(XDIM, XDIM)
if __name__ == "__main__": def _write_png(tmp_path, xdim, ydim):
unittest.main() dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype)
f = str(tmp_path / "temp.png")
im = Image.fromarray(a, "L")
im.save(f)
def test_large(tmp_path):
""" succeeded prepatch"""
_write_png(tmp_path, XDIM, YDIM)
def test_2gpx(tmp_path):
"""failed prepatch"""
_write_png(tmp_path, XDIM, XDIM)

View File

@ -1,23 +1,14 @@
import unittest
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
TEST_FILE = "Tests/images/libtiff_segfault.tif" TEST_FILE = "Tests/images/libtiff_segfault.tif"
class TestLibtiffSegfault(PillowTestCase): def test_libtiff_segfault():
def test_segfault(self): """ 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 """
"""
with pytest.raises(IOError): with pytest.raises(IOError):
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.load() im.load()
if __name__ == "__main__":
unittest.main()

View File

@ -1,67 +1,61 @@
import unittest
import zlib import zlib
from io import BytesIO from io import BytesIO
from PIL import Image, ImageFile, PngImagePlugin from PIL import Image, ImageFile, PngImagePlugin
from .helper import PillowTestCase
TEST_FILE = "Tests/images/png_decompression_dos.png" TEST_FILE = "Tests/images/png_decompression_dos.png"
class TestPngDos(PillowTestCase): def test_ignore_dos_text():
def test_ignore_dos_text(self): ImageFile.LOAD_TRUNCATED_IMAGES = True
ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
finally: finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
for s in im.text.values(): for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M" assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
for s in im.info.values(): for s in im.info.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M" assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_text(self):
try:
im = Image.open(TEST_FILE)
im.load()
except ValueError as msg:
assert msg, "Decompressed Data Too Large"
return
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_total_memory(self):
im = Image.new("L", (1, 1))
compressed_data = zlib.compress(b"a" * 1024 * 1023)
info = PngImagePlugin.PngInfo()
for x in range(64):
info.add_text("t%s" % x, compressed_data, zip=True)
info.add_itxt("i%s" % x, compressed_data, zip=True)
b = BytesIO()
im.save(b, "PNG", pnginfo=info)
b.seek(0)
try:
im2 = Image.open(b)
except ValueError as msg:
assert "Too much memory" in msg
return
total_len = 0
for txt in im2.text.values():
total_len += len(txt)
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
if __name__ == "__main__": def test_dos_text():
unittest.main()
try:
im = Image.open(TEST_FILE)
im.load()
except ValueError as msg:
assert msg, "Decompressed Data Too Large"
return
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_total_memory():
im = Image.new("L", (1, 1))
compressed_data = zlib.compress(b"a" * 1024 * 1023)
info = PngImagePlugin.PngInfo()
for x in range(64):
info.add_text("t%s" % x, compressed_data, zip=True)
info.add_itxt("i%s" % x, compressed_data, zip=True)
b = BytesIO()
im.save(b, "PNG", pnginfo=info)
b.seek(0)
try:
im2 = Image.open(b)
except ValueError as msg:
assert "Too much memory" in msg
return
total_len = 0
for txt in im2.text.values():
total_len += len(txt)
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"

View File

@ -7,7 +7,6 @@ import os
import shutil import shutil
import sys import sys
import tempfile import tempfile
import unittest
from io import BytesIO from io import BytesIO
import pytest import pytest
@ -176,22 +175,6 @@ def skip_unless_feature(feature):
return pytest.mark.skipif(not features.check(feature), reason=reason) return pytest.mark.skipif(not features.check(feature), reason=reason)
class PillowTestCase(unittest.TestCase):
def delete_tempfile(self, path):
try:
os.remove(path)
except OSError:
pass # report?
def tempfile(self, template):
assert template[:5] in ("temp.", "temp_")
fd, path = tempfile.mkstemp(template[4:], template[:4])
os.close(fd)
self.addCleanup(self.delete_tempfile, path)
return path
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS") @pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
class PillowLeakTestCase: class PillowLeakTestCase:
# requires unix/macOS # requires unix/macOS

View File

@ -1,15 +1,16 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, hopper from .helper import hopper
TEST_FILE = "Tests/images/hopper.ppm" TEST_FILE = "Tests/images/hopper.ppm"
ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS
class TestDecompressionBomb(PillowTestCase): class TestDecompressionBomb:
def tearDown(self): @classmethod
def teardown_class(self):
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def test_no_warning_small_file(self): def test_no_warning_small_file(self):
@ -59,12 +60,14 @@ class TestDecompressionBomb(PillowTestCase):
Image.open("Tests/images/decompression_bomb.gif") Image.open("Tests/images/decompression_bomb.gif")
class TestDecompressionCrop(PillowTestCase): class TestDecompressionCrop:
def setUp(self): @classmethod
def setup_class(self):
width, height = 128, 128 width, height = 128, 128
Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 Image.MAX_IMAGE_PIXELS = height * width * 4 - 1
def tearDown(self): @classmethod
def teardown_class(self):
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def testEnlargeCrop(self): def testEnlargeCrop(self):

View File

@ -1,74 +1,77 @@
import pytest import pytest
from PIL import Image, WmfImagePlugin from PIL import Image, WmfImagePlugin
from .helper import PillowTestCase, assert_image_similar, hopper from .helper import assert_image_similar, hopper
class TestFileWmf(PillowTestCase): def test_load_raw():
def test_load_raw(self):
# Test basic EMF open and rendering # Test basic EMF open and rendering
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
# Currently, support for WMF/EMF is Windows-only # Currently, support for WMF/EMF is Windows-only
im.load() im.load()
# Compare to reference rendering # Compare to reference rendering
with Image.open("Tests/images/drawing_emf_ref.png") as imref: with Image.open("Tests/images/drawing_emf_ref.png") as imref:
imref.load() imref.load()
assert_image_similar(im, imref, 0) assert_image_similar(im, imref, 0)
# Test basic WMF open and rendering # Test basic WMF open and rendering
with Image.open("Tests/images/drawing.wmf") as im: with Image.open("Tests/images/drawing.wmf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
# Currently, support for WMF/EMF is Windows-only # Currently, support for WMF/EMF is Windows-only
im.load() im.load()
# Compare to reference rendering # Compare to reference rendering
with Image.open("Tests/images/drawing_wmf_ref.png") as imref: with Image.open("Tests/images/drawing_wmf_ref.png") as imref:
imref.load() imref.load()
assert_image_similar(im, imref, 2.0) assert_image_similar(im, imref, 2.0)
def test_register_handler(self):
class TestHandler:
methodCalled = False
def save(self, im, fp, filename): def test_register_handler(tmp_path):
self.methodCalled = True class TestHandler:
methodCalled = False
handler = TestHandler() def save(self, im, fp, filename):
WmfImagePlugin.register_handler(handler) self.methodCalled = True
im = hopper() handler = TestHandler()
tmpfile = self.tempfile("temp.wmf") WmfImagePlugin.register_handler(handler)
im.save(tmpfile)
assert handler.methodCalled
# Restore the state before this test im = hopper()
WmfImagePlugin.register_handler(None) tmpfile = str(tmp_path / "temp.wmf")
im.save(tmpfile)
assert handler.methodCalled
def test_load_dpi_rounding(self): # Restore the state before this test
# Round up WmfImagePlugin.register_handler(None)
with Image.open("Tests/images/drawing.emf") as im:
assert im.info["dpi"] == 1424
# Round down
with Image.open("Tests/images/drawing_roundDown.emf") as im:
assert im.info["dpi"] == 1426
def test_load_set_dpi(self): def test_load_dpi_rounding():
with Image.open("Tests/images/drawing.wmf") as im: # Round up
assert im.size == (82, 82) with Image.open("Tests/images/drawing.emf") as im:
assert im.info["dpi"] == 1424
if hasattr(Image.core, "drawwmf"): # Round down
im.load(144) with Image.open("Tests/images/drawing_roundDown.emf") as im:
assert im.size == (164, 164) assert im.info["dpi"] == 1426
with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected:
assert_image_similar(im, expected, 2.0)
def test_save(self): def test_load_set_dpi():
im = hopper() with Image.open("Tests/images/drawing.wmf") as im:
assert im.size == (82, 82)
for ext in [".wmf", ".emf"]: if hasattr(Image.core, "drawwmf"):
tmpfile = self.tempfile("temp" + ext) im.load(144)
with pytest.raises(IOError): assert im.size == (164, 164)
im.save(tmpfile)
with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected:
assert_image_similar(im, expected, 2.0)
def test_save(tmp_path):
im = hopper()
for ext in [".wmf", ".emf"]:
tmpfile = str(tmp_path / ("temp" + ext))
with pytest.raises(IOError):
im.save(tmpfile)

View File

@ -1,12 +1,9 @@
import os import os
import pytest import pytest
from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile
from .helper import ( from .helper import assert_image_equal, assert_image_similar, skip_unless_feature
assert_image_equal,
assert_image_similar,
skip_unless_feature,
)
fontname = "Tests/fonts/10x20-ISO8859-1.pcf" fontname = "Tests/fonts/10x20-ISO8859-1.pcf"

View File

@ -2,10 +2,10 @@ import pytest
from PIL import Image, ImageQt from PIL import Image, ImageQt
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
from .test_imageqt import skip_if_qt_is_not_installed
pytestmark = pytest.mark.skipif(
pytestmark = skip_if_qt_is_not_installed() not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
)
@pytest.fixture @pytest.fixture
@ -18,7 +18,7 @@ def test_images():
try: try:
yield ims yield ims
finally: finally:
for im in ims.values(): for im in ims:
im.close() im.close()

View File

@ -1,9 +1,9 @@
from PIL import Image from PIL import Image
from .helper import PillowTestCase, assert_image_equal, cached_property from .helper import assert_image_equal, cached_property
class TestImagingPaste(PillowTestCase): class TestImagingPaste:
masks = {} masks = {}
size = 128 size = 128

View File

@ -6,10 +6,10 @@ from itertools import permutations
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper
class TestImagingCoreResize(PillowTestCase): class TestImagingCoreResize:
def resize(self, im, size, f): def resize(self, im, size, f):
# Image class independent version of resize. # Image class independent version of resize.
im.load() im.load()
@ -135,31 +135,36 @@ class TestImagingCoreResize(PillowTestCase):
self.resize(hopper(), (10, 10), 9) self.resize(hopper(), (10, 10), 9)
class TestReducingGapResize(PillowTestCase): @pytest.fixture
@classmethod def gradients_image():
def setUpClass(cls): im = Image.open("Tests/images/radial_gradients.png")
cls.gradients_image = Image.open("Tests/images/radial_gradients.png") im.load()
cls.gradients_image.load() try:
yield im
finally:
im.close()
def test_reducing_gap_values(self):
ref = self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=None) class TestReducingGapResize:
im = self.gradients_image.resize((52, 34), Image.BICUBIC) def test_reducing_gap_values(self, gradients_image):
ref = gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=None)
im = gradients_image.resize((52, 34), Image.BICUBIC)
assert_image_equal(ref, im) assert_image_equal(ref, im)
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0) gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0)
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0.99) gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0.99)
def test_reducing_gap_1(self): def test_reducing_gap_1(self, gradients_image):
for box, epsilon in [ for box, epsilon in [
(None, 4), (None, 4),
((1.1, 2.2, 510.8, 510.9), 4), ((1.1, 2.2, 510.8, 510.9), 4),
((3, 10, 410, 256), 10), ((3, 10, 410, 256), 10),
]: ]:
ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box)
im = self.gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.BICUBIC, box=box, reducing_gap=1.0 (52, 34), Image.BICUBIC, box=box, reducing_gap=1.0
) )
@ -168,14 +173,14 @@ class TestReducingGapResize(PillowTestCase):
assert_image_similar(ref, im, epsilon) assert_image_similar(ref, im, epsilon)
def test_reducing_gap_2(self): def test_reducing_gap_2(self, gradients_image):
for box, epsilon in [ for box, epsilon in [
(None, 1.5), (None, 1.5),
((1.1, 2.2, 510.8, 510.9), 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5),
((3, 10, 410, 256), 1), ((3, 10, 410, 256), 1),
]: ]:
ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box)
im = self.gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.BICUBIC, box=box, reducing_gap=2.0 (52, 34), Image.BICUBIC, box=box, reducing_gap=2.0
) )
@ -184,14 +189,14 @@ class TestReducingGapResize(PillowTestCase):
assert_image_similar(ref, im, epsilon) assert_image_similar(ref, im, epsilon)
def test_reducing_gap_3(self): def test_reducing_gap_3(self, gradients_image):
for box, epsilon in [ for box, epsilon in [
(None, 1), (None, 1),
((1.1, 2.2, 510.8, 510.9), 1), ((1.1, 2.2, 510.8, 510.9), 1),
((3, 10, 410, 256), 0.5), ((3, 10, 410, 256), 0.5),
]: ]:
ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box)
im = self.gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.BICUBIC, box=box, reducing_gap=3.0 (52, 34), Image.BICUBIC, box=box, reducing_gap=3.0
) )
@ -200,29 +205,27 @@ class TestReducingGapResize(PillowTestCase):
assert_image_similar(ref, im, epsilon) assert_image_similar(ref, im, epsilon)
def test_reducing_gap_8(self): def test_reducing_gap_8(self, gradients_image):
for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]: for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]:
ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box)
im = self.gradients_image.resize( im = gradients_image.resize(
(52, 34), Image.BICUBIC, box=box, reducing_gap=8.0 (52, 34), Image.BICUBIC, box=box, reducing_gap=8.0
) )
assert_image_equal(ref, im) assert_image_equal(ref, im)
def test_box_filter(self): def test_box_filter(self, gradients_image):
for box, epsilon in [ for box, epsilon in [
((0, 0, 512, 512), 5.5), ((0, 0, 512, 512), 5.5),
((0.9, 1.7, 128, 128), 9.5), ((0.9, 1.7, 128, 128), 9.5),
]: ]:
ref = self.gradients_image.resize((52, 34), Image.BOX, box=box) ref = gradients_image.resize((52, 34), Image.BOX, box=box)
im = self.gradients_image.resize( im = gradients_image.resize((52, 34), Image.BOX, box=box, reducing_gap=1.0)
(52, 34), Image.BOX, box=box, reducing_gap=1.0
)
assert_image_similar(ref, im, epsilon) assert_image_similar(ref, im, epsilon)
class TestImageResize(PillowTestCase): class TestImageResize:
def test_resize(self): def test_resize(self):
def resize(mode, size): def resize(mode, size):
out = hopper(mode).resize(size) out = hopper(mode).resize(size)

View File

@ -10,7 +10,6 @@ import pytest
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from .helper import ( from .helper import (
PillowTestCase,
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar,
assert_image_similar_tofile, assert_image_similar_tofile,
@ -25,8 +24,10 @@ FONT_SIZE = 20
TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward"
@skip_unless_feature("freetype2") pytestmark = skip_unless_feature("freetype2")
class TestImageFont(PillowTestCase):
class TestImageFont:
LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC
# Freetype has different metrics depending on the version. # Freetype has different metrics depending on the version.
@ -37,7 +38,8 @@ class TestImageFont(PillowTestCase):
"Default": {"multiline": 0.5, "textsize": 0.5, "getters": (12, 16)}, "Default": {"multiline": 0.5, "textsize": 0.5, "getters": (12, 16)},
} }
def setUp(self): @classmethod
def setup_class(self):
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
self.metrics = self.METRICS["Default"] self.metrics = self.METRICS["Default"]
@ -107,12 +109,12 @@ class TestImageFont(PillowTestCase):
with open(FONT_PATH, "rb") as f: with open(FONT_PATH, "rb") as f:
self._render(f) self._render(f)
def test_non_unicode_path(self): def test_non_unicode_path(self, tmp_path):
tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf"))
try: try:
tempfile = self.tempfile("temp_" + chr(128) + ".ttf") shutil.copy(FONT_PATH, tempfile)
except UnicodeEncodeError: except UnicodeEncodeError:
self.skipTest("Unicode path could not be created") pytest.skip("Unicode path could not be created")
shutil.copy(FONT_PATH, tempfile)
ImageFont.truetype(tempfile, FONT_SIZE) ImageFont.truetype(tempfile, FONT_SIZE)

View File

@ -6,11 +6,11 @@ from .helper import hopper
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
from PIL.ImageQt import qRgba from PIL.ImageQt import qRgba
def skip_if_qt_is_not_installed():
pass
@pytest.fixture @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
def qpixmap_app(): class PillowQPixmapTestCase:
@classmethod
def setup_class(self):
try: try:
if ImageQt.qt_version == "5": if ImageQt.qt_version == "5":
from PyQt5.QtGui import QGuiApplication from PyQt5.QtGui import QGuiApplication
@ -20,22 +20,15 @@ if ImageQt.qt_is_installed:
pytest.skip("QGuiApplication not installed") pytest.skip("QGuiApplication not installed")
return return
app = QGuiApplication([]) self.app = QGuiApplication([])
try:
yield @classmethod
finally: def teardown_class(self):
app.quit() self.app.quit()
self.app = None
else:
def skip_if_qt_is_not_installed():
return pytest.mark.skip(reason="Qt bindings are not installed")
pytestmark = skip_if_qt_is_not_installed()
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
def test_rgb(): def test_rgb():
# from https://doc.qt.io/archives/qt-4.8/qcolor.html # from https://doc.qt.io/archives/qt-4.8/qcolor.html
# typedef QRgb # typedef QRgb
@ -61,6 +54,7 @@ def test_rgb():
checkrgb(0, 0, 255) checkrgb(0, 0, 255)
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
def test_image(): def test_image():
for mode in ("1", "RGB", "RGBA", "L", "P"): for mode in ("1", "RGB", "RGBA", "L", "P"):
ImageQt.ImageQt(hopper(mode)) ImageQt.ImageQt(hopper(mode))

View File

@ -1,18 +1,15 @@
import pytest
from PIL import ImageQt from PIL import ImageQt
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
from .test_imageqt import qpixmap_app, skip_if_qt_is_not_installed from .test_imageqt import PillowQPixmapTestCase
pytestmark = skip_if_qt_is_not_installed()
def roundtrip(expected): class TestFromQPixmap(PillowQPixmapTestCase):
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) def roundtrip(self, expected):
# Qt saves all pixmaps as rgb result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
assert_image_equal(result, expected.convert("RGB")) # Qt saves all pixmaps as rgb
assert_image_equal(result, expected.convert("RGB"))
def test_sanity(self):
def test_sanity(qpixmap_app): for mode in ("1", "RGB", "RGBA", "L", "P"):
for mode in ("1", "RGB", "RGBA", "L", "P"): self.roundtrip(hopper(mode))
roundtrip(hopper(mode))

View File

@ -1,9 +1,11 @@
import pytest
from PIL import Image, ImageQt from PIL import Image, ImageQt
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
from .test_imageqt import skip_if_qt_is_not_installed
pytestmark = skip_if_qt_is_not_installed() pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
)
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
from PIL.ImageQt import QImage from PIL.ImageQt import QImage

View File

@ -1,22 +1,20 @@
import pytest
from PIL import ImageQt from PIL import ImageQt
from .helper import hopper from .helper import hopper
from .test_imageqt import qpixmap_app, skip_if_qt_is_not_installed from .test_imageqt import PillowQPixmapTestCase
if ImageQt.qt_is_installed: if ImageQt.qt_is_installed:
from PIL.ImageQt import QPixmap from PIL.ImageQt import QPixmap
pytestmark = skip_if_qt_is_not_installed()
class TestToQPixmap(PillowQPixmapTestCase):
def test_sanity(self, tmp_path):
for mode in ("1", "RGB", "RGBA", "L", "P"):
data = ImageQt.toqpixmap(hopper(mode))
def test_sanity(qpixmap, tmp_path): assert isinstance(data, QPixmap)
for mode in ("1", "RGB", "RGBA", "L", "P"): assert not data.isNull()
data = ImageQt.toqpixmap(hopper(mode))
assert isinstance(data, QPixmap) # Test saving the file
assert not data.isNull() tempfile = str(tmp_path / "temp_{}.png".format(mode))
data.save(tempfile)
# Test saving the file
tempfile = str(tmp_path / "temp_{}.png".format(mode))
data.save(tempfile)