diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 8b172343c..f196757dc 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,9 +1,8 @@ import time -import unittest from PIL import PyAccess -from .helper import PillowTestCase, hopper +from .helper import hopper # 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(self): - im = hopper() - im.load() - # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) +def test_direct(): + im = hopper() + im.load() + # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) + caccess = im.im.pixel_access(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) - timer(iterate_get, "PyAccess - get", im.size, access) - timer(iterate_set, "PyAccess - set", im.size, access) - timer(iterate_get, "C-api - get", im.size, caccess) - timer(iterate_set, "C-api - set", im.size, caccess) - - -if __name__ == "__main__": - unittest.main() + print("Size: %sx%s" % im.size) + timer(iterate_get, "PyAccess - get", im.size, access) + timer(iterate_set, "PyAccess - set", im.size, access) + timer(iterate_get, "C-api - get", im.size, caccess) + timer(iterate_set, "C-api - set", im.size, caccess) diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index 206a86007..08a55d349 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -1,19 +1,10 @@ -import unittest - from PIL import Image -from .helper import PillowTestCase - TEST_FILE = "Tests/images/fli_overflow.fli" -class TestFliOverflow(PillowTestCase): - def test_fli_overflow(self): +def test_fli_overflow(): - # this should not crash with a malloc error or access violation - with Image.open(TEST_FILE) as im: - im.load() - - -if __name__ == "__main__": - unittest.main() + # this should not crash with a malloc error or access violation + with Image.open(TEST_FILE) as im: + im.load() diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 8ca955ac7..db12d00e3 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,46 +1,44 @@ #!/usr/bin/env python -import unittest - +import pytest from PIL import Image -from .helper import PillowTestCase, is_win32 +from .helper import is_win32 min_iterations = 100 max_iterations = 10000 - -@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)), - ) +pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") -if __name__ == "__main__": - unittest.main() +def _get_mem_usage(): + 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)), + ) diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index a7a91f782..5cef4b544 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,9 +1,9 @@ -import unittest from io import BytesIO +import pytest 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 mem_limit = 1024 * 1048576 @@ -11,32 +11,31 @@ stack_size = 8 * 1048576 iterations = int((mem_limit / stack_size) * 2) test_file = "Tests/images/rgb_trns_ycbc.jp2" - -@unittest.skipIf(is_win32(), "requires Unix or macOS") -@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() +pytestmark = [ + pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"), + skip_unless_feature("jpg_2000"), +] -if __name__ == "__main__": - unittest.main() +def test_leak_load(): + 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() diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index d5b6e455f..f20ad6748 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,18 +1,9 @@ -import unittest - +import pytest from PIL import Image -from .helper import PillowTestCase - -class TestJ2kEncodeOverflow(PillowTestCase): - def test_j2k_overflow(self): - - im = Image.new("RGBA", (1024, 131584)) - target = self.tempfile("temp.jpc") - with self.assertRaises(IOError): - im.save(target) - - -if __name__ == "__main__": - unittest.main() +def test_j2k_overflow(tmp_path): + im = Image.new("RGBA", (1024, 131584)) + target = str(tmp_path / "temp.jpc") + with pytest.raises(IOError): + im.save(target) diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index 6b2801a21..b63fa2a1e 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -1,7 +1,8 @@ -import unittest from io import BytesIO -from .helper import PillowTestCase, hopper, is_win32 +import pytest + +from .helper import hopper, is_win32 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") -class TestJpegLeaks(PillowTestCase): +pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - """ +""" pre patch: MB @@ -74,49 +74,51 @@ post-patch: """ - def test_qtables_leak(self): - im = hopper("RGB") - standard_l_qtable = [ - int(s) - 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 - ) - ] +def test_qtables_leak(): + im = hopper("RGB") - 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 - ) - ] + standard_l_qtable = [ + int(s) + 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 + ) + ] - 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): - test_output = BytesIO() - im.save(test_output, "JPEG", qtables=qtables) + qtables = [standard_l_qtable, standard_chrominance_qtable] - 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: MB @@ -171,15 +173,16 @@ post patch: 0 11.33 """ - im = hopper("RGB") - exif = b"12345678" * 4096 + im = hopper("RGB") + exif = b"12345678" * 4096 - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", exif=exif) + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", exif=exif) - def test_base_save(self): - """ + +def test_base_save(): + """ base case: MB 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: @@ -205,12 +208,8 @@ base case: 0 +----------------------------------------------------------------------->Gi 0 7.882 """ - im = hopper("RGB") + im = hopper("RGB") - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG") - - -if __name__ == "__main__": - unittest.main() + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG") diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 7fcaa4cf9..f44a5a5bb 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,10 +1,8 @@ import sys -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase - # This test is not run automatically. # # It requires > 2gb memory for the >2 gigapixel image generated in the @@ -24,26 +22,26 @@ YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2 ** 32, "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) +pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") -if __name__ == "__main__": - unittest.main() +def _write_png(tmp_path, xdim, ydim): + 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) diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index 8e65dc1cb..de6f4571c 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,10 +1,8 @@ import sys -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase - # This test is not run automatically. # # It requires > 2gb memory for the >2 gigapixel image generated in the @@ -14,32 +12,28 @@ from .helper import PillowTestCase # Raspberry Pis). -try: - import numpy as np -except ImportError: - raise unittest.SkipTest("numpy not installed") +np = pytest.importorskip("numpy", reason="NumPy not installed") YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2 ** 32, "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) +pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") -if __name__ == "__main__": - unittest.main() +def _write_png(tmp_path, xdim, ydim): + 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) diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index 711168f65..5187385d6 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,23 +1,14 @@ -import unittest - import pytest from PIL import Image -from .helper import PillowTestCase - TEST_FILE = "Tests/images/libtiff_segfault.tif" -class TestLibtiffSegfault(PillowTestCase): - def test_segfault(self): - """ This test should not segfault. It will on Pillow <= 3.1.0 and - libtiff >= 4.0.0 - """ +def test_libtiff_segfault(): + """ This test should not segfault. It will on Pillow <= 3.1.0 and + libtiff >= 4.0.0 + """ - with pytest.raises(IOError): - with Image.open(TEST_FILE) as im: - im.load() - - -if __name__ == "__main__": - unittest.main() + with pytest.raises(IOError): + with Image.open(TEST_FILE) as im: + im.load() diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index b981d36bf..86eb937e9 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -1,67 +1,61 @@ -import unittest import zlib from io import BytesIO from PIL import Image, ImageFile, PngImagePlugin -from .helper import PillowTestCase - TEST_FILE = "Tests/images/png_decompression_dos.png" -class TestPngDos(PillowTestCase): - def test_ignore_dos_text(self): - ImageFile.LOAD_TRUNCATED_IMAGES = True +def test_ignore_dos_text(): + ImageFile.LOAD_TRUNCATED_IMAGES = True - try: - im = Image.open(TEST_FILE) - im.load() - finally: - ImageFile.LOAD_TRUNCATED_IMAGES = False + try: + im = Image.open(TEST_FILE) + im.load() + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False - for s in im.text.values(): - assert len(s) < 1024 * 1024, "Text chunk larger than 1M" + for s in im.text.values(): + assert len(s) < 1024 * 1024, "Text chunk larger than 1M" - for s in im.info.values(): - 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" + for s in im.info.values(): + assert len(s) < 1024 * 1024, "Text chunk larger than 1M" -if __name__ == "__main__": - unittest.main() +def test_dos_text(): + + 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" diff --git a/Tests/helper.py b/Tests/helper.py index 39d3ed482..15a51ccd1 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -7,7 +7,6 @@ import os import shutil import sys import tempfile -import unittest from io import BytesIO import pytest @@ -176,22 +175,6 @@ def skip_unless_feature(feature): 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") class PillowLeakTestCase: # requires unix/macOS diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 58bcbd085..1704400b4 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,15 +1,16 @@ import pytest from PIL import Image -from .helper import PillowTestCase, hopper +from .helper import hopper TEST_FILE = "Tests/images/hopper.ppm" ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS -class TestDecompressionBomb(PillowTestCase): - def tearDown(self): +class TestDecompressionBomb: + @classmethod + def teardown_class(self): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT def test_no_warning_small_file(self): @@ -59,12 +60,14 @@ class TestDecompressionBomb(PillowTestCase): Image.open("Tests/images/decompression_bomb.gif") -class TestDecompressionCrop(PillowTestCase): - def setUp(self): +class TestDecompressionCrop: + @classmethod + def setup_class(self): width, height = 128, 128 Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 - def tearDown(self): + @classmethod + def teardown_class(self): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT def testEnlargeCrop(self): diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 1c2c0442b..71b6012c3 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,74 +1,77 @@ import pytest 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(self): +def test_load_raw(): - # Test basic EMF open and rendering - with Image.open("Tests/images/drawing.emf") as im: - if hasattr(Image.core, "drawwmf"): - # Currently, support for WMF/EMF is Windows-only - im.load() - # Compare to reference rendering - with Image.open("Tests/images/drawing_emf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 0) + # Test basic EMF open and rendering + with Image.open("Tests/images/drawing.emf") as im: + if hasattr(Image.core, "drawwmf"): + # Currently, support for WMF/EMF is Windows-only + im.load() + # Compare to reference rendering + with Image.open("Tests/images/drawing_emf_ref.png") as imref: + imref.load() + assert_image_similar(im, imref, 0) - # Test basic WMF open and rendering - with Image.open("Tests/images/drawing.wmf") as im: - if hasattr(Image.core, "drawwmf"): - # Currently, support for WMF/EMF is Windows-only - im.load() - # Compare to reference rendering - with Image.open("Tests/images/drawing_wmf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 2.0) + # Test basic WMF open and rendering + with Image.open("Tests/images/drawing.wmf") as im: + if hasattr(Image.core, "drawwmf"): + # Currently, support for WMF/EMF is Windows-only + im.load() + # Compare to reference rendering + with Image.open("Tests/images/drawing_wmf_ref.png") as imref: + imref.load() + assert_image_similar(im, imref, 2.0) - def test_register_handler(self): - class TestHandler: - methodCalled = False - def save(self, im, fp, filename): - self.methodCalled = True +def test_register_handler(tmp_path): + class TestHandler: + methodCalled = False - handler = TestHandler() - WmfImagePlugin.register_handler(handler) + def save(self, im, fp, filename): + self.methodCalled = True - im = hopper() - tmpfile = self.tempfile("temp.wmf") - im.save(tmpfile) - assert handler.methodCalled + handler = TestHandler() + WmfImagePlugin.register_handler(handler) - # Restore the state before this test - WmfImagePlugin.register_handler(None) + im = hopper() + tmpfile = str(tmp_path / "temp.wmf") + im.save(tmpfile) + assert handler.methodCalled - def test_load_dpi_rounding(self): - # Round up - with Image.open("Tests/images/drawing.emf") as im: - assert im.info["dpi"] == 1424 + # Restore the state before this test + WmfImagePlugin.register_handler(None) - # Round down - with Image.open("Tests/images/drawing_roundDown.emf") as im: - assert im.info["dpi"] == 1426 - def test_load_set_dpi(self): - with Image.open("Tests/images/drawing.wmf") as im: - assert im.size == (82, 82) +def test_load_dpi_rounding(): + # Round up + with Image.open("Tests/images/drawing.emf") as im: + assert im.info["dpi"] == 1424 - if hasattr(Image.core, "drawwmf"): - im.load(144) - assert im.size == (164, 164) + # Round down + with Image.open("Tests/images/drawing_roundDown.emf") as im: + 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): - im = hopper() +def test_load_set_dpi(): + with Image.open("Tests/images/drawing.wmf") as im: + assert im.size == (82, 82) - for ext in [".wmf", ".emf"]: - tmpfile = self.tempfile("temp" + ext) - with pytest.raises(IOError): - im.save(tmpfile) + if hasattr(Image.core, "drawwmf"): + im.load(144) + assert im.size == (164, 164) + + 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) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 2f19beeb2..afd0c38b2 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,12 +1,9 @@ import os + import pytest from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import ( - assert_image_equal, - assert_image_similar, - skip_unless_feature, -) +from .helper import assert_image_equal, assert_image_similar, skip_unless_feature fontname = "Tests/fonts/10x20-ISO8859-1.pcf" diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index b653edb16..170d49ae1 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -2,10 +2,10 @@ import pytest from PIL import Image, ImageQt 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" +) @pytest.fixture @@ -18,7 +18,7 @@ def test_images(): try: yield ims finally: - for im in ims.values(): + for im in ims: im.close() diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 4e97ee50b..1d3ca8135 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -1,9 +1,9 @@ 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 = {} size = 128 diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 3281836d5..ad4be135a 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -6,10 +6,10 @@ from itertools import permutations import pytest 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): # Image class independent version of resize. im.load() @@ -135,31 +135,36 @@ class TestImagingCoreResize(PillowTestCase): self.resize(hopper(), (10, 10), 9) -class TestReducingGapResize(PillowTestCase): - @classmethod - def setUpClass(cls): - cls.gradients_image = Image.open("Tests/images/radial_gradients.png") - cls.gradients_image.load() +@pytest.fixture +def gradients_image(): + im = Image.open("Tests/images/radial_gradients.png") + im.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) - im = self.gradients_image.resize((52, 34), Image.BICUBIC) + +class TestReducingGapResize: + 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) 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): - 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 [ (None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=1.0 ) @@ -168,14 +173,14 @@ class TestReducingGapResize(PillowTestCase): assert_image_similar(ref, im, epsilon) - def test_reducing_gap_2(self): + def test_reducing_gap_2(self, gradients_image): for box, epsilon in [ (None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=2.0 ) @@ -184,14 +189,14 @@ class TestReducingGapResize(PillowTestCase): assert_image_similar(ref, im, epsilon) - def test_reducing_gap_3(self): + def test_reducing_gap_3(self, gradients_image): for box, epsilon in [ (None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=3.0 ) @@ -200,29 +205,27 @@ class TestReducingGapResize(PillowTestCase): 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)]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=8.0 ) assert_image_equal(ref, im) - def test_box_filter(self): + def test_box_filter(self, gradients_image): for box, epsilon in [ ((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5), ]: - ref = self.gradients_image.resize((52, 34), Image.BOX, box=box) - im = self.gradients_image.resize( - (52, 34), Image.BOX, box=box, reducing_gap=1.0 - ) + ref = gradients_image.resize((52, 34), Image.BOX, box=box) + im = gradients_image.resize((52, 34), Image.BOX, box=box, reducing_gap=1.0) assert_image_similar(ref, im, epsilon) -class TestImageResize(PillowTestCase): +class TestImageResize: def test_resize(self): def resize(mode, size): out = hopper(mode).resize(size) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index b3686aea1..75eec44a0 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -10,7 +10,6 @@ import pytest from PIL import Image, ImageDraw, ImageFont from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, assert_image_similar_tofile, @@ -25,8 +24,10 @@ FONT_SIZE = 20 TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" -@skip_unless_feature("freetype2") -class TestImageFont(PillowTestCase): +pytestmark = skip_unless_feature("freetype2") + + +class TestImageFont: LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC # 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)}, } - def setUp(self): + @classmethod + def setup_class(self): freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) self.metrics = self.METRICS["Default"] @@ -107,12 +109,12 @@ class TestImageFont(PillowTestCase): with open(FONT_PATH, "rb") as 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: - tempfile = self.tempfile("temp_" + chr(128) + ".ttf") + shutil.copy(FONT_PATH, tempfile) except UnicodeEncodeError: - self.skipTest("Unicode path could not be created") - shutil.copy(FONT_PATH, tempfile) + pytest.skip("Unicode path could not be created") ImageFont.truetype(tempfile, FONT_SIZE) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index fbb650861..d723690ef 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -6,11 +6,11 @@ from .helper import hopper if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba - def skip_if_qt_is_not_installed(): - pass - @pytest.fixture - def qpixmap_app(): +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") +class PillowQPixmapTestCase: + @classmethod + def setup_class(self): try: if ImageQt.qt_version == "5": from PyQt5.QtGui import QGuiApplication @@ -20,22 +20,15 @@ if ImageQt.qt_is_installed: pytest.skip("QGuiApplication not installed") return - app = QGuiApplication([]) - try: - yield - finally: - app.quit() - - -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() + self.app = QGuiApplication([]) + + @classmethod + def teardown_class(self): + self.app.quit() + self.app = None +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_rgb(): # from https://doc.qt.io/archives/qt-4.8/qcolor.html # typedef QRgb @@ -61,6 +54,7 @@ def test_rgb(): checkrgb(0, 0, 255) +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") def test_image(): for mode in ("1", "RGB", "RGBA", "L", "P"): ImageQt.ImageQt(hopper(mode)) diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py index 96eaba41f..cb1b385ec 100644 --- a/Tests/test_qt_image_fromqpixmap.py +++ b/Tests/test_qt_image_fromqpixmap.py @@ -1,18 +1,15 @@ -import pytest from PIL import ImageQt from .helper import assert_image_equal, hopper -from .test_imageqt import qpixmap_app, skip_if_qt_is_not_installed - -pytestmark = skip_if_qt_is_not_installed() +from .test_imageqt import PillowQPixmapTestCase -def roundtrip(expected): - result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) - # Qt saves all pixmaps as rgb - assert_image_equal(result, expected.convert("RGB")) +class TestFromQPixmap(PillowQPixmapTestCase): + def roundtrip(self, expected): + result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) + # Qt saves all pixmaps as rgb + assert_image_equal(result, expected.convert("RGB")) - -def test_sanity(qpixmap_app): - for mode in ("1", "RGB", "RGBA", "L", "P"): - roundtrip(hopper(mode)) + def test_sanity(self): + for mode in ("1", "RGB", "RGBA", "L", "P"): + self.roundtrip(hopper(mode)) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index e6fd18c52..4c98bf0b4 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,9 +1,11 @@ +import pytest from PIL import Image, ImageQt 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: from PIL.ImageQt import QImage diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index f7cf59709..af281da69 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -1,22 +1,20 @@ -import pytest from PIL import ImageQt 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: 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): - for mode in ("1", "RGB", "RGBA", "L", "P"): - data = ImageQt.toqpixmap(hopper(mode)) + assert isinstance(data, QPixmap) + assert not data.isNull() - assert isinstance(data, QPixmap) - assert not data.isNull() - - # Test saving the file - 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)