Skip to content

Commit 459d547

Browse files
committed
Add a new --fail-on-increase option
As requested from some users, add a new --fail-on-increase option that makes the test run fail if the memory usage increases from previous runs in tests marked with "limit_memory". Signed-off-by: Pablo Galindo <[email protected]>
1 parent 0e33179 commit 459d547

File tree

6 files changed

+128
-3
lines changed

6 files changed

+128
-3
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ MEMORY PROBLEMS demo/test_ok.py::test_memory_exceed
9797
- `--stacks=STACKS` - Show the N stack entries when showing tracebacks of memory allocations
9898
- `--native` - Show native frames when showing tracebacks of memory allocations (will be slower)
9999
- `--trace-python-allocators` - Record allocations made by the Pymalloc allocator (will be slower)
100+
- `--fail-on-increase` - Fail if the memory usage increases from previous runs in tests marked with "limit_memory"
100101

101102
## Configuration - INI
102103

@@ -105,7 +106,8 @@ MEMORY PROBLEMS demo/test_ok.py::test_memory_exceed
105106
- `hide_memray_summary(bool)` - hide the memray summary at the end of the execution
106107
- `stacks(int)` - Show the N stack entries when showing tracebacks of memory allocations
107108
- `native(bool)`- Show native frames when showing tracebacks of memory allocations (will be slower)
108-
- `trace_python_allocators` - Record allocations made by the Pymalloc allocator (will be slower)
109+
- `trace_python_allocators(bool)` - Record allocations made by the Pymalloc allocator (will be slower)
110+
- `fail-on-increase(bool)` - Fail if the memory usage increases from previous runs in tests marked with "limit_memory"
109111

110112
## License
111113

docs/configuration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ The complete list of command line options is:
2929

3030
``--trace-python-allocators``
3131
Record allocations made by the Pymalloc allocator (will be slower)
32+
33+
``--fail-on-increase``
34+
Fail if the memory usage increases from previous runs in tests marked with "limit_memory"
3235

3336
.. tab:: Config file options
3437

@@ -49,3 +52,6 @@ The complete list of command line options is:
4952

5053
``trace_python_allocators(bool)``
5154
Record allocations made by the Pymalloc allocator (will be slower)
55+
56+
``--fail-on-increase(bool)``
57+
Fail if the memory usage increases from previous runs in tests marked with "limit_memory"

docs/news/91.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a new --fail-on-increase option that makes the test run fail if the memory usage increases from previous runs in tests marked with "limit_memory"

src/pytest_memray/marks.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ def long_repr(self) -> str:
127127
)
128128

129129

130+
@dataclass
131+
class _MoreMemoryInfo:
132+
previous_memory: float
133+
new_memory: float
134+
135+
@property
136+
def section(self) -> PytestSection:
137+
"""Return a tuple in the format expected by section reporters."""
138+
return (
139+
"memray-max-memory",
140+
"Test uses more memory than previous run",
141+
)
142+
143+
@property
144+
def long_repr(self) -> str:
145+
"""Generate a longrepr user-facing error message."""
146+
return (
147+
f"Test previously used {sizeof_fmt(self.previous_memory)} "
148+
f"but now uses {sizeof_fmt(self.new_memory)}"
149+
)
150+
151+
130152
def _generate_section_text(
131153
allocations: list[AllocationRecord], native_stacks: bool, num_stacks: int
132154
) -> str:
@@ -162,15 +184,30 @@ def _passes_filter(
162184

163185

164186
def limit_memory(
165-
limit: str, *, _result_file: Path, _config: Config
166-
) -> _MemoryInfo | None:
187+
limit: str,
188+
*,
189+
_result_file: Path,
190+
_config: Config,
191+
_test_id: str,
192+
) -> _MemoryInfo | _MoreMemoryInfo | None:
167193
"""Limit memory used by the test."""
168194
reader = FileReader(_result_file)
169195
allocations: list[AllocationRecord] = list(
170196
reader.get_high_watermark_allocation_records(merge_threads=True)
171197
)
172198
max_memory = parse_memory_string(limit)
173199
total_allocated_memory = sum(record.size for record in allocations)
200+
201+
if _config.cache is not None:
202+
cache = _config.cache.get(f"memray/{_test_id}", {})
203+
previous = cache.get("total_allocated_memory", float("inf"))
204+
fail_on_increase = cast(bool, value_or_ini(_config, "fail_on_increase"))
205+
if fail_on_increase and total_allocated_memory > previous:
206+
return _MoreMemoryInfo(previous, total_allocated_memory)
207+
208+
cache["total_allocated_memory"] = total_allocated_memory
209+
_config.cache.set(f"memray/{_test_id}", cache)
210+
174211
if total_allocated_memory < max_memory:
175212
return None
176213
num_stacks: int = cast(int, value_or_ini(_config, "stacks"))
@@ -190,6 +227,7 @@ def limit_leaks(
190227
filter_fn: Optional[LeaksFilterFunction] = None,
191228
_result_file: Path,
192229
_config: Config,
230+
_test_id: str,
193231
) -> _LeakedInfo | None:
194232
reader = FileReader(_result_file)
195233
allocations: list[AllocationRecord] = list(

src/pytest_memray/plugin.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def __call__(
5252
*args: Any,
5353
_result_file: Path,
5454
_config: Config,
55+
_test_id: str,
5556
**kwargs: Any,
5657
) -> SectionMetadata | None:
5758
...
@@ -237,6 +238,7 @@ def pytest_runtest_makereport(
237238
**marker.kwargs,
238239
_result_file=result.result_file,
239240
_config=self.config,
241+
_test_id=item.nodeid,
240242
)
241243
if res:
242244
report.outcome = "failed"
@@ -382,6 +384,12 @@ def pytest_addoption(parser: Parser) -> None:
382384
default=False,
383385
help="Record allocations made by the Pymalloc allocator (will be slower)",
384386
)
387+
group.addoption(
388+
"--fail-on-increase",
389+
action="store_true",
390+
default=False,
391+
help="Fail if the memory usage increases from previous runs in tests marked with limit_memory",
392+
)
385393

