Skip to content

Commit

Permalink
Add a new current_thread_only to all markers (#117)
Browse files Browse the repository at this point in the history
Add a new "current_thread_only" keyword to the "limit_memory" and
"limit_leaks" markers to ignore all allocations made in threads other
than the one running the test.
  • Loading branch information
pablogsal committed Apr 18, 2024
1 parent bdcfd2c commit 2f05466
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 9 deletions.
3 changes: 3 additions & 0 deletions docs/news/117.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add a new ``current_thread_only`` keyword argument to the ``limit_memory`` and
``limit_leaks`` markers to ignore all allocations made in threads other than
the one running the test.
12 changes: 10 additions & 2 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ This plugin provides `markers <https://docs.pytest.org/en/latest/example/markers
that can be used to enforce additional checks and validations on tests.


.. py:function:: pytest.mark.limit_memory(memory_limit: str)
.. py:function:: pytest.mark.limit_memory(memory_limit: str, current_thread_only: bool = False)
Fail the execution of the test if the test allocates more peak memory than allowed.

Expand All @@ -47,6 +47,10 @@ that can be used to enforce additional checks and validations on tests.
The format for the string is ``<NUMBER> ([KMGTP]B|B)``. The marker will raise
``ValueError`` if the string format cannot be parsed correctly.

If the optional keyword-only argument ``current_thread_only`` is set to *True*, the
plugin will only track memory allocations made by the current thread and all other
allocations will be ignored.

.. warning::

As the Python interpreter has its own
Expand All @@ -63,7 +67,7 @@ that can be used to enforce additional checks and validations on tests.
pass # do some stuff that allocates memory
.. py:function:: pytest.mark.limit_leaks(location_limit: str, filter_fn: LeaksFilterFunction | None = None)
.. py:function:: pytest.mark.limit_leaks(location_limit: str, filter_fn: LeaksFilterFunction | None = None, current_thread_only: bool = False)
Fail the execution of the test if any call stack in the test leaks more memory than
allowed.
Expand Down Expand Up @@ -96,6 +100,10 @@ that can be used to enforce additional checks and validations on tests.
ignored, the test will not fail. This can be used to discard any known false
positives.

If the optional keyword-only argument ``current_thread_only`` is set to *True*, the
plugin will only track memory allocations made by the current thread and all other
allocations will be ignored.

.. tip::

You can pass the ``--memray-bin-path`` argument to ``pytest`` to specify
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ maintainers = [
requires-python = ">=3.8"
dependencies = [
"pytest>=7.2",
"memray>=1.5",
"memray>=1.12",
]
optional-dependencies.docs = [
"furo>=2022.12.7",
Expand Down
22 changes: 16 additions & 6 deletions src/pytest_memray/marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,20 @@ def _passes_filter(
def limit_memory(
limit: str,
*,
current_thread_only: bool = False,
_result_file: Path,
_config: Config,
_test_id: str,
) -> _MemoryInfo | _MoreMemoryInfo | None:
"""Limit memory used by the test."""
reader = FileReader(_result_file)
allocations: list[AllocationRecord] = list(
reader.get_high_watermark_allocation_records(merge_threads=True)
)
allocations: list[AllocationRecord] = [
record
for record in reader.get_high_watermark_allocation_records(
merge_threads=not current_thread_only
)
if not current_thread_only or record.tid == reader.metadata.main_thread_id
]
max_memory = parse_memory_string(limit)
total_allocated_memory = sum(record.size for record in allocations)

Expand Down Expand Up @@ -225,14 +230,19 @@ def limit_leaks(
location_limit: str,
*,
filter_fn: Optional[LeaksFilterFunction] = None,
current_thread_only: bool = False,
_result_file: Path,
_config: Config,
_test_id: str,
) -> _LeakedInfo | None:
reader = FileReader(_result_file)
allocations: list[AllocationRecord] = list(
reader.get_leaked_allocation_records(merge_threads=True)
)
allocations: list[AllocationRecord] = [
record
for record in reader.get_leaked_allocation_records(
merge_threads=not current_thread_only
)
if not current_thread_only or record.tid == reader.metadata.main_thread_id
]

memory_limit = parse_memory_string(location_limit)

Expand Down
50 changes: 50 additions & 0 deletions tests/test_pytest_memray.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,3 +868,53 @@ def test_memory_alloc_fails():
)
result = pytester.runpytest("--memray")
assert result.ret == ExitCode.OK


def test_limit_memory_in_current_thread(pytester: Pytester) -> None:
pytester.makepyfile(
"""
import pytest
from memray._test import MemoryAllocator
allocator = MemoryAllocator()
import threading
def allocating_func():
for _ in range(10):
allocator.valloc(1024*5)
# No free call here
@pytest.mark.limit_memory("5KB", current_thread_only=True)
def test_memory_alloc_fails():
t = threading.Thread(target=allocating_func)
t.start()
t.join()
"""
)

result = pytester.runpytest("--memray")

assert result.ret == ExitCode.OK


def test_leaks_in_current_thread(pytester: Pytester) -> None:
pytester.makepyfile(
"""
import pytest
from memray._test import MemoryAllocator
allocator = MemoryAllocator()
import threading
def allocating_func():
for _ in range(10):
allocator.valloc(1024*5)
# No free call here
@pytest.mark.limit_leaks("5KB", current_thread_only=True)
def test_memory_alloc_fails():
t = threading.Thread(target=allocating_func)
t.start()
t.join()
"""
)

result = pytester.runpytest("--memray")

assert result.ret == ExitCode.OK

0 comments on commit 2f05466

Please sign in to comment.