tests: Switch from PIL to pyvips.

This commit is contained in:
Alex Vandiver 2024-06-13 03:48:22 +00:00 committed by Tim Abbott
parent b14a33c659
commit 0070b5da78
3 changed files with 59 additions and 33 deletions

View File

@ -1,4 +1,3 @@
import io
import os
import re
import time
@ -8,9 +7,9 @@ from unittest.mock import patch
from urllib.parse import quote
import orjson
import pyvips
from django.conf import settings
from django.utils.timezone import now as timezone_now
from PIL import Image
from typing_extensions import override
from urllib3 import encode_multipart_formdata
from urllib3.fields import RequestField
@ -1258,7 +1257,9 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
if rfname is not None:
response = self.client_get(url)
data = response.getvalue()
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
avatar_image = pyvips.Image.new_from_buffer(data, "")
self.assertEqual(avatar_image.height, 100)
self.assertEqual(avatar_image.width, 100)
# Verify that the medium-size avatar was created
user_profile = self.example_user("hamlet")
@ -1385,21 +1386,27 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
class EmojiTest(UploadSerializeMixin, ZulipTestCase):
# While testing GIF resizing, we can't test if the final GIF has the same
# number of frames as the original one because PIL drops duplicate frames
# with a corresponding increase in the duration of the previous frame.
def test_resize_emoji(self) -> None:
# Test unequal width and height of animated GIF image
animated_unequal_img_data = read_test_image_file("animated_unequal_img.gif")
original_image = pyvips.Image.new_from_buffer(animated_unequal_img_data, "n=-1")
resized_img_data, is_animated, still_img_data = resize_emoji(
animated_unequal_img_data, "animated_unequal_img.gif", size=50
)
im = Image.open(io.BytesIO(resized_img_data))
self.assertEqual((50, 50), im.size)
self.assertTrue(is_animated)
assert still_img_data is not None
still_image = Image.open(io.BytesIO(still_img_data))
self.assertEqual((50, 50), still_image.size)
emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual(emoji_image.get("vips-loader"), "gifload_buffer")
self.assertEqual(emoji_image.get_n_pages(), original_image.get_n_pages())
self.assertEqual(emoji_image.get("page-height"), 50)
self.assertEqual(emoji_image.height, 150)
self.assertEqual(emoji_image.width, 50)
still_image = pyvips.Image.new_from_buffer(still_img_data, "")
self.assertEqual(still_image.get("vips-loader"), "pngload_buffer")
self.assertEqual(still_image.get_n_pages(), 1)
self.assertEqual(still_image.height, 50)
self.assertEqual(still_image.width, 50)
# Test corrupt image exception
corrupted_img_data = read_test_image_file("corrupt.gif")
@ -1407,15 +1414,23 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
resize_emoji(corrupted_img_data, "corrupt.gif")
animated_large_img_data = read_test_image_file("animated_large_img.gif")
original_image = pyvips.Image.new_from_buffer(animated_large_img_data, "n=-1")
resized_img_data, is_animated, still_img_data = resize_emoji(
animated_large_img_data, "animated_large_img.gif", size=50
)
im = Image.open(io.BytesIO(resized_img_data))
self.assertEqual((50, 50), im.size)
emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual(emoji_image.get("vips-loader"), "gifload_buffer")
self.assertEqual(emoji_image.get_n_pages(), original_image.get_n_pages())
self.assertEqual(emoji_image.get("page-height"), 50)
self.assertEqual(emoji_image.height, 150)
self.assertEqual(emoji_image.width, 50)
self.assertTrue(is_animated)
assert still_img_data
still_image = Image.open(io.BytesIO(still_img_data))
self.assertEqual((50, 50), still_image.size)
still_image = pyvips.Image.new_from_buffer(still_img_data, "")
self.assertEqual(still_image.get("vips-loader"), "pngload_buffer")
self.assertEqual(still_image.get_n_pages(), 1)
self.assertEqual(still_image.height, 50)
self.assertEqual(still_image.width, 50)
# Test an image file with too many bytes is not resized
with patch("zerver.lib.thumbnail.MAX_EMOJI_GIF_FILE_SIZE_BYTES", 1024):
@ -1432,8 +1447,11 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
resized_img_data, is_animated, no_still_data = resize_emoji(
still_large_img_data, "still_large_img.gif", size=50
)
im = Image.open(io.BytesIO(resized_img_data))
self.assertEqual((50, 50), im.size)
emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual(emoji_image.get("vips-loader"), "gifload_buffer")
self.assertEqual(emoji_image.height, 50)
self.assertEqual(emoji_image.width, 50)
self.assertEqual(emoji_image.get_n_pages(), 1)
self.assertFalse(is_animated)
assert no_still_data is None
@ -1442,8 +1460,11 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
resized_img_data, is_animated, no_still_data = resize_emoji(
still_large_img_data, "img.jpg", size=50
)
im = Image.open(io.BytesIO(resized_img_data))
self.assertEqual((50, 50), im.size)
emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "")
self.assertEqual(emoji_image.get("vips-loader"), "jpegload_buffer")
self.assertEqual(emoji_image.height, 50)
self.assertEqual(emoji_image.width, 50)
self.assertEqual(emoji_image.get_n_pages(), 1)
self.assertFalse(is_animated)
assert no_still_data is None
@ -1535,7 +1556,9 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
if rfname is not None:
response = self.client_get(url)
data = response.getvalue()
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
response_image = pyvips.Image.new_from_buffer(data, "")
self.assertEqual(response_image.height, 100)
self.assertEqual(response_image.width, 100)
def test_invalid_icons(self) -> None:
"""
@ -1719,7 +1742,9 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
data = response.getvalue()
# size should be 100 x 100 because thumbnail keeps aspect ratio
# while trying to fit in a 800 x 100 box without losing part of the image
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
response_image = pyvips.Image.new_from_buffer(data, "")
self.assertEqual(response_image.height, 100)
self.assertEqual(response_image.width, 100)
def test_invalid_logo_upload(self) -> None:
"""

