diff --git a/Tests/helper.py b/Tests/helper.py index d20b64b5f0a..39d3ed4828d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,7 +5,6 @@ import logging import os import shutil -import subprocess import sys import tempfile import unittest @@ -192,21 +191,9 @@ def tempfile(self, template): self.addCleanup(self.delete_tempfile, path) return path - def open_withImagemagick(self, f): - if not imagemagick_available(): - raise OSError() - outfile = self.tempfile("temp.png") - rc = subprocess.call( - [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT - ) - if rc: - raise OSError - return Image.open(outfile) - - -@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") -class PillowLeakTestCase(PillowTestCase): +@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS") +class PillowLeakTestCase: # requires unix/macOS iterations = 100 # count mem_limit = 512 # k diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 3b3ebc59f13..a8fe8bfebeb 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,10 +1,9 @@ import sys -import unittest import pytest from PIL import Image -from .helper import PillowTestCase, is_pypy +from .helper import is_pypy def test_get_stats(): @@ -32,8 +31,8 @@ def test_reset_stats(): assert stats["blocks_cached"] == 0 -class TestCoreMemory(PillowTestCase): - def tearDown(self): +class TestCoreMemory: + def teardown_method(self): # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) @@ -114,7 +113,7 @@ def test_set_blocks_max(self): with pytest.raises(ValueError): Image.core.set_blocks_max(2 ** 29) - @unittest.skipIf(is_pypy(), "images are not collected") + @pytest.mark.skipif(is_pypy(), reason="Images not collected") def test_set_blocks_max_stats(self): Image.core.reset_stats() Image.core.set_blocks_max(128) @@ -129,7 +128,7 @@ def test_set_blocks_max_stats(self): assert stats["freed_blocks"] == 0 assert stats["blocks_cached"] == 64 - @unittest.skipIf(is_pypy(), "images are not collected") + @pytest.mark.skipif(is_pypy(), reason="Images not collected") def test_clear_cache_stats(self): Image.core.reset_stats() Image.core.clear_cache() @@ -163,8 +162,8 @@ def test_large_images(self): assert stats["freed_blocks"] >= 16 -class TestEnvVars(PillowTestCase): - def tearDown(self): +class TestEnvVars: + def teardown_method(self): # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index f704ac60a7d..f13536d5868 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -6,7 +6,6 @@ from PIL import ExifTags, Image, ImageFile, JpegImagePlugin from .helper import ( - PillowTestCase, assert_image, assert_image_equal, assert_image_similar, @@ -15,14 +14,13 @@ hopper, is_win32, skip_unless_feature, - unittest, ) TEST_FILE = "Tests/images/hopper.jpg" @skip_unless_feature("jpg") -class TestFileJpeg(PillowTestCase): +class TestFileJpeg: def roundtrip(self, im, **options): out = BytesIO() im.save(out, "JPEG", **options) @@ -101,13 +99,13 @@ def test(xdpi, ydpi=None): assert test(100, 200) == (100, 200) assert test(0) is None # square pixels - def test_icc(self): + def test_icc(self, tmp_path): # Test ICC support with Image.open("Tests/images/rgb.jpg") as im1: icc_profile = im1.info["icc_profile"] assert len(icc_profile) == 3144 # Roundtrip via physical file. - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im1.save(f, icc_profile=icc_profile) with Image.open(f) as im2: assert im2.info.get("icc_profile") == icc_profile @@ -140,12 +138,12 @@ def test(n): test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte test(ImageFile.MAXBLOCK * 4 + 3) # large block - def test_large_icc_meta(self): + def test_large_icc_meta(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than # Image.MAXBLOCK or the image size. with Image.open("Tests/images/icc_profile_big.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") icc_profile = im.info["icc_profile"] # Should not raise IOError for image with icc larger than image size. im.save( @@ -166,9 +164,9 @@ def test_optimize(self): assert im1.bytes >= im2.bytes assert im1.bytes >= im3.bytes - def test_optimize_large_buffer(self): + def test_optimize_large_buffer(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", optimize=True) @@ -184,14 +182,14 @@ def test_progressive(self): assert_image_equal(im1, im3) assert im1.bytes >= im3.bytes - def test_progressive_large_buffer(self): - f = self.tempfile("temp.jpg") + def test_progressive_large_buffer(self, tmp_path): + f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", progressive=True) - def test_progressive_large_buffer_highest_quality(self): - f = self.tempfile("temp.jpg") + def test_progressive_large_buffer_highest_quality(self, tmp_path): + f = str(tmp_path / "temp.jpg") im = self.gen_random_image((255, 255)) # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) @@ -202,9 +200,9 @@ def test_progressive_cmyk_buffer(self): im = self.gen_random_image((256, 256), "CMYK") im.save(f, format="JPEG", progressive=True, quality=94) - def test_large_exif(self): + def test_large_exif(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im = hopper() im.save(f, "JPEG", quality=90, exif=b"1" * 65532) @@ -301,7 +299,7 @@ def test_quality(self): im3 = self.roundtrip(hopper(), quality=0) assert_image(im1, im3.mode, im3.size) - self.assertGreater(im2.bytes, im3.bytes) + assert im2.bytes > im3.bytes def test_smooth(self): im1 = self.roundtrip(hopper()) @@ -346,18 +344,18 @@ def test_mp(self): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: assert im._getmp() is None - def test_quality_keep(self): + def test_quality_keep(self, tmp_path): # RGB with Image.open("Tests/images/hopper.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") # Grayscale with Image.open("Tests/images/hopper_gray.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") # CMYK with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") def test_junk_jpeg_header(self): @@ -389,16 +387,16 @@ def test_truncated_jpeg_throws_IOError(self): with pytest.raises(IOError): im.load() - def _n_qtables_helper(self, n, test_file): - with Image.open(test_file) as im: - f = self.tempfile("temp.jpg") - im.save(f, qtables=[[n] * 64] * n) - with Image.open(f) as im: - assert len(im.quantization) == n - reloaded = self.roundtrip(im, qtables="keep") - assert im.quantization == reloaded.quantization + def test_qtables(self, tmp_path): + def _n_qtables_helper(n, test_file): + with Image.open(test_file) as im: + f = str(tmp_path / "temp.jpg") + im.save(f, qtables=[[n] * 64] * n) + with Image.open(f) as im: + assert len(im.quantization) == n + reloaded = self.roundtrip(im, qtables="keep") + assert im.quantization == reloaded.quantization - def test_qtables(self): with Image.open("Tests/images/hopper.jpg") as im: qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) @@ -470,14 +468,14 @@ def test_qtables(self): 30, ) - self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(1, "Tests/images/hopper_gray.jpg") + _n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") # not a sequence with pytest.raises(ValueError): @@ -496,16 +494,16 @@ def test_qtables(self): with pytest.raises(ValueError): self.roundtrip(im, qtables=[[1, 2, 3, 4]]) - @unittest.skipUnless(djpeg_available(), "djpeg not available") + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") def test_load_djpeg(self): with Image.open(TEST_FILE) as img: img.load_djpeg() assert_image_similar(img, Image.open(TEST_FILE), 0) - @unittest.skipUnless(cjpeg_available(), "cjpeg not available") - def test_save_cjpeg(self): + @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") + def test_save_cjpeg(self, tmp_path): with Image.open(TEST_FILE) as img: - tempfile = self.tempfile("temp.jpg") + tempfile = str(tmp_path / "temp.jpg") JpegImagePlugin._save_cjpeg(img, 0, tempfile) # Default save quality is 75%, so a tiny bit of difference is alright assert_image_similar(img, Image.open(tempfile), 17) @@ -518,9 +516,9 @@ def test_no_duplicate_0x1001_tag(self): assert tag_ids["RelatedImageWidth"] == 0x1001 assert tag_ids["RelatedImageLength"] == 0x1002 - def test_MAXBLOCK_scaling(self): + def test_MAXBLOCK_scaling(self, tmp_path): im = self.gen_random_image((512, 512)) - f = self.tempfile("temp.jpeg") + f = str(tmp_path / "temp.jpeg") im.save(f, quality=100, optimize=True) with Image.open(f) as reloaded: @@ -555,9 +553,9 @@ def test_save_wrong_modes(self): with pytest.raises(IOError): img.save(out, "JPEG") - def test_save_tiff_with_dpi(self): + def test_save_tiff_with_dpi(self, tmp_path): # Arrange - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: # Act @@ -577,8 +575,8 @@ def test_load_dpi_rounding(self): with Image.open("Tests/images/iptc_roundDown.jpg") as im: assert im.info["dpi"] == (2, 2) - def test_save_dpi_rounding(self): - outfile = self.tempfile("temp.jpg") + def test_save_dpi_rounding(self, tmp_path): + outfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: im.save(outfile, dpi=(72.2, 72.2)) @@ -690,11 +688,11 @@ def test_photoshop_malformed_and_multiple(self): assert [65504, 24] == apps_13_lengths -@unittest.skipUnless(is_win32(), "Windows only") +@pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") -class TestFileCloseW32(PillowTestCase): - def test_fd_leak(self): - tmpfile = self.tempfile("temp.jpg") +class TestFileCloseW32: + def test_fd_leak(self, tmp_path): + tmpfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: im.save(tmpfile) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 1a0e9a35894..e37b46a41d3 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -5,7 +5,6 @@ from PIL import Image, ImageFile, Jpeg2KImagePlugin from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, is_big_endian, @@ -13,6 +12,8 @@ skip_unless_feature, ) +pytestmark = skip_unless_feature("jpg_2000") + test_card = Image.open("Tests/images/test-card.png") test_card.load() @@ -21,190 +22,207 @@ # 'Not enough memory to handle tile data' -@skip_unless_feature("jpg_2000") -class TestFileJpeg2k(PillowTestCase): - def roundtrip(self, im, **options): - out = BytesIO() - im.save(out, "JPEG2000", **options) - test_bytes = out.tell() - out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only +def roundtrip(im, **options): + out = BytesIO() + im.save(out, "JPEG2000", **options) + test_bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = test_bytes # for testing only + im.load() + return im + + +def test_sanity(): + # Internal version number + assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) + + with Image.open("Tests/images/test-card-lossless.jp2") as im: + px = im.load() + assert px[0, 0] == (0, 0, 0) + assert im.mode == "RGB" + assert im.size == (640, 480) + assert im.format == "JPEG2000" + assert im.get_format_mimetype() == "image/jp2" + + +def test_jpf(): + with Image.open("Tests/images/balloon.jpf") as im: + assert im.format == "JPEG2000" + assert im.get_format_mimetype() == "image/jpx" + + +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) + + +def test_bytesio(): + with open("Tests/images/test-card-lossless.jp2", "rb") as f: + data = BytesIO(f.read()) + with Image.open(data) as im: im.load() - return im - - def test_sanity(self): - # Internal version number - assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) - - with Image.open("Tests/images/test-card-lossless.jp2") as im: - px = im.load() - assert px[0, 0] == (0, 0, 0) - assert im.mode == "RGB" - assert im.size == (640, 480) - assert im.format == "JPEG2000" - assert im.get_format_mimetype() == "image/jp2" - - def test_jpf(self): - with Image.open("Tests/images/balloon.jpf") as im: - assert im.format == "JPEG2000" - assert im.get_format_mimetype() == "image/jpx" - - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - - with pytest.raises(SyntaxError): - Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) - - def test_bytesio(self): - with open("Tests/images/test-card-lossless.jp2", "rb") as f: - data = BytesIO(f.read()) - with Image.open(data) as im: - im.load() - assert_image_similar(im, test_card, 1.0e-3) - - # These two test pre-written JPEG 2000 files that were not written with - # PIL (they were made using Adobe Photoshop) - - def test_lossless(self): - with Image.open("Tests/images/test-card-lossless.jp2") as im: - im.load() - outfile = self.tempfile("temp_test-card.png") - im.save(outfile) assert_image_similar(im, test_card, 1.0e-3) - def test_lossy_tiled(self): - with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: - im.load() - assert_image_similar(im, test_card, 2.0) - def test_lossless_rt(self): - im = self.roundtrip(test_card) - assert_image_equal(im, test_card) +# These two test pre-written JPEG 2000 files that were not written with +# PIL (they were made using Adobe Photoshop) + - def test_lossy_rt(self): - im = self.roundtrip(test_card, quality_layers=[20]) +def test_lossless(tmp_path): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + im.load() + outfile = str(tmp_path / "temp_test-card.png") + im.save(outfile) + assert_image_similar(im, test_card, 1.0e-3) + + +def test_lossy_tiled(): + with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: + im.load() assert_image_similar(im, test_card, 2.0) - def test_tiled_rt(self): - im = self.roundtrip(test_card, tile_size=(128, 128)) - assert_image_equal(im, test_card) - def test_tiled_offset_rt(self): - im = self.roundtrip( - test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32) - ) - assert_image_equal(im, test_card) +def test_lossless_rt(): + im = roundtrip(test_card) + assert_image_equal(im, test_card) - def test_tiled_offset_too_small(self): - with pytest.raises(ValueError): - self.roundtrip( - test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32) - ) - def test_irreversible_rt(self): - im = self.roundtrip(test_card, irreversible=True, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) +def test_lossy_rt(): + im = roundtrip(test_card, quality_layers=[20]) + assert_image_similar(im, test_card, 2.0) - def test_prog_qual_rt(self): - im = self.roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") - assert_image_similar(im, test_card, 2.0) - def test_prog_res_rt(self): - im = self.roundtrip(test_card, num_resolutions=8, progression="RLCP") - assert_image_equal(im, test_card) +def test_tiled_rt(): + im = roundtrip(test_card, tile_size=(128, 128)) + assert_image_equal(im, test_card) + + +def test_tiled_offset_rt(): + im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) + assert_image_equal(im, test_card) + + +def test_tiled_offset_too_small(): + with pytest.raises(ValueError): + roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) + - def test_reduce(self): - with Image.open("Tests/images/test-card-lossless.jp2") as im: - im.reduce = 2 - im.load() - assert im.size == (160, 120) +def test_irreversible_rt(): + im = roundtrip(test_card, irreversible=True, quality_layers=[20]) + assert_image_similar(im, test_card, 2.0) + + +def test_prog_qual_rt(): + im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") + assert_image_similar(im, test_card, 2.0) + + +def test_prog_res_rt(): + im = roundtrip(test_card, num_resolutions=8, progression="RLCP") + assert_image_equal(im, test_card) + + +def test_reduce(): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + im.reduce = 2 + im.load() + assert im.size == (160, 120) - def test_layers_type(self): - outfile = self.tempfile("temp_layers.jp2") - for quality_layers in [[100, 50, 10], (100, 50, 10), None]: + +def test_layers_type(tmp_path): + outfile = str(tmp_path / "temp_layers.jp2") + for quality_layers in [[100, 50, 10], (100, 50, 10), None]: + test_card.save(outfile, quality_layers=quality_layers) + + for quality_layers in ["quality_layers", ("100", "50", "10")]: + with pytest.raises(ValueError): test_card.save(outfile, quality_layers=quality_layers) - for quality_layers in ["quality_layers", ("100", "50", "10")]: - with pytest.raises(ValueError): - test_card.save(outfile, quality_layers=quality_layers) - - def test_layers(self): - out = BytesIO() - test_card.save( - out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP" - ) - out.seek(0) - - with Image.open(out) as im: - im.layers = 1 - im.load() - assert_image_similar(im, test_card, 13) - - out.seek(0) - with Image.open(out) as im: - im.layers = 3 - im.load() - assert_image_similar(im, test_card, 0.4) - - def test_rgba(self): - # Arrange - with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: - with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: - - # Act - j2k.load() - jp2.load() - - # Assert - assert j2k.mode == "RGBA" - assert jp2.mode == "RGBA" - - def test_16bit_monochrome_has_correct_mode(self): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - j2k.load() - assert j2k.mode == "I;16" - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: +def test_layers(): + out = BytesIO() + test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") + out.seek(0) + + with Image.open(out) as im: + im.layers = 1 + im.load() + assert_image_similar(im, test_card, 13) + + out.seek(0) + with Image.open(out) as im: + im.layers = 3 + im.load() + assert_image_similar(im, test_card, 0.4) + + +def test_rgba(): + # Arrange + with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: + with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: + + # Act + j2k.load() jp2.load() - assert jp2.mode == "I;16" - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_16bit_monochrome_jp2_like_tiff(self): - with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - assert_image_similar(jp2, tiff_16bit, 1e-3) + # Assert + assert j2k.mode == "RGBA" + assert jp2.mode == "RGBA" - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_16bit_monochrome_j2k_like_tiff(self): - with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - assert_image_similar(j2k, tiff_16bit, 1e-3) - def test_16bit_j2k_roundtrips(self): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - im = self.roundtrip(j2k) - assert_image_equal(im, j2k) +def test_16bit_monochrome_has_correct_mode(): + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + j2k.load() + assert j2k.mode == "I;16" - def test_16bit_jp2_roundtrips(self): + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + jp2.load() + assert jp2.mode == "I;16" + + +@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") +def test_16bit_monochrome_jp2_like_tiff(): + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - im = self.roundtrip(jp2) - assert_image_equal(im, jp2) - - def test_unbound_local(self): - # prepatch, a malformed jp2 file could cause an UnboundLocalError - # exception. - with pytest.raises(IOError): - Image.open("Tests/images/unbound_variable.jp2") - - def test_parser_feed(self): - # Arrange - with open("Tests/images/test-card-lossless.jp2", "rb") as f: - data = f.read() - - # Act - p = ImageFile.Parser() - p.feed(data) - - # Assert - assert p.image.size == (640, 480) + assert_image_similar(jp2, tiff_16bit, 1e-3) + + +@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") +def test_16bit_monochrome_j2k_like_tiff(): + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + assert_image_similar(j2k, tiff_16bit, 1e-3) + + +def test_16bit_j2k_roundtrips(): + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + im = roundtrip(j2k) + assert_image_equal(im, j2k) + + +def test_16bit_jp2_roundtrips(): + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + im = roundtrip(jp2) + assert_image_equal(im, jp2) + + +def test_unbound_local(): + # prepatch, a malformed jp2 file could cause an UnboundLocalError exception. + with pytest.raises(IOError): + Image.open("Tests/images/unbound_variable.jp2") + + +def test_parser_feed(): + # Arrange + with open("Tests/images/test-card-lossless.jp2", "rb") as f: + data = f.read() + + # Act + p = ImageFile.Parser() + p.feed(data) + + # Assert + assert p.image.size == (640, 480) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 175ae987f39..923bd610714 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -10,7 +10,6 @@ from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags from .helper import ( - PillowTestCase, assert_image_equal, assert_image_equal_tofile, assert_image_similar, @@ -23,8 +22,8 @@ @skip_unless_feature("libtiff") -class LibTiffTestCase(PillowTestCase): - def _assert_noerr(self, im): +class LibTiffTestCase: + def _assert_noerr(self, tmp_path, im): """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit assert im.mode == "1" @@ -40,7 +39,7 @@ def _assert_noerr(self, im): print(dir(im)) # can we write it back out, in a different form. - out = self.tempfile("temp.png") + out = str(tmp_path / "temp.png") im.save(out) out_bytes = io.BytesIO() @@ -48,29 +47,29 @@ def _assert_noerr(self, im): class TestFileLibTiff(LibTiffTestCase): - def test_g4_tiff(self): + def test_g4_tiff(self, tmp_path): """Test the ordinary file path load path""" test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as im: assert im.size == (500, 500) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_large(self): + def test_g4_large(self, tmp_path): test_file = "Tests/images/pport_g4.tif" with Image.open(test_file) as im: - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_tiff_file(self): + def test_g4_tiff_file(self, tmp_path): """Testing the string load path""" test_file = "Tests/images/hopper_g4_500.tif" with open(test_file, "rb") as f: with Image.open(f) as im: assert im.size == (500, 500) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_tiff_bytesio(self): + def test_g4_tiff_bytesio(self, tmp_path): """Testing the stringio loading code path""" test_file = "Tests/images/hopper_g4_500.tif" s = io.BytesIO() @@ -79,9 +78,9 @@ def test_g4_tiff_bytesio(self): s.seek(0) with Image.open(s) as im: assert im.size == (500, 500) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_non_disk_file_object(self): + def test_g4_non_disk_file_object(self, tmp_path): """Testing loading from non-disk non-BytesIO file object""" test_file = "Tests/images/hopper_g4_500.tif" s = io.BytesIO() @@ -91,7 +90,7 @@ def test_g4_non_disk_file_object(self): r = io.BufferedReader(s) with Image.open(r) as im: assert im.size == (500, 500) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) def test_g4_eq_png(self): """ Checking that we're actually getting the data that we expect""" @@ -106,18 +105,18 @@ def test_g4_fillorder_eq_png(self): with Image.open("Tests/images/g4-fillorder-test.tif") as g4: assert_image_equal(g4, png) - def test_g4_write(self): + def test_g4_write(self, tmp_path): """Checking to see that the saved image is the same as what we wrote""" test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as orig: - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") rot = orig.transpose(Image.ROTATE_90) assert rot.size == (500, 500) rot.save(out) with Image.open(out) as reread: assert reread.size == (500, 500) - self._assert_noerr(reread) + self._assert_noerr(tmp_path, reread) assert_image_equal(reread, rot) assert reread.info["compression"] == "group4" @@ -135,10 +134,10 @@ def test_adobe_deflate_tiff(self): assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_write_metadata(self): + def test_write_metadata(self, tmp_path): """ Test metadata writing through libtiff """ for legacy_api in [False, True]: - f = self.tempfile("temp.tiff") + f = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper_g4.tif") as img: img.save(f, tiffinfo=img.tag) @@ -183,7 +182,7 @@ def test_write_metadata(self): for field in requested_fields: assert field in reloaded, "%s not in metadata" % field - def test_additional_metadata(self): + def test_additional_metadata(self, tmp_path): # these should not crash. Seriously dummy data, most of it doesn't make # any sense, so we're running up against limits where we're asking # libtiff to do stupid things. @@ -232,14 +231,14 @@ def test_additional_metadata(self): # Extra samples really doesn't make sense in this application. del new_ifd[338] - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True im.save(out, tiffinfo=new_ifd) TiffImagePlugin.WRITE_LIBTIFF = False - def test_custom_metadata(self): + def test_custom_metadata(self, tmp_path): tc = namedtuple("test_case", "value,type,supported_by_default") custom = { 37000 + k: v @@ -284,7 +283,7 @@ def test_custom_metadata(self): def check_tags(tiffinfo): im = hopper() - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out, tiffinfo=tiffinfo) with Image.open(out) as reloaded: @@ -323,26 +322,26 @@ def check_tags(tiffinfo): ) TiffImagePlugin.WRITE_LIBTIFF = False - def test_int_dpi(self): + def test_int_dpi(self, tmp_path): # issue #1765 im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True im.save(out, dpi=(72, 72)) TiffImagePlugin.WRITE_LIBTIFF = False with Image.open(out) as reloaded: assert reloaded.info["dpi"] == (72.0, 72.0) - def test_g3_compression(self): + def test_g3_compression(self, tmp_path): with Image.open("Tests/images/hopper_g4_500.tif") as i: - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") i.save(out, compression="group3") with Image.open(out) as reread: assert reread.info["compression"] == "group3" assert_image_equal(reread, i) - def test_little_endian(self): + def test_little_endian(self, tmp_path): with Image.open("Tests/images/16bit.deflate.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16" @@ -352,7 +351,7 @@ def test_little_endian(self): assert b[0] == ord(b"\xe0") assert b[1] == ord(b"\x01") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") # out = "temp.le.tif" im.save(out) with Image.open(out) as reread: @@ -361,7 +360,7 @@ def test_little_endian(self): # UNDONE - libtiff defaults to writing in native endian, so # on big endian, we'll get back mode = 'I;16B' here. - def test_big_endian(self): + def test_big_endian(self, tmp_path): with Image.open("Tests/images/16bit.MM.deflate.tif") as im: assert im.getpixel((0, 0)) == 480 assert im.mode == "I;16B" @@ -372,17 +371,17 @@ def test_big_endian(self): assert b[0] == ord(b"\x01") assert b[1] == ord(b"\xe0") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out) with Image.open(out) as reread: assert reread.info["compression"] == im.info["compression"] assert reread.getpixel((0, 0)) == 480 - def test_g4_string_info(self): + def test_g4_string_info(self, tmp_path): """Tests String data in info directory""" test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as orig: - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") orig.tag[269] = "temp.tif" orig.save(out) @@ -406,10 +405,10 @@ def test_12bit_rawmode(self): assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") - def test_blur(self): + def test_blur(self, tmp_path): # test case from irc, how to do blur on b/w image # and save to compressed tif. - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") with Image.open("Tests/images/pport_g4.tif") as im: im = im.convert("L") @@ -421,11 +420,11 @@ def test_blur(self): assert_image_equal(im, im2) - def test_compressions(self): + def test_compressions(self, tmp_path): # Test various tiff compressions and assert similar image content but reduced # file sizes. im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out) size_raw = os.path.getsize(out) @@ -449,9 +448,9 @@ def test_compressions(self): assert size_compressed > size_jpeg assert size_jpeg > size_jpeg_30 - def test_quality(self): + def test_quality(self, tmp_path): im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") with pytest.raises(ValueError): im.save(out, compression="tiff_lzw", quality=50) @@ -464,21 +463,21 @@ def test_quality(self): im.save(out, compression="jpeg", quality=0) im.save(out, compression="jpeg", quality=100) - def test_cmyk_save(self): + def test_cmyk_save(self, tmp_path): im = hopper("CMYK") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_adobe_deflate") with Image.open(out) as im2: assert_image_equal(im, im2) - def xtest_bw_compression_w_rgb(self): + def xtest_bw_compression_w_rgb(self, tmp_path): """ This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") with pytest.raises(IOError): im.save(out, compression="tiff_ccitt") @@ -619,22 +618,22 @@ def save_bytesio(compression=None): TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False - def test_crashing_metadata(self): + def test_crashing_metadata(self, tmp_path): # issue 1597 with Image.open("Tests/images/rdf.tif") as im: - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") TiffImagePlugin.WRITE_LIBTIFF = True # this shouldn't crash im.save(out, format="TIFF") TiffImagePlugin.WRITE_LIBTIFF = False - def test_page_number_x_0(self): + def test_page_number_x_0(self, tmp_path): # Issue 973 # Test TIFF with tag 297 (Page Number) having value of 0 0. # The first number is the current page number. # The second is the total number of pages, zero means not available. - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") # Created by printing a page in Chrome to PDF, then: # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif # -dNOPAUSE /tmp/test.pdf -c quit @@ -643,10 +642,10 @@ def test_page_number_x_0(self): # Should not divide by zero im.save(outfile) - def test_fd_duplication(self): + def test_fd_duplication(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/1651 - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") with open(tmpfile, "wb") as f: with open("Tests/images/g4-multi.tiff", "rb") as src: f.write(src.read()) @@ -685,9 +684,9 @@ def test_multipage_compression(self): assert im.size == (10, 10) im.load() - def test_save_tiff_with_jpegtables(self): + def test_save_tiff_with_jpegtables(self, tmp_path): # Arrange - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif # Contains JPEGTables (347) tag diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 5bac8ebf71e..593a8eda83f 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -15,16 +15,16 @@ class TestFileLibTiffSmall(LibTiffTestCase): file just before reading in libtiff. These tests remain to ensure that it stays fixed. """ - def test_g4_hopper_file(self): + def test_g4_hopper_file(self, tmp_path): """Testing the open file load path""" test_file = "Tests/images/hopper_g4.tif" with open(test_file, "rb") as f: with Image.open(f) as im: assert im.size == (128, 128) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_hopper_bytesio(self): + def test_g4_hopper_bytesio(self, tmp_path): """Testing the bytesio loading code path""" test_file = "Tests/images/hopper_g4.tif" s = BytesIO() @@ -33,12 +33,12 @@ def test_g4_hopper_bytesio(self): s.seek(0) with Image.open(s) as im: assert im.size == (128, 128) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_hopper(self): + def test_g4_hopper(self, tmp_path): """The 128x128 lena image failed for some reason.""" test_file = "Tests/images/hopper_g4.tif" with Image.open(test_file) as im: assert im.size == (128, 128) - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 90927253235..886332dea82 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -1,71 +1,90 @@ import os.path +import subprocess import pytest +from PIL import Image from .helper import ( - PillowTestCase, + IMCONVERT, assert_image_equal, hopper, imagemagick_available, skip_known_bad_test, ) +_roundtrip = imagemagick_available() -class TestFilePalm(PillowTestCase): - _roundtrip = imagemagick_available() - def helper_save_as_palm(self, mode): - # Arrange - im = hopper(mode) - outfile = self.tempfile("temp_" + mode + ".palm") +def helper_save_as_palm(tmp_path, mode): + # Arrange + im = hopper(mode) + outfile = str(tmp_path / ("temp_" + mode + ".palm")) - # Act - im.save(outfile) + # Act + im.save(outfile) - # Assert - assert os.path.isfile(outfile) - assert os.path.getsize(outfile) > 0 + # Assert + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - def roundtrip(self, mode): - if not self._roundtrip: - return - im = hopper(mode) - outfile = self.tempfile("temp.palm") +def open_with_imagemagick(tmp_path, f): + if not imagemagick_available(): + raise OSError() - im.save(outfile) - converted = self.open_withImagemagick(outfile) - assert_image_equal(converted, im) + outfile = str(tmp_path / "temp.png") + rc = subprocess.call( + [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ) + if rc: + raise OSError + return Image.open(outfile) - def test_monochrome(self): - # Arrange - mode = "1" - # Act / Assert - self.helper_save_as_palm(mode) - self.roundtrip(mode) +def roundtrip(tmp_path, mode): + if not _roundtrip: + return - def test_p_mode(self): - # Arrange - mode = "P" + im = hopper(mode) + outfile = str(tmp_path / "temp.palm") - # Act / Assert - self.helper_save_as_palm(mode) - skip_known_bad_test("Palm P image is wrong") - self.roundtrip(mode) + im.save(outfile) + converted = open_with_imagemagick(tmp_path, outfile) + assert_image_equal(converted, im) - def test_l_ioerror(self): - # Arrange - mode = "L" - # Act / Assert - with pytest.raises(IOError): - self.helper_save_as_palm(mode) +def test_monochrome(tmp_path): + # Arrange + mode = "1" - def test_rgb_ioerror(self): - # Arrange - mode = "RGB" + # Act / Assert + helper_save_as_palm(tmp_path, mode) + roundtrip(tmp_path, mode) - # Act / Assert - with pytest.raises(IOError): - self.helper_save_as_palm(mode) + +def test_p_mode(tmp_path): + # Arrange + mode = "P" + + # Act / Assert + helper_save_as_palm(tmp_path, mode) + skip_known_bad_test("Palm P image is wrong") + roundtrip(tmp_path, mode) + + +def test_l_ioerror(tmp_path): + # Arrange + mode = "L" + + # Act / Assert + with pytest.raises(IOError): + helper_save_as_palm(tmp_path, mode) + + +def test_rgb_ioerror(tmp_path): + # Arrange + mode = "RGB" + + # Act / Assert + with pytest.raises(IOError): + helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 3ae27213c65..5af7469c7fd 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,130 +1,142 @@ import pytest from PIL import Image, ImageFile, PcxImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestFilePcx(PillowTestCase): - def _roundtrip(self, im): - f = self.tempfile("temp.pcx") +def _roundtrip(tmp_path, im): + f = str(tmp_path / "temp.pcx") + im.save(f) + with Image.open(f) as im2: + assert im2.mode == im.mode + assert im2.size == im.size + assert im2.format == "PCX" + assert im2.get_format_mimetype() == "image/x-pcx" + assert_image_equal(im2, im) + + +def test_sanity(tmp_path): + for mode in ("1", "L", "P", "RGB"): + _roundtrip(tmp_path, hopper(mode)) + + # Test an unsupported mode + f = str(tmp_path / "temp.pcx") + im = hopper("RGBA") + with pytest.raises(ValueError): im.save(f) - with Image.open(f) as im2: - assert im2.mode == im.mode - assert im2.size == im.size - assert im2.format == "PCX" - assert im2.get_format_mimetype() == "image/x-pcx" - assert_image_equal(im2, im) - - def test_sanity(self): - for mode in ("1", "L", "P", "RGB"): - self._roundtrip(hopper(mode)) - - # Test an unsupported mode - f = self.tempfile("temp.pcx") - im = hopper("RGBA") - with pytest.raises(ValueError): - im.save(f) - - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - - with pytest.raises(SyntaxError): - PcxImagePlugin.PcxImageFile(invalid_file) - - def test_odd(self): - # see issue #523, odd sized images should have a stride that's even. - # not that imagemagick or gimp write pcx that way. - # we were not handling properly. - for mode in ("1", "L", "P", "RGB"): - # larger, odd sized images are better here to ensure that - # we handle interrupted scan lines properly. - self._roundtrip(hopper(mode).resize((511, 511))) - - def test_pil184(self): - # Check reading of files where xmin/xmax is not zero. - - test_file = "Tests/images/pil184.pcx" - with Image.open(test_file) as im: - assert im.size == (447, 144) - assert im.tile[0][1] == (0, 0, 447, 144) - - # Make sure all pixels are either 0 or 255. - assert im.histogram()[0] + im.histogram()[255] == 447 * 144 - - def test_1px_width(self): - im = Image.new("L", (1, 256)) - px = im.load() - for y in range(256): - px[0, y] = y - self._roundtrip(im) - - def test_large_count(self): - im = Image.new("L", (256, 1)) - px = im.load() + + +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + PcxImagePlugin.PcxImageFile(invalid_file) + + +def test_odd(tmp_path): + # See issue #523, odd sized images should have a stride that's even. + # Not that ImageMagick or GIMP write PCX that way. + # We were not handling properly. + for mode in ("1", "L", "P", "RGB"): + # larger, odd sized images are better here to ensure that + # we handle interrupted scan lines properly. + _roundtrip(tmp_path, hopper(mode).resize((511, 511))) + + +def test_pil184(): + # Check reading of files where xmin/xmax is not zero. + + test_file = "Tests/images/pil184.pcx" + with Image.open(test_file) as im: + assert im.size == (447, 144) + assert im.tile[0][1] == (0, 0, 447, 144) + + # Make sure all pixels are either 0 or 255. + assert im.histogram()[0] + im.histogram()[255] == 447 * 144 + + +def test_1px_width(tmp_path): + im = Image.new("L", (1, 256)) + px = im.load() + for y in range(256): + px[0, y] = y + _roundtrip(tmp_path, im) + + +def test_large_count(tmp_path): + im = Image.new("L", (256, 1)) + px = im.load() + for x in range(256): + px[x, 0] = x // 67 * 67 + _roundtrip(tmp_path, im) + + +def _test_buffer_overflow(tmp_path, im, size=1024): + _last = ImageFile.MAXBLOCK + ImageFile.MAXBLOCK = size + try: + _roundtrip(tmp_path, im) + finally: + ImageFile.MAXBLOCK = _last + + +def test_break_in_count_overflow(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(4): + for x in range(256): + px[x, y] = x % 128 + _test_buffer_overflow(tmp_path, im) + + +def test_break_one_in_loop(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + _test_buffer_overflow(tmp_path, im) + + +def test_break_many_in_loop(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(4): + for x in range(256): + px[x, y] = x % 128 + for x in range(8): + px[x, 4] = 16 + _test_buffer_overflow(tmp_path, im) + + +def test_break_one_at_end(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + px[0, 3] = 128 + 64 + _test_buffer_overflow(tmp_path, im) + + +def test_break_many_at_end(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): for x in range(256): - px[x, 0] = x // 67 * 67 - self._roundtrip(im) - - def _test_buffer_overflow(self, im, size=1024): - _last = ImageFile.MAXBLOCK - ImageFile.MAXBLOCK = size - try: - self._roundtrip(im) - finally: - ImageFile.MAXBLOCK = _last - - def test_break_in_count_overflow(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(4): - for x in range(256): - px[x, y] = x % 128 - self._test_buffer_overflow(im) - - def test_break_one_in_loop(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - self._test_buffer_overflow(im) - - def test_break_many_in_loop(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(4): - for x in range(256): - px[x, y] = x % 128 - for x in range(8): - px[x, 4] = 16 - self._test_buffer_overflow(im) - - def test_break_one_at_end(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - px[0, 3] = 128 + 64 - self._test_buffer_overflow(im) - - def test_break_many_at_end(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - for x in range(4): - px[x * 2, 3] = 128 + 64 - px[x + 256 - 4, 3] = 0 - self._test_buffer_overflow(im) - - def test_break_padding(self): - im = Image.new("L", (257, 5)) - px = im.load() - for y in range(5): - for x in range(257): - px[x, y] = x % 128 - for x in range(5): - px[x, 3] = 0 - self._test_buffer_overflow(im) + px[x, y] = x % 128 + for x in range(4): + px[x * 2, 3] = 128 + 64 + px[x + 256 - 4, 3] = 0 + _test_buffer_overflow(tmp_path, im) + + +def test_break_padding(tmp_path): + im = Image.new("L", (257, 5)) + px = im.load() + for y in range(5): + for x in range(257): + px[x, y] = x % 128 + for x in range(5): + px[x, 3] = 0 + _test_buffer_overflow(tmp_path, im) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 6fbb4e414a2..6789cf66eaf 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,5 +1,4 @@ import re -import unittest import zlib from io import BytesIO @@ -8,7 +7,6 @@ from .helper import ( PillowLeakTestCase, - PillowTestCase, assert_image, assert_image_equal, assert_image_similar, @@ -56,7 +54,7 @@ def roundtrip(im, **options): @skip_unless_feature("zlib") -class TestFilePng(PillowTestCase): +class TestFilePng: def get_chunks(self, filename): chunks = [] with open(filename, "rb") as fp: @@ -73,12 +71,12 @@ def get_chunks(self, filename): return chunks @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_sanity(self): + def test_sanity(self, tmp_path): # internal version number assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") hopper("RGB").save(test_file) @@ -233,14 +231,14 @@ def test_load_transparent_rgb(self): # image has 876 transparent pixels assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_palette(self): + def test_save_p_transparent_palette(self, tmp_path): in_file = "Tests/images/pil123p.png" with Image.open(in_file) as im: # 'transparency' contains a byte string with the opacity for # each palette entry assert len(im.info["transparency"]) == 256 - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency @@ -254,14 +252,14 @@ def test_save_p_transparent_palette(self): # image has 124 unique alpha values assert len(im.getchannel("A").getcolors()) == 124 - def test_save_p_single_transparency(self): + def test_save_p_single_transparency(self, tmp_path): in_file = "Tests/images/p_trns_single.png" with Image.open(in_file) as im: # pixel value 164 is full transparent assert im.info["transparency"] == 164 assert im.getpixel((31, 31)) == 164 - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency @@ -277,14 +275,14 @@ def test_save_p_single_transparency(self): # image has 876 transparent pixels assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_black(self): + def test_save_p_transparent_black(self, tmp_path): # check if solid black image with full transparency # is supported (check for #1838) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) assert im.getcolors() == [(100, (0, 0, 0, 0))] im = im.convert("P") - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency @@ -295,7 +293,7 @@ def test_save_p_transparent_black(self): assert_image(im, "RGBA", (10, 10)) assert im.getcolors() == [(100, (0, 0, 0, 0))] - def test_save_greyscale_transparency(self): + def test_save_greyscale_transparency(self, tmp_path): for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): in_file = "Tests/images/" + mode.lower() + "_trns.png" with Image.open(in_file) as im: @@ -305,7 +303,7 @@ def test_save_greyscale_transparency(self): im_rgba = im.convert("RGBA") assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as test_im: @@ -316,10 +314,10 @@ def test_save_greyscale_transparency(self): test_im_rgba = test_im.convert("RGBA") assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent - def test_save_rgb_single_transparency(self): + def test_save_rgb_single_transparency(self, tmp_path): in_file = "Tests/images/caption_6_33_22.png" with Image.open(in_file) as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) def test_load_verify(self): @@ -484,12 +482,12 @@ def test_trns_rgb(self): im = roundtrip(im, transparency=(0, 1, 2)) assert im.info["transparency"] == (0, 1, 2) - def test_trns_p(self): + def test_trns_p(self, tmp_path): # Check writing a transparency of 0, issue #528 im = hopper("P") im.info["transparency"] = 0 - f = self.tempfile("temp.png") + f = str(tmp_path / "temp.png") im.save(f) with Image.open(f) as im2: @@ -540,9 +538,9 @@ def test_repr_png(self): assert repr_png.format == "PNG" assert_image_equal(im, repr_png) - def test_chunk_order(self): + def test_chunk_order(self, tmp_path): with Image.open("Tests/images/icc_profile.png") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.convert("P").save(test_file, dpi=(100, 100)) chunks = self.get_chunks(test_file) @@ -599,27 +597,27 @@ def test_exif(self): exif = im._getexif() assert exif[274] == 1 - def test_exif_save(self): + def test_exif_save(self, tmp_path): with Image.open("Tests/images/exif.png") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() assert exif[274] == 1 - def test_exif_from_jpg(self): + def test_exif_from_jpg(self, tmp_path): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() assert exif[305] == "Adobe Photoshop CS Macintosh" - def test_exif_argument(self): + def test_exif_argument(self, tmp_path): with Image.open(TEST_PNG_FILE) as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file, exif=b"exifstring") with Image.open(test_file) as reloaded: @@ -636,7 +634,7 @@ def test_apng(self): assert_image_similar(im, expected, 0.23) -@unittest.skipIf(is_win32(), "requires Unix or macOS") +@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @skip_unless_feature("zlib") class TestTruncatedPngPLeaks(PillowLeakTestCase): mem_limit = 2 * 1024 # max increase in K diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 0569e15cc95..a996f0b0e7f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,6 +1,5 @@ import logging import os -import unittest from io import BytesIO import pytest @@ -8,7 +7,6 @@ from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import ( - PillowTestCase, assert_image_equal, assert_image_equal_tofile, assert_image_similar, @@ -21,10 +19,10 @@ logger = logging.getLogger(__name__) -class TestFileTiff(PillowTestCase): - def test_sanity(self): +class TestFileTiff: + def test_sanity(self, tmp_path): - filename = self.tempfile("temp.tif") + filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename) @@ -54,7 +52,7 @@ def test_sanity(self): with Image.open(filename): pass - @unittest.skipIf(is_pypy(), "Requires CPython") + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file(self): def open(): im = Image.open("Tests/images/multipage.tiff") @@ -155,8 +153,8 @@ def test_load_dpi_rounding(self): assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit assert im.info["dpi"] == (dpi[1], dpi[1]) - def test_save_dpi_rounding(self): - outfile = self.tempfile("temp.tif") + def test_save_dpi_rounding(self, tmp_path): + outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: for dpi in (72.2, 72.8): im.save(outfile, dpi=(dpi, dpi)) @@ -190,14 +188,14 @@ def test_bad_exif(self): # Should not raise struct.error. pytest.warns(UserWarning, i._getexif) - def test_save_rgba(self): + def test_save_rgba(self, tmp_path): im = hopper("RGBA") - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") im.save(outfile) - def test_save_unsupported_mode(self): + def test_save_unsupported_mode(self, tmp_path): im = hopper("HSV") - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") with pytest.raises(IOError): im.save(outfile) @@ -459,9 +457,9 @@ def test_gray_semibyte_per_pixel(self): assert im2.mode == "L" assert_image_equal(im, im2) - def test_with_underscores(self): + def test_with_underscores(self, tmp_path): kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} - filename = self.tempfile("temp.tif") + filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename, **kwargs) with Image.open(filename) as im: @@ -473,14 +471,14 @@ def test_with_underscores(self): assert im.tag_v2[X_RESOLUTION] == 72 assert im.tag_v2[Y_RESOLUTION] == 36 - def test_roundtrip_tiff_uint16(self): + def test_roundtrip_tiff_uint16(self, tmp_path): # Test an image of all '0' values pixel_value = 0x1234 infile = "Tests/images/uint16_1_4660.tif" with Image.open(infile) as im: assert im.getpixel((0, 0)) == pixel_value - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile) with Image.open(tmpfile) as reloaded: @@ -512,9 +510,9 @@ def test_tiled_planar_raw(self): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_palette(self): - for mode in ["P", "PA"]: - outfile = self.tempfile("temp.tif") + def test_palette(self, tmp_path): + def roundtrip(mode): + outfile = str(tmp_path / "temp.tif") im = hopper(mode) im.save(outfile) @@ -522,6 +520,9 @@ def test_palette(self): with Image.open(outfile) as reloaded: assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) + for mode in ["P", "PA"]: + roundtrip(mode) + def test_tiff_save_all(self): mp = BytesIO() with Image.open("Tests/images/multipage.tiff") as im: @@ -552,7 +553,7 @@ def imGenerator(ims): with Image.open(mp) as reread: assert reread.n_frames == 3 - def test_saving_icc_profile(self): + def test_saving_icc_profile(self, tmp_path): # Tests saving TIFF with icc_profile set. # At the time of writing this will only work for non-compressed tiffs # as libtiff does not support embedded ICC profiles, @@ -561,14 +562,14 @@ def test_saving_icc_profile(self): im.info["icc_profile"] = "Dummy value" # Try save-load round trip to make sure both handle icc_profile. - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile, "TIFF", compression="raw") with Image.open(tmpfile) as reloaded: assert b"Dummy value" == reloaded.info["icc_profile"] - def test_close_on_load_exclusive(self): + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/uint16_1_4660.tif") as im: im.save(tmpfile) @@ -579,8 +580,8 @@ def test_close_on_load_exclusive(self): im.load() assert fp.closed - def test_close_on_load_nonexclusive(self): - tmpfile = self.tempfile("temp.tif") + def test_close_on_load_nonexclusive(self, tmp_path): + tmpfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/uint16_1_4660.tif") as im: im.save(tmpfile) @@ -601,10 +602,10 @@ def test_string_dimension(self): Image.open("Tests/images/string_dimension.tiff") -@unittest.skipUnless(is_win32(), "Windows only") -class TestFileTiffW32(PillowTestCase): - def test_fd_leak(self): - tmpfile = self.tempfile("temp.tif") +@pytest.mark.skipif(not is_win32(), reason="Windows only") +class TestFileTiffW32: + def test_fd_leak(self, tmp_path): + tmpfile = str(tmp_path / "temp.tif") # this is an mmaped file. with Image.open("Tests/images/uint16_1_4660.tif") as im: diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 35d61f90466..25cc9fef4e3 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,13 +2,12 @@ import os import subprocess import sys -import unittest from distutils import ccompiler, sysconfig import pytest from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper, is_win32, on_ci +from .helper import assert_image_equal, hopper, is_win32, on_ci # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 @@ -22,17 +21,17 @@ cffi = None -class AccessTest(PillowTestCase): +class AccessTest: # initial value _init_cffi_access = Image.USE_CFFI_ACCESS _need_cffi_access = False @classmethod - def setUpClass(cls): + def setup_class(cls): Image.USE_CFFI_ACCESS = cls._need_cffi_access @classmethod - def tearDownClass(cls): + def teardown_class(cls): Image.USE_CFFI_ACCESS = cls._init_cffi_access @@ -200,17 +199,17 @@ def test_p_putpixel_rgb_rgba(self): assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffiPutPixel(TestImagePutPixel): _need_cffi_access = True -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffiGetPixel(TestImageGetPixel): _need_cffi_access = True -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffi(AccessTest): _need_cffi_access = True @@ -326,10 +325,11 @@ def test_p_putpixel_rgb_rgba(self): assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) -class TestEmbeddable(unittest.TestCase): - @unittest.skipIf( +class TestEmbeddable: + @pytest.mark.skipif( not is_win32() or on_ci(), - "Failing on AppVeyor / GitHub Actions when run from subprocess, not from shell", + reason="Failing on AppVeyor / GitHub Actions when run from subprocess, " + "not from shell", ) def test_embeddable(self): with open("embed_pil.c", "w") as fh: diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index d6e63d44fb2..b1ddc75e95a 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,11 +1,10 @@ -import unittest - +import pytest from PIL import ImageWin -from .helper import PillowTestCase, hopper, is_win32 +from .helper import hopper, is_win32 -class TestImageWin(PillowTestCase): +class TestImageWin: def test_sanity(self): dir(ImageWin) @@ -32,8 +31,8 @@ def test_hwnd(self): assert wnd2 == 50 -@unittest.skipUnless(is_win32(), "Windows only") -class TestImageWinDib(PillowTestCase): +@pytest.mark.skipif(not is_win32(), reason="Windows only") +class TestImageWinDib: def test_dib_image(self): # Arrange im = hopper() diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 179bd17ac90..45c60fa107d 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,15 +1,9 @@ import shutil +import pytest from PIL import GifImagePlugin, Image, JpegImagePlugin -from .helper import ( - PillowTestCase, - cjpeg_available, - djpeg_available, - is_win32, - netpbm_available, - unittest, -) +from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available TEST_JPG = "Tests/images/hopper.jpg" TEST_GIF = "Tests/images/hopper.gif" @@ -17,38 +11,38 @@ test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&") -@unittest.skipIf(is_win32(), "requires Unix or macOS") -class TestShellInjection(PillowTestCase): - def assert_save_filename_check(self, src_img, save_func): +@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") +class TestShellInjection: + def assert_save_filename_check(self, tmp_path, src_img, save_func): for filename in test_filenames: - dest_file = self.tempfile(filename) + dest_file = str(tmp_path / filename) save_func(src_img, 0, dest_file) # If file can't be opened, shell injection probably occurred with Image.open(dest_file) as im: im.load() - @unittest.skipUnless(djpeg_available(), "djpeg not available") - def test_load_djpeg_filename(self): + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") + def test_load_djpeg_filename(self, tmp_path): for filename in test_filenames: - src_file = self.tempfile(filename) + src_file = str(tmp_path / filename) shutil.copy(TEST_JPG, src_file) with Image.open(src_file) as im: im.load_djpeg() - @unittest.skipUnless(cjpeg_available(), "cjpeg not available") - def test_save_cjpeg_filename(self): + @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") + def test_save_cjpeg_filename(self, tmp_path): with Image.open(TEST_JPG) as im: - self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) + self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg) - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_filename_bmp_mode(self): + @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") + def test_save_netpbm_filename_bmp_mode(self, tmp_path): with Image.open(TEST_GIF) as im: im = im.convert("RGB") - self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_filename_l_mode(self): + @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") + def test_save_netpbm_filename_l_mode(self, tmp_path): with Image.open(TEST_GIF) as im: im = im.convert("L") - self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)