Skip to content

Commit ed4519c

Browse files
committed
Measure resident memory in leak tests, not peak memory
* ru_maxrss never falls, so leak tests were measuring occasional transient spikes, not true leaks. Instead, for leak tests we now read the current RSS, which falls (to a degree, depending on system allocator strategy) when memory is released, and also doesn't depend on the process's past. * The tests' limits were adjusted based on empirical measurements.
1 parent b2ccf23 commit ed4519c

4 files changed

Lines changed: 25 additions & 18 deletions

File tree

.ci/requirements-mypy.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ pytest
1212
types-atheris
1313
types-defusedxml
1414
types-olefile
15+
types-psutil
1516
types-setuptools

Tests/helper.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@
2222
if TYPE_CHECKING:
2323
from collections.abc import Callable, Sequence
2424
from pathlib import Path
25+
from types import ModuleType
2526
from typing import Any
2627

28+
psutil: ModuleType | None
29+
try:
30+
import psutil
31+
except ImportError:
32+
psutil = None
33+
2734
logger = logging.getLogger(__name__)
2835

2936
uploader = None
@@ -212,33 +219,33 @@ def is_pypy() -> bool:
212219

213220

214221
@pytest.mark.isolated
215-
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
222+
@pytest.mark.skipif(psutil is None, reason="psutil not installed")
223+
@pytest.mark.skipif(
224+
sys.platform.startswith("win32"),
225+
reason="Leak limits are not calibrated for Windows",
226+
)
216227
# Per https://stackoverflow.com/a/29007723/51685, due to JIT compilation,
217228
# RSS utilization is known to grow in PyPy.
218229
@pytest.mark.skipif(is_pypy(), reason="max RSS utilization is not stable on PyPy")
219230
class PillowLeakTestCase:
220-
# requires unix/macOS
221231
iterations = 100 # count
222232
mem_limit = 512 # k
223233

224234
def _get_mem_usage(self) -> float:
225235
"""
226-
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
227-
between macOS and Linux rss reporting
236+
Gets the resident set size currently used by this process.
228237
229238
:returns: memory usage in kilobytes
230239
"""
231240

232-
from resource import RUSAGE_SELF, getrusage
233-
234-
mem = getrusage(RUSAGE_SELF).ru_maxrss
235-
# man 2 getrusage:
236-
# ru_maxrss
237-
# This is the maximum resident set size utilized
238-
# in bytes on macOS, in kilobytes on Linux
239-
return mem / 1024 if sys.platform == "darwin" else mem
241+
assert psutil is not None
242+
return psutil.Process().memory_info().rss / 1024
240243

241244
def _test_leak(self, core: Callable[[], None]) -> None:
245+
# Warm up so allocator arenas, caches, etc. are allocated,
246+
# before taking the baseline measurement.
247+
core()
248+
242249
start_mem = self._get_mem_usage()
243250
for cycle in range(self.iterations):
244251
core()

Tests/test_font_leaks.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ def _test_font(self, font: ImageFont.FreeTypeFont | ImageFont.ImageFont) -> None
2323

2424

2525
class TestTTypeFontLeak(TestFontLeak):
26-
# fails at iteration 3 in main
27-
iterations = 10
28-
mem_limit = 4096 # k
26+
iterations = 30
27+
mem_limit = 32768 # k
2928

3029
@skip_unless_feature("freetype2")
3130
def test_leak(self) -> None:
@@ -34,9 +33,8 @@ def test_leak(self) -> None:
3433

3534

3635
class TestDefaultFontLeak(TestFontLeak):
37-
# fails at iteration 37 in main
38-
iterations = 100
39-
mem_limit = 1024 # k
36+
iterations = 1000
37+
mem_limit = 16384 # k
4038

4139
def test_leak(self, monkeypatch: pytest.MonkeyPatch) -> None:
4240
if features.check_module("freetype2"):

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ optional-dependencies.tests = [
7171
"markdown2",
7272
"olefile",
7373
"packaging",
74+
"psutil; sys_platform=='darwin' or sys_platform=='linux'",
7475
"pytest",
7576
"pytest-cov",
7677
"pytest-timeout",

0 commit comments

Comments
 (0)