Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new current_thread_only to all markers #117

Merged
merged 3 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
10 changes: 9 additions & 1 deletion 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 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
32 changes: 26 additions & 6 deletions src/pytest_memray/marks.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,27 @@ 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] = []
if current_thread_only:
main_thread = reader.metadata.main_thread_id
allocations.extend(
record
for record in reader.get_high_watermark_allocation_records(
merge_threads=False
)
if record.tid == main_thread
)
else:
allocations.extend(
reader.get_high_watermark_allocation_records(merge_threads=True)
)
pablogsal marked this conversation as resolved.
Show resolved Hide resolved
max_memory = parse_memory_string(limit)
total_allocated_memory = sum(record.size for record in allocations)

Expand Down Expand Up @@ -225,14 +237,22 @@ 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] = []
if current_thread_only:
main_thread_id = reader.metadata.main_thread_id
allocations.extend(
record
for record in reader.get_leaked_allocation_records(merge_threads=False)
if record.tid == main_thread_id
)
else:
allocations.extend(reader.get_leaked_allocation_records(merge_threads=True))
pablogsal marked this conversation as resolved.
Show resolved Hide resolved

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