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 os
import re import re
import time import time
@ -8,9 +7,9 @@ from unittest.mock import patch
from urllib.parse import quote from urllib.parse import quote
import orjson import orjson
import pyvips
from django.conf import settings from django.conf import settings
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from PIL import Image
from typing_extensions import override from typing_extensions import override
from urllib3 import encode_multipart_formdata from urllib3 import encode_multipart_formdata
from urllib3.fields import RequestField from urllib3.fields import RequestField
@ -1258,7 +1257,9 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
if rfname is not None: if rfname is not None:
response = self.client_get(url) response = self.client_get(url)
data = response.getvalue() 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 # Verify that the medium-size avatar was created
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
@ -1385,21 +1386,27 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
class EmojiTest(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: def test_resize_emoji(self) -> None:
# Test unequal width and height of animated GIF image # Test unequal width and height of animated GIF image
animated_unequal_img_data = read_test_image_file("animated_unequal_img.gif") 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( resized_img_data, is_animated, still_img_data = resize_emoji(
animated_unequal_img_data, "animated_unequal_img.gif", size=50 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) self.assertTrue(is_animated)
assert still_img_data is not None assert still_img_data is not None
still_image = Image.open(io.BytesIO(still_img_data)) emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual((50, 50), still_image.size) 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 # Test corrupt image exception
corrupted_img_data = read_test_image_file("corrupt.gif") corrupted_img_data = read_test_image_file("corrupt.gif")
@ -1407,15 +1414,23 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
resize_emoji(corrupted_img_data, "corrupt.gif") resize_emoji(corrupted_img_data, "corrupt.gif")
animated_large_img_data = read_test_image_file("animated_large_img.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( resized_img_data, is_animated, still_img_data = resize_emoji(
animated_large_img_data, "animated_large_img.gif", size=50 animated_large_img_data, "animated_large_img.gif", size=50
) )
im = Image.open(io.BytesIO(resized_img_data)) emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual((50, 50), im.size) 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) self.assertTrue(is_animated)
assert still_img_data assert still_img_data
still_image = Image.open(io.BytesIO(still_img_data)) still_image = pyvips.Image.new_from_buffer(still_img_data, "")
self.assertEqual((50, 50), still_image.size) 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 # Test an image file with too many bytes is not resized
with patch("zerver.lib.thumbnail.MAX_EMOJI_GIF_FILE_SIZE_BYTES", 1024): 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( resized_img_data, is_animated, no_still_data = resize_emoji(
still_large_img_data, "still_large_img.gif", size=50 still_large_img_data, "still_large_img.gif", size=50
) )
im = Image.open(io.BytesIO(resized_img_data)) emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "n=-1")
self.assertEqual((50, 50), im.size) 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) self.assertFalse(is_animated)
assert no_still_data is None assert no_still_data is None
@ -1442,8 +1460,11 @@ class EmojiTest(UploadSerializeMixin, ZulipTestCase):
resized_img_data, is_animated, no_still_data = resize_emoji( resized_img_data, is_animated, no_still_data = resize_emoji(
still_large_img_data, "img.jpg", size=50 still_large_img_data, "img.jpg", size=50
) )
im = Image.open(io.BytesIO(resized_img_data)) emoji_image = pyvips.Image.new_from_buffer(resized_img_data, "")
self.assertEqual((50, 50), im.size) 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) self.assertFalse(is_animated)
assert no_still_data is None assert no_still_data is None
@ -1535,7 +1556,9 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
if rfname is not None: if rfname is not None:
response = self.client_get(url) response = self.client_get(url)
data = response.getvalue() 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: def test_invalid_icons(self) -> None:
""" """
@ -1719,7 +1742,9 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
data = response.getvalue() data = response.getvalue()
# size should be 100 x 100 because thumbnail keeps aspect ratio # 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 # 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: def test_invalid_logo_upload(self) -> None:
""" """

View File

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

View File

@ -1,4 +1,3 @@
import io
import os import os
import re import re
from io import BytesIO, StringIO from io import BytesIO, StringIO
@ -6,8 +5,8 @@ from unittest.mock import patch
from urllib.parse import urlsplit from urllib.parse import urlsplit
import botocore.exceptions import botocore.exceptions
import pyvips
from django.conf import settings from django.conf import settings
from PIL import Image
import zerver.lib.upload import zerver.lib.upload
from zerver.actions.user_settings import do_delete_avatar_image 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_path_id = os.path.join(str(user_profile.realm.id), "realm", "icon.png")
resized_data = bucket.Object(resized_path_id).get()["Body"].read() 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 = pyvips.Image.new_from_buffer(resized_data, "")
resized_image = Image.open(io.BytesIO(resized_data)).size self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.height)
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE)) self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.width)
@use_s3_backend @use_s3_backend
def _test_upload_logo_image(self, night: bool, file_name: str) -> None: 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_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_data = bucket.Object(resized_path_id).get()["Body"].read()
resized_image = Image.open(io.BytesIO(resized_data)).size resized_image = pyvips.Image.new_from_buffer(resized_data, "")
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE)) self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.height)
self.assertEqual(DEFAULT_AVATAR_SIZE, resized_image.width)
def test_upload_realm_logo_image(self) -> None: def test_upload_realm_logo_image(self) -> None:
self._test_upload_logo_image(night=False, file_name="logo") 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()) self.assertEqual(read_test_image_file("img.png"), original_key.get()["Body"].read())
resized_data = bucket.Object(emoji_path).get()["Body"].read() resized_data = bucket.Object(emoji_path).get()["Body"].read()
resized_image = Image.open(io.BytesIO(resized_data)) resized_image = pyvips.Image.new_from_buffer(resized_data, "")
self.assertEqual(resized_image.size, (DEFAULT_EMOJI_SIZE, DEFAULT_EMOJI_SIZE)) self.assertEqual(DEFAULT_EMOJI_SIZE, resized_image.height)
self.assertEqual(DEFAULT_EMOJI_SIZE, resized_image.width)
@use_s3_backend @use_s3_backend
def test_tarball_upload_and_deletion(self) -> None: def test_tarball_upload_and_deletion(self) -> None: