diff --git a/Tests/images/mcidas_mode_L.ara b/Tests/images/mcidas_mode_L.ara new file mode 100644 index 000000000..a5bc09f3e Binary files /dev/null and b/Tests/images/mcidas_mode_L.ara differ diff --git a/Tests/test_frombytes_image.py b/Tests/test_frombytes_image.py deleted file mode 100644 index 77e3363a1..000000000 --- a/Tests/test_frombytes_image.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -from PIL import Image -import unittest - - -class TestFromBytes(unittest.TestCase): - def test_frombytes(self): - # Test case 1: Empty bytes - data = b"" - image = Image.frombytes("RGB", (0, 0), data) - self.assertEqual(image.size, (0, 0)) - - # Test case 2: Non-empty bytes - data = b"\x00\x00\xFF\xFF\x00\x00" - image = Image.frombytes("RGB", (2, 1), data) - self.assertEqual(image.size, (2, 1)) - self.assertEqual(image.getpixel((0, 0)), (0, 0, 255)) - self.assertEqual(image.getpixel((1, 0)), (255, 0, 0)) - - # Test case 3: Invalid mode - data = b"\x00\x00\xFF\xFF\x00\x00" - with self.assertRaises(ValueError): - Image.frombytes("RGBA", (2, 1), data) - - # Test case 4: Non-RGB mode - data = b"\x00\x00\xFF\xFF\x00\x00" - image = Image.frombytes("L", (2, 1), data) - self.assertEqual(image.size, (2, 1)) - self.assertEqual(image.getpixel((0, 0)), 0) - # self.assertEqual(image.getpixel((1, 0)), 255) - - # Test case 5: Zero width - data = b"" - image = Image.frombytes("RGB", (0, 1), data) - self.assertEqual(image.size, (0, 1)) - - # Test case 6: Zero height - data = b"" - image = Image.frombytes("RGB", (1, 0), data) - self.assertEqual(image.size, (1, 0)) - - # Test case 7: s[0] < 0 - data = b"\x00\x00\xFF\xFF\x00\x00" - s = (-1, 1) - with self.assertRaises(ValueError): - Image.frombytes("RGB", s, data) - - # Test case 8: s[1] == 0 - data = b"\x00\x00\xFF\xFF\x00\x00" - s = (2, 0) - # with self.assertRaises(ValueError): - # Image.frombytes("RGB", s, data) - - # Test case 5: Different size - data = b"\x00\x00\xFF\xFF\x00\x00\xFF\xFF\x00\x00" - image = Image.frombytes("RGB", (3, 1), data) - self.assertEqual(image.size, (3, 1)) - self.assertEqual(image.getpixel((0, 0)), (0, 0, 255)) - self.assertEqual(image.getpixel((1, 0)), (255, 0, 0)) - # self.assertEqual(image.getpixel((2, 0)), (255, 0, 0)) - -if __name__ == "__main__": - unittest.main() - diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 968667892..899ec5b01 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -39,7 +39,6 @@ HAVE_PROFILE = os.path.exists(SRGB) def setup_module() -> None: try: from PIL import ImageCms - # need to hit getattr to trigger the delayed import error ImageCms.core.profile_open except ImportError as v: @@ -699,3 +698,37 @@ def test_deprecation() -> None: assert ImageCms.VERSION == "1.0.0 pil" with pytest.warns(DeprecationWarning): assert isinstance(ImageCms.FLAGS, dict) + +def test_buildTransform_flags_non_integer(): + with pytest.raises(ImageCms.PyCMSError): + ImageCms.buildTransform( + inputProfile="path/to/input/profile", + outputProfile="path/to/output/profile", + inMode="RGB", + outMode="CMYK", + renderingIntent=ImageCms.Intent.PERCEPTUAL, + flags=123 + ) + +def test_buildTransform_flags_invalid(): + with pytest.raises(ImageCms.PyCMSError): + ImageCms.buildTransform( + inputProfile="path/to/input/profile", + outputProfile="path/to/output/profile", + inMode="RGB", + outMode="CMYK", + renderingIntent=ImageCms.Intent.PERCEPTUAL, + flags=999999 + ) + +def test_rendering_intent_non_integer(): + with pytest.raises(ImageCms.PyCMSError) as exc_info: + ImageCms.buildTransform( + inputProfile="path/to/input/profile", + outputProfile="path/to/output/profile", + inMode="RGB", + outMode="CMYK", + renderingIntent="not an integer", + flags=0 + ) + assert str(exc_info.value) == "renderingIntent must be an integer between 0 and 3" diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 1863c20a9..1d528b49f 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -215,19 +215,44 @@ class MockPyDecoder(ImageFile.PyDecoder): class MockPyEncoder(ImageFile.PyEncoder): - last: MockPyEncoder | None + last = None def __init__(self, mode: str, *args: Any) -> None: + super().__init__(mode, *args) + self._pushes_fd = False + self.cleanup_called = False MockPyEncoder.last = self - super().__init__(mode, *args) - def encode(self, buffer): + # Simulate encoding + if buffer is None: + raise NotImplementedError return 1, 1, b"" def cleanup(self) -> None: self.cleanup_called = True +def test_encode_to_file() -> None: + encoder = MockPyEncoder("RGBA") + + with pytest.raises(NotImplementedError): + encoder.encode_to_file(None, None) + + encoder._pushes_fd = True + with pytest.raises(NotImplementedError): + encoder.encode_to_file(None, None) + + buffer = BytesIO(b"\x00" * 10) + encoder._pushes_fd = False + encoder.encode = lambda buffer: (1, 1, b"") + try: + encoder.encode_to_file(buffer, None) + except NotImplementedError: + pass + + encoder.encode = lambda buffer: (_ for _ in ()).throw(NotImplementedError) + with pytest.raises(NotImplementedError): + encoder.encode_to_file(buffer, None) xoff, yoff, xsize, ysize = 10, 20, 100, 100 diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 027ab74aa..f6bbe39fd 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,25 +1,25 @@ from __future__ import annotations import pytest +import tkinter as tk +from unittest import mock from PIL import Image +from PIL import ImageTk + +from unittest.mock import patch + from .helper import assert_image_equal, hopper +TK_MODES = ("1", "L", "P", "RGB", "RGBA") + try: - import tkinter as tk - - from PIL import ImageTk - dir(ImageTk) HAS_TK = True except (OSError, ImportError): - # Skipped via pytestmark HAS_TK = False -TK_MODES = ("1", "L", "P", "RGB", "RGBA") - - pytestmark = pytest.mark.skipif(not HAS_TK, reason="Tk not installed") @@ -27,7 +27,6 @@ def setup_module() -> None: try: # setup tk tk.Frame() - # root = tk.Tk() except RuntimeError as v: pytest.skip(f"RuntimeError: {v}") except tk.TclError as v: @@ -102,3 +101,5 @@ def test_bitmapimage() -> None: # reloaded = ImageTk.getimage(im_tk) # assert_image_equal(reloaded, im) + + diff --git a/Tests/test_new_pdfparser.py b/Tests/test_new_pdfparser.py index b9d4728c1..cbefc04ea 100644 --- a/Tests/test_new_pdfparser.py +++ b/Tests/test_new_pdfparser.py @@ -1,26 +1,26 @@ -import pytest -from PIL import PdfParser - -def test_delitem_new_entries(): - parser = PdfParser.XrefTable() - parser.new_entries["test_key"] = ("value", 0) - - del parser["test_key"] - - assert "test_key" not in parser.new_entries - assert parser.deleted_entries["test_key"] == 1 - - -def test_delitem_deleted_entries(): - parser = PdfParser.XrefTable() - parser.deleted_entries["test_key"] = 0 - - del parser["test_key"] - - assert parser.deleted_entries["test_key"] == 0 - -def test_delitem_nonexistent_key(): - parser = PdfParser.XrefTable() - - with pytest.raises(IndexError): +import pytest +from PIL import PdfParser + +def test_delitem_new_entries(): + parser = PdfParser.XrefTable() + parser.new_entries["test_key"] = ("value", 0) + + del parser["test_key"] + + assert "test_key" not in parser.new_entries + assert parser.deleted_entries["test_key"] == 1 + + +def test_delitem_deleted_entries(): + parser = PdfParser.XrefTable() + parser.deleted_entries["test_key"] = 0 + + del parser["test_key"] + + assert parser.deleted_entries["test_key"] == 0 + +def test_delitem_nonexistent_key(): + parser = PdfParser.XrefTable() + + with pytest.raises(IndexError): del parser["nonexistent_key"] \ No newline at end of file diff --git a/conftest.py b/conftest.py index 805c67409..31630fb64 100644 --- a/conftest.py +++ b/conftest.py @@ -7,19 +7,19 @@ from PIL import PdfParser from PIL import SpiderImagePlugin from PIL import MpegImagePlugin from PIL import ImageCms -from PIL import McIdasImagePlugin +from PIL import ImageFile pytest_plugins = ["Tests.helper"] def calculate_coverage(test_name): all_branches = { - "branches1": Image.branches, - "branches2": PdfParser.XrefTable.branches, - "branches3": SpiderImagePlugin.branches, - "branches4": MpegImagePlugin.BitStream.branches, - "branches5": ImageCms.ImageCmsProfile.branches, - "branches6": McIdasImagePlugin.McIdasImageFile.branches, + "branches1": Image.branches, # duru + "branches2": PdfParser.XrefTable.branches, # duru + "branches3": SpiderImagePlugin.branches, # isidora + "branches4": MpegImagePlugin.BitStream.branches, # isidora + "branches5": ImageCms.branches, # deekshu + "branches6": ImageFile.PyEncoder.branches, # deekshu # Add more } @@ -54,4 +54,3 @@ def pytest_sessionfinish(session, exitstatus): coverage = calculate_coverage(test_name) print("\nBRANCH COVERAGE for", test_name, ":", coverage, "%\n") - diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 70f5a5de0..dad70f3e9 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -29,6 +29,15 @@ from . import Image, __version__ from ._deprecate import deprecate from ._typing import SupportsRead +branches = { + "1": False, + "2": False, + "3": False, + "4": False, + "5": False, + "6": False, +} + try: from . import _imagingcms as core except ImportError as ex: @@ -237,17 +246,6 @@ _FLAGS = { class ImageCmsProfile: - branches = { - "1": False, - "2": False, - "3": False, - "4": False, - "5": False, - "6": False, - "7": False, - "8": False, - } - def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None: """ :param profile: Either a string representing a filename, @@ -257,28 +255,21 @@ class ImageCmsProfile: """ if isinstance(profile, str): - ImageCmsProfile.branches["1"] = True if sys.platform == "win32": - ImageCmsProfile.branches["2"] = True profile_bytes_path = profile.encode() try: - ImageCmsProfile.branches["3"] = True profile_bytes_path.decode("ascii") except UnicodeDecodeError: - ImageCmsProfile.branches["4"] = True with open(profile, "rb") as f: - ImageCmsProfile.branches["5"] = True self._set(core.profile_frombytes(f.read())) return self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): - ImageCmsProfile.branches["6"] = True self._set(core.profile_frombytes(profile.read())) elif isinstance(profile, core.CmsProfile): - ImageCmsProfile.branches["7"] = True self._set(profile) else: - ImageCmsProfile.branches["8"] = True + msg = "Invalid type for Profile" # type: ignore[unreachable] raise TypeError(msg) @@ -582,22 +573,28 @@ def buildTransform( """ if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): + branches["1"] = True msg = "renderingIntent must be an integer between 0 and 3" raise PyCMSError(msg) if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): + branches["2"] = True msg = f"flags must be an integer between 0 and {_MAX_FLAG}" raise PyCMSError(msg) try: + branches["3"] = True if not isinstance(inputProfile, ImageCmsProfile): + branches["4"] = True inputProfile = ImageCmsProfile(inputProfile) if not isinstance(outputProfile, ImageCmsProfile): + branches["5"] = True outputProfile = ImageCmsProfile(outputProfile) return ImageCmsTransform( inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags ) except (OSError, TypeError, ValueError) as v: + branches["6"] = True raise PyCMSError(v) from v diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 3b6100e13..a2283a1d9 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -39,6 +39,7 @@ from . import Image from ._deprecate import deprecate from ._util import is_path + MAXBLOCK = 65536 SAFEBLOCK = 1024 * 1024 @@ -750,6 +751,11 @@ class PyDecoder(PyCodec): class PyEncoder(PyCodec): + + branches = { + "1": False, + "2": False, + } """ Python implementation of a format encoder. Override this class and add the decoding logic in the :meth:`encode` method. @@ -801,7 +807,9 @@ class PyEncoder(PyCodec): """ errcode = 0 while errcode == 0: + PyEncoder.branches["1"] = True status, errcode, buf = self.encode(bufsize) if status > 0: + PyEncoder.branches["2"] = True fh.write(buf[status:]) return errcode diff --git a/src/PIL/McIdasImagePlugin.py b/src/PIL/McIdasImagePlugin.py index 5bb5f1eba..3aaa2c829 100644 --- a/src/PIL/McIdasImagePlugin.py +++ b/src/PIL/McIdasImagePlugin.py @@ -21,7 +21,6 @@ import struct from . import Image, ImageFile - def _accept(prefix: bytes) -> bool: return prefix[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04" diff --git a/src/PIL/test_ImageFile.py b/src/PIL/test_ImageFile.py new file mode 100644 index 000000000..1c43a38d6 --- /dev/null +++ b/src/PIL/test_ImageFile.py @@ -0,0 +1,25 @@ +from io import BytesIO +import pytest +from PIL import ImageFile + +def test_encode_to_file_branches(): + # Create a mock file object + mock_file = io.BytesIO() + + # Create a PyEncoder instance + encoder = ImageFile.PyEncoder("RGB") + + # Set the branches dictionary to False to ensure both branches are covered + encoder.branches = {"1": False, "2": False} + + # Call the encode_to_file method + errcode = encoder.encode_to_file(mock_file, 1024) + + # Check that the branches dictionary has been updated + assert encoder.branches["1"] is True + assert encoder.branches["2"] is True + + # Check that the error code is 0, indicating successful encoding + assert errcode == 0 + + mock_file = BytesIO() \ No newline at end of file