From 4f2e062a902001b8da9a42fe3528e64694a8efa3 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Mon, 5 Jun 2023 22:15:18 +0100 Subject: [PATCH 01/20] Enable build and test with python 3.12 --- .github/workflows/test.yml | 2 +- .github/workflows/wheels.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2cb0b57f..bdede3d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8c9798d0..8f22131c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -46,6 +46,7 @@ jobs: CIBW_SKIP: pp* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: auto universal2 + CIBW_PRERELEASE_PYTHONS: True - uses: actions/upload-artifact@v2 with: From 306f52f7d01b297c458df93c678fd6e616cf7daf Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Mon, 5 Jun 2023 22:30:28 +0100 Subject: [PATCH 02/20] Add build-system to pyproject.toml --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1a11b681..eab21fc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + [tool.black] line-length = 100 From 3ad31919c3148a64dd7e41daec32953159309795 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Mon, 5 Jun 2023 22:34:12 +0100 Subject: [PATCH 03/20] update setuptools before CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bdede3d1..bbf2667f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools wheel pip install -r requirements-dev.txt - name: Test with pytest From 0bf604260e3896a91c70a0598b68fb3d1f9eba94 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 22 Jul 2023 23:52:21 +0100 Subject: [PATCH 04/20] Make dev dependencies more specific --- .github/workflows/test.yml | 4 ++-- requirements-dev.txt | 20 +------------------- setup.py | 30 +++++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bbf2667f..b1afc7ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel - pip install -r requirements-dev.txt + pip install -e '.[test]' - name: Test with pytest run: | @@ -69,7 +69,7 @@ jobs: bash -exc '${{ env.py }} -m venv .env && \ source .env/bin/activate && \ pip install --upgrade pip && \ - pip install -r requirements-dev.txt && \ + pip install -e ".[test]" && \ pytest && \ pytest --only-ipython-magic && \ deactivate' diff --git a/requirements-dev.txt b/requirements-dev.txt index 7c51c5c4..b2c6c14d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,19 +1 @@ --e . -pytest -flaky -trio -click -django # used by middleware -sphinx==4.2.0 -myst-parser==0.15.1 -furo==2021.6.18b36 -sphinxcontrib-programoutput==0.17 -pytest-asyncio==0.12.0 # pinned to an older version due to an incompatibility with flaky -greenlet>=1.1.3 -nox -typing_extensions -https://github.com/nyurik/py-ascii-graph/archive/refs/heads/fix-python310.zip # used by a metric -ipython -numpy # used by an example -pre-commit # used to run pre-commit hooks -sphinx-autobuild==2021.3.14 +-e .[test,bin,jupyter,docs,examples,types] diff --git a/setup.py b/setup.py index 9b776919..31b69e60 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,35 @@ url="https://github.com/joerick/pyinstrument", keywords=["profiling", "profile", "profiler", "cpu", "time", "sampling"], install_requires=[], - extras_require={"jupyter": ["ipython"]}, + extras_require={ + "test": [ + "pytest", + "flaky", + "trio", + "greenlet>=1.1.3", + "pytest-asyncio==0.12.0", # pinned to an older version due to an incompatibility with flaky + "sphinx-autobuild==2021.3.14", + ], + "bin": [ + "click", + "nox", + ], + "jupyter": ["ipython"], + "docs": [ + "sphinx==4.2.0", + "myst-parser==0.15.1", + "furo==2021.6.18b36", + "sphinxcontrib-programoutput==0.17", + ], + "examples": [ + "numpy", + "django", + "ascii_graph @ https://github.com/nyurik/py-ascii-graph/archive/refs/heads/fix-python310.zip", + ], + "types": [ + "typing_extensions", + ], + }, include_package_data=True, python_requires=">=3.7", entry_points={"console_scripts": ["pyinstrument = pyinstrument.__main__:main"]}, From 427ee1f83f0a6ff325406da50513c3fa29779329 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sun, 23 Jul 2023 00:02:30 +0100 Subject: [PATCH 05/20] Upgrade greenlet dep for 3.12 compat --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 31b69e60..8441c85b 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ "pytest", "flaky", "trio", - "greenlet>=1.1.3", + "greenlet>=3.0.0a1", "pytest-asyncio==0.12.0", # pinned to an older version due to an incompatibility with flaky "sphinx-autobuild==2021.3.14", ], From ac68496456551fae024d7a374a206d125415cea6 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sun, 23 Jul 2023 00:04:55 +0100 Subject: [PATCH 06/20] ipython is a dev dependency --- requirements-dev.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b2c6c14d..e98522ec 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1 @@ --e .[test,bin,jupyter,docs,examples,types] +-e .[test,bin,docs,examples,types] diff --git a/setup.py b/setup.py index 8441c85b..f3b1050b 100644 --- a/setup.py +++ b/setup.py @@ -32,12 +32,12 @@ "greenlet>=3.0.0a1", "pytest-asyncio==0.12.0", # pinned to an older version due to an incompatibility with flaky "sphinx-autobuild==2021.3.14", + "ipython", ], "bin": [ "click", "nox", ], - "jupyter": ["ipython"], "docs": [ "sphinx==4.2.0", "myst-parser==0.15.1", From 04e2d62b977bda3f6fa543e1c38188d86ab19361 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Wed, 9 Aug 2023 09:17:45 +0100 Subject: [PATCH 07/20] Move trio imports inside the tests to prevent segfaults on import --- test/fake_time_util.py | 10 +++++++--- test/test_profiler_async.py | 8 +++++--- test/util.py | 1 - 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/test/fake_time_util.py b/test/fake_time_util.py index 7549be27..97dbc001 100644 --- a/test/fake_time_util.py +++ b/test/fake_time_util.py @@ -2,12 +2,14 @@ import contextlib import functools import random +from typing import TYPE_CHECKING from unittest import mock -from trio.testing import MockClock - from pyinstrument import stack_sampler +if TYPE_CHECKING: + from trio.testing import MockClock + class FakeClock: def __init__(self) -> None: @@ -71,7 +73,7 @@ def fake_time_asyncio(loop=None): class FakeClockTrio: - def __init__(self, clock: MockClock) -> None: + def __init__(self, clock: "MockClock") -> None: self.trio_clock = clock def get_time(self): @@ -83,6 +85,8 @@ def sleep(self, duration): @contextlib.contextmanager def fake_time_trio(): + from trio.testing import MockClock + trio_clock = MockClock(autojump_threshold=0) fake_clock = FakeClockTrio(trio_clock) diff --git a/test/test_profiler_async.py b/test/test_profiler_async.py index fc9b9a44..48cb793c 100644 --- a/test/test_profiler_async.py +++ b/test/test_profiler_async.py @@ -7,9 +7,6 @@ import greenlet import pytest -import trio -import trio._core._run -import trio.lowlevel from pyinstrument import processors, stack_sampler from pyinstrument.frame import AWAIT_FRAME_IDENTIFIER, OUT_OF_CONTEXT_FRAME_IDENTIFIER, Frame @@ -61,6 +58,8 @@ async def test_sleep(): def test_sleep_trio(): + import trio + async def run(): profiler = Profiler() profiler.start() @@ -102,6 +101,8 @@ async def async_wait(sync_time, async_time, profile=False, engine="asyncio"): if engine == "asyncio": await asyncio.sleep(async_time) else: + import trio + await trio.sleep(async_time) time.sleep(sync_time / 2) @@ -124,6 +125,7 @@ async def async_wait(sync_time, async_time, profile=False, engine="asyncio"): profiler_session = profile_task.result() elif engine == "trio": + import trio async def async_wait_and_capture(**kwargs): nonlocal profiler_session diff --git a/test/util.py b/test/util.py index 8bcbbf43..210ffb6f 100644 --- a/test/util.py +++ b/test/util.py @@ -3,7 +3,6 @@ import time from typing import Generator, Generic, Iterable, Iterator, NoReturn, Optional, TypeVar -import trio from flaky import flaky from pyinstrument.frame import SYNTHETIC_LEAF_IDENTIFIERS, Frame From d1008e2eca604606cdd9e6a33c00d885ce82f91a Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 10 Aug 2023 20:00:39 +0100 Subject: [PATCH 08/20] Remove unnecessary trio import: --- test/test_profiler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_profiler.py b/test/test_profiler.py index e21d744b..7d5ce70e 100644 --- a/test/test_profiler.py +++ b/test/test_profiler.py @@ -7,7 +7,6 @@ from typing import Generator, Optional import pytest -import trio from pyinstrument import Profiler, renderers from pyinstrument.frame import Frame From 0fae211754a8a3326f159d4bf091e5199be317de Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 10 Aug 2023 20:12:16 +0100 Subject: [PATCH 09/20] Stop windows ignoring test failures? --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1afc7ed..0c38b2fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,8 +43,7 @@ jobs: - name: Test with pytest run: | - pytest - pytest --only-ipython-magic + pytest && pytest --only-ipython-magic test-aarch64: name: "test (aarch64, ${{ matrix.pyver }})" From 5c4542b3e5ec2c141cea32615b5779685ec7f129 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Thu, 10 Aug 2023 21:00:29 +0100 Subject: [PATCH 10/20] Fix test on windows --- test/test_processors.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/test_processors.py b/test/test_processors.py index b3be2a14..4b0c30bd 100644 --- a/test/test_processors.py +++ b/test/test_processors.py @@ -1,3 +1,4 @@ +import os import sys from test.util import calculate_frame_tree_times @@ -21,6 +22,20 @@ def self_time_frame(time): return Frame(SELF_TIME_FRAME_IDENTIFIER, time=time) +def fixup_windows_paths(frame: Frame): + """ + Deeply fixes windows paths within a frame tree. These tests are written with forward-slashes, but windows uses backslashes + """ + identifier_parts = frame._identifier_parts + if len(identifier_parts) > 1: + identifier_parts[1] = os.path.normpath(identifier_parts[1]) + frame._identifier_parts = identifier_parts + frame.identifier = "\x00".join(identifier_parts) + + for child in frame.children: + fixup_windows_paths(child) + + def test_frame_passthrough_none(): for processor in ALL_PROCESSORS: assert processor(None, options={}) is None @@ -296,7 +311,8 @@ def test_remove_unnecessary_self_time_nodes(): assert strip_newlines_frame.time == 0.2 -def test_group_library_frames_processor(): +def test_group_library_frames_processor(monkeypatch): + monkeypatch.syspath_prepend("env/lib/python3.6") frame = Frame( identifier_or_frame_info="\x00cibuildwheel/__init__.py\x0012", children=[ @@ -330,6 +346,10 @@ def test_group_library_frames_processor(): ), ], ) + + if sys.platform.startswith("win"): + fixup_windows_paths(frame) + calculate_frame_tree_times(frame) frame.self_check() @@ -355,7 +375,4 @@ def test_group_library_frames_processor(): assert group_root.children[0].children[0] in group.exit_frames assert group_root.children[0].children[0].children[0] not in group.frames - old_sys_path = sys.path[:] - sys.path.append("env/lib/python3.6") assert group.libraries == ["django"] - sys.path[:] = old_sys_path From be1750e44ae0cbfd99f3a99aaaac01174e9277d2 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 15:19:43 +0100 Subject: [PATCH 11/20] Add test that fails on windows for binary data roundtripping --- test/test_pstats_renderer.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_pstats_renderer.py b/test/test_pstats_renderer.py index f4466792..31fb2a5c 100644 --- a/test/test_pstats_renderer.py +++ b/test/test_pstats_renderer.py @@ -1,3 +1,5 @@ +import os +from pathlib import Path import time from pstats import Stats from test.fake_time_util import fake_time @@ -82,3 +84,17 @@ def test_pstats_renderer(profiler_session, tmp_path): e_val = stats.stats[e_key] e_cumtime = e_val[3] assert e_cumtime == pytest.approx(2) + + +def test_round_trip_encoding_of_binary_data(tmp_path: Path): + # as used by the pstats renderer + data_blob = os.urandom(1024) + file = tmp_path / 'file.dat' + + data_blob_string = data_blob.decode(encoding='utf-8', errors="surrogateescape") + + with open(file, mode='w', encoding='utf-8', errors='surrogateescape') as f: + f.write(data_blob_string) + + assert data_blob == data_blob_string.encode(encoding='utf-8', errors="surrogateescape") + assert data_blob == file.read_bytes() From a74a1f448dd613695dc0aabbcb238896c7b6f474 Mon Sep 17 00:00:00 2001 From: pre-commit Date: Sat, 12 Aug 2023 14:21:38 +0000 Subject: [PATCH 12/20] pre-commit fixes --- test/test_pstats_renderer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_pstats_renderer.py b/test/test_pstats_renderer.py index 31fb2a5c..c724e37f 100644 --- a/test/test_pstats_renderer.py +++ b/test/test_pstats_renderer.py @@ -1,6 +1,6 @@ import os -from pathlib import Path import time +from pathlib import Path from pstats import Stats from test.fake_time_util import fake_time from typing import Any @@ -89,12 +89,12 @@ def test_pstats_renderer(profiler_session, tmp_path): def test_round_trip_encoding_of_binary_data(tmp_path: Path): # as used by the pstats renderer data_blob = os.urandom(1024) - file = tmp_path / 'file.dat' + file = tmp_path / "file.dat" - data_blob_string = data_blob.decode(encoding='utf-8', errors="surrogateescape") + data_blob_string = data_blob.decode(encoding="utf-8", errors="surrogateescape") - with open(file, mode='w', encoding='utf-8', errors='surrogateescape') as f: + with open(file, mode="w", encoding="utf-8", errors="surrogateescape") as f: f.write(data_blob_string) - assert data_blob == data_blob_string.encode(encoding='utf-8', errors="surrogateescape") + assert data_blob == data_blob_string.encode(encoding="utf-8", errors="surrogateescape") assert data_blob == file.read_bytes() From e1abd554bae6bd376e6b5188ab56803da7a95f2c Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:10:24 +0100 Subject: [PATCH 13/20] Add a test for binary output at the command line --- test/test_cmdline.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/test_cmdline.py b/test/test_cmdline.py index 8c5765eb..3b6f9eae 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -221,3 +221,27 @@ def test_invocation_machinery_is_trimmed(self, pyinstrument_invocation, tmp_path assert function_name == "" assert "busy_wait.py" in location + + def test_binary_output(self, pyinstrument_invocation, tmp_path: Path): + busy_wait_py = tmp_path / "busy_wait.py" + busy_wait_py.write_text(BUSY_WAIT_SCRIPT) + + output_file = tmp_path / "output.pstats" + + subprocess.check_call( + [ + *pyinstrument_invocation, + "--renderer=pstats", + f"--outfile={output_file}", + str(busy_wait_py), + ], + universal_newlines=True, + ) + + assert output_file.exists() + + # check it can be loaded + import pstats + + stats = pstats.Stats(str(output_file)) + assert stats From 4017a6649a38d7b4471f69a2b5d772b6d91e88c7 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:24:40 +0100 Subject: [PATCH 14/20] Fix problem with binary output on windows --- pyinstrument/__main__.py | 41 +++++++++++++++++++++++------------- test/test_pstats_renderer.py | 6 +++--- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/pyinstrument/__main__.py b/pyinstrument/__main__.py index 9c31eefa..4f758bf3 100644 --- a/pyinstrument/__main__.py +++ b/pyinstrument/__main__.py @@ -259,10 +259,18 @@ def dash_m_callback(option: str, opt: str, value: str, parser: optparse.OptionPa if options.from_path and sys.platform == "win32": parser.error("--from-path is not supported on Windows") + renderer_class = get_renderer_class(options) + # open the output file if options.outfile: - f = open(options.outfile, "w", encoding="utf-8", errors="surrogateescape") + f = open( + options.outfile, + "w", + encoding="utf-8", + errors="surrogateescape", + newline="" if renderer_class.output_is_binary else None, + ) should_close_f_after_writing = True else: f = sys.stdout @@ -271,7 +279,7 @@ def dash_m_callback(option: str, opt: str, value: str, parser: optparse.OptionPa # create the renderer try: - renderer = create_renderer(options, output_file=f) + renderer = create_renderer(renderer_class, options, output_file=f) except OptionsParseError as e: parser.error(e.args[0]) exit(1) @@ -338,7 +346,7 @@ def dash_m_callback(option: str, opt: str, value: str, parser: optparse.OptionPa if should_close_f_after_writing: f.close() - if options.renderer == "text": + if isinstance(renderer, renderers.ConsoleRenderer) and not options.outfile: _, report_identifier = save_report_to_temp_storage(session) print("To view this report with different options, run:") print(" pyinstrument --load-prev %s [options]" % report_identifier) @@ -421,17 +429,9 @@ class OptionsParseError(Exception): pass -def create_renderer(options: CommandLineOptions, output_file: TextIO) -> renderers.Renderer: - if options.output_html: - options.renderer = "html" - - if options.renderer is None and options.outfile: - options.renderer = guess_renderer_from_outfile(options.outfile) - - if options.renderer is None: - options.renderer = "text" - - renderer_class = get_renderer_class(options.renderer) +def create_renderer( + renderer_class: type[renderers.Renderer], options: CommandLineOptions, output_file: TextIO +) -> renderers.Renderer: render_options = compute_render_options( options, renderer_class=renderer_class, output_file=output_file ) @@ -445,7 +445,18 @@ def create_renderer(options: CommandLineOptions, output_file: TextIO) -> rendere ) -def get_renderer_class(renderer: str) -> type[renderers.Renderer]: +def get_renderer_class(options: CommandLineOptions) -> type[renderers.Renderer]: + renderer = options.renderer + + if options.output_html: + renderer = "html" + + if renderer is None and options.outfile: + renderer = guess_renderer_from_outfile(options.outfile) + + if renderer is None: + renderer = "text" + if renderer == "text": return renderers.ConsoleRenderer elif renderer == "html": diff --git a/test/test_pstats_renderer.py b/test/test_pstats_renderer.py index c724e37f..51451dae 100644 --- a/test/test_pstats_renderer.py +++ b/test/test_pstats_renderer.py @@ -46,9 +46,9 @@ def profiler_session(): def test_pstats_renderer(profiler_session, tmp_path): fname = tmp_path / "test.pstats" - pstats = PstatsRenderer().render(profiler_session) - with open(fname, "w", encoding="utf-8", errors="surrogateescape") as fid: - fid.write(pstats) + pstats_data = PstatsRenderer().render(profiler_session) + with open(fname, "wb") as fid: + fid.write(pstats_data.encode(encoding="utf-8", errors="surrogateescape")) stats: Any = Stats(str(fname)) # Sanity check assert stats.total_tt > 0 From 8ee06a79544baa308d7a1a4d37bb8a882cd07e4f Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:24:59 +0100 Subject: [PATCH 15/20] Make tests quicker with a shorter busywait --- test/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util.py b/test/util.py index 210ffb6f..61dd22bd 100644 --- a/test/util.py +++ b/test/util.py @@ -77,7 +77,7 @@ def busy_wait(duration): def main(): print('sys.argv: ', sys.argv) - busy_wait(0.25) + busy_wait(0.1) if __name__ == '__main__': From 1082e46fe82ad82a78b629a057dc41142c0a3f16 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:48:55 +0100 Subject: [PATCH 16/20] Add test for show_all and timeline options --- test/test_renderers.py | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 test/test_renderers.py diff --git a/test/test_renderers.py b/test/test_renderers.py new file mode 100644 index 00000000..9f18f0b0 --- /dev/null +++ b/test/test_renderers.py @@ -0,0 +1,87 @@ +# some tests for the renderer classes + +from __future__ import annotations + +import time + +import pytest + +from pyinstrument import renderers +from pyinstrument.profiler import Profiler + +from .fake_time_util import fake_time + +# utils + +frame_renderer_classes: list[type[renderers.FrameRenderer]] = [ + renderers.ConsoleRenderer, + renderers.HTMLRenderer, + renderers.JSONRenderer, + renderers.PstatsRenderer, + renderers.SpeedscopeRenderer, +] + +parametrize_frame_renderer_class = pytest.mark.parametrize( + "frame_renderer_class", frame_renderer_classes, ids=lambda c: c.__name__ +) + +# fixtures + + +def a(): + b() + c() + + +def b(): + d() + + +def c(): + d() + + +def d(): + e() + + +def e(): + time.sleep(1) + + +@pytest.fixture(scope="module") +def profiler_session(): + with fake_time(): + profiler = Profiler() + profiler.start() + + a() + + profiler.stop() + return profiler.last_session + + +# tests + + +@parametrize_frame_renderer_class +def test_empty_profile(frame_renderer_class: type[renderers.FrameRenderer]): + with Profiler() as profiler: + pass + profiler.output(renderer=frame_renderer_class()) + + +@parametrize_frame_renderer_class +def test_timeline_doesnt_crash( + profiler_session, frame_renderer_class: type[renderers.FrameRenderer] +): + renderer = frame_renderer_class(timeline=True) + renderer.render(profiler_session) + + +@parametrize_frame_renderer_class +def test_show_all_doesnt_crash( + profiler_session, frame_renderer_class: type[renderers.FrameRenderer] +): + renderer = frame_renderer_class(show_all=True) + renderer.render(profiler_session) From d073e5448341f979548439f9df127ced62fc59d6 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:50:06 +0100 Subject: [PATCH 17/20] Fix bug with timeline/show_all args Fix #262 --- pyinstrument/renderers/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyinstrument/renderers/base.py b/pyinstrument/renderers/base.py index 90daca53..f14a7493 100644 --- a/pyinstrument/renderers/base.py +++ b/pyinstrument/renderers/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib from typing import Any, List from pyinstrument import processors @@ -93,9 +94,13 @@ def __init__( # processors.remove_first_pyinstrument_frames_processor, # (still hide the outer pyinstrument calling frames) ): - self.processors.remove(p) + with contextlib.suppress(ValueError): + # don't care if the processor isn't in the list + self.processors.remove(p) + if timeline: - self.processors.remove(processors.aggregate_repeated_calls) + with contextlib.suppress(ValueError): + self.processors.remove(processors.aggregate_repeated_calls) def default_processors(self) -> ProcessorList: """ From c7e0d9b88bbc9192f7c1565a3198929abb877c2a Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 16:53:39 +0100 Subject: [PATCH 18/20] fix test --- test/test_pstats_renderer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_pstats_renderer.py b/test/test_pstats_renderer.py index 51451dae..909d5c0b 100644 --- a/test/test_pstats_renderer.py +++ b/test/test_pstats_renderer.py @@ -93,7 +93,8 @@ def test_round_trip_encoding_of_binary_data(tmp_path: Path): data_blob_string = data_blob.decode(encoding="utf-8", errors="surrogateescape") - with open(file, mode="w", encoding="utf-8", errors="surrogateescape") as f: + # newline='' is required to prevent the default newline translation + with open(file, mode="w", encoding="utf-8", errors="surrogateescape", newline="") as f: f.write(data_blob_string) assert data_blob == data_blob_string.encode(encoding="utf-8", errors="surrogateescape") From 93a121ed4f37ad60887470767d8f39fdcbeb9951 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sat, 12 Aug 2023 17:10:24 +0100 Subject: [PATCH 19/20] Update cibuildwheel --- .github/workflows/wheels.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 3807201d..d9b0af98 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -41,12 +41,11 @@ jobs: python-version: '3.8' - name: Build wheels - uses: joerick/cibuildwheel@v2.14.1 + uses: joerick/cibuildwheel@v2.15.0 env: CIBW_SKIP: pp* CIBW_ARCHS: ${{matrix.archs}} CIBW_ARCHS_MACOS: auto universal2 - CIBW_PRERELEASE_PYTHONS: True - uses: actions/upload-artifact@v2 with: From 9bcdfae983f4d5d968d0764aa519c5692c6a5cf5 Mon Sep 17 00:00:00 2001 From: Joe Rickerby Date: Sun, 13 Aug 2023 19:30:58 +0100 Subject: [PATCH 20/20] disable fail-fast on the tests --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c38b2fe..e65311b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"] + fail-fast: false steps: - uses: actions/checkout@v2