from __future__ import annotations import tempfile import warnings from io import BytesIO from pathlib import Path from unittest.mock import MagicMock, patch import unittest import struct import pytest from PIL import Image, ImageSequence, SpiderImagePlugin from .helper import assert_image_equal, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" def test_sanity() -> None: with Image.open(TEST_FILE) as im: im.load() assert im.mode == "F" assert im.size == (128, 128) assert im.format == "SPIDER" @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: def open() -> None: im = Image.open(TEST_FILE) im.load() with pytest.warns(ResourceWarning): open() def test_closed_file() -> None: with warnings.catch_warnings(): im = Image.open(TEST_FILE) im.load() im.close() def test_context_manager() -> None: with warnings.catch_warnings(): with Image.open(TEST_FILE) as im: im.load() def test_save(tmp_path: Path) -> None: # Arrange temp = str(tmp_path / "temp.spider") im = hopper() # Act im.save(temp, "SPIDER") # Assert with Image.open(temp) as im2: assert im2.mode == "F" assert im2.size == (128, 128) assert im2.format == "SPIDER" def test_tempfile() -> None: # Arrange im = hopper() # Act with tempfile.TemporaryFile() as fp: im.save(fp, "SPIDER") # Assert fp.seek(0) with Image.open(fp) as reloaded: assert reloaded.mode == "F" assert reloaded.size == (128, 128) assert reloaded.format == "SPIDER" def test_is_spider_image() -> None: assert SpiderImagePlugin.isSpiderImage(TEST_FILE) def test_tell() -> None: # Arrange with Image.open(TEST_FILE) as im: # Act index = im.tell() # Assert assert index == 0 def test_n_frames() -> None: with Image.open(TEST_FILE) as im: assert im.n_frames == 1 assert not im.is_animated def test_load_image_series() -> None: # Arrange not_spider_file = "Tests/images/hopper.ppm" file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] # Act img_list = SpiderImagePlugin.loadImageSeries(file_list) # Assert assert len(img_list) == 1 assert isinstance(img_list[0], Image.Image) assert img_list[0].size == (128, 128) def test_load_image_series_no_input() -> None: # Arrange file_list = None # Act img_list = SpiderImagePlugin.loadImageSeries(file_list) # Assert assert img_list is None def test_is_int_not_a_number() -> None: # Arrange not_a_number = "a" # Act ret = SpiderImagePlugin.isInt(not_a_number) # Assert assert ret == 0 def test_invalid_file() -> None: invalid_file = "Tests/images/invalid.spider" with pytest.raises(OSError): with Image.open(invalid_file): pass def test_nonstack_file() -> None: with Image.open(TEST_FILE) as im: with pytest.raises(EOFError): im.seek(0) def test_nonstack_dos() -> None: with Image.open(TEST_FILE) as im: for i, frame in enumerate(ImageSequence.Iterator(im)): assert i <= 1, "Non-stack DOS file test failed" # for issue #4093 def test_odd_size() -> None: data = BytesIO() width = 100 im = Image.new("F", (width, 64)) im.save(data, format="SPIDER") data.seek(0) with Image.open(data) as im2: assert_image_equal(im, im2) def test_seek_no_frame() -> None: with Image.open(TEST_FILE) as im: im.istack = 1 im.seek(0) def test_save_small_header() -> None: width, height = 10, 10 im = Image.new("F", (width, height)) fp = BytesIO() corrupted_header = [b'\x00' * 4] * 22 with patch("PIL.SpiderImagePlugin.makeSpiderHeader", return_value=corrupted_header): try: im.save(fp, format="SPIDER") except OSError as e: assert str(e) == "Error creating Spider header" else: assert False, "Expected an OSError due to corrupted header"