diff --git a/Tests/helper.py b/Tests/helper.py index 1ede4b7d5b1..cd2c9b71f39 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -10,6 +10,7 @@ import subprocess import sys import tempfile +import time from functools import lru_cache from io import BytesIO @@ -339,3 +340,17 @@ def is_ppc64le() -> bool: def is_win32() -> bool: return sys.platform.startswith("win32") + + +def measure_cpu_seconds(f: Callable[[], Any], times: int = 3) -> float: + """ + Measure the execution time of a function. + """ + best = float("inf") + for _ in range(times): + # `process_time` is the sum of the system and user CPU time + # of the current process, not wallclock time. + start = time.process_time() + f() + best = min(best, time.process_time() - start) + return best diff --git a/Tests/test_image.py b/Tests/test_image.py index d0516b849f2..ecc21c1736f 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -30,8 +30,8 @@ hopper, is_win32, mark_if_feature_version, + measure_cpu_seconds, skip_unless_feature, - timeout_unless_slower_valgrind, ) TYPE_CHECKING = False @@ -571,12 +571,18 @@ def test_check_size(self) -> None: i = Image.new("RGB", [1, 1]) assert isinstance(i.size, tuple) - @timeout_unless_slower_valgrind(0.75) @pytest.mark.parametrize( - "size", ((0, 10_000_000), (10_000_000, 0)), ids=("tall", "wide") + "size", ((0, 100_000_000), (100_000_000, 0)), ids=("tall", "wide") ) def test_empty_image(self, size: tuple[int, int]) -> None: - Image.new("RGB", size) + # Test that initializing an empty image does not do + # extra work to try and fill the image. + # Allocating a tall image can still take a while + # since the internal row pointers are necessarily allocated, + # so measure the difference between doing that work and not doing it. + unfilled = measure_cpu_seconds(lambda: Image.new("RGB", size, None)) + filled = measure_cpu_seconds(lambda: Image.new("RGB", size, 0)) + assert filled - unfilled < 0.1 def test_storage_neg(self) -> None: # Storage.c accepted negative values for xsize, ysize. Was