pytest-timing 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. pytest_timing-0.1.0/.github/workflows/ci.yml +60 -0
  2. pytest_timing-0.1.0/.github/workflows/release.yml +17 -0
  3. pytest_timing-0.1.0/.gitignore +12 -0
  4. pytest_timing-0.1.0/CHANGELOG.md +10 -0
  5. pytest_timing-0.1.0/LICENSE +21 -0
  6. pytest_timing-0.1.0/PKG-INFO +161 -0
  7. pytest_timing-0.1.0/README.md +135 -0
  8. pytest_timing-0.1.0/docs/report.png +0 -0
  9. pytest_timing-0.1.0/examples/test_demo.py +41 -0
  10. pytest_timing-0.1.0/pyproject.toml +67 -0
  11. pytest_timing-0.1.0/src/pytest_timing/__init__.py +5 -0
  12. pytest_timing-0.1.0/src/pytest_timing/cli.py +150 -0
  13. pytest_timing-0.1.0/src/pytest_timing/collector.py +210 -0
  14. pytest_timing-0.1.0/src/pytest_timing/model.py +522 -0
  15. pytest_timing-0.1.0/src/pytest_timing/outputs.py +54 -0
  16. pytest_timing-0.1.0/src/pytest_timing/plugin.py +382 -0
  17. pytest_timing-0.1.0/src/pytest_timing/render/__init__.py +1 -0
  18. pytest_timing-0.1.0/src/pytest_timing/render/ascii.py +283 -0
  19. pytest_timing-0.1.0/src/pytest_timing/render/html.py +32 -0
  20. pytest_timing-0.1.0/src/pytest_timing/render/trace.py +126 -0
  21. pytest_timing-0.1.0/src/pytest_timing/static/__init__.py +0 -0
  22. pytest_timing-0.1.0/src/pytest_timing/static/report.html +653 -0
  23. pytest_timing-0.1.0/src/pytest_timing/xdist_compat.py +158 -0
  24. pytest_timing-0.1.0/tests/conftest.py +170 -0
  25. pytest_timing-0.1.0/tests/fixtures/legacy_complete_false.json +90 -0
  26. pytest_timing-0.1.0/tests/fixtures/legacy_complete_true.json +90 -0
  27. pytest_timing-0.1.0/tests/fixtures/legacy_no_flag.json +89 -0
  28. pytest_timing-0.1.0/tests/test_ascii.py +174 -0
  29. pytest_timing-0.1.0/tests/test_cli.py +182 -0
  30. pytest_timing-0.1.0/tests/test_collector.py +389 -0
  31. pytest_timing-0.1.0/tests/test_html.py +86 -0
  32. pytest_timing-0.1.0/tests/test_plugin.py +629 -0
  33. pytest_timing-0.1.0/tests/test_trace.py +27 -0
  34. pytest_timing-0.1.0/uv.lock +623 -0
