pytest-memray
Advanced tools
+13
-0
@@ -8,2 +8,15 @@ Release History | ||
| v1.4.0 (2022-12-02) | ||
| ------------------- | ||
| Features - 1.4.0 | ||
| ~~~~~~~~~~~~~~~~ | ||
| - Allow to run tests marked with memray markers without having to provide "--memray" in the command line. (:issue:`57`) | ||
| - Add two new options that allow to customize the ammount of frames in allocation tracebacks as well as including hybrid stack traces. (:issue:`58`) | ||
| Bug Fixes - 1.4.0 | ||
| ~~~~~~~~~~~~~~~~~ | ||
| - Fix pytest raising ``pytest.PytestReturnNotNoneWarning`` from test decorated with memray markers. (:issue:`60`) | ||
| v1.3.2 (2022-11-30) | ||
@@ -10,0 +23,0 @@ ------------------- |
+5
-1
| Metadata-Version: 2.1 | ||
| Name: pytest-memray | ||
| Version: 1.3.2 | ||
| Version: 1.4.0 | ||
| Summary: A simple plugin to use with pytest | ||
@@ -139,2 +139,4 @@ Project-URL: Bug Tracker, https://github.com/bloomberg/pytest-memray/issues | ||
| hex) | ||
| --stacks=STACKS - Show the N stack entries when showing tracebacks of memory allocations | ||
| --native - Show native frames when showing tracebacks of memory allocations (will be slower) | ||
@@ -146,2 +148,4 @@ ## Configuration - INI | ||
| - `hide_memray_summary(bool)` - hide the memray summary at the end of the execution | ||
| - `stacks(int)` - Show the N stack entries when showing tracebacks of memory allocations | ||
| - `native(bool)`- Show native frames when showing tracebacks of memory allocations (will be slower) | ||
@@ -148,0 +152,0 @@ ## License |
+4
-0
@@ -97,2 +97,4 @@ <img src="https://raw.githubusercontent.com/bloomberg/pytest-memray/main/docs/_static/images/logo.png" width="70%" style="display: block; margin: 0 auto" alt="logo"/> | ||
| hex) | ||
| --stacks=STACKS - Show the N stack entries when showing tracebacks of memory allocations | ||
| --native - Show native frames when showing tracebacks of memory allocations (will be slower) | ||
@@ -104,2 +106,4 @@ ## Configuration - INI | ||
| - `hide_memray_summary(bool)` - hide the memray summary at the end of the execution | ||
| - `stacks(int)` - Show the N stack entries when showing tracebacks of memory allocations | ||
| - `native(bool)`- Show native frames when showing tracebacks of memory allocations (will be slower) | ||
@@ -106,0 +110,0 @@ ## License |
@@ -1,1 +0,1 @@ | ||
| __version__ = "1.3.2" | ||
| __version__ = "1.4.0" |
@@ -5,7 +5,10 @@ from __future__ import annotations | ||
| from typing import Tuple | ||
| from typing import cast | ||
| from memray import AllocationRecord | ||
| from pytest import Config | ||
| from .utils import parse_memory_string | ||
| from .utils import sizeof_fmt | ||
| from .utils import value_or_ini | ||
@@ -22,2 +25,4 @@ PytestSection = Tuple[str, str] | ||
| allocations: list[AllocationRecord] | ||
| num_stacks: int | ||
| native_stacks: bool | ||
@@ -35,7 +40,18 @@ @property | ||
| size = record.size | ||
| stack_trace = record.stack_trace() | ||
| stack_trace = ( | ||
| record.hybrid_stack_trace() | ||
| if self.native_stacks | ||
| else record.stack_trace() | ||
| ) | ||
| if not stack_trace: | ||
| continue | ||
| (function, file, line), *_ = stack_trace | ||
| text_lines.append(f"\t- {function}:{file}:{line} -> {sizeof_fmt(size)}") | ||
| padding = " " * 4 | ||
| text_lines.append(f"{padding}- {sizeof_fmt(size)} allocated here:") | ||
| stacks_left = self.num_stacks | ||
| for function, file, line in stack_trace: | ||
| if stacks_left <= 0: | ||
| break | ||
| text_lines.append(f"{padding*2}{function}:{file}:{line}") | ||
| stacks_left -= 1 | ||
| return "memray-max-memory", "\n".join(text_lines) | ||
@@ -52,3 +68,3 @@ | ||
| def limit_memory( | ||
| limit: str, *, _allocations: list[AllocationRecord] | ||
| limit: str, *, _allocations: list[AllocationRecord], _config: Config | ||
| ) -> _MemoryInfo | None: | ||
@@ -60,3 +76,7 @@ """Limit memory used by the test.""" | ||
| return None | ||
| return _MemoryInfo(max_memory, total_allocated_memory, _allocations) | ||
| num_stacks: int = cast(int, value_or_ini(_config, "stacks")) | ||
| native_stacks: bool = cast(bool, value_or_ini(_config, "native")) | ||
| return _MemoryInfo( | ||
| max_memory, total_allocated_memory, _allocations, num_stacks, native_stacks | ||
| ) | ||
@@ -63,0 +83,0 @@ |
@@ -38,3 +38,5 @@ from __future__ import annotations | ||
| from .utils import WriteEnabledDirectoryAction | ||
| from .utils import positive_int | ||
| from .utils import sizeof_fmt | ||
| from .utils import value_or_ini | ||
@@ -126,2 +128,12 @@ MARKERS = {"limit_memory": limit_memory} | ||
| markers = { | ||
| marker.name | ||
| for marker in pyfuncitem.iter_markers() | ||
| if marker.name in MARKERS | ||
| } | ||
| if not markers and not value_or_ini(self.config, "memray"): | ||
| yield | ||
| return | ||
| def _build_bin_path() -> Path: | ||
@@ -139,8 +151,11 @@ if self._tmp_dir is None: | ||
| native: bool = bool(value_or_ini(self.config, "native")) | ||
| @functools.wraps(func) | ||
| def wrapper(*args: Any, **kwargs: Any) -> object | None: | ||
| test_result: object | Any = None | ||
| try: | ||
| result_file = _build_bin_path() | ||
| with Tracker(result_file): | ||
| result: object | None = func(*args, **kwargs) | ||
| with Tracker(result_file, native_traces=native): | ||
| test_result = func(*args, **kwargs) | ||
| try: | ||
@@ -164,3 +179,3 @@ metadata = FileReader(result_file).metadata | ||
| pyfuncitem.obj = func | ||
| return result | ||
| return test_result | ||
@@ -191,4 +206,9 @@ pyfuncitem.obj = wrapper | ||
| func = reader.get_high_watermark_allocation_records | ||
| allocations = list(func(merge_threads=True)) | ||
| res = marker_fn(*marker.args, **marker.kwargs, _allocations=allocations) | ||
| allocations = list((func(merge_threads=True))) | ||
| res = marker_fn( | ||
| *marker.args, | ||
| **marker.kwargs, | ||
| _allocations=allocations, | ||
| _config=self.config, | ||
| ) | ||
| if res: | ||
@@ -217,3 +237,5 @@ report.outcome = "failed" | ||
| ) -> None: | ||
| if value_or_ini(self.config, "hide_memray_summary"): | ||
| if value_or_ini(self.config, "hide_memray_summary") or not value_or_ini( | ||
| self.config, "memray" | ||
| ): | ||
| return | ||
@@ -318,2 +340,15 @@ | ||
| ) | ||
| group.addoption( | ||
| "--stacks", | ||
| type=positive_int, | ||
| default=1, | ||
| help="Show the N stack entries when showing tracebacks of memory allocations", | ||
| ) | ||
| group.addoption( | ||
| "--native", | ||
| action="store_true", | ||
| default=False, | ||
| help="Show native frames when showing tracebacks of memory allocations " | ||
| "(will be slower)", | ||
| ) | ||
@@ -326,2 +361,13 @@ parser.addini("memray", "Activate pytest.ini setting", type="bool") | ||
| ) | ||
| parser.addini( | ||
| "stacks", | ||
| help="Show the N stack entries when showing tracebacks of memory allocations", | ||
| type="string", | ||
| ) | ||
| parser.addini( | ||
| "native", | ||
| help="Show native frames when showing tracebacks of memory allocations " | ||
| "(will be slower)", | ||
| type="bool", | ||
| ) | ||
| help_msg = "Show the N tests that allocate most memory (N=0 for all)" | ||
@@ -331,9 +377,3 @@ parser.addini("most_allocations", help_msg) | ||
| def value_or_ini(config: Config, key: str) -> object: | ||
| return config.getvalue(key) or config.getini(key) | ||
| def pytest_configure(config: Config) -> None: | ||
| if not value_or_ini(config, "memray"): | ||
| return | ||
| pytest_memray = Manager(config) | ||
@@ -340,0 +380,0 @@ config.pluginmanager.register(pytest_memray, "memray_manager") |
| from __future__ import annotations | ||
| import argparse | ||
| import os | ||
@@ -11,3 +12,5 @@ import re | ||
| from pytest import Config | ||
| def sizeof_fmt(num: int | float, suffix: str = "B") -> str: | ||
@@ -47,2 +50,12 @@ for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: | ||
| def value_or_ini(config: Config, key: str) -> object: | ||
| value = config.getvalue(key) | ||
| if value: | ||
| return value | ||
| try: | ||
| return config.getini(key) | ||
| except (KeyError, ValueError): | ||
| return value | ||
| class WriteEnabledDirectoryAction(Action): | ||
@@ -72,2 +85,9 @@ def __call__( | ||
| def positive_int(value: str) -> int: | ||
| the_int = int(value) | ||
| if the_int <= 0: | ||
| raise argparse.ArgumentTypeError(f"{value} is an invalid positive int value") | ||
| return the_int | ||
| __all__ = [ | ||
@@ -77,2 +97,4 @@ "WriteEnabledDirectoryAction", | ||
| "sizeof_fmt", | ||
| "value_or_ini", | ||
| "positive_int", | ||
| ] |
@@ -5,5 +5,7 @@ from __future__ import annotations | ||
| from types import SimpleNamespace | ||
| from unittest.mock import ANY | ||
| from unittest.mock import patch | ||
| import pytest | ||
| from memray import Tracker | ||
| from pytest import ExitCode | ||
@@ -83,3 +85,3 @@ from pytest import Pytester | ||
| def test_limit_memory_marker_does_not_work_if_memray_inactive( | ||
| def test_limit_memory_marker_does_work_if_memray_not_passed( | ||
| pytester: Pytester, | ||
@@ -102,3 +104,3 @@ ) -> None: | ||
| assert result.ret == ExitCode.OK | ||
| assert result.ret == ExitCode.TESTS_FAILED | ||
@@ -154,2 +156,63 @@ | ||
| @pytest.mark.parametrize("num_stacks", [1, 5, 100]) | ||
| def test_memray_report_limit_number_stacks(num_stacks: int, pytester: Pytester) -> None: | ||
| pytester.makepyfile( | ||
| """ | ||
| import pytest | ||
| from memray._test import MemoryAllocator | ||
| allocator = MemoryAllocator() | ||
| def rec(n): | ||
| if n <= 1: | ||
| allocator.valloc(1024*2) | ||
| allocator.free() | ||
| return None | ||
| return rec(n - 1) | ||
| @pytest.mark.limit_memory("1kb") | ||
| def test_foo(): | ||
| rec(10) | ||
| """ | ||
| ) | ||
| result = pytester.runpytest("--memray", f"--stacks={num_stacks}") | ||
| assert result.ret == ExitCode.TESTS_FAILED | ||
| output = result.stdout.str() | ||
| assert "valloc:" in output | ||
| assert output.count("rec:") == min(num_stacks - 1, 10) | ||
| @pytest.mark.parametrize("native", [True, False]) | ||
| def test_memray_report_native(native: bool, pytester: Pytester) -> None: | ||
| pytester.makepyfile( | ||
| """ | ||
| import pytest | ||
| from memray._test import MemoryAllocator | ||
| allocator = MemoryAllocator() | ||
| @pytest.mark.limit_memory("1kb") | ||
| def test_foo(): | ||
| allocator.valloc(1024*2) | ||
| allocator.free() | ||
| """ | ||
| ) | ||
| with patch("pytest_memray.plugin.Tracker", wraps=Tracker) as mock: | ||
| result = pytester.runpytest("--memray", *(["--native"] if native else [])) | ||
| assert result.ret == ExitCode.TESTS_FAILED | ||
| output = result.stdout.str() | ||
| mock.assert_called_once_with(ANY, native_traces=native) | ||
| if native: | ||
| assert "MemoryAllocator_1" in output | ||
| else: | ||
| assert "MemoryAllocator_1" not in output | ||
| def test_memray_report(pytester: Pytester) -> None: | ||
@@ -476,1 +539,18 @@ pytester.makepyfile( | ||
| assert result.ret == outcome | ||
| def test_memray_does_not_raise_warnings(pytester: Pytester) -> None: | ||
| pytester.makepyfile( | ||
| """ | ||
| import pytest | ||
| from memray._test import MemoryAllocator | ||
| allocator = MemoryAllocator() | ||
| @pytest.mark.limit_memory("1MB") | ||
| def test_memory_alloc_fails(): | ||
| allocator.valloc(1234) | ||
| allocator.free() | ||
| """ | ||
| ) | ||
| result = pytester.runpytest("-Werror", "--memray") | ||
| assert result.ret == ExitCode.OK |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
337642
1.73%1068
14.59%