From 1a3ebafdd234633c2ca915de9d8b051970994d1a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 17 Feb 2020 10:01:04 -0800 Subject: [PATCH 1/4] Replace SimplePatcher with builtin unittest.mock module The class more or less duplicates the features of the mock module. Can avoid the duplication by using the stdlib. --- Tests/test_imagefont.py | 45 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 12e6ea55a..3e9a48959 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -3,9 +3,9 @@ import distutils.version import os import re import shutil -import sys import unittest from io import BytesIO +from unittest import mock from PIL import Image, ImageDraw, ImageFont, features @@ -27,32 +27,6 @@ HAS_FREETYPE = features.check("freetype2") HAS_RAQM = features.check("raqm") -class SimplePatcher: - def __init__(self, parent_obj, attr_name, value): - self._parent_obj = parent_obj - self._attr_name = attr_name - self._saved = None - self._is_saved = False - self._value = value - - def __enter__(self): - # Patch the attr on the object - if hasattr(self._parent_obj, self._attr_name): - self._saved = getattr(self._parent_obj, self._attr_name) - setattr(self._parent_obj, self._attr_name, self._value) - self._is_saved = True - else: - setattr(self._parent_obj, self._attr_name, self._value) - self._is_saved = False - - def __exit__(self, type, value, traceback): - # Restore the original value - if self._is_saved: - setattr(self._parent_obj, self._attr_name, self._saved) - else: - delattr(self._parent_obj, self._attr_name) - - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") class TestImageFont(PillowTestCase): LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC @@ -491,7 +465,7 @@ class TestImageFont(PillowTestCase): def _test_fake_loading_font(self, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) - with SimplePatcher(ImageFont, "_FreeTypeFont", free_type_font): + with mock.patch.object(ImageFont, "_FreeTypeFont", free_type_font, create=True): def loadable_font(filepath, size, index, encoding, *args, **kwargs): if filepath == path_to_fake: @@ -502,7 +476,7 @@ class TestImageFont(PillowTestCase): filepath, size, index, encoding, *args, **kwargs ) - with SimplePatcher(ImageFont, "FreeTypeFont", loadable_font): + with mock.patch.object(ImageFont, "FreeTypeFont", loadable_font): font = ImageFont.truetype(fontname) # Make sure it's loaded name = font.getname() @@ -513,10 +487,9 @@ class TestImageFont(PillowTestCase): # A lot of mocking here - this is more for hitting code and # catching syntax like errors font_directory = "/usr/local/share/fonts" - with SimplePatcher(sys, "platform", "linux"): - patched_env = copy.deepcopy(os.environ) - patched_env["XDG_DATA_DIRS"] = "/usr/share/:/usr/local/share/" - with SimplePatcher(os, "environ", patched_env): + with mock.patch("sys.platform", "linux"): + patched_env = {"XDG_DATA_DIRS": "/usr/share/:/usr/local/share/"} + with mock.patch.dict(os.environ, patched_env): def fake_walker(path): if path == font_directory: @@ -534,7 +507,7 @@ class TestImageFont(PillowTestCase): ] return [(path, [], ["some_random_font.ttf"])] - with SimplePatcher(os, "walk", fake_walker): + with mock.patch("os.walk", fake_walker): # Test that the font loads both with and without the # extension self._test_fake_loading_font( @@ -559,7 +532,7 @@ class TestImageFont(PillowTestCase): # Like the linux test, more cover hitting code rather than testing # correctness. font_directory = "/System/Library/Fonts" - with SimplePatcher(sys, "platform", "darwin"): + with mock.patch("sys.platform", "darwin"): def fake_walker(path): if path == font_directory: @@ -577,7 +550,7 @@ class TestImageFont(PillowTestCase): ] return [(path, [], ["some_random_font.ttf"])] - with SimplePatcher(os, "walk", fake_walker): + with mock.patch("os.walk", fake_walker): self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial.ttf") self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") self._test_fake_loading_font(font_directory + "/Single.otf", "Single") From 967f46d1d8d243560795917bf03d67b8dcbd401e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 17 Feb 2020 13:46:01 -0800 Subject: [PATCH 2/4] Remove unnecessary skip test logic The gif_encoder and gif_decoder are always compiled and included in src/_imaging.c. --- Tests/test_file_gif.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index d9226cdba..380154581 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -20,8 +20,6 @@ try: except ImportError: HAVE_WEBP = False -codecs = dir(Image.core) - # sample gif stream TEST_GIF = "Tests/images/hopper.gif" @@ -30,10 +28,6 @@ with open(TEST_GIF, "rb") as f: class TestFileGif(PillowTestCase): - def setUp(self): - if "gif_encoder" not in codecs or "gif_decoder" not in codecs: - self.skipTest("gif support not available") # can this happen? - def test_sanity(self): with Image.open(TEST_GIF) as im: im.load() From f72e64b90bc01861240fbbb8f44c0fab020d1400 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 17 Feb 2020 18:28:41 -0800 Subject: [PATCH 3/4] Remove unnecessary setup_module() from test_file_tar.py The test_sanity() already checks the decorder exists and the other tests can run without zlib/jpeg installed. --- Tests/test_file_tar.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index b0e4926b7..f4ca7be44 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -9,11 +9,6 @@ codecs = dir(Image.core) TEST_TAR_FILE = "Tests/images/hopper.tar" -def setup_module(): - if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: - pytest.skip("neither jpeg nor zip support available") - - def test_sanity(): for codec, test_path, format in [ ["zip_decoder", "hopper.png", "PNG"], From 4f185329f4ec22cdb341ed7b2485d38237eb9134 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 17 Feb 2020 14:03:32 -0800 Subject: [PATCH 4/4] Streamline test skipping based on supported features This adds a new test decorator: skip_unless_feature(). The argument is the same as passed to features.check(). If the feature is not supported, the test will be skipped. This removes several kinds of boilerplate copied and pasted around tests so test feature checking is handled and displayed more consistently. Refs #4193 --- Tests/check_j2k_leaks.py | 8 ++---- Tests/helper.py | 7 ++++- Tests/test_features.py | 12 ++++----- Tests/test_file_eps.py | 14 ++++------ Tests/test_file_gif.py | 11 ++------ Tests/test_file_jpeg.py | 13 +++------- Tests/test_file_jpeg2k.py | 8 ++---- Tests/test_file_libtiff.py | 14 +++------- Tests/test_file_mic.py | 6 ++--- Tests/test_file_mpo.py | 8 ++---- Tests/test_file_png.py | 26 ++++--------------- Tests/test_file_tar.py | 10 +++----- Tests/test_file_webp.py | 10 +++----- Tests/test_file_webp_animated.py | 21 +++------------ Tests/test_file_webp_lossless.py | 11 +++----- Tests/test_file_webp_metadata.py | 23 +++-------------- Tests/test_font_leaks.py | 8 +++--- Tests/test_font_pcf.py | 14 +++++----- Tests/test_image_draft.py | 9 ++----- Tests/test_image_split.py | 8 +++--- Tests/test_imagedraw.py | 17 +++++++----- Tests/test_imagedraw2.py | 19 ++++++++------ Tests/test_imagefile.py | 44 ++++++++------------------------ Tests/test_imagefont.py | 10 +++----- Tests/test_imagefontctl.py | 8 +++--- Tests/test_imageops.py | 11 ++------ Tests/test_imagesequence.py | 8 ++---- Tests/test_tiff_ifdrational.py | 4 +-- Tests/test_webp_leaks.py | 7 +++-- 29 files changed, 121 insertions(+), 248 deletions(-) diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 1635f1001..a7a91f782 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -3,22 +3,18 @@ from io import BytesIO from PIL import Image -from .helper import PillowTestCase, is_win32 +from .helper import PillowTestCase, is_win32, skip_unless_feature # Limits for testing the leak mem_limit = 1024 * 1048576 stack_size = 8 * 1048576 iterations = int((mem_limit / stack_size) * 2) -codecs = dir(Image.core) 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 setUp(self): - if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest("JPEG 2000 support not available") - def test_leak_load(self): from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK diff --git a/Tests/helper.py b/Tests/helper.py index 3b2341012..621830333 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -12,7 +12,7 @@ import unittest from io import BytesIO import pytest -from PIL import Image, ImageMath +from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) @@ -172,6 +172,11 @@ def skip_known_bad_test(msg=None): pytest.skip(msg or "Known bad test") +def skip_unless_feature(feature): + reason = "%s not available" % feature + return pytest.mark.skipif(not features.check(feature), reason=reason) + + class PillowTestCase(unittest.TestCase): def delete_tempfile(self, path): try: diff --git a/Tests/test_features.py b/Tests/test_features.py index 88d10f652..10799df33 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -3,12 +3,12 @@ import io import pytest from PIL import features +from .helper import skip_unless_feature + try: from PIL import _webp - - HAVE_WEBP = True except ImportError: - HAVE_WEBP = False + pass def test_check(): @@ -21,18 +21,18 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +@skip_unless_feature("webp") def test_webp_transparency(): assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() assert features.check("transp_webp") == _webp.HAVE_TRANSPARENCY -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +@skip_unless_feature("webp") def test_webp_mux(): assert features.check("webp_mux") == _webp.HAVE_WEBPMUX -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +@skip_unless_feature("webp") def test_webp_anim(): assert features.check("webp_anim") == _webp.HAVE_WEBPANIM diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index c49139d82..77fbe9c4d 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,9 +1,9 @@ import io import unittest -from PIL import EpsImagePlugin, Image +from PIL import EpsImagePlugin, Image, features -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import PillowTestCase, assert_image_similar, hopper, skip_unless_feature HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() @@ -67,7 +67,7 @@ class TestFileEps(PillowTestCase): cmyk_image.load() self.assertEqual(cmyk_image.mode, "RGB") - if "jpeg_decoder" in dir(Image.core): + if features.check("jpg"): with Image.open("Tests/images/pil_sample_rgb.jpg") as target: assert_image_similar(cmyk_image, target, 10) @@ -114,11 +114,9 @@ class TestFileEps(PillowTestCase): self.assertRaises(ValueError, im.save, tmpfile) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") + @skip_unless_feature("zlib") def test_render_scale1(self): # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") # Zero bounding box with Image.open(file1) as image1_scale1: @@ -137,11 +135,9 @@ class TestFileEps(PillowTestCase): assert_image_similar(image2_scale1, image2_scale1_compare, 10) @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") + @skip_unless_feature("zlib") def test_render_scale2(self): # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") # Zero bounding box with Image.open(file1) as image1_scale2: diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 380154581..7da1b9d05 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -2,7 +2,7 @@ import unittest from io import BytesIO import pytest -from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette +from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from .helper import ( PillowTestCase, @@ -13,13 +13,6 @@ from .helper import ( netpbm_available, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - # sample gif stream TEST_GIF = "Tests/images/hopper.gif" @@ -556,7 +549,7 @@ class TestFileGif(PillowTestCase): self.assertEqual(reread.info["background"], im.info["background"]) - if HAVE_WEBP and _webp.HAVE_WEBPANIM: + if features.check("webp") and features.check("webp_anim"): with Image.open("Tests/images/hopper.webp") as im: self.assertIsInstance(im.info["background"], tuple) im.save(out) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 2c0c46d88..a2a848b41 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -13,19 +13,15 @@ from .helper import ( djpeg_available, hopper, is_win32, + skip_unless_feature, unittest, ) -codecs = dir(Image.core) - TEST_FILE = "Tests/images/hopper.jpg" +@skip_unless_feature("jpg") class TestFileJpeg(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - def roundtrip(self, im, **options): out = BytesIO() im.save(out, "JPEG", **options) @@ -687,11 +683,8 @@ class TestFileJpeg(PillowTestCase): @unittest.skipUnless(is_win32(), "Windows only") +@skip_unless_feature("jpg") class TestFileCloseW32(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - def test_fd_leak(self): tmpfile = self.tempfile("temp.jpg") diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index daa5ee375..a1b812429 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -9,10 +9,9 @@ from .helper import ( assert_image_similar, is_big_endian, on_ci, + skip_unless_feature, ) -codecs = dir(Image.core) - test_card = Image.open("Tests/images/test-card.png") test_card.load() @@ -21,11 +20,8 @@ test_card.load() # 'Not enough memory to handle tile data' +@skip_unless_feature("jpg_2000") class TestFileJpeg2k(PillowTestCase): - def setUp(self): - if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest("JPEG 2000 support not available") - def roundtrip(self, im, **options): out = BytesIO() im.save(out, "JPEG2000", **options) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index cf160389f..c43bdb7a1 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -6,7 +6,7 @@ import os from collections import namedtuple from ctypes import c_float -from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features +from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags from .helper import ( PillowTestCase, @@ -15,16 +15,14 @@ from .helper import ( assert_image_similar, assert_image_similar_tofile, hopper, + skip_unless_feature, ) logger = logging.getLogger(__name__) +@skip_unless_feature("libtiff") class LibTiffTestCase(PillowTestCase): - def setUp(self): - if not features.check("libtiff"): - self.skipTest("tiff support not available") - def _assert_noerr(self, im): """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit @@ -727,13 +725,9 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + @skip_unless_feature("jpg") def test_gimp_tiff(self): # Read TIFF JPEG images from GIMP [@PIL168] - - codecs = dir(Image.core) - if "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - filename = "Tests/images/pil168.tif" with Image.open(filename) as im: self.assertEqual(im.mode, "RGB") diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index f7ab774dd..f71c6adf8 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,7 +1,7 @@ import pytest -from PIL import Image, ImagePalette, features +from PIL import Image, ImagePalette -from .helper import assert_image_similar, hopper +from .helper import assert_image_similar, hopper, skip_unless_feature try: from PIL import MicImagePlugin @@ -15,7 +15,7 @@ TEST_FILE = "Tests/images/hopper.mic" pytestmark = [ pytest.mark.skipif(not olefile_installed, reason="olefile package not installed"), - pytest.mark.skipif(not features.check("libtiff"), reason="libtiff not installed"), + skip_unless_feature("libtiff"), ] diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index fd951eff0..893f9075d 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -3,15 +3,11 @@ from io import BytesIO import pytest from PIL import Image -from .helper import assert_image_similar, is_pypy +from .helper import assert_image_similar, is_pypy, skip_unless_feature test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] - -def setup_module(): - codecs = dir(Image.core) - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - pytest.skip("jpeg support not available") +pytestmark = skip_unless_feature("jpg") def frame_roundtrip(im, **options): diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a3d3c0897..b319bc41e 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -15,18 +15,9 @@ from .helper import ( is_big_endian, is_win32, on_ci, + skip_unless_feature, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - -codecs = dir(Image.core) - - # sample png stream TEST_PNG_FILE = "Tests/images/hopper.png" @@ -63,11 +54,8 @@ def roundtrip(im, **options): return Image.open(out) +@skip_unless_feature("zlib") class TestFilePng(PillowTestCase): - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") - def get_chunks(self, filename): chunks = [] with open(filename, "rb") as fp: @@ -632,9 +620,8 @@ class TestFilePng(PillowTestCase): with Image.open(test_file) as reloaded: self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring") - @unittest.skipUnless( - HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP support not installed with animation" - ) + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") def test_apng(self): with Image.open("Tests/images/iss634.apng") as im: self.assertEqual(im.get_format_mimetype(), "image/apng") @@ -645,14 +632,11 @@ class TestFilePng(PillowTestCase): @unittest.skipIf(is_win32(), "requires Unix or macOS") +@skip_unless_feature("zlib") class TestTruncatedPngPLeaks(PillowLeakTestCase): mem_limit = 2 * 1024 # max increase in K iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") - def test_leak_load(self): with open("Tests/images/hopper.png", "rb") as f: DATA = BytesIO(f.read(16 * 1024)) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index f4ca7be44..3fe0cd04e 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,20 +1,18 @@ import pytest -from PIL import Image, TarIO +from PIL import Image, TarIO, features from .helper import is_pypy -codecs = dir(Image.core) - # Sample tar archive TEST_TAR_FILE = "Tests/images/hopper.tar" def test_sanity(): for codec, test_path, format in [ - ["zip_decoder", "hopper.png", "PNG"], - ["jpeg_decoder", "hopper.jpg", "JPEG"], + ["zlib", "hopper.png", "PNG"], + ["jpg", "hopper.jpg", "JPEG"], ]: - if codec in codecs: + if features.check(codec): with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: with Image.open(tar) as im: im.load() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 81742c659..df179d256 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,5 +1,3 @@ -import unittest - import pytest from PIL import Image, WebPImagePlugin @@ -8,6 +6,7 @@ from .helper import ( assert_image_similar, assert_image_similar_tofile, hopper, + skip_unless_feature, ) try: @@ -32,7 +31,7 @@ class TestUnsupportedWebp(PillowTestCase): WebPImagePlugin.SUPPORTED = True -@unittest.skipUnless(HAVE_WEBP, "WebP support not installed") +@skip_unless_feature("webp") class TestFileWebp(PillowTestCase): def setUp(self): self.rgb_mode = "RGB" @@ -155,9 +154,8 @@ class TestFileWebp(PillowTestCase): Image.open(blob).load() Image.open(blob).load() - @unittest.skipUnless( - HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP save all not available" - ) + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") def test_background_from_gif(self): with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 3aab61e79..02b43b63e 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -7,28 +7,13 @@ from .helper import ( assert_image_similar, is_big_endian, on_ci, + skip_unless_feature, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - +@skip_unless_feature("webp") +@skip_unless_feature("webp_anim") class TestFileWebpAnimation(PillowTestCase): - def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return - - if not _webp.HAVE_WEBPANIM: - self.skipTest( - "WebP library does not contain animation support, " - "not testing animation" - ) - def test_n_frames(self): """ Ensure that WebP format sets n_frames and is_animated diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4c48237af..2bb603023 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,21 +1,16 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import PillowTestCase, assert_image_equal, hopper, skip_unless_feature try: from PIL import _webp - - HAVE_WEBP = True except ImportError: - HAVE_WEBP = False + pass +@skip_unless_feature("webp") class TestFileWebpLossless(PillowTestCase): def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return - if _webp.WebPDecoderVersion() < 0x0200: self.skipTest("lossless not included") diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 06c780299..092167083 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -2,25 +2,12 @@ from io import BytesIO from PIL import Image -from .helper import PillowTestCase - -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False +from .helper import PillowTestCase, skip_unless_feature +@skip_unless_feature("webp") +@skip_unless_feature("webp_mux") class TestFileWebpMetadata(PillowTestCase): - def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return - - if not _webp.HAVE_WEBPMUX: - self.skipTest("WebPMux support not installed") - def test_read_exif_metadata(self): file_path = "Tests/images/flower.webp" @@ -100,10 +87,8 @@ class TestFileWebpMetadata(PillowTestCase): with Image.open(test_buffer) as webp_image: self.assertFalse(webp_image._getexif()) + @skip_unless_feature("webp_anim") def test_write_animated_metadata(self): - if not _webp.HAVE_WEBPANIM: - self.skipTest("WebP animation support not available") - iccp_data = b"" exif_data = b"" xmp_data = b"" diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index be0612fa2..015210b4d 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -1,8 +1,6 @@ -import unittest +from PIL import Image, ImageDraw, ImageFont -from PIL import Image, ImageDraw, ImageFont, features - -from .helper import PillowLeakTestCase +from .helper import PillowLeakTestCase, skip_unless_feature class TestTTypeFontLeak(PillowLeakTestCase): @@ -19,7 +17,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): ) ) - @unittest.skipUnless(features.check("freetype2"), "Test requires freetype2") + @skip_unless_feature("freetype2") def test_leak(self): ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) self._test_font(ttype) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 4a4edf889..358798948 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,19 +1,19 @@ from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import PillowTestCase, assert_image_equal, assert_image_similar - -codecs = dir(Image.core) +from .helper import ( + PillowTestCase, + assert_image_equal, + assert_image_similar, + skip_unless_feature, +) fontname = "Tests/fonts/10x20-ISO8859-1.pcf" message = "hello, world" +@skip_unless_feature("zlib") class TestFontPcf(PillowTestCase): - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zlib support not available") - def save_font(self): with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 0090ce378..8b4b44768 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -1,13 +1,8 @@ -import pytest from PIL import Image -from .helper import fromstring, tostring +from .helper import fromstring, skip_unless_feature, tostring - -def setup_module(): - codecs = dir(Image.core) - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - pytest.skip("jpeg support not available") +pytestmark = skip_unless_feature("jpg") def draft_roundtrip(in_mode, in_size, req_mode, req_size): diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 1be7eaf46..80a531103 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,4 +1,4 @@ -from PIL import Image +from PIL import Image, features from .helper import PillowTestCase, assert_image_equal, hopper @@ -44,9 +44,7 @@ class TestImageSplit(PillowTestCase): assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) def test_split_open(self): - codecs = dir(Image.core) - - if "zip_encoder" in codecs: + if features.check("zlib"): test_file = self.tempfile("temp.png") else: test_file = self.tempfile("temp.pcx") @@ -60,5 +58,5 @@ class TestImageSplit(PillowTestCase): self.assertEqual(split_open("L"), 1) self.assertEqual(split_open("P"), 1) self.assertEqual(split_open("RGB"), 3) - if "zip_encoder" in codecs: + if features.check("zlib"): self.assertEqual(split_open("RGBA"), 4) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 46ce0fa7d..9036e1842 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,9 +1,14 @@ import os.path import pytest -from PIL import Image, ImageColor, ImageDraw, ImageFont, features +from PIL import Image, ImageColor, ImageDraw, ImageFont -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + hopper, + skip_unless_feature, +) BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -30,8 +35,6 @@ POINTS2 = [10, 10, 20, 40, 30, 30] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] -HAS_FREETYPE = features.check("freetype2") - def test_sanity(): im = hopper("RGB").copy() @@ -912,7 +915,7 @@ def test_textsize_empty_string(): draw.textsize("test\n") -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_textsize_stroke(): # Arrange im = Image.new("RGB", (W, H)) @@ -924,7 +927,7 @@ def test_textsize_stroke(): assert draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) == (52, 44) -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_stroke(): for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): # Arrange @@ -941,7 +944,7 @@ def test_stroke(): ) -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_stroke_multiline(): # Arrange im = Image.new("RGB", (100, 250)) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index c9ea687ec..72cbb79b8 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -1,9 +1,13 @@ import os.path -import pytest -from PIL import Image, ImageDraw, ImageDraw2, features +from PIL import Image, ImageDraw, ImageDraw2 -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + hopper, + skip_unless_feature, +) BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -30,7 +34,6 @@ POINTS2 = [10, 10, 20, 40, 30, 30] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] -HAS_FREETYPE = features.check("freetype2") FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -178,7 +181,7 @@ def test_big_rectangle(): assert_image_similar(im, Image.open(expected), 1) -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_text(): # Arrange im = Image.new("RGB", (W, H)) @@ -193,7 +196,7 @@ def test_text(): assert_image_similar(im, Image.open(expected), 13) -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_textsize(): # Arrange im = Image.new("RGB", (W, H)) @@ -207,7 +210,7 @@ def test_textsize(): assert size[1] == 12 -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_textsize_empty_string(): # Arrange im = Image.new("RGB", (W, H)) @@ -222,7 +225,7 @@ def test_textsize_empty_string(): draw.textsize("test\n", font) -@pytest.mark.skipif(not HAS_FREETYPE, reason="ImageFont not available") +@skip_unless_feature("freetype2") def test_flush(): # Arrange im = Image.new("RGB", (W, H)) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 13fe3e192..18771fd41 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,7 +1,6 @@ -import unittest from io import BytesIO -from PIL import EpsImagePlugin, Image, ImageFile +from PIL import EpsImagePlugin, Image, ImageFile, features from .helper import ( PillowTestCase, @@ -10,19 +9,10 @@ from .helper import ( assert_image_similar, fromstring, hopper, + skip_unless_feature, tostring, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - - -codecs = dir(Image.core) - # save original block sizes MAXBLOCK = ImageFile.MAXBLOCK SAFEBLOCK = ImageFile.SAFEBLOCK @@ -53,7 +43,7 @@ class TestImageFile(PillowTestCase): assert_image_similar(im1.convert("P"), im2, 1) assert_image_equal(*roundtrip("IM")) assert_image_equal(*roundtrip("MSP")) - if "zip_encoder" in codecs: + if features.check("zlib"): try: # force multiple blocks in PNG driver ImageFile.MAXBLOCK = 8192 @@ -77,7 +67,7 @@ class TestImageFile(PillowTestCase): # EPS comes back in RGB: assert_image_similar(im1, im2.convert("L"), 20) - if "jpeg_encoder" in codecs: + if features.check("jpg"): im1, im2 = roundtrip("JPEG") # lossy compression assert_image(im1, im2.mode, im2.size) @@ -90,10 +80,8 @@ class TestImageFile(PillowTestCase): p.feed(data) self.assertEqual((48, 48), p.image.size) + @skip_unless_feature("zlib") def test_safeblock(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - im1 = hopper() try: @@ -120,10 +108,8 @@ class TestImageFile(PillowTestCase): with self.assertRaises(IOError): p.close() + @skip_unless_feature("zlib") def test_truncated_with_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/truncated_image.png") as im: with self.assertRaises(IOError): im.load() @@ -132,10 +118,8 @@ class TestImageFile(PillowTestCase): with self.assertRaises(IOError): im.load() + @skip_unless_feature("zlib") def test_truncated_without_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/truncated_image.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -143,18 +127,14 @@ class TestImageFile(PillowTestCase): finally: ImageFile.LOAD_TRUNCATED_IMAGES = False + @skip_unless_feature("zlib") def test_broken_datastream_with_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/broken_data_stream.png") as im: with self.assertRaises(IOError): im.load() + @skip_unless_feature("zlib") def test_broken_datastream_without_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/broken_data_stream.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -292,10 +272,8 @@ class TestPyDecoder(PillowTestCase): self.assertEqual(reloaded_exif[40963], 455) self.assertEqual(exif[305], "Pillow test") - @unittest.skipIf( - not HAVE_WEBP or not _webp.HAVE_WEBPANIM, - "WebP support not installed with animation", - ) + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") def test_exif_webp(self): with Image.open("Tests/images/hopper.webp") as im: exif = im.getexif() diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 3e9a48959..93adc0911 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,7 +7,7 @@ import unittest from io import BytesIO from unittest import mock -from PIL import Image, ImageDraw, ImageFont, features +from PIL import Image, ImageDraw, ImageFont from .helper import ( PillowTestCase, @@ -16,6 +16,7 @@ from .helper import ( assert_image_similar_tofile, is_pypy, is_win32, + skip_unless_feature, ) FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -23,11 +24,8 @@ FONT_SIZE = 20 TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" -HAS_FREETYPE = features.check("freetype2") -HAS_RAQM = features.check("raqm") - -@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") +@skip_unless_feature("freetype2") class TestImageFont(PillowTestCase): LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC @@ -725,6 +723,6 @@ class TestImageFont(PillowTestCase): _check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) -@unittest.skipUnless(HAS_RAQM, "Raqm not Available") +@skip_unless_feature("raqm") class TestImageFont_RaqmLayout(TestImageFont): LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 796059e5d..8f619b9f9 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,14 +1,12 @@ -import unittest +from PIL import Image, ImageDraw, ImageFont -from PIL import Image, ImageDraw, ImageFont, features - -from .helper import PillowTestCase, assert_image_similar +from .helper import PillowTestCase, assert_image_similar, skip_unless_feature FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans.ttf" -@unittest.skipUnless(features.check("raqm"), "Raqm Library is not installed.") +@skip_unless_feature("raqm") class TestImagecomplextext(PillowTestCase): def test_english(self): # smoke test, this should not fail diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index af78c1197..3d0afba9c 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,5 +1,5 @@ import pytest -from PIL import Image, ImageOps +from PIL import Image, ImageOps, features from .helper import ( assert_image_equal, @@ -8,13 +8,6 @@ from .helper import ( hopper, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - class Deformer: def getmesh(self, im): @@ -274,7 +267,7 @@ def test_colorize_3color_offset(): def test_exif_transpose(): exts = [".jpg"] - if HAVE_WEBP and _webp.HAVE_WEBPANIM: + if features.check("webp") and features.check("webp_anim"): exts.append(".webp") for ext in exts: with Image.open("Tests/images/hopper" + ext) as base_im: diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 741d71a1c..e0c3f8dec 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,6 +1,6 @@ from PIL import Image, ImageSequence, TiffImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import PillowTestCase, assert_image_equal, hopper, skip_unless_feature class TestImageSequence(PillowTestCase): @@ -47,12 +47,8 @@ class TestImageSequence(PillowTestCase): def test_tiff(self): self._test_multipage_tiff() + @skip_unless_feature("libtiff") def test_libtiff(self): - codecs = dir(Image.core) - - if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: - self.skipTest("tiff support not available") - TiffImagePlugin.READ_LIBTIFF = True self._test_multipage_tiff() TiffImagePlugin.READ_LIBTIFF = False diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index dedbbfe6d..e570ecb99 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,6 +1,6 @@ from fractions import Fraction -from PIL import Image, TiffImagePlugin +from PIL import Image, TiffImagePlugin, features from PIL.TiffImagePlugin import IFDRational from .helper import PillowTestCase, hopper @@ -43,7 +43,7 @@ class Test_IFDRational(PillowTestCase): def test_ifd_rational_save(self): methods = (True, False) - if "libtiff_encoder" not in dir(Image.core): + if not features.check("libtiff"): methods = (False,) for libtiff in methods: diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index 713fc161e..34197c14f 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -1,14 +1,13 @@ -import unittest from io import BytesIO -from PIL import Image, features +from PIL import Image -from .helper import PillowLeakTestCase +from .helper import PillowLeakTestCase, skip_unless_feature test_file = "Tests/images/hopper.webp" -@unittest.skipUnless(features.check("webp"), "WebP is not installed") +@skip_unless_feature("webp") class TestWebPLeaks(PillowLeakTestCase): mem_limit = 3 * 1024 # kb