View File

@ -3,8 +3,8 @@ import re
from io import BytesIO, StringIO
from urllib.parse import urlsplit
import pyvips
from django.conf import settings
from PIL import Image
import zerver.lib.upload
from zerver.lib.avatar_hash import user_avatar_path
@ -229,9 +229,9 @@ class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
with open(file_path + ".original", "rb") as original_file:
self.assertEqual(read_test_image_file("img.png"), original_file.read())
expected_size = (DEFAULT_EMOJI_SIZE, DEFAULT_EMOJI_SIZE)
with Image.open(file_path) as resized_image:
self.assertEqual(expected_size, resized_image.size)
resized_emoji = pyvips.Image.new_from_file(file_path)
self.assertEqual(DEFAULT_EMOJI_SIZE, resized_emoji.width)
self.assertEqual(DEFAULT_EMOJI_SIZE, resized_emoji.height)
def test_tarball_upload_and_deletion(self) -> None:
user_profile = self.example_user("iago")

View File

@ -1,4 +1,3 @@
import io
import os
import re
from io import BytesIO, StringIO
@ -6,8 +5,8 @@ from unittest.mock import patch
from urllib.parse import urlsplit
import botocore.exceptions
import pyvips
from django.conf import settings
from PIL import Image
import zerver.lib.upload
from zerver.actions.user_settings import do_delete_avatar_image
@ -414,9 +413,9 @@ class S3Test(ZulipTestCase):
resized_path_id = os.path.join(str(user_profile.realm.id), "realm", "icon.png")
resized_data = bucket.Object(resized_path_id).get()["Body"].read()
# while trying to fit in a 800 x 100 box without losing part of the image
resized_image = Image.open(io.BytesIO(resized_data)).size
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
resized_image = pyvips.Image.new_from_buffer(resized_data, "")
self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.height)
self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.width)
@use_s3_backend
def _test_upload_logo_image(self, night: bool, file_name: str) -> None:
@ -436,8 +435,9 @@ class S3Test(ZulipTestCase):
resized_path_id = os.path.join(str(user_profile.realm.id), "realm", f"{file_name}.png")
resized_data = bucket.Object(resized_path_id).get()["Body"].read()
resized_image = Image.open(io.BytesIO(resized_data)).size
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
resized_image = pyvips.Image.new_from_buffer(resized_data, "")
self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.height)
self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.width)
def test_upload_realm_logo_image(self) -> None:
self._test_upload_logo_image(night=False, file_name="logo")
@ -489,8 +489,9 @@ class S3Test(ZulipTestCase):
self.assertEqual(read_test_image_file("img.png"), original_key.get()["Body"].read())
resized_data = bucket.Object(emoji_path).get()["Body"].read()
resized_image = Image.open(io.BytesIO(resized_data))
self.assertEqual(resized_image.size, (DEFAULT_EMOJI_SIZE, DEFAULT_EMOJI_SIZE))
resized_image = pyvips.Image.new_from_buffer(resized_data, "")
self.assertEqual(DEFAULT_EMOJI_SIZE, resized_image.height)
self.assertEqual(DEFAULT_EMOJI_SIZE, resized_image.width)
@use_s3_backend
def test_tarball_upload_and_deletion(self) -> None: