From dc1ab90f1e0858429289ee04848c7b5fb4448ba1 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 4 Sep 2026 11:37:10 +0300 Subject: [PATCH] test_empty_image: measure CPU time between filling and not filling --- Tests/helper.py | 15 +++++++++++++++ Tests/test_image.py | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 924b9733403..63deb305772 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 @@ -336,3 +337,17 @@ def is_win32() -> bool: def is_pypy() -> bool: return sys.implementation.name == "pypy" + + +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 24a61ab5b4d..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,10 +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, 100000000), (100000000, 0))) + @pytest.mark.parametrize( + "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