@@ -0,0 +1,60 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: py${{ matrix.python }} / pytest ${{ matrix.pytest }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest]
16
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
17
+ pytest: ["latest"]
18
+ include:
19
+ - { os: ubuntu-latest, python: "3.10", pytest: "7.3" }
20
+ - { os: ubuntu-latest, python: "3.11", pytest: "7.4" }
21
+ - { os: ubuntu-latest, python: "3.12", pytest: "8.0" }
22
+ - { os: windows-latest, python: "3.12", pytest: "latest" }
23
+ - { os: macos-latest, python: "3.13", pytest: "latest" }
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: astral-sh/setup-uv@v5
27
+ with:
28
+ python-version: ${{ matrix.python }}
29
+ - name: Install
30
+ run: |
31
+ uv sync --no-dev
32
+ uv pip install pytest-xdist pytest-rerunfailures hypothesis
33
+ if [ "${{ matrix.pytest }}" != "latest" ]; then uv pip install "pytest~=${{ matrix.pytest }}.0"; fi
34
+ shell: bash
35
+ - name: Test
36
+ run: uv run --no-sync pytest -v -p no:cacheprovider
37
+
38
+ no-xdist:
39
+ name: without pytest-xdist
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ - uses: astral-sh/setup-uv@v5
44
+ with:
45
+ python-version: "3.12"
46
+ - run: uv sync --no-dev
47
+ - run: uv pip install hypothesis
48
+ - run: uv run --no-sync pytest -v -p no:cacheprovider
49
+
50
+ lint:
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - uses: actions/checkout@v4
54
+ - uses: astral-sh/setup-uv@v5
55
+ with:
56
+ python-version: "3.12"
57
+ - run: uv sync
58
+ - run: uv run ruff check src tests
59
+ - run: uv run ruff format --check src tests
60
+ - run: uv run mypy
@@ -0,0 +1,17 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ - run: uv build
17
+ - run: uv publish
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ pytest-timing.html
11
+ pytest-timing.json
12
+ pytest-timing.trace.json
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Output options are boolean flags (`--timing-json`) with separate `--timing-json-file PATH`
6
+ options, so an output flag can never swallow a test path.
7
+ - Runs record their termination (`finished`, `interrupted`, `aborted`, ...) explicitly.
8
+ - Initial release: controller-side timing capture with and without pytest-xdist,
9
+ ASCII Gantt chart in the terminal summary, self-contained HTML report, JSON output,
10
+ Chrome trace output, and a `pytest-timing` CLI with `render` and `merge` commands.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026-present Messense Lv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.5
2
+ Name: pytest-timing
3
+ Version: 0.1.0
4
+ Summary: Record test timings under pytest-xdist and render cargo-style timing reports (ASCII Gantt and HTML).
5
+ Project-URL: Homepage, https://github.com/messense/pytest-timing
6
+ Author-email: messense <messense@icloud.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: gantt,profiling,pytest,timing,xdist
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: pytest>=7.3
23
+ Provides-Extra: xdist
24
+ Requires-Dist: pytest-xdist>=3.0; extra == 'xdist'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # pytest-timing
28
+
29
+ Record when every test ran, on which [pytest-xdist](https://github.com/pytest-dev/pytest-xdist)
30
+ worker, and for how long, then render the run as a timing report in the spirit of
31
+ `cargo build --timings`: an ASCII Gantt chart in the terminal and a self-contained
32
+ HTML report with worker lanes, a concurrency graph and a sortable table.
33
+
34
+ ```
35
+ pytest -n 4 --timing --timing-html
36
+ ```
37
+
38
+ ```
39
+ ================================ timing report =================================
40
+ pytest-timing: 129 tests (1 error, 1 failed), 4 workers, wall 1.37s, busy 95.4%
41
+ worker |-----------|-----------|------------|-----------|-----------|-------- busy%
42
+ gw0 ░░░░░░ ▄███████████████▄███████████████████████████████████████████ 93.6%
43
+ gw1 ░░░░░░░ ▄██████████████████████████████████████████████████X 94.1%
44
+ gw2 ░░░░░░░░▒▄███████████████████████████████████████████████████▄ 96.2%
45
+ gw3 ░░░░░░░░░▄████████████████████████████████▄███████████XXXXX 98.2%
46
+ 0s 0.20s 0.40s 0.60s 0.80s 1.00s 1.37s
47
+ legend: ░ boot ▒ collect █ tests ▄ <50% busy X failure
48
+
49
+ slowest 3 tests (setup ░ / call █ / teardown ▒):
50
+ gw0 ████████████████████▒ 0.36s
51
+ test_big.py::test_slow[5]
52
+ gw2 ████████████████▒ 0.30s
53
+ test_big.py::test_slow[4]
54
+ gw1 ░░░░░░░░░░░░░▒ 0.21s
55
+ test_big.py::test_many[8]
56
+ HTML report written to pytest-timing.html
57
+ ```
58
+
59
+ Works with and without `-n`. Without xdist the run is a single `main` lane.
60
+
61
+ Each lane shows worker boot, collection, tests (`▄` marks a column that is under half busy,
62
+ so idle gaps stand out) and failures. The slowest tests are drawn on the same axis with
63
+ their setup / call / teardown split; note how the module-scoped fixture above lands in the
64
+ setup phase of the first test on each worker.
65
+
66
+ The HTML report has the same data with a zoomable lane Gantt, a concurrency graph, filters,
67
+ hover details, and a sortable table:
68
+
69
+ ![HTML report](docs/report.png)
70
+
71
+ ## Install
72
+
73
+ ```
74
+ pip install pytest-timing # plugin only
75
+ pip install "pytest-timing[xdist]" # with pytest-xdist
76
+ ```
77
+
78
+ Python 3.10+, pytest 7.3+ (the version that added wall-clock `start`/`stop` to test reports).
79
+
80
+ ## Usage
81
+
82
+ | Option | Effect |
83
+ |---|---|
84
+ | `--timing` | Record timings and print the ASCII chart in the terminal summary. |
85
+ | `--timing-html` | Write a self-contained HTML report to `pytest-timing.html`. |
86
+ | `--timing-json` | Write the recorded run to `pytest-timing.json`. |
87
+ | `--timing-trace` | Write a Chrome trace file for [Perfetto](https://ui.perfetto.dev) to `pytest-timing.trace.json`. |
88
+ | `--timing-html-file PATH`, `--timing-json-file PATH`, `--timing-trace-file PATH` | Same, to an explicit path. |
89
+ | `--timing-top=N` | Rows in the slowest-tests section (default 10, `0` hides it). |
90
+ | `--timing-min=SECONDS` | Hide tests shorter than this from the slowest-tests section. |
91
+ | `--timing-ascii-style=unicode\|ascii` | Chart glyphs. |
92
+ | `--timing-width=N` | Override the terminal width for the chart. |
93
+
94
+ Any output option implies `--timing`. The output flags are plain booleans and the `-file`
95
+ options always take a path, so `pytest --timing-json test_x.py` runs exactly `test_x.py`.
96
+
97
+ The same settings are accepted as ini keys (`timing`, `timing_html`, `timing_json`,
98
+ `timing_trace` as paths or `true`, plus `timing_top`, `timing_min`, `timing_ascii_style`)
99
+ and environment variables (`PYTEST_TIMING=1`, `PYTEST_TIMING_HTML=path`, ...), so CI can
100
+ enable it without touching the command line.
101
+
102
+ Every JSON run records how the session ended (`finished`, `collect_only`, `interrupted`,
103
+ `aborted`, `internal_error`) with the reason pytest gave, and `complete` is derived from that.
104
+
105
+ ### View the trace
106
+
107
+ `--timing-trace` writes a Chrome Trace Event file. Open it in
108
+ [Perfetto UI](https://ui.perfetto.dev) with "Open trace file", or in `chrome://tracing`.
109
+ Each worker is a track, every test is a bar, and the setup / call / teardown phases nest
110
+ underneath it. Zoom with W/A/S/D and select a range to aggregate durations.
111
+
112
+ ### Re-render or merge saved runs
113
+
114
+ ```
115
+ pytest-timing render pytest-timing.json --html report.html --ascii
116
+ pytest-timing merge shard1.json shard2.json -o all.json
117
+ ```
118
+
119
+ `merge` places several runs (for example CI shards) on one shared time axis using their
120
+ absolute start times.
121
+
122
+ ## How it works
123
+
124
+ Since pytest 7.3 every `TestReport` carries wall-clock `start` and `stop` timestamps. xdist
125
+ serialises those to the controller unchanged and attaches the worker to the report, so the
126
+ plugin only needs controller-side hooks: `pytest_runtest_logreport` for the setup / call /
127
+ teardown phases, plus xdist's node-ready, collection-finished and node-down hooks for the
128
+ worker lifecycle. Nothing runs inside the workers and nothing touches the execnet channel.
129
+
130
+ Each lane in the report shows boot (worker start-up until it is ready), collection, tests,
131
+ idle gaps and the point the worker shut down. Session-scoped fixture setup is attributed to
132
+ the first test's setup phase and its teardown to the last test's teardown phase, exactly as
133
+ pytest reports it.
134
+
135
+ ## Overhead
136
+
137
+ Nothing runs inside the workers: the plugin only listens to the reports xdist already
138
+ sends to the controller, and each of the three phase reports per test costs a few
139
+ microseconds of bookkeeping. Rendering happens once, at the end of the session.
140
+
141
+ Measured on 5,000 trivial tests (a worst case, since the per-test cost is fixed while the
142
+ tests themselves take almost nothing), best of five runs:
143
+
144
+ | Configuration | Wall time | Overhead |
145
+ |---|---|---|
146
+ | single process, plugin disabled | 1.29 s | |
147
+ | single process, `--timing` | 1.38 s | +0.09 s |
148
+ | single process, `--timing` plus JSON, HTML and trace files | 1.45 s | +0.16 s |
149
+ | `-n 4`, plugin disabled | 1.18 s | |
150
+ | `-n 4`, `--timing` | 1.25 s | +0.07 s |
151
+ | `-n 4`, `--timing` plus all three files | 1.32 s | +0.14 s |
152
+
153
+ That is under 20 microseconds per test for recording, plus a fixed serialisation cost per
154
+ output file of roughly 10 ms per thousand tests. Output size is about 250 bytes per test
155
+ for the JSON and HTML files and 600 bytes for the trace. Memory held during the run is on
156
+ the same order as the JSON. The plugin registers nothing at all unless one of its options
157
+ is enabled.
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,135 @@
1
+ # pytest-timing
2
+
3
+ Record when every test ran, on which [pytest-xdist](https://github.com/pytest-dev/pytest-xdist)
4
+ worker, and for how long, then render the run as a timing report in the spirit of
5
+ `cargo build --timings`: an ASCII Gantt chart in the terminal and a self-contained
6
+ HTML report with worker lanes, a concurrency graph and a sortable table.
7
+
8
+ ```
9
+ pytest -n 4 --timing --timing-html
10
+ ```
11
+
12
+ ```
13
+ ================================ timing report =================================
14
+ pytest-timing: 129 tests (1 error, 1 failed), 4 workers, wall 1.37s, busy 95.4%
15
+ worker |-----------|-----------|------------|-----------|-----------|-------- busy%
16
+ gw0 ░░░░░░ ▄███████████████▄███████████████████████████████████████████ 93.6%
17
+ gw1 ░░░░░░░ ▄██████████████████████████████████████████████████X 94.1%
18
+ gw2 ░░░░░░░░▒▄███████████████████████████████████████████████████▄ 96.2%
19
+ gw3 ░░░░░░░░░▄████████████████████████████████▄███████████XXXXX 98.2%
20
+ 0s 0.20s 0.40s 0.60s 0.80s 1.00s 1.37s
21
+ legend: ░ boot ▒ collect █ tests ▄ <50% busy X failure
22
+
23
+ slowest 3 tests (setup ░ / call █ / teardown ▒):
24
+ gw0 ████████████████████▒ 0.36s
25
+ test_big.py::test_slow[5]
26
+ gw2 ████████████████▒ 0.30s
27
+ test_big.py::test_slow[4]
28
+ gw1 ░░░░░░░░░░░░░▒ 0.21s
29
+ test_big.py::test_many[8]
30
+ HTML report written to pytest-timing.html
31
+ ```
32
+
33
+ Works with and without `-n`. Without xdist the run is a single `main` lane.
34
+
35
+ Each lane shows worker boot, collection, tests (`▄` marks a column that is under half busy,
36
+ so idle gaps stand out) and failures. The slowest tests are drawn on the same axis with
37
+ their setup / call / teardown split; note how the module-scoped fixture above lands in the
38
+ setup phase of the first test on each worker.
39
+
40
+ The HTML report has the same data with a zoomable lane Gantt, a concurrency graph, filters,
41
+ hover details, and a sortable table:
42
+
43
+ ![HTML report](docs/report.png)
44
+
45
+ ## Install
46
+
47
+ ```
48
+ pip install pytest-timing # plugin only
49
+ pip install "pytest-timing[xdist]" # with pytest-xdist
50
+ ```
51
+
52
+ Python 3.10+, pytest 7.3+ (the version that added wall-clock `start`/`stop` to test reports).
53
+
54
+ ## Usage
55
+
56
+ | Option | Effect |
57
+ |---|---|
58
+ | `--timing` | Record timings and print the ASCII chart in the terminal summary. |
59
+ | `--timing-html` | Write a self-contained HTML report to `pytest-timing.html`. |
60
+ | `--timing-json` | Write the recorded run to `pytest-timing.json`. |
61
+ | `--timing-trace` | Write a Chrome trace file for [Perfetto](https://ui.perfetto.dev) to `pytest-timing.trace.json`. |
62
+ | `--timing-html-file PATH`, `--timing-json-file PATH`, `--timing-trace-file PATH` | Same, to an explicit path. |
63
+ | `--timing-top=N` | Rows in the slowest-tests section (default 10, `0` hides it). |
64
+ | `--timing-min=SECONDS` | Hide tests shorter than this from the slowest-tests section. |
65
+ | `--timing-ascii-style=unicode\|ascii` | Chart glyphs. |
66
+ | `--timing-width=N` | Override the terminal width for the chart. |
67
+
68
+ Any output option implies `--timing`. The output flags are plain booleans and the `-file`
69
+ options always take a path, so `pytest --timing-json test_x.py` runs exactly `test_x.py`.
70
+
71
+ The same settings are accepted as ini keys (`timing`, `timing_html`, `timing_json`,
72
+ `timing_trace` as paths or `true`, plus `timing_top`, `timing_min`, `timing_ascii_style`)
73
+ and environment variables (`PYTEST_TIMING=1`, `PYTEST_TIMING_HTML=path`, ...), so CI can
74
+ enable it without touching the command line.
75
+
76
+ Every JSON run records how the session ended (`finished`, `collect_only`, `interrupted`,
77
+ `aborted`, `internal_error`) with the reason pytest gave, and `complete` is derived from that.
78
+
79
+ ### View the trace
80
+
81
+ `--timing-trace` writes a Chrome Trace Event file. Open it in
82
+ [Perfetto UI](https://ui.perfetto.dev) with "Open trace file", or in `chrome://tracing`.
83
+ Each worker is a track, every test is a bar, and the setup / call / teardown phases nest
84
+ underneath it. Zoom with W/A/S/D and select a range to aggregate durations.
85
+
86
+ ### Re-render or merge saved runs
87
+
88
+ ```
89
+ pytest-timing render pytest-timing.json --html report.html --ascii
90
+ pytest-timing merge shard1.json shard2.json -o all.json
91
+ ```
92
+
93
+ `merge` places several runs (for example CI shards) on one shared time axis using their
94
+ absolute start times.
95
+
96
+ ## How it works
97
+
98
+ Since pytest 7.3 every `TestReport` carries wall-clock `start` and `stop` timestamps. xdist
99
+ serialises those to the controller unchanged and attaches the worker to the report, so the
100
+ plugin only needs controller-side hooks: `pytest_runtest_logreport` for the setup / call /
101
+ teardown phases, plus xdist's node-ready, collection-finished and node-down hooks for the
102
+ worker lifecycle. Nothing runs inside the workers and nothing touches the execnet channel.
103
+
104
+ Each lane in the report shows boot (worker start-up until it is ready), collection, tests,
105
+ idle gaps and the point the worker shut down. Session-scoped fixture setup is attributed to
106
+ the first test's setup phase and its teardown to the last test's teardown phase, exactly as
107
+ pytest reports it.
108
+
109
+ ## Overhead
110
+
111
+ Nothing runs inside the workers: the plugin only listens to the reports xdist already
112
+ sends to the controller, and each of the three phase reports per test costs a few
113
+ microseconds of bookkeeping. Rendering happens once, at the end of the session.
114
+
115
+ Measured on 5,000 trivial tests (a worst case, since the per-test cost is fixed while the
116
+ tests themselves take almost nothing), best of five runs:
117
+
118
+ | Configuration | Wall time | Overhead |
119
+ |---|---|---|
120
+ | single process, plugin disabled | 1.29 s | |
121
+ | single process, `--timing` | 1.38 s | +0.09 s |
122
+ | single process, `--timing` plus JSON, HTML and trace files | 1.45 s | +0.16 s |
123
+ | `-n 4`, plugin disabled | 1.18 s | |
124
+ | `-n 4`, `--timing` | 1.25 s | +0.07 s |
125
+ | `-n 4`, `--timing` plus all three files | 1.32 s | +0.14 s |
126
+
127
+ That is under 20 microseconds per test for recording, plus a fixed serialisation cost per
128
+ output file of roughly 10 ms per thousand tests. Output size is about 250 bytes per test
129
+ for the JSON and HTML files and 600 bytes for the trace. Memory held during the run is on
130
+ the same order as the JSON. The plugin registers nothing at all unless one of its options
131
+ is enabled.
132
+
133
+ ## License
134
+
135
+ MIT
Binary file
@@ -0,0 +1,41 @@
1
+ """Small demo suite for trying pytest-timing. Run it with:
2
+
3
+ uv run pytest examples -n 3 --timing --timing-html
4
+ """
5
+
6
+ import time
7
+
8
+ import pytest
9
+
10
+
11
+ @pytest.fixture(scope="module")
12
+ def database():
13
+ time.sleep(0.3) # expensive module fixture: shows up as setup on the first test per worker
14
+ yield
15
+
16
+
17
+ @pytest.fixture
18
+ def client(database):
19
+ time.sleep(0.05)
20
+ yield
21
+ time.sleep(0.02)
22
+
23
+
24
+ @pytest.mark.parametrize("i", range(12))
25
+ def test_api(client, i):
26
+ time.sleep(0.02 * (i % 4 + 1))
27
+
28
+
29
+ @pytest.mark.parametrize("i", range(3))
30
+ def test_slow(i):
31
+ time.sleep(0.2 + 0.1 * i)
32
+
33
+
34
+ def test_fails():
35
+ time.sleep(0.05)
36
+ assert 1 == 2
37
+
38
+
39
+ @pytest.mark.skip(reason="demo")
40
+ def test_skipped():
41
+ pass
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pytest-timing"
7
+ version = "0.1.0"
8
+ description = "Record test timings under pytest-xdist and render cargo-style timing reports (ASCII Gantt and HTML)."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "messense", email = "messense@icloud.com" }]
13
+ keywords = ["pytest", "xdist", "timing", "gantt", "profiling"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Framework :: Pytest",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+ dependencies = ["pytest>=7.3"]
28
+
29
+ [project.optional-dependencies]
30
+ xdist = ["pytest-xdist>=3.0"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/messense/pytest-timing"
34
+
35
+ [project.entry-points.pytest11]
36
+ timing = "pytest_timing.plugin"
37
+
38
+ [project.scripts]
39
+ pytest-timing = "pytest_timing.cli:main"
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "pytest-xdist>=3.0",
44
+ "pytest-rerunfailures>=12",
45
+ "hypothesis>=6",
46
+ "ruff>=0.6",
47
+ "mypy>=1.10",
48
+ ]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/pytest_timing"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ addopts = "-p pytester"
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py310"
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I", "UP", "B", "W"]
63
+
64
+ [tool.mypy]
65
+ strict = true
66
+ python_version = "3.10"
67
+ files = ["src"]
@@ -0,0 +1,5 @@
1
+ """pytest-timing: record test timings under pytest-xdist and render timing reports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"