File: test_optimizers.py

package info (click to toggle)
willow 1.11.0-0.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,952 kB
  • sloc: xml: 20,346; python: 3,969; makefile: 153; sh: 11
file content (181 lines) | stat: -rw-r--r-- 6,260 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import io
import os
import unittest
from subprocess import STDOUT, CalledProcessError
from tempfile import NamedTemporaryFile
from unittest import TestCase, mock

from willow.optimizers import Cwebp, Gifsicle, Jpegoptim, Pngquant
from willow.optimizers.base import OptimizerBase
from willow.registry import WillowRegistry


class OptimizerTest(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        class DummyOptimizer(OptimizerBase):
            library_name = "dummy"
            image_format = "FOO"

        cls.DummyOptimizer = DummyOptimizer

    def setUp(self) -> None:
        self.registry = WillowRegistry()

    @mock.patch("willow.optimizers.base.subprocess.check_output")
    def test_check_library(self, mock_check_output):
        self.assertTrue(self.DummyOptimizer.check_library())

    @mock.patch("willow.optimizers.base.subprocess.check_output")
    def test_check_library_fail(self, mock_check_output):
        mock_check_output.side_effect = CalledProcessError(-1, "dummy")
        self.assertFalse(self.DummyOptimizer.check_library())

        mock_check_output.side_effect = FileNotFoundError
        self.assertFalse(self.DummyOptimizer.check_library())

    def test_applies_to(self):
        self.assertTrue(self.DummyOptimizer.applies_to("foo"))
        self.assertTrue(self.DummyOptimizer.applies_to("FOO"))
        self.assertFalse(self.DummyOptimizer.applies_to("JPEG"))

    def test_get_check_library_arguments(self):
        self.assertEqual(self.DummyOptimizer.get_check_library_arguments(), ["--help"])

    def test_get_command_arguments(self):
        self.assertEqual(self.DummyOptimizer.get_command_arguments("file.png"), [])

    @mock.patch("willow.optimizers.base.subprocess.check_output")
    def test_process(self, mock_check_output):
        self.DummyOptimizer.process("file.png")
        mock_check_output.assert_called_once_with(["dummy"], stderr=STDOUT)

    @mock.patch("willow.optimizers.base.subprocess.check_output")
    def test_process_logs_any_issue(self, mock_check_output):
        # Simulates a CalledProcessError and tests that we log the error
        mock_check_output.side_effect = CalledProcessError(1, "dummy")
        with self.assertLogs("willow", level="ERROR") as log_output:
            self.DummyOptimizer.process("file.png")

        self.assertIn(
            "Error optimizing file.png with the 'dummy' library", log_output.output[0]
        )


class DefaultOptimizerTestBase:
    @classmethod
    def setUpClass(cls) -> None:
        with open(f"tests/images/optimizers/original.{cls.extension}", "rb") as f:
            cls.original_size = os.fstat(f.fileno()).st_size
            cls.original_image = f.read()

        with open(f"tests/images/optimizers/optimized.{cls.extension}", "rb") as f:
            f.seek(0, io.SEEK_END)
            cls.optimized_size = os.fstat(f.fileno()).st_size
            cls.optimized_image = f.read()

    def test_process_optimizes_image(self):
        try:
            with NamedTemporaryFile(delete=False) as named_temporary_file:
                named_temporary_file.write(self.original_image)
                image_file = named_temporary_file.name

            self.optimizer.process(image_file)

            with open(image_file, "rb") as f:
                self.assertAlmostEqual(
                    self.optimized_size, os.fstat(f.fileno()).st_size, delta=60
                )
        finally:
            os.unlink(image_file)


@unittest.skipUnless(Gifsicle.check_library(), "gifsicle not installed")
class GifsicleOptimizer(DefaultOptimizerTestBase, TestCase):
    extension = "gif"
    optimizer = Gifsicle

    def test_applies_to(self):
        self.assertTrue(Gifsicle.applies_to("gif"))
        for ext in ("png", "jpeg", "webp", "tiff", "bmp"):
            self.assertFalse(Gifsicle.applies_to(ext))

    def test_get_command_arguments(self):
        self.assertListEqual(
            Gifsicle.get_command_arguments("file.gif"), ["-b", "-O3", "file.gif"]
        )


@unittest.skipUnless(Jpegoptim.check_library(), "jpegoptim not installed")
class JpegoptimOptimizer(DefaultOptimizerTestBase, TestCase):
    extension = "jpg"
    optimizer = Jpegoptim

    def test_applies_to(self):
        self.assertTrue(Jpegoptim.applies_to("jpeg"))
        for ext in ("png", "gif", "webp", "tiff", "bmp"):
            self.assertFalse(Jpegoptim.applies_to(ext))

    def test_get_command_arguments(self):
        self.assertListEqual(
            Jpegoptim.get_command_arguments("file.jpg"),
            ["--strip-all", "--max=85", "--all-progressive", "file.jpg"],
        )


@unittest.skipUnless(Pngquant.check_library(), "pngquant not installed")
class PngquantOptimizer(DefaultOptimizerTestBase, TestCase):
    extension = "png"
    optimizer = Pngquant

    def test_applies_to(self):
        self.assertTrue(Pngquant.applies_to("png"))
        for ext in ("gif", "jpeg", "webp", "tiff", "bmp"):
            self.assertFalse(Pngquant.applies_to(ext))

    def test_get_command_arguments(self):
        self.assertListEqual(
            Pngquant.get_command_arguments("file.png"),
            [
                "--force",
                "--strip",
                "--skip-if-larger",
                "file.png",
                "--output",
                "file.png",
            ],
        )


@unittest.skipUnless(Cwebp.check_library(), "cwebp not installed")
class CwebpOptimizer(DefaultOptimizerTestBase, TestCase):
    extension = "webp"
    optimizer = Cwebp

    def test_applies_to(self):
        self.assertTrue(Cwebp.applies_to("webp"))
        for ext in ("png", "jpeg", "gif", "tiff", "bmp"):
            self.assertFalse(Cwebp.applies_to(ext))

    def test_get_command_arguments(self):
        self.assertListEqual(
            Cwebp.get_command_arguments("file.webp"),
            [
                "-m",
                "6",
                "-mt",
                "-pass",
                "10",
                "-q",
                "75",
                "file.webp",
                "-o",
                "file.webp",
            ],
        )

    def get_check_library_command_arguments(self):
        self.assertListEqual(
            Cwebp.get_check_library_arguments(),
            [],
        )