From 748739c9928a3961c3991ffd48359228417f2b08 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Mar 2020 19:50:51 +1100 Subject: [PATCH 1/6] Converted addCleanup --- Tests/test_decompression_bomb.py | 10 +- Tests/test_font_pcf.py | 117 +++++++++++---------- Tests/test_image_fromqimage.py | 75 +++++++------- Tests/test_imageops_usm.py | 172 +++++++++++++++++-------------- Tests/test_imageqt.py | 7 +- 5 files changed, 204 insertions(+), 177 deletions(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index f3981b3ec..58bcbd085 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -61,9 +61,8 @@ class TestDecompressionBomb(PillowTestCase): class TestDecompressionCrop(PillowTestCase): def setUp(self): - self.src = hopper() - self.addCleanup(self.src.close) - Image.MAX_IMAGE_PIXELS = self.src.height * self.src.width * 4 - 1 + width, height = 128, 128 + Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 def tearDown(self): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT @@ -71,8 +70,9 @@ class TestDecompressionCrop(PillowTestCase): def testEnlargeCrop(self): # Crops can extend the extents, therefore we should have the # same decompression bomb warnings on them. - box = (0, 0, self.src.width * 2, self.src.height * 2) - pytest.warns(Image.DecompressionBombWarning, self.src.crop, box) + with hopper() as src: + box = (0, 0, src.width * 2, src.height * 2) + pytest.warns(Image.DecompressionBombWarning, src.crop, box) def test_crop_decompression_checks(self): diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 95250e5ee..16f8571f5 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,3 +1,4 @@ +import os import pytest from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile @@ -13,67 +14,73 @@ fontname = "Tests/fonts/10x20-ISO8859-1.pcf" message = "hello, world" -@skip_unless_feature("zlib") -class TestFontPcf(PillowTestCase): - def save_font(self): - with open(fontname, "rb") as test_file: - font = PcfFontFile.PcfFontFile(test_file) - assert isinstance(font, FontFile.FontFile) - # check the number of characters in the font - assert len([_f for _f in font.glyph if _f]) == 223 +pytestmark = skip_unless_feature("zlib") - tempname = self.tempfile("temp.pil") - self.addCleanup(self.delete_tempfile, tempname[:-4] + ".pbm") - font.save(tempname) +def save_font(request, tmp_path): + with open(fontname, "rb") as test_file: + font = PcfFontFile.PcfFontFile(test_file) + assert isinstance(font, FontFile.FontFile) + # check the number of characters in the font + assert len([_f for _f in font.glyph if _f]) == 223 - with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/10x20.pbm") as target: - assert_image_equal(loaded, target) + tempname = str(tmp_path / "temp.pil") - with open(tempname, "rb") as f_loaded: - with open("Tests/fonts/10x20.pil", "rb") as f_target: - assert f_loaded.read() == f_target.read() - return tempname + def delete_tempfile(): + try: + os.remove(tempname[:-4] + ".pbm") + except OSError: + pass # report? + request.addfinalizer(delete_tempfile) + font.save(tempname) - def test_sanity(self): - self.save_font() + with Image.open(tempname.replace(".pil", ".pbm")) as loaded: + with Image.open("Tests/fonts/10x20.pbm") as target: + assert_image_equal(loaded, target) - def test_invalid_file(self): - with open("Tests/images/flower.jpg", "rb") as fp: - with pytest.raises(SyntaxError): - PcfFontFile.PcfFontFile(fp) + with open(tempname, "rb") as f_loaded: + with open("Tests/fonts/10x20.pil", "rb") as f_target: + assert f_loaded.read() == f_target.read() + return tempname - def test_draw(self): - tempname = self.save_font() - font = ImageFont.load(tempname) - im = Image.new("L", (130, 30), "white") - draw = ImageDraw.Draw(im) - draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/test_draw_pbm_target.png") as target: - assert_image_similar(im, target, 0) +def test_sanity(request, tmp_path): + save_font(request, tmp_path) - def test_textsize(self): - tempname = self.save_font() - font = ImageFont.load(tempname) - for i in range(255): - (dx, dy) = font.getsize(chr(i)) - assert dy == 20 - assert dx in (0, 10) - for l in range(len(message)): - msg = message[: l + 1] - assert font.getsize(msg) == (len(msg) * 10, 20) +def test_invalid_file(): + with open("Tests/images/flower.jpg", "rb") as fp: + with pytest.raises(SyntaxError): + PcfFontFile.PcfFontFile(fp) - def _test_high_characters(self, message): - tempname = self.save_font() - font = ImageFont.load(tempname) - im = Image.new("L", (750, 30), "white") - draw = ImageDraw.Draw(im) - draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/high_ascii_chars.png") as target: - assert_image_similar(im, target, 0) +def test_draw(request, tmp_path): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + im = Image.new("L", (130, 30), "white") + draw = ImageDraw.Draw(im) + draw.text((0, 0), message, "black", font=font) + with Image.open("Tests/images/test_draw_pbm_target.png") as target: + assert_image_similar(im, target, 0) - def test_high_characters(self): - message = "".join(chr(i + 1) for i in range(140, 232)) - self._test_high_characters(message) - # accept bytes instances. - self._test_high_characters(message.encode("latin1")) +def test_textsize(request, tmp_path): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + for i in range(255): + (dx, dy) = font.getsize(chr(i)) + assert dy == 20 + assert dx in (0, 10) + for l in range(len(message)): + msg = message[: l + 1] + assert font.getsize(msg) == (len(msg) * 10, 20) + +def _test_high_characters(request, tmp_path, message): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + im = Image.new("L", (750, 30), "white") + draw = ImageDraw.Draw(im) + draw.text((0, 0), message, "black", font=font) + with Image.open("Tests/images/high_ascii_chars.png") as target: + assert_image_similar(im, target, 0) + +def test_high_characters(request, tmp_path): + message = "".join(chr(i + 1) for i in range(140, 232)) + _test_high_characters(request, tmp_path, message) + # accept bytes instances. + _test_high_characters(request, tmp_path, message.encode("latin1")) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index ee26b75bc..f8c27bbfa 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,47 +1,52 @@ +import pytest from PIL import Image, ImageQt from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQtTestCase +from .test_imageqt import skip_if_qt_is_not_installed -class TestFromQImage(PillowQtTestCase, PillowTestCase): - def setUp(self): - super().setUp() - self.files_to_test = [ - hopper(), - Image.open("Tests/images/transparent.png"), - Image.open("Tests/images/7x13.png"), - ] - for im in self.files_to_test: - self.addCleanup(im.close) +pytestmark = skip_if_qt_is_not_installed() - def roundtrip(self, expected): - # PIL -> Qt - intermediate = expected.toqimage() - # Qt -> PIL - result = ImageQt.fromqimage(intermediate) +@pytest.fixture +def test_images(): + ims = [ + hopper(), + Image.open("Tests/images/transparent.png"), + Image.open("Tests/images/7x13.png"), + ] + try: + yield ims + finally: + for im in ims.values(): + im.close() - if intermediate.hasAlphaChannel(): - assert_image_equal(result, expected.convert("RGBA")) - else: - assert_image_equal(result, expected.convert("RGB")) +def roundtrip(expected): + # PIL -> Qt + intermediate = expected.toqimage() + # Qt -> PIL + result = ImageQt.fromqimage(intermediate) - def test_sanity_1(self): - for im in self.files_to_test: - self.roundtrip(im.convert("1")) + if intermediate.hasAlphaChannel(): + assert_image_equal(result, expected.convert("RGBA")) + else: + assert_image_equal(result, expected.convert("RGB")) - def test_sanity_rgb(self): - for im in self.files_to_test: - self.roundtrip(im.convert("RGB")) +def test_sanity_1(test_images): + for im in test_images: + roundtrip(im.convert("1")) - def test_sanity_rgba(self): - for im in self.files_to_test: - self.roundtrip(im.convert("RGBA")) +def test_sanity_rgb(test_images): + for im in test_images: + roundtrip(im.convert("RGB")) - def test_sanity_l(self): - for im in self.files_to_test: - self.roundtrip(im.convert("L")) +def test_sanity_rgba(test_images): + for im in test_images: + roundtrip(im.convert("RGBA")) - def test_sanity_p(self): - for im in self.files_to_test: - self.roundtrip(im.convert("P")) +def test_sanity_l(test_images): + for im in test_images: + roundtrip(im.convert("L")) + +def test_sanity_p(test_images): + for im in test_images: + roundtrip(im.convert("P")) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 8bd01d588..8fd49baf9 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -4,95 +4,109 @@ from PIL import Image, ImageFilter from .helper import PillowTestCase -class TestImageOpsUsm(PillowTestCase): - def setUp(self): - super().setUp() - self.im = Image.open("Tests/images/hopper.ppm") - self.addCleanup(self.im.close) - self.snakes = Image.open("Tests/images/color_snakes.png") - self.addCleanup(self.snakes.close) +@pytest.fixture +def test_images(): + ims = { + "im": Image.open("Tests/images/hopper.ppm"), + "snakes": Image.open("Tests/images/color_snakes.png"), + } + try: + yield ims + finally: + for im in ims.values(): + im.close() - def test_filter_api(self): - test_filter = ImageFilter.GaussianBlur(2.0) - i = self.im.filter(test_filter) - assert i.mode == "RGB" - assert i.size == (128, 128) +def test_filter_api(test_images): + im = test_images["im"] - test_filter = ImageFilter.UnsharpMask(2.0, 125, 8) - i = self.im.filter(test_filter) - assert i.mode == "RGB" - assert i.size == (128, 128) + test_filter = ImageFilter.GaussianBlur(2.0) + i = im.filter(test_filter) + assert i.mode == "RGB" + assert i.size == (128, 128) - def test_usm_formats(self): + test_filter = ImageFilter.UnsharpMask(2.0, 125, 8) + i = im.filter(test_filter) + assert i.mode == "RGB" + assert i.size == (128, 128) - usm = ImageFilter.UnsharpMask - with pytest.raises(ValueError): - self.im.convert("1").filter(usm) - self.im.convert("L").filter(usm) - with pytest.raises(ValueError): - self.im.convert("I").filter(usm) - with pytest.raises(ValueError): - self.im.convert("F").filter(usm) - self.im.convert("RGB").filter(usm) - self.im.convert("RGBA").filter(usm) - self.im.convert("CMYK").filter(usm) - with pytest.raises(ValueError): - self.im.convert("YCbCr").filter(usm) - def test_blur_formats(self): +def test_usm_formats(test_images): + im = test_images["im"] - blur = ImageFilter.GaussianBlur - with pytest.raises(ValueError): - self.im.convert("1").filter(blur) - blur(self.im.convert("L")) - with pytest.raises(ValueError): - self.im.convert("I").filter(blur) - with pytest.raises(ValueError): - self.im.convert("F").filter(blur) - self.im.convert("RGB").filter(blur) - self.im.convert("RGBA").filter(blur) - self.im.convert("CMYK").filter(blur) - with pytest.raises(ValueError): - self.im.convert("YCbCr").filter(blur) + usm = ImageFilter.UnsharpMask + with pytest.raises(ValueError): + im.convert("1").filter(usm) + im.convert("L").filter(usm) + with pytest.raises(ValueError): + im.convert("I").filter(usm) + with pytest.raises(ValueError): + im.convert("F").filter(usm) + im.convert("RGB").filter(usm) + im.convert("RGBA").filter(usm) + im.convert("CMYK").filter(usm) + with pytest.raises(ValueError): + im.convert("YCbCr").filter(usm) - def test_usm_accuracy(self): - src = self.snakes.convert("RGB") - i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) - # Image should not be changed because it have only 0 and 255 levels. - assert i.tobytes() == src.tobytes() +def test_blur_formats(test_images): + im = test_images["im"] - def test_blur_accuracy(self): + blur = ImageFilter.GaussianBlur + with pytest.raises(ValueError): + im.convert("1").filter(blur) + blur(im.convert("L")) + with pytest.raises(ValueError): + im.convert("I").filter(blur) + with pytest.raises(ValueError): + im.convert("F").filter(blur) + im.convert("RGB").filter(blur) + im.convert("RGBA").filter(blur) + im.convert("CMYK").filter(blur) + with pytest.raises(ValueError): + im.convert("YCbCr").filter(blur) - i = self.snakes.filter(ImageFilter.GaussianBlur(0.4)) - # These pixels surrounded with pixels with 255 intensity. - # They must be very close to 255. - for x, y, c in [ - (1, 0, 1), - (2, 0, 1), - (7, 8, 1), - (8, 8, 1), - (2, 9, 1), - (7, 3, 0), - (8, 3, 0), - (5, 8, 0), - (5, 9, 0), - (1, 3, 0), - (4, 3, 2), - (4, 2, 2), - ]: - assert i.im.getpixel((x, y))[c] >= 250 - # Fuzzy match. - def gp(x, y): - return i.im.getpixel((x, y)) +def test_usm_accuracy(test_images): + snakes = test_images["snakes"] - assert 236 <= gp(7, 4)[0] <= 239 - assert 236 <= gp(7, 5)[2] <= 239 - assert 236 <= gp(7, 6)[2] <= 239 - assert 236 <= gp(7, 7)[1] <= 239 - assert 236 <= gp(8, 4)[0] <= 239 - assert 236 <= gp(8, 5)[2] <= 239 - assert 236 <= gp(8, 6)[2] <= 239 - assert 236 <= gp(8, 7)[1] <= 239 + src = snakes.convert("RGB") + i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) + # Image should not be changed because it have only 0 and 255 levels. + assert i.tobytes() == src.tobytes() + + +def test_blur_accuracy(test_images): + snakes = test_images["snakes"] + + i = snakes.filter(ImageFilter.GaussianBlur(0.4)) + # These pixels surrounded with pixels with 255 intensity. + # They must be very close to 255. + for x, y, c in [ + (1, 0, 1), + (2, 0, 1), + (7, 8, 1), + (8, 8, 1), + (2, 9, 1), + (7, 3, 0), + (8, 3, 0), + (5, 8, 0), + (5, 9, 0), + (1, 3, 0), + (4, 3, 2), + (4, 2, 2), + ]: + assert i.im.getpixel((x, y))[c] >= 250 + # Fuzzy match. + + def gp(x, y): + return i.im.getpixel((x, y)) + + assert 236 <= gp(7, 4)[0] <= 239 + assert 236 <= gp(7, 5)[2] <= 239 + assert 236 <= gp(7, 6)[2] <= 239 + assert 236 <= gp(7, 7)[1] <= 239 + assert 236 <= gp(8, 4)[0] <= 239 + assert 236 <= gp(8, 5)[2] <= 239 + assert 236 <= gp(8, 6)[2] <= 239 + assert 236 <= gp(8, 7)[1] <= 239 diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index ef777374f..d4a56e001 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,3 +1,4 @@ +import pytest from PIL import ImageQt from .helper import PillowTestCase, hopper @@ -5,14 +6,14 @@ from .helper import PillowTestCase, hopper if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba - def skip_if_qt_is_not_installed(_): + def skip_if_qt_is_not_installed(): pass else: - def skip_if_qt_is_not_installed(test_case): - test_case.skipTest("Qt bindings are not installed") + def skip_if_qt_is_not_installed(): + return pytest.mark.skip(reason="Qt bindings are not installed") class PillowQtTestCase: From a8637449b9e21d575cf1103ea1b2351b9f9e6116 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Mar 2020 20:30:00 +1100 Subject: [PATCH 2/6] Converted common Qt test classes --- Tests/test_font_pcf.py | 9 ++- Tests/test_image_fromqimage.py | 9 ++- Tests/test_imageops_usm.py | 2 - Tests/test_imageqt.py | 92 ++++++++++++++---------------- Tests/test_qt_image_fromqpixmap.py | 23 ++++---- Tests/test_qt_image_toqimage.py | 68 +++++++++++----------- Tests/test_qt_image_toqpixmap.py | 24 ++++---- 7 files changed, 120 insertions(+), 107 deletions(-) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 16f8571f5..2f19beeb2 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -3,7 +3,6 @@ import pytest from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, skip_unless_feature, @@ -16,6 +15,7 @@ message = "hello, world" pytestmark = skip_unless_feature("zlib") + def save_font(request, tmp_path): with open(fontname, "rb") as test_file: font = PcfFontFile.PcfFontFile(test_file) @@ -30,6 +30,7 @@ def save_font(request, tmp_path): os.remove(tempname[:-4] + ".pbm") except OSError: pass # report? + request.addfinalizer(delete_tempfile) font.save(tempname) @@ -42,14 +43,17 @@ def save_font(request, tmp_path): assert f_loaded.read() == f_target.read() return tempname + def test_sanity(request, tmp_path): save_font(request, tmp_path) + def test_invalid_file(): with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): PcfFontFile.PcfFontFile(fp) + def test_draw(request, tmp_path): tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) @@ -59,6 +63,7 @@ def test_draw(request, tmp_path): with Image.open("Tests/images/test_draw_pbm_target.png") as target: assert_image_similar(im, target, 0) + def test_textsize(request, tmp_path): tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) @@ -70,6 +75,7 @@ def test_textsize(request, tmp_path): msg = message[: l + 1] assert font.getsize(msg) == (len(msg) * 10, 20) + def _test_high_characters(request, tmp_path, message): tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) @@ -79,6 +85,7 @@ def _test_high_characters(request, tmp_path, message): with Image.open("Tests/images/high_ascii_chars.png") as target: assert_image_similar(im, target, 0) + def test_high_characters(request, tmp_path): message = "".join(chr(i + 1) for i in range(140, 232)) _test_high_characters(request, tmp_path, message) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index f8c27bbfa..b653edb16 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,12 +1,13 @@ import pytest from PIL import Image, ImageQt -from .helper import PillowTestCase, 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() + @pytest.fixture def test_images(): ims = [ @@ -20,6 +21,7 @@ def test_images(): for im in ims.values(): im.close() + def roundtrip(expected): # PIL -> Qt intermediate = expected.toqimage() @@ -31,22 +33,27 @@ def roundtrip(expected): else: assert_image_equal(result, expected.convert("RGB")) + def test_sanity_1(test_images): for im in test_images: roundtrip(im.convert("1")) + def test_sanity_rgb(test_images): for im in test_images: roundtrip(im.convert("RGB")) + def test_sanity_rgba(test_images): for im in test_images: roundtrip(im.convert("RGBA")) + def test_sanity_l(test_images): for im in test_images: roundtrip(im.convert("L")) + def test_sanity_p(test_images): for im in test_images: roundtrip(im.convert("P")) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 8fd49baf9..61f8dc2ba 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,8 +1,6 @@ import pytest from PIL import Image, ImageFilter -from .helper import PillowTestCase - @pytest.fixture def test_images(): diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index d4a56e001..fbb650861 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,7 +1,7 @@ import pytest from PIL import ImageQt -from .helper import PillowTestCase, hopper +from .helper import hopper if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba @@ -9,6 +9,23 @@ if ImageQt.qt_is_installed: def skip_if_qt_is_not_installed(): pass + @pytest.fixture + def qpixmap_app(): + try: + if ImageQt.qt_version == "5": + from PyQt5.QtGui import QGuiApplication + elif ImageQt.qt_version == "side2": + from PySide2.QtGui import QGuiApplication + except ImportError: + pytest.skip("QGuiApplication not installed") + return + + app = QGuiApplication([]) + try: + yield + finally: + app.quit() + else: @@ -16,57 +33,34 @@ else: return pytest.mark.skip(reason="Qt bindings are not installed") -class PillowQtTestCase: - def setUp(self): - skip_if_qt_is_not_installed(self) - - def tearDown(self): - pass +pytestmark = skip_if_qt_is_not_installed() -class PillowQPixmapTestCase(PillowQtTestCase): - def setUp(self): - super().setUp() - try: - if ImageQt.qt_version == "5": - from PyQt5.QtGui import QGuiApplication - elif ImageQt.qt_version == "side2": - from PySide2.QtGui import QGuiApplication - except ImportError: - self.skipTest("QGuiApplication not installed") +def test_rgb(): + # from https://doc.qt.io/archives/qt-4.8/qcolor.html + # typedef QRgb + # An ARGB quadruplet on the format #AARRGGBB, + # equivalent to an unsigned int. + if ImageQt.qt_version == "5": + from PyQt5.QtGui import qRgb + elif ImageQt.qt_version == "side2": + from PySide2.QtGui import qRgb - self.app = QGuiApplication([]) + assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) - def tearDown(self): - super().tearDown() - self.app.quit() + def checkrgb(r, g, b): + val = ImageQt.rgb(r, g, b) + val = val % 2 ** 24 # drop the alpha + assert val >> 16 == r + assert ((val >> 8) % 2 ** 8) == g + assert val % 2 ** 8 == b + + checkrgb(0, 0, 0) + checkrgb(255, 0, 0) + checkrgb(0, 255, 0) + checkrgb(0, 0, 255) -class TestImageQt(PillowQtTestCase, PillowTestCase): - def test_rgb(self): - # from https://doc.qt.io/archives/qt-4.8/qcolor.html - # typedef QRgb - # An ARGB quadruplet on the format #AARRGGBB, - # equivalent to an unsigned int. - if ImageQt.qt_version == "5": - from PyQt5.QtGui import qRgb - elif ImageQt.qt_version == "side2": - from PySide2.QtGui import qRgb - - assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) - - def checkrgb(r, g, b): - val = ImageQt.rgb(r, g, b) - val = val % 2 ** 24 # drop the alpha - assert val >> 16 == r - assert ((val >> 8) % 2 ** 8) == g - assert val % 2 ** 8 == b - - checkrgb(0, 0, 0) - checkrgb(255, 0, 0) - checkrgb(0, 255, 0) - checkrgb(0, 0, 255) - - def test_image(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - ImageQt.ImageQt(hopper(mode)) +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 1c7184376..96eaba41f 100644 --- a/Tests/test_qt_image_fromqpixmap.py +++ b/Tests/test_qt_image_fromqpixmap.py @@ -1,15 +1,18 @@ +import pytest from PIL import ImageQt -from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQPixmapTestCase +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() -class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): - def roundtrip(self, expected): - result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) - # Qt saves all pixmaps as rgb - assert_image_equal(result, expected.convert("RGB")) +def roundtrip(expected): + result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) + # Qt saves all pixmaps as rgb + assert_image_equal(result, expected.convert("RGB")) - def test_sanity(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - self.roundtrip(hopper(mode)) + +def test_sanity(qpixmap_app): + for mode in ("1", "RGB", "RGBA", "L", "P"): + roundtrip(hopper(mode)) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 8283753b9..e6fd18c52 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,7 +1,9 @@ from PIL import Image, ImageQt -from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQtTestCase +from .helper import assert_image_equal, hopper +from .test_imageqt import skip_if_qt_is_not_installed + +pytestmark = skip_if_qt_is_not_installed() if ImageQt.qt_is_installed: from PIL.ImageQt import QImage @@ -14,43 +16,43 @@ if ImageQt.qt_is_installed: from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication -class TestToQImage(PillowQtTestCase, PillowTestCase): - def test_sanity(self): - for mode in ("RGB", "RGBA", "L", "P", "1"): - src = hopper(mode) - data = ImageQt.toqimage(src) +def test_sanity(tmp_path): + for mode in ("RGB", "RGBA", "L", "P", "1"): + src = hopper(mode) + data = ImageQt.toqimage(src) - assert isinstance(data, QImage) - assert not data.isNull() + assert isinstance(data, QImage) + assert not data.isNull() - # reload directly from the qimage - rt = ImageQt.fromqimage(data) - if mode in ("L", "P", "1"): - assert_image_equal(rt, src.convert("RGB")) - else: - assert_image_equal(rt, src) + # reload directly from the qimage + rt = ImageQt.fromqimage(data) + if mode in ("L", "P", "1"): + assert_image_equal(rt, src.convert("RGB")) + else: + assert_image_equal(rt, src) - if mode == "1": - # BW appears to not save correctly on QT4 and QT5 - # kicks out errors on console: - # libpng warning: Invalid color type/bit depth combination - # in IHDR - # libpng error: Invalid IHDR data - continue + if mode == "1": + # BW appears to not save correctly on QT4 and QT5 + # kicks out errors on console: + # libpng warning: Invalid color type/bit depth combination + # in IHDR + # libpng error: Invalid IHDR data + continue - # Test saving the file - tempfile = self.tempfile("temp_{}.png".format(mode)) - data.save(tempfile) + # Test saving the file + tempfile = str(tmp_path / "temp_{}.png".format(mode)) + data.save(tempfile) - # Check that it actually worked. - with Image.open(tempfile) as reloaded: - assert_image_equal(reloaded, src) + # Check that it actually worked. + with Image.open(tempfile) as reloaded: + assert_image_equal(reloaded, src) - def test_segfault(self): - app = QApplication([]) - ex = Example() - assert app # Silence warning - assert ex # Silence warning + +def test_segfault(): + app = QApplication([]) + ex = Example() + assert app # Silence warning + assert ex # Silence warning if ImageQt.qt_is_installed: diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index 70b0d6839..f7cf59709 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -1,20 +1,22 @@ +import pytest from PIL import ImageQt -from .helper import PillowTestCase, hopper -from .test_imageqt import PillowQPixmapTestCase +from .helper import hopper +from .test_imageqt import qpixmap_app, skip_if_qt_is_not_installed if ImageQt.qt_is_installed: from PIL.ImageQt import QPixmap +pytestmark = skip_if_qt_is_not_installed() -class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): - def test_sanity(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - data = ImageQt.toqpixmap(hopper(mode)) - assert isinstance(data, QPixmap) - assert not data.isNull() +def test_sanity(qpixmap, tmp_path): + for mode in ("1", "RGB", "RGBA", "L", "P"): + data = ImageQt.toqpixmap(hopper(mode)) - # Test saving the file - tempfile = self.tempfile("temp_{}.png".format(mode)) - data.save(tempfile) + assert isinstance(data, QPixmap) + assert not data.isNull() + + # Test saving the file + tempfile = str(tmp_path / "temp_{}.png".format(mode)) + data.save(tempfile) From b602f365ae3aa5d2656b1717f2c4c4cf25d1f9c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Mar 2020 12:51:28 +1100 Subject: [PATCH 3/6] Removed PillowTestCase helper class --- Tests/bench_cffi_access.py | 32 ++++---- Tests/check_fli_overflow.py | 17 +---- Tests/check_imaging_leaks.py | 72 +++++++++--------- Tests/check_j2k_leaks.py | 57 +++++++------- Tests/check_j2k_overflow.py | 21 ++---- Tests/check_jpeg_leaks.py | 115 ++++++++++++++--------------- Tests/check_large_memory.py | 46 ++++++------ Tests/check_large_memory_numpy.py | 44 +++++------ Tests/check_libtiff_segfault.py | 23 ++---- Tests/check_png_dos.py | 102 ++++++++++++------------- Tests/helper.py | 17 ----- Tests/test_decompression_bomb.py | 15 ++-- Tests/test_file_wmf.py | 113 ++++++++++++++-------------- Tests/test_font_pcf.py | 7 +- Tests/test_image_fromqimage.py | 8 +- Tests/test_image_paste.py | 4 +- Tests/test_image_resize.py | 63 ++++++++-------- Tests/test_imagefont.py | 18 +++-- Tests/test_imageqt.py | 30 +++----- Tests/test_qt_image_fromqpixmap.py | 21 +++--- Tests/test_qt_image_toqimage.py | 6 +- Tests/test_qt_image_toqpixmap.py | 22 +++--- 22 files changed, 392 insertions(+), 461 deletions(-) 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) From cbf0bf1010c4611565a377e13e83a518f26b9156 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 27 Mar 2020 22:15:04 +1100 Subject: [PATCH 4/6] Fixed restoring original state --- Tests/test_file_wmf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 71b6012c3..03444eb9d 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -35,6 +35,7 @@ def test_register_handler(tmp_path): self.methodCalled = True handler = TestHandler() + original_handler = WmfImagePlugin._handler WmfImagePlugin.register_handler(handler) im = hopper() @@ -43,7 +44,7 @@ def test_register_handler(tmp_path): assert handler.methodCalled # Restore the state before this test - WmfImagePlugin.register_handler(None) + WmfImagePlugin.register_handler(original_handler) def test_load_dpi_rounding(): From 30a2d694cf1b07d104a7869fb0f87132932c64cb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 29 Mar 2020 10:40:46 +1100 Subject: [PATCH 5/6] Converted unittest mock to pytest monkeypatch --- Tests/test_imagefont.py | 134 ++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 75eec44a0..e93aff4b2 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -3,8 +3,8 @@ import distutils.version import os import re import shutil +import sys from io import BytesIO -from unittest import mock import pytest from PIL import Image, ImageDraw, ImageFont @@ -459,10 +459,11 @@ class TestImageFont: assert_image_similar_tofile(img, target, self.metrics["multiline"]) - def _test_fake_loading_font(self, path_to_fake, fontname): + def _test_fake_loading_font(self, monkeypatch, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) - with mock.patch.object(ImageFont, "_FreeTypeFont", free_type_font, create=True): + with monkeypatch.context() as m: + m.setattr(ImageFont, "_FreeTypeFont", free_type_font, raising=False) def loadable_font(filepath, size, index, encoding, *args, **kwargs): if filepath == path_to_fake: @@ -473,87 +474,84 @@ class TestImageFont: filepath, size, index, encoding, *args, **kwargs ) - with mock.patch.object(ImageFont, "FreeTypeFont", loadable_font): - font = ImageFont.truetype(fontname) - # Make sure it's loaded - name = font.getname() - assert ("FreeMono", "Regular") == name + m.setattr(ImageFont, "FreeTypeFont", loadable_font) + font = ImageFont.truetype(fontname) + # Make sure it's loaded + name = font.getname() + assert ("FreeMono", "Regular") == name @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - def test_find_linux_font(self): + def test_find_linux_font(self, monkeypatch): # A lot of mocking here - this is more for hitting code and # catching syntax like errors font_directory = "/usr/local/share/fonts" - with mock.patch("sys.platform", "linux"): - patched_env = {"XDG_DATA_DIRS": "/usr/share/:/usr/local/share/"} - with mock.patch.dict(os.environ, patched_env): + monkeypatch.setattr(sys, "platform", "linux") + monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - [ - "Arial.ttf", - "Single.otf", - "Duplicate.otf", - "Duplicate.ttf", - ], - ) - ] - return [(path, [], ["some_random_font.ttf"])] - - with mock.patch("os.walk", fake_walker): - # Test that the font loads both with and without the - # extension - self._test_fake_loading_font( - font_directory + "/Arial.ttf", "Arial.ttf" + def fake_walker(path): + if path == font_directory: + return [ + ( + path, + [], + ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], ) - self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") + ] + return [(path, [], ["some_random_font.ttf"])] - # Test that non-ttf fonts can be found without the - # extension - self._test_fake_loading_font( - font_directory + "/Single.otf", "Single" - ) + monkeypatch.setattr(os, "walk", fake_walker) + # Test that the font loads both with and without the + # extension + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial" + ) - # Test that ttf fonts are preferred if the extension is - # not specified - self._test_fake_loading_font( - font_directory + "/Duplicate.ttf", "Duplicate" - ) + # Test that non-ttf fonts can be found without the + # extension + self._test_fake_loading_font( + monkeypatch, font_directory + "/Single.otf", "Single" + ) + + # Test that ttf fonts are preferred if the extension is + # not specified + self._test_fake_loading_font( + monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" + ) @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - def test_find_macos_font(self): + def test_find_macos_font(self, monkeypatch): # Like the linux test, more cover hitting code rather than testing # correctness. font_directory = "/System/Library/Fonts" - with mock.patch("sys.platform", "darwin"): + monkeypatch.setattr(sys, "platform", "darwin") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - [ - "Arial.ttf", - "Single.otf", - "Duplicate.otf", - "Duplicate.ttf", - ], - ) - ] - return [(path, [], ["some_random_font.ttf"])] + def fake_walker(path): + if path == font_directory: + return [ + ( + path, + [], + ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], + ) + ] + return [(path, [], ["some_random_font.ttf"])] - 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") - self._test_fake_loading_font( - font_directory + "/Duplicate.ttf", "Duplicate" - ) + monkeypatch.setattr(os, "walk", fake_walker) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Single.otf", "Single" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" + ) def test_imagefont_getters(self): # Arrange From 963dfe6dbc0bfb97d19304de3937f466dea391ef Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 29 Mar 2020 12:34:00 +1100 Subject: [PATCH 6/6] Allow 0.01% drop in coverage --- codecov.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codecov.yml b/codecov.yml index e93896692..f3afccc1c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,6 +8,12 @@ codecov: comment: false +coverage: + status: + project: + default: + threshold: 0.01% + # Matches 'omit:' in .coveragerc ignore: - "Tests/32bit_segfault_check.py"