386394
parser.addini("memray", "Activate pytest.ini setting", type="bool")
387395
parser.addini(
@@ -405,6 +413,11 @@ def pytest_addoption(parser: Parser) -> None:
405413
help="Record allocations made by the Pymalloc allocator (will be slower)",
406414
type="bool",
407415
)
416+
parser.addini(
417+
"fail-on-increase",
418+
help="Fail if the memory usage increases from previous runs in tests marked with limit_memory",
419+
type="bool",
420+
)
408421
help_msg = "Show the N tests that allocate most memory (N=0 for all)"
409422
parser.addini("most_allocations", help_msg)
410423

tests/test_pytest_memray.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,68 @@ def test_bar():
742742

743743
output = result.stdout.str()
744744
assert "Only one Memray marker can be applied to each test" in output
745+
746+
747+
def test_fail_on_increase(pytester: Pytester):
748+
pytester.makepyfile(
749+
"""
750+
import pytest
751+
from memray._test import MemoryAllocator
752+
allocator = MemoryAllocator()
753+
754+
@pytest.mark.limit_memory("100MB")
755+
def test_memory_alloc_fails():
756+
allocator.valloc(1024)
757+
allocator.free()
758+
"""
759+
)
760+
result = pytester.runpytest("--memray")
761+
assert result.ret == ExitCode.OK
762+
pytester.makepyfile(
763+
"""
764+
import pytest
765+
from memray._test import MemoryAllocator
766+
allocator = MemoryAllocator()
767+
768+
@pytest.mark.limit_memory("100MB")
769+
def test_memory_alloc_fails():
770+
allocator.valloc(1024 * 10)
771+
allocator.free()
772+
"""
773+
)
774+
result = pytester.runpytest("--memray", "--fail-on-increase")
775+
assert result.ret == ExitCode.TESTS_FAILED
776+
output = result.stdout.str()
777+
assert "Test uses more memory than previous run" in output
778+
assert "Test previously used 1.0KiB but now uses 10.0KiB" in output
779+
780+
781+
def test_fail_on_increase_unset(pytester: Pytester):
782+
pytester.makepyfile(
783+
"""
784+
import pytest
785+
from memray._test import MemoryAllocator
786+
allocator = MemoryAllocator()
787+
788+
@pytest.mark.limit_memory("100MB")
789+
def test_memory_alloc_fails():
790+
allocator.valloc(1024)
791+
allocator.free()
792+
"""
793+
)
794+
result = pytester.runpytest("--memray")
795+
assert result.ret == ExitCode.OK
796+
pytester.makepyfile(
797+
"""
798+
import pytest
799+
from memray._test import MemoryAllocator
800+
allocator = MemoryAllocator()
801+
802+
@pytest.mark.limit_memory("100MB")
803+
def test_memory_alloc_fails():
804+
allocator.valloc(1024 * 10)
805+
allocator.free()
806+
"""
807+
)
808+
result = pytester.runpytest("--memray")
809+
assert result.ret == ExitCode.OK

0 commit comments

Comments
 (0)