tempest-cli 0.5.1__tar.gz → 0.6.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.
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/CHANGELOG.md +22 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/PKG-INFO +5 -4
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/README.md +4 -3
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/changelog.en.md +16 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/changelog.md +16 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/commands.en.md +63 -43
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/commands.md +48 -29
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/installation.en.md +3 -2
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/installation.md +3 -2
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/library.en.md +3 -2
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/library.md +3 -2
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/pyproject.toml +1 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/__init__.py +1 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/lint.py +88 -29
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/main.py +55 -27
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_extra_steps.py +1 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_fast.py +82 -9
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/uv.lock +1 -1
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/.github/workflows/docs.yml +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/.gitignore +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/LICENSE +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/Makefile +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/configuration.en.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/configuration.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/index.en.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/index.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/pr-prompt.en.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/pr-prompt.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/reference.en.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/reference.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/rules.en.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/docs/rules.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/mkdocs.yml +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.en-US.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.pt-BR.md +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/config.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/pr_prompt.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/py.typed +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/residuals.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/rewrite.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/steps.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/__init__.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_cli.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_config.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_fix_residuals.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_lint_runners.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_lint_strictness.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_pr_prompt.py +0 -0
- {tempest_cli-0.5.1 → tempest_cli-0.6.0}/tests/test_rewrite.py +0 -0
|
@@ -5,6 +5,28 @@ All notable changes to **tempest-cli** are listed below.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.6.0] — 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`test` and `check` run the suite in parallel by default.** The
|
|
15
|
+
pytest-xdist run that needed `--fast` (`-n auto -p no:cacheprovider`) is
|
|
16
|
+
now what a bare `tempest-cli test` does; `--serial` opts out (for
|
|
17
|
+
`--lf` / `--ff`, or a debugger), and `--workers` tunes the default
|
|
18
|
+
without a flag. Measured on a FastAPI service suite of 1136 tests on a
|
|
19
|
+
12-CPU machine: 262 s serially, 37.6 s in parallel, same outcome.
|
|
20
|
+
- **Without pytest-xdist the default degrades instead of failing.** When
|
|
21
|
+
the environment pytest runs in cannot import it, a bare `test` / `check`
|
|
22
|
+
prints a one-line notice naming the install command and runs the suite
|
|
23
|
+
serially (exit code is pytest's); an explicit `--fast` keeps the old
|
|
24
|
+
behavior — the fix is printed and the exit code is 127, nothing runs.
|
|
25
|
+
`run_pytest` / `run_full_check` take `require_xdist=` (default `True`,
|
|
26
|
+
the old library behavior) to pick between the two.
|
|
27
|
+
- `--workers` with `--serial` is a usage error (exit 2), as `--workers`
|
|
28
|
+
without `--fast` used to be.
|
|
29
|
+
|
|
8
30
|
## [0.5.1] — 2026-10-15
|
|
9
31
|
|
|
10
32
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tempest-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Framework-agnostic quality gate for Python projects: ruff + mypy + pytest behind one command, with a typing-strictness dial and a PR-description prompt generator.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempest-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/mauriciobenjamin700/tempest-cli
|
|
@@ -74,8 +74,8 @@ remembered to pass.
|
|
|
74
74
|
| `tempest-cli format` | `ruff format` (writes) |
|
|
75
75
|
| `tempest-cli fmt-check` | `ruff format --check` (read-only) |
|
|
76
76
|
| `tempest-cli type` | `mypy` |
|
|
77
|
-
| `tempest-cli test` | `pytest
|
|
78
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (
|
|
77
|
+
| `tempest-cli test` | `pytest`, in parallel by default with pytest-xdist (`-n auto -p no:cacheprovider`, `-w N` to choose; serial with a notice when it is not installed; `--serial` opts out) |
|
|
78
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (the test step in parallel as in `test`; a host CLI can add steps between `mypy` and `pytest`) |
|
|
79
79
|
| `tempest-cli pr-prompt` | builds the prompt that makes an AI write this branch's PR description |
|
|
80
80
|
|
|
81
81
|
Every command takes an optional path (`tempest-cli lint src/`) and
|
|
@@ -188,7 +188,8 @@ your suite. Add them yourself, or take the bundle:
|
|
|
188
188
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
`pytest-xdist` is what `test
|
|
191
|
+
`pytest-xdist` is what `test` runs the suite in parallel with, by
|
|
192
|
+
default (without it the suite runs serially, with a notice); its
|
|
192
193
|
`requires-dist` (`execnet>=2.1`, `pytest>=7.0.0`) carries no upper bound.
|
|
193
194
|
|
|
194
195
|
Whatever you pin wins over the bundled ruff. The lookup runs in this
|
|
@@ -42,8 +42,8 @@ remembered to pass.
|
|
|
42
42
|
| `tempest-cli format` | `ruff format` (writes) |
|
|
43
43
|
| `tempest-cli fmt-check` | `ruff format --check` (read-only) |
|
|
44
44
|
| `tempest-cli type` | `mypy` |
|
|
45
|
-
| `tempest-cli test` | `pytest
|
|
46
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (
|
|
45
|
+
| `tempest-cli test` | `pytest`, in parallel by default with pytest-xdist (`-n auto -p no:cacheprovider`, `-w N` to choose; serial with a notice when it is not installed; `--serial` opts out) |
|
|
46
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (the test step in parallel as in `test`; a host CLI can add steps between `mypy` and `pytest`) |
|
|
47
47
|
| `tempest-cli pr-prompt` | builds the prompt that makes an AI write this branch's PR description |
|
|
48
48
|
|
|
49
49
|
Every command takes an optional path (`tempest-cli lint src/`) and
|
|
@@ -156,7 +156,8 @@ your suite. Add them yourself, or take the bundle:
|
|
|
156
156
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
`pytest-xdist` is what `test
|
|
159
|
+
`pytest-xdist` is what `test` runs the suite in parallel with, by
|
|
160
|
+
default (without it the suite runs serially, with a notice); its
|
|
160
161
|
`requires-dist` (`execnet>=2.1`, `pytest>=7.0.0`) carries no upper bound.
|
|
161
162
|
|
|
162
163
|
Whatever you pin wins over the bundled ruff. The lookup runs in this
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
The full history lives in the repository's
|
|
4
4
|
[`CHANGELOG.md`](https://github.com/mauriciobenjamin700/tempest-cli/blob/main/CHANGELOG.md).
|
|
5
5
|
|
|
6
|
+
## [0.6.0] — 2026-10-20
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **`test` and `check` run the suite in parallel by default.** The
|
|
11
|
+
pytest-xdist `-n auto` run that needed `--fast` is now what a bare
|
|
12
|
+
`test` does; `--serial` opts out (for `--lf` / `--ff`, or a debugger).
|
|
13
|
+
Measured on a FastAPI suite of 1136 tests on 12 CPUs: 262 s serially,
|
|
14
|
+
37.6 s in parallel, same outcome.
|
|
15
|
+
[Details](commands.md#the-suite-in-parallel-the-default).
|
|
16
|
+
- **Without pytest-xdist the default degrades to serial instead of
|
|
17
|
+
failing**, with a notice naming the install command; only an explicit
|
|
18
|
+
`--fast` still requires the plugin (exit code 127).
|
|
19
|
+
- `--workers` with `--serial` is a usage error (exit code 2).
|
|
20
|
+
|
|
6
21
|
## [0.5.1] — 2026-10-15
|
|
7
22
|
|
|
8
23
|
### Fixed
|
|
@@ -46,7 +61,7 @@ The full history lives in the repository's
|
|
|
46
61
|
the default — or `logical`) and is a usage error without `--fast`. The
|
|
47
62
|
target is forwarded and pytest's exit code comes back untranslated. It
|
|
48
63
|
is a flag, not a subcommand, because `test fast` keeps meaning the
|
|
49
|
-
`fast/` folder. [Details](commands.md#the-suite-in-parallel-
|
|
64
|
+
`fast/` folder. [Details](commands.md#the-suite-in-parallel-the-default).
|
|
50
65
|
- **A missing pytest-xdist is a sentence, not `unrecognized arguments:
|
|
51
66
|
-n`.** `--fast` asks the interpreter that will run pytest whether it
|
|
52
67
|
can import `xdist`; when it cannot, the message names the package and
|
|
@@ -4,6 +4,21 @@ O histórico completo vive no
|
|
|
4
4
|
[`CHANGELOG.md`](https://github.com/mauriciobenjamin700/tempest-cli/blob/main/CHANGELOG.md)
|
|
5
5
|
do repositório.
|
|
6
6
|
|
|
7
|
+
## [0.6.0] — 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Alterado
|
|
10
|
+
|
|
11
|
+
- **O `test` e o `check` rodam a suíte em paralelo por padrão.** O
|
|
12
|
+
`-n auto` do pytest-xdist, que pedia `--fast`, agora é o que um
|
|
13
|
+
`test` puro faz; `--serial` desliga (para `--lf` / `--ff` ou depurador).
|
|
14
|
+
Medido numa suíte FastAPI de 1136 testes em 12 CPUs: 262 s em série,
|
|
15
|
+
37,6 s em paralelo, mesmo resultado.
|
|
16
|
+
[Detalhes](commands.md#a-suite-em-paralelo-o-padrao).
|
|
17
|
+
- **Sem o pytest-xdist, o padrão cai para série em vez de falhar**, com um
|
|
18
|
+
aviso de como instalar; só o `--fast` explícito continua exigindo o
|
|
19
|
+
plugin (código de saída 127).
|
|
20
|
+
- `--workers` com `--serial` é erro de uso (código de saída 2).
|
|
21
|
+
|
|
7
22
|
## [0.5.1] — 2026-10-15
|
|
8
23
|
|
|
9
24
|
### Corrigido
|
|
@@ -45,7 +60,7 @@ do repositório.
|
|
|
45
60
|
o padrão — ou `logical`) e é erro de uso sem `--fast`. O alvo é
|
|
46
61
|
repassado e o código de saída do pytest volta sem tradução. É flag, e
|
|
47
62
|
não subcomando, porque `test fast` continua significando a pasta
|
|
48
|
-
`fast/`. [Detalhes](commands.md#a-suite-em-paralelo-
|
|
63
|
+
`fast/`. [Detalhes](commands.md#a-suite-em-paralelo-o-padrao).
|
|
49
64
|
- **pytest-xdist ausente vira frase, não `unrecognized arguments: -n`.**
|
|
50
65
|
O `--fast` pergunta ao interpretador que vai rodar o pytest se ele
|
|
51
66
|
importa o `xdist`; faltando, a mensagem nomeia o pacote e os extras
|
|
@@ -181,76 +181,93 @@ tempest-cli test tests/unit
|
|
|
181
181
|
`test` forwards the path to pytest as a filter; with no argument it runs
|
|
182
182
|
the whole suite.
|
|
183
183
|
|
|
184
|
-
## The suite in parallel
|
|
184
|
+
## The suite in parallel (the default)
|
|
185
185
|
|
|
186
186
|
A suite whose tests are already isolated (a database per test, no global
|
|
187
|
-
state) pays the serial time and buys nothing for it.
|
|
188
|
-
suite across
|
|
189
|
-
[pytest-xdist](https://pytest-xdist.readthedocs.io/)
|
|
187
|
+
state) pays the serial time and buys nothing for it. That is why `test`
|
|
188
|
+
and `check` spread the suite across cores with
|
|
189
|
+
[pytest-xdist](https://pytest-xdist.readthedocs.io/) **without being
|
|
190
|
+
asked**:
|
|
190
191
|
|
|
191
192
|
```bash
|
|
192
|
-
tempest-cli test
|
|
193
|
-
tempest-cli test
|
|
194
|
-
tempest-cli test tests/unit
|
|
195
|
-
tempest-cli check
|
|
193
|
+
tempest-cli test # -n auto: one worker per core
|
|
194
|
+
tempest-cli test -w 4 # four workers
|
|
195
|
+
tempest-cli test tests/unit # the target is still forwarded
|
|
196
|
+
tempest-cli check # the full gate, with the test step in parallel
|
|
197
|
+
tempest-cli test --serial # a single process (for --lf / --ff, or a debugger)
|
|
196
198
|
```
|
|
197
199
|
|
|
198
200
|
Underneath it becomes `pytest -n <workers> -p no:cacheprovider [target]`.
|
|
199
201
|
`-p no:cacheprovider` is there because several workers writing
|
|
200
202
|
`.pytest_cache` at once is a race that buys nothing — and that cache is
|
|
201
|
-
what `--lf` / `--ff` read, so those two
|
|
203
|
+
what `--lf` / `--ff` read, so those two are a `--serial` affair.
|
|
204
|
+
|
|
205
|
+
On a FastAPI service suite of 1136 tests (an in-memory SQLite database
|
|
206
|
+
per test), on a 12-CPU machine: **262 s** serially, **37.6 s** with
|
|
207
|
+
`-n auto` — same outcome, 1136 passed.
|
|
202
208
|
|
|
203
209
|
`--workers` (`-w`) takes what `pytest -n` takes: an integer, `auto` (the
|
|
204
|
-
default) or `logical`.
|
|
210
|
+
default) or `logical`. With `--serial` it is a usage error (exit 2)
|
|
205
211
|
instead of being silently ignored.
|
|
206
212
|
|
|
207
213
|
!!! info "`auto` counts physical cores when `psutil` is installed"
|
|
208
214
|
pytest-xdist decides: with `psutil` importable, `auto` is the number of
|
|
209
|
-
**physical** cores; without it, the number of logical CPUs. On a
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
**physical** cores; without it, the number of logical CPUs. On a
|
|
216
|
+
6-core, 12-thread machine with `psutil` in the environment, `auto`
|
|
217
|
+
reported `created: 6/6 workers`. Want the 12 threads? `-w logical` or
|
|
212
218
|
`-w 12`.
|
|
213
219
|
|
|
214
|
-
!!! note "Why a flag, not `tempest-cli test
|
|
215
|
-
`test` already takes a positional path. `test fast` keeps meaning
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
!!! note "Why a flag, and not `tempest-cli test serial`"
|
|
221
|
+
`test` already takes a positional path. `test fast` keeps meaning "run
|
|
222
|
+
pytest on the `fast/` folder" — a subcommand by that name would break
|
|
223
|
+
anyone who has that folder.
|
|
218
224
|
|
|
219
225
|
### Without pytest-xdist
|
|
220
226
|
|
|
221
227
|
Before running anything, the CLI asks **the very interpreter that will
|
|
222
228
|
run pytest** whether it can import `xdist` — not the CLI's own
|
|
223
|
-
interpreter, which under `uv tool install` or pipx lives in
|
|
224
|
-
environment. When it is missing,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
interpreter, which under `uv tool install` or pipx lives in another
|
|
230
|
+
environment. When it is missing, it depends on who asked for parallel:
|
|
231
|
+
|
|
232
|
+
- **Nobody (the default):** the suite runs serially, with a notice on
|
|
233
|
+
stderr naming what to install. `tempest-cli test` keeps working in a
|
|
234
|
+
project that never installed pytest-xdist — it is just slower.
|
|
235
|
+
|
|
236
|
+
```console
|
|
237
|
+
$ tempest-cli test
|
|
238
|
+
note: pytest-xdist is not installed in the environment pytest runs in (.venv/bin/pytest), so the suite runs serially. Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — to run it in parallel.
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- **`--fast` by name:** parallel becomes a requirement. The message names
|
|
242
|
+
what to install and the exit code is **127**, with nothing run:
|
|
243
|
+
|
|
244
|
+
```console
|
|
245
|
+
$ tempest-cli test --fast
|
|
246
|
+
error: --fast needs pytest-xdist, which is not installed in the environment pytest runs in (.venv/bin/pytest). Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — and retry, or pass --serial to run the suite serially.
|
|
247
|
+
$ echo $?
|
|
248
|
+
127
|
|
249
|
+
```
|
|
233
250
|
|
|
234
|
-
Under `check
|
|
235
|
-
is no point waiting for lint and mypy to finish to learn the
|
|
236
|
-
not run.
|
|
251
|
+
Under `check` the probe happens **before** the first step: with `--fast`
|
|
252
|
+
there is no point waiting for lint and mypy to finish only to learn the
|
|
253
|
+
tests will not run.
|
|
237
254
|
|
|
238
255
|
### A test that fails only in parallel
|
|
239
256
|
|
|
240
|
-
Parallelism exposes tests that depend on order or on an idle machine:
|
|
241
|
-
|
|
242
|
-
fixed `sleep` waiting for something that takes longer
|
|
257
|
+
Parallelism exposes tests that depend on order or on an idle machine: two
|
|
258
|
+
tests writing the same file, the same port, a module-level global, a
|
|
259
|
+
fixed `sleep` waiting for something that takes longer with every core
|
|
243
260
|
busy. Before treating the failure as a regression, run the test **alone
|
|
244
261
|
and serially**:
|
|
245
262
|
|
|
246
263
|
```bash
|
|
247
|
-
tempest-cli test "tests/test_scheduler.py::test_lease_expires"
|
|
264
|
+
tempest-cli test --serial "tests/test_scheduler.py::test_lease_expires"
|
|
248
265
|
```
|
|
249
266
|
|
|
250
|
-
- **
|
|
267
|
+
- **Passes alone**: the defect is the test's isolation, not the change
|
|
251
268
|
under review. Fix the test (a file under `tmp_path`, a free port, wait
|
|
252
|
-
on a condition instead of
|
|
253
|
-
- **
|
|
269
|
+
on a condition instead of `sleep`).
|
|
270
|
+
- **Fails alone too**: it is a real regression.
|
|
254
271
|
|
|
255
272
|
## What each returns
|
|
256
273
|
|
|
@@ -265,8 +282,9 @@ of the places the CLI looks — the run's environment, `PATH`, `uv run
|
|
|
265
282
|
--with` — and the message then names what was missing and how to install
|
|
266
283
|
it. The lookup order is in
|
|
267
284
|
[Installation](installation.md#where-ruff-mypy-and-pytest-come-from).
|
|
268
|
-
The same 127 comes out of `--fast` when pytest-xdist is not in
|
|
269
|
-
environment
|
|
285
|
+
The same 127 comes out of an explicit `--fast` when pytest-xdist is not in
|
|
286
|
+
pytest's environment; without the flag, its absence only makes the suite
|
|
287
|
+
serial.
|
|
270
288
|
|
|
271
289
|
## Recap
|
|
272
290
|
|
|
@@ -276,8 +294,10 @@ environment.
|
|
|
276
294
|
- Long comments and strings `fix` shortens on its own, with the AST
|
|
277
295
|
proven equal. An E501 left after it has no mechanical rewrite: `fix`
|
|
278
296
|
names the token and the action — change the code's shape.
|
|
279
|
-
- `test
|
|
280
|
-
(`-n auto`, `-w N` to choose);
|
|
281
|
-
|
|
297
|
+
- `test` and `check` run the suite in parallel by default, with
|
|
298
|
+
pytest-xdist (`-n auto`, `-w N` to choose); `--serial` turns it off.
|
|
299
|
+
Without pytest-xdist the default falls back to serial with a notice,
|
|
300
|
+
and an explicit `--fast` becomes exit 127. A test that fails only in
|
|
301
|
+
parallel is checked by running it alone, with `--serial`.
|
|
282
302
|
- Exit codes are the tools'; 127 means a missing tool (or the
|
|
283
|
-
pytest-xdist `--fast`
|
|
303
|
+
pytest-xdist an explicit `--fast` requires).
|
|
@@ -181,28 +181,33 @@ tempest-cli test tests/unit
|
|
|
181
181
|
`test` repassa o caminho ao pytest como filtro; sem argumento, roda a
|
|
182
182
|
suíte inteira.
|
|
183
183
|
|
|
184
|
-
## A suíte em paralelo
|
|
184
|
+
## A suíte em paralelo (o padrão)
|
|
185
185
|
|
|
186
186
|
Suíte cujos testes já são isolados (banco por teste, nada de estado
|
|
187
|
-
global) paga o tempo serial sem comprar nada.
|
|
188
|
-
pelos núcleos com o
|
|
187
|
+
global) paga o tempo serial sem comprar nada. Por isso `test` e `check`
|
|
188
|
+
espalham a suíte pelos núcleos com o
|
|
189
|
+
[pytest-xdist](https://pytest-xdist.readthedocs.io/) **sem você pedir**:
|
|
189
190
|
|
|
190
191
|
```bash
|
|
191
|
-
tempest-cli test
|
|
192
|
-
tempest-cli test
|
|
193
|
-
tempest-cli test tests/unit
|
|
194
|
-
tempest-cli check
|
|
192
|
+
tempest-cli test # -n auto: um worker por núcleo
|
|
193
|
+
tempest-cli test -w 4 # quatro workers
|
|
194
|
+
tempest-cli test tests/unit # o alvo continua sendo repassado
|
|
195
|
+
tempest-cli check # o gate completo, com o passo de teste em paralelo
|
|
196
|
+
tempest-cli test --serial # um processo só (para --lf / --ff, ou depurar)
|
|
195
197
|
```
|
|
196
198
|
|
|
197
199
|
Por baixo, vira `pytest -n <workers> -p no:cacheprovider [alvo]`. O
|
|
198
200
|
`-p no:cacheprovider` existe porque vários workers escrevendo
|
|
199
201
|
`.pytest_cache` ao mesmo tempo é corrida à toa — e é esse cache que o
|
|
200
|
-
`--lf` / `--ff` leem, então esses dois
|
|
201
|
-
|
|
202
|
+
`--lf` / `--ff` leem, então esses dois são coisa de `--serial`.
|
|
203
|
+
|
|
204
|
+
Numa suíte de serviço FastAPI com 1136 testes (banco SQLite in-memory por
|
|
205
|
+
teste), numa máquina de 12 CPUs: **262 s** em série, **37,6 s** com
|
|
206
|
+
`-n auto` — o mesmo resultado, 1136 passed.
|
|
202
207
|
|
|
203
208
|
`--workers` (`-w`) aceita o que o `pytest -n` aceita: um inteiro, `auto`
|
|
204
|
-
(o padrão) ou `logical`.
|
|
205
|
-
de ser ignorado em silêncio.
|
|
209
|
+
(o padrão) ou `logical`. Com `--serial` ele é erro de uso (saída 2), em
|
|
210
|
+
vez de ser ignorado em silêncio.
|
|
206
211
|
|
|
207
212
|
!!! info "`auto` conta núcleo físico quando o `psutil` está instalado"
|
|
208
213
|
É o pytest-xdist que decide: com `psutil` importável, `auto` é o número
|
|
@@ -210,7 +215,7 @@ de ser ignorado em silêncio.
|
|
|
210
215
|
núcleos e 12 threads, com `psutil` no ambiente, `auto` subiu
|
|
211
216
|
`created: 6/6 workers`. Quer as 12 threads? `-w logical` ou `-w 12`.
|
|
212
217
|
|
|
213
|
-
!!! note "Por que flag, e não `tempest-cli test
|
|
218
|
+
!!! note "Por que flag, e não `tempest-cli test serial`"
|
|
214
219
|
`test` já recebe um caminho posicional. `test fast` continua
|
|
215
220
|
significando "rode o pytest na pasta `fast/`" — um subcomando com esse
|
|
216
221
|
nome quebraria quem tem essa pasta.
|
|
@@ -220,18 +225,30 @@ de ser ignorado em silêncio.
|
|
|
220
225
|
Antes de rodar qualquer coisa, a CLI pergunta ao **mesmo interpretador
|
|
221
226
|
que vai rodar o pytest** se ele importa o `xdist` — não ao interpretador
|
|
222
227
|
da própria CLI, que sob `uv tool install` ou pipx mora em outro ambiente.
|
|
223
|
-
Faltou,
|
|
228
|
+
Faltou, depende de quem pediu o paralelismo:
|
|
224
229
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
- **Ninguém pediu (o padrão):** a suíte roda em série, com um aviso no
|
|
231
|
+
stderr dizendo o que instalar. `tempest-cli test` continua funcionando
|
|
232
|
+
em projeto que nunca instalou o pytest-xdist — só fica mais lento.
|
|
233
|
+
|
|
234
|
+
```console
|
|
235
|
+
$ tempest-cli test
|
|
236
|
+
note: pytest-xdist is not installed in the environment pytest runs in (.venv/bin/pytest), so the suite runs serially. Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — to run it in parallel.
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- **`--fast` explícito:** o paralelismo vira exigência. A mensagem diz o
|
|
240
|
+
que instalar e a saída é **127**, sem rodar nada:
|
|
241
|
+
|
|
242
|
+
```console
|
|
243
|
+
$ tempest-cli test --fast
|
|
244
|
+
error: --fast needs pytest-xdist, which is not installed in the environment pytest runs in (.venv/bin/pytest). Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — and retry, or pass --serial to run the suite serially.
|
|
245
|
+
$ echo $?
|
|
246
|
+
127
|
|
247
|
+
```
|
|
231
248
|
|
|
232
|
-
No `check
|
|
233
|
-
não adianta esperar lint e mypy terminarem para descobrir que o
|
|
234
|
-
não vai rodar.
|
|
249
|
+
No `check` essa checagem acontece **antes** do primeiro passo: com
|
|
250
|
+
`--fast`, não adianta esperar lint e mypy terminarem para descobrir que o
|
|
251
|
+
teste não vai rodar.
|
|
235
252
|
|
|
236
253
|
### Teste que só falha em paralelo
|
|
237
254
|
|
|
@@ -242,7 +259,7 @@ demora mais. Antes de tratar a falha como regressão, rode o teste
|
|
|
242
259
|
**sozinho e em série**:
|
|
243
260
|
|
|
244
261
|
```bash
|
|
245
|
-
tempest-cli test "tests/test_scheduler.py::test_lease_expires"
|
|
262
|
+
tempest-cli test --serial "tests/test_scheduler.py::test_lease_expires"
|
|
246
263
|
```
|
|
247
264
|
|
|
248
265
|
- **Passou sozinho**: o defeito é o isolamento do teste, não a mudança
|
|
@@ -262,8 +279,8 @@ Só há um código próprio: **127**, quando a ferramenta não está em nenhum
|
|
|
262
279
|
dos lugares onde a CLI procura — ambiente da execução, `PATH`, `uv run
|
|
263
280
|
--with` — e aí a mensagem diz qual faltou e como instalá-la. A ordem de
|
|
264
281
|
busca está em [Instalação](installation.md#de-onde-vem-o-ruff-o-mypy-e-o-pytest).
|
|
265
|
-
O mesmo 127 sai do `--fast` quando o pytest-xdist não está no
|
|
266
|
-
pytest.
|
|
282
|
+
O mesmo 127 sai do `--fast` explícito quando o pytest-xdist não está no
|
|
283
|
+
ambiente do pytest; sem a flag, a falta dele só deixa a suíte em série.
|
|
267
284
|
|
|
268
285
|
## Recap
|
|
269
286
|
|
|
@@ -273,8 +290,10 @@ pytest.
|
|
|
273
290
|
- Comentário e string longos o `fix` encurta sozinho, com a AST provada
|
|
274
291
|
igual. O E501 que sobra é linha sem reescrita mecânica: o `fix` diz o
|
|
275
292
|
token e a ação — mude a forma do código.
|
|
276
|
-
- `test
|
|
277
|
-
(`-n auto`, `-w N` para escolher);
|
|
278
|
-
|
|
293
|
+
- `test` e `check` rodam a suíte em paralelo por padrão, com pytest-xdist
|
|
294
|
+
(`-n auto`, `-w N` para escolher); `--serial` desliga. Sem o
|
|
295
|
+
pytest-xdist, o padrão cai para série com um aviso, e o `--fast`
|
|
296
|
+
explícito vira erro 127. Teste que só falha em paralelo se confere
|
|
297
|
+
rodando-o sozinho, com `--serial`.
|
|
279
298
|
- Código de saída é o da ferramenta; 127 significa ferramenta (ou o
|
|
280
|
-
pytest-xdist
|
|
299
|
+
pytest-xdist que o `--fast` exige) ausente.
|
|
@@ -45,8 +45,9 @@ take the bundle:
|
|
|
45
45
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
`pytest-xdist` is what [`test
|
|
49
|
-
uses to run the suite in parallel
|
|
48
|
+
`pytest-xdist` is what [`test`](commands.md#the-suite-in-parallel-the-default)
|
|
49
|
+
uses to run the suite in parallel, the default; without it the suite runs
|
|
50
|
+
serially, with a notice. Its `requires-dist` (`execnet>=2.1`,
|
|
50
51
|
`pytest>=7.0.0`) carries no upper bound, so it tightens nobody's
|
|
51
52
|
resolution.
|
|
52
53
|
|
|
@@ -46,8 +46,9 @@ mesmo, ou pegue o pacote pronto:
|
|
|
46
46
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
O `pytest-xdist` é o que o [`test
|
|
50
|
-
usa para rodar a suíte em paralelo
|
|
49
|
+
O `pytest-xdist` é o que o [`test`](commands.md#a-suite-em-paralelo-o-padrao)
|
|
50
|
+
usa para rodar a suíte em paralelo, o padrão; sem ele a suíte roda em
|
|
51
|
+
série, com um aviso. O `requires-dist` dele
|
|
51
52
|
(`execnet>=2.1`, `pytest>=7.0.0`) não tem teto nenhum, então ele não
|
|
52
53
|
aperta a resolução de ninguém.
|
|
53
54
|
|
|
@@ -30,7 +30,8 @@ run_ruff_fix("src", unsafe=False, config=strict)
|
|
|
30
30
|
run_ruff_format("src", check=True) # --check: writes nothing
|
|
31
31
|
run_mypy("src", config=strict)
|
|
32
32
|
run_pytest("tests/unit")
|
|
33
|
-
run_pytest("tests/unit", fast=True, workers="4") # pytest-xdist: -n 4
|
|
33
|
+
run_pytest("tests/unit", fast=True, workers="4") # pytest-xdist: -n 4; without it, 127
|
|
34
|
+
run_pytest("tests", fast=True, require_xdist=False) # without pytest-xdist: serial, with a notice
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
`load_tempest_config(start)` walks up from `start` (the cwd by default)
|
|
@@ -135,7 +136,7 @@ The order is `ruff check` → `ruff format --check` → `mypy` → the extra
|
|
|
135
136
|
steps, in list order → `pytest`. An extra step is a static check: it
|
|
136
137
|
sits before the suite so it fails in a second, not after it. The
|
|
137
138
|
contract is the built-in steps' — the first non-zero code stops the gate
|
|
138
|
-
and becomes `check`'s exit code,
|
|
139
|
+
and becomes `check`'s exit code, in parallel or with `--serial`.
|
|
139
140
|
|
|
140
141
|
!!! tip "The message is yours"
|
|
141
142
|
`check` only echoes the name. What the step prints is what the user
|
|
@@ -31,7 +31,8 @@ run_ruff_fix("src", unsafe=False, config=strict)
|
|
|
31
31
|
run_ruff_format("src", check=True) # --check: não escreve
|
|
32
32
|
run_mypy("src", config=strict)
|
|
33
33
|
run_pytest("tests/unit")
|
|
34
|
-
run_pytest("tests/unit", fast=True, workers="4") # pytest-xdist: -n 4
|
|
34
|
+
run_pytest("tests/unit", fast=True, workers="4") # pytest-xdist: -n 4; sem ele, 127
|
|
35
|
+
run_pytest("tests", fast=True, require_xdist=False) # sem pytest-xdist: série, com aviso
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
`load_tempest_config(start)` procura o `pyproject.toml` mais próximo
|
|
@@ -136,7 +137,7 @@ A ordem é `ruff check` → `ruff format --check` → `mypy` → os passos
|
|
|
136
137
|
extras, na ordem da lista → `pytest`. Passo extra é checagem estática:
|
|
137
138
|
fica antes da suíte para falhar em um segundo, não depois dela. O
|
|
138
139
|
contrato é o dos passos embutidos — o primeiro código diferente de `0`
|
|
139
|
-
para o gate e vira o código de saída do `check`,
|
|
140
|
+
para o gate e vira o código de saída do `check`, em paralelo ou com `--serial`.
|
|
140
141
|
|
|
141
142
|
!!! tip "A mensagem é sua"
|
|
142
143
|
O `check` só ecoa o nome. O que o passo imprime é o que o usuário lê,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tempest-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Framework-agnostic quality gate for Python projects: ruff + mypy + pytest behind one command, with a typing-strictness dial and a PR-description prompt generator."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -57,7 +57,7 @@ from tempest_cli.pr_prompt import PromptLanguage as PromptLanguage
|
|
|
57
57
|
from tempest_cli.pr_prompt import generate_pr_prompt as generate_pr_prompt
|
|
58
58
|
from tempest_cli.steps import CheckStep as CheckStep
|
|
59
59
|
|
|
60
|
-
__version__: str = "0.
|
|
60
|
+
__version__: str = "0.6.0"
|
|
61
61
|
"""Installed package version."""
|
|
62
62
|
|
|
63
63
|
__all__: list[str] = [
|
|
@@ -8,6 +8,7 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
from collections.abc import Callable, Sequence
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from typing import Literal
|
|
11
12
|
|
|
12
13
|
import typer
|
|
13
14
|
|
|
@@ -494,41 +495,86 @@ def _module_available(interpreter: list[str], module: str) -> bool | None:
|
|
|
494
495
|
return None
|
|
495
496
|
|
|
496
497
|
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
ParallelDecision = Literal["parallel", "serial", "stop"]
|
|
499
|
+
"""How a requested parallel run proceeds once pytest-xdist was looked for."""
|
|
500
|
+
|
|
501
|
+
XDIST_INSTALL_HINT = (
|
|
502
|
+
"'uv add --dev pytest-xdist' — or a bundle that carries it, "
|
|
503
|
+
"'uv add --dev \"tempest-cli[tools]\"' or "
|
|
504
|
+
"'uv add --dev \"tempest-fastapi-sdk[tests]\"'"
|
|
505
|
+
)
|
|
506
|
+
"""How to get pytest-xdist, quoted by both the error and the fallback notice."""
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _xdist_missing() -> list[str] | None:
|
|
510
|
+
"""Tell whether the environment pytest runs in lacks pytest-xdist.
|
|
499
511
|
|
|
500
512
|
Without the plugin, ``pytest -n auto`` stops at argument parsing with
|
|
501
513
|
``unrecognized arguments: -n`` — true, but it names neither the
|
|
502
514
|
missing package nor the way to get it. This preflight answers first,
|
|
503
|
-
in words.
|
|
515
|
+
so the caller can say it in words.
|
|
504
516
|
|
|
505
|
-
An unknown answer is not
|
|
517
|
+
An unknown answer is not "missing": when pytest cannot be resolved
|
|
506
518
|
(:func:`_execute` reports that with its own message) or its
|
|
507
519
|
interpreter cannot be told, the run proceeds and pytest speaks.
|
|
508
520
|
|
|
509
521
|
Returns:
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
522
|
+
list[str] | None: The pytest argv prefix when the plugin is
|
|
523
|
+
known to be absent; ``None`` when it is present or its presence
|
|
524
|
+
cannot be told.
|
|
513
525
|
"""
|
|
514
526
|
argv = resolve_tool("pytest")
|
|
515
527
|
if argv is None:
|
|
516
|
-
return
|
|
528
|
+
return None
|
|
517
529
|
interpreter = _pytest_interpreter(argv)
|
|
518
530
|
if interpreter is None:
|
|
519
|
-
return
|
|
531
|
+
return None
|
|
520
532
|
if _module_available(interpreter, XDIST_MODULE) is not False:
|
|
521
|
-
return
|
|
533
|
+
return None
|
|
534
|
+
return argv
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _parallel_allowed(*, require_xdist: bool) -> ParallelDecision:
|
|
538
|
+
"""Decide how a parallel run proceeds, given whether xdist is present.
|
|
539
|
+
|
|
540
|
+
Two policies, picked by ``require_xdist``:
|
|
541
|
+
|
|
542
|
+
- **required** (the parallel run was asked for by name, ``--fast``):
|
|
543
|
+
a missing plugin is an error — the fix is printed and
|
|
544
|
+
:data:`MISSING_TOOL_EXIT_CODE` comes back, so nothing runs;
|
|
545
|
+
- **preferred** (parallel is only the default): a missing plugin
|
|
546
|
+
degrades the run to serial, with a one-line notice on stderr
|
|
547
|
+
naming the install command. ``tempest test`` keeps working in a
|
|
548
|
+
project that never installed pytest-xdist, it is just slower.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
require_xdist (bool): Whether a missing plugin is an error.
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
ParallelDecision: ``"parallel"`` when the plugin is there (or its
|
|
555
|
+
presence cannot be told), ``"serial"`` to fall back, ``"stop"``
|
|
556
|
+
after printing the fix.
|
|
557
|
+
"""
|
|
558
|
+
argv = _xdist_missing()
|
|
559
|
+
if argv is None:
|
|
560
|
+
return "parallel"
|
|
561
|
+
where = " ".join(argv)
|
|
562
|
+
if require_xdist:
|
|
563
|
+
typer.echo(
|
|
564
|
+
"error: --fast needs pytest-xdist, which is not installed in the "
|
|
565
|
+
f"environment pytest runs in ({where}). Install it with "
|
|
566
|
+
f"{XDIST_INSTALL_HINT} — and retry, or pass --serial to run the "
|
|
567
|
+
"suite serially.",
|
|
568
|
+
err=True,
|
|
569
|
+
)
|
|
570
|
+
return "stop"
|
|
522
571
|
typer.echo(
|
|
523
|
-
"
|
|
524
|
-
f"
|
|
525
|
-
"
|
|
526
|
-
"'uv add --dev \"tempest-cli[tools]\"' or "
|
|
527
|
-
"'uv add --dev \"tempest-fastapi-sdk[tests]\"' — and retry, "
|
|
528
|
-
"or drop --fast to run the suite serially.",
|
|
572
|
+
"note: pytest-xdist is not installed in the environment pytest runs "
|
|
573
|
+
f"in ({where}), so the suite runs serially. Install it with "
|
|
574
|
+
f"{XDIST_INSTALL_HINT} — to run it in parallel.",
|
|
529
575
|
err=True,
|
|
530
576
|
)
|
|
531
|
-
return
|
|
577
|
+
return "serial"
|
|
532
578
|
|
|
533
579
|
|
|
534
580
|
def _pytest_args(target: str | None, *, fast: bool, workers: str) -> list[str]:
|
|
@@ -555,6 +601,7 @@ def run_pytest(
|
|
|
555
601
|
*,
|
|
556
602
|
fast: bool = False,
|
|
557
603
|
workers: str = DEFAULT_FAST_WORKERS,
|
|
604
|
+
require_xdist: bool = True,
|
|
558
605
|
) -> int:
|
|
559
606
|
"""Invoke ``pytest`` with an optional target.
|
|
560
607
|
|
|
@@ -563,8 +610,8 @@ def run_pytest(
|
|
|
563
610
|
writing ``.pytest_cache`` at once is a race that buys nothing. The
|
|
564
611
|
cache is what ``--lf`` / ``--ff`` read, so those belong to serial
|
|
565
612
|
runs. Before spawning, the interpreter pytest runs under is asked
|
|
566
|
-
whether it can import pytest-xdist; when it cannot
|
|
567
|
-
|
|
613
|
+
whether it can import pytest-xdist; what happens when it cannot is
|
|
614
|
+
``require_xdist``'s call (:func:`_parallel_allowed`).
|
|
568
615
|
|
|
569
616
|
Args:
|
|
570
617
|
target (str | None): Optional pytest path filter. ``None`` runs
|
|
@@ -574,16 +621,21 @@ def run_pytest(
|
|
|
574
621
|
workers (str): Worker count for ``fast`` — an integer, ``auto``
|
|
575
622
|
(one per CPU core: physical when ``psutil`` is importable) or
|
|
576
623
|
``logical``, exactly as ``pytest -n`` takes it. Ignored when
|
|
577
|
-
|
|
624
|
+
the run is serial.
|
|
625
|
+
require_xdist (bool): With ``fast``, whether a missing
|
|
626
|
+
pytest-xdist stops the run (``True``, the default) or degrades
|
|
627
|
+
it to serial with a notice (``False``, what the CLI passes
|
|
628
|
+
when parallel is only its default).
|
|
578
629
|
|
|
579
630
|
Returns:
|
|
580
631
|
int: The pytest exit code, or :data:`MISSING_TOOL_EXIT_CODE` when
|
|
581
|
-
pytest — or
|
|
632
|
+
pytest — or a required pytest-xdist — is absent.
|
|
582
633
|
"""
|
|
583
634
|
if fast:
|
|
584
|
-
|
|
585
|
-
if
|
|
586
|
-
return
|
|
635
|
+
decision = _parallel_allowed(require_xdist=require_xdist)
|
|
636
|
+
if decision == "stop":
|
|
637
|
+
return MISSING_TOOL_EXIT_CODE
|
|
638
|
+
fast = decision == "parallel"
|
|
587
639
|
return _execute("pytest", _pytest_args(target, fast=fast, workers=workers))
|
|
588
640
|
|
|
589
641
|
|
|
@@ -622,6 +674,7 @@ def run_full_check(
|
|
|
622
674
|
fast: bool = False,
|
|
623
675
|
workers: str = DEFAULT_FAST_WORKERS,
|
|
624
676
|
extra_steps: Sequence[CheckStep] = (),
|
|
677
|
+
require_xdist: bool = True,
|
|
625
678
|
) -> int:
|
|
626
679
|
"""Run the entire quality gate sequentially.
|
|
627
680
|
|
|
@@ -635,8 +688,9 @@ def run_full_check(
|
|
|
635
688
|
|
|
636
689
|
With ``fast=True`` the pytest step runs the way :func:`run_pytest`
|
|
637
690
|
runs it with ``fast=True``. The pytest-xdist preflight happens
|
|
638
|
-
*before* the first step, so a
|
|
639
|
-
instead of after lint and mypy have already run
|
|
691
|
+
*before* the first step, so a required plugin that is missing fails
|
|
692
|
+
in a second instead of after lint and mypy have already run, and a
|
|
693
|
+
merely preferred one degrades the pytest step to serial up front.
|
|
640
694
|
|
|
641
695
|
Args:
|
|
642
696
|
target (str): The path inspected by ruff/mypy and handed to every
|
|
@@ -651,15 +705,19 @@ def run_full_check(
|
|
|
651
705
|
extra_steps (Sequence[CheckStep]): Caller-supplied steps run
|
|
652
706
|
after ``mypy`` and before ``pytest``. Empty by default, which
|
|
653
707
|
leaves the gate exactly as it was without them.
|
|
708
|
+
require_xdist (bool): With ``fast``, whether a missing
|
|
709
|
+
pytest-xdist stops the gate or degrades the pytest step to
|
|
710
|
+
serial — as in :func:`run_pytest`.
|
|
654
711
|
|
|
655
712
|
Returns:
|
|
656
713
|
int: The first non-zero exit code — a tool's or an extra step's —
|
|
657
714
|
or ``0`` when every gate passed.
|
|
658
715
|
"""
|
|
659
716
|
if fast:
|
|
660
|
-
|
|
661
|
-
if
|
|
662
|
-
return
|
|
717
|
+
decision = _parallel_allowed(require_xdist=require_xdist)
|
|
718
|
+
if decision == "stop":
|
|
719
|
+
return MISSING_TOOL_EXIT_CODE
|
|
720
|
+
fast = decision == "parallel"
|
|
663
721
|
resolved = config or TempestConfig()
|
|
664
722
|
steps: list[tuple[str, Callable[[], int]]] = [
|
|
665
723
|
_tool_step("ruff", ["check", *_ruff_ann_args(resolved), target]),
|
|
@@ -679,6 +737,7 @@ def run_full_check(
|
|
|
679
737
|
__all__: list[str] = [
|
|
680
738
|
"DEFAULT_FAST_WORKERS",
|
|
681
739
|
"MISSING_TOOL_EXIT_CODE",
|
|
740
|
+
"XDIST_INSTALL_HINT",
|
|
682
741
|
"XDIST_MODULE",
|
|
683
742
|
"resolve_tool",
|
|
684
743
|
"run_full_check",
|
|
@@ -77,7 +77,11 @@ WORKER_KEYWORDS: frozenset[str] = frozenset({"auto", "logical"})
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def _fast_option() -> typer.models.OptionInfo:
|
|
80
|
-
"""Build a fresh ``--fast`` option.
|
|
80
|
+
"""Build a fresh ``--fast/--serial`` option.
|
|
81
|
+
|
|
82
|
+
Parallel is the default. ``--fast`` asks for it by name, which turns a
|
|
83
|
+
missing pytest-xdist into an error; ``--serial`` opts out (for
|
|
84
|
+
``--lf`` / ``--ff``, or a debugger).
|
|
81
85
|
|
|
82
86
|
Returns:
|
|
83
87
|
typer.models.OptionInfo: The configured option.
|
|
@@ -85,11 +89,12 @@ def _fast_option() -> typer.models.OptionInfo:
|
|
|
85
89
|
return cast(
|
|
86
90
|
"typer.models.OptionInfo",
|
|
87
91
|
typer.Option(
|
|
88
|
-
"--fast",
|
|
92
|
+
"--fast/--serial",
|
|
89
93
|
help=(
|
|
90
|
-
"
|
|
91
|
-
"-p no:cacheprovider.
|
|
92
|
-
"
|
|
94
|
+
"Parallel (the default) runs the suite with pytest-xdist: "
|
|
95
|
+
"-n <workers> -p no:cacheprovider. Without pytest-xdist the "
|
|
96
|
+
"default falls back to a serial run with a notice; --fast "
|
|
97
|
+
"makes that an error instead. --serial runs one process."
|
|
93
98
|
),
|
|
94
99
|
),
|
|
95
100
|
)
|
|
@@ -107,9 +112,9 @@ def _workers_option() -> typer.models.OptionInfo:
|
|
|
107
112
|
"--workers",
|
|
108
113
|
"-w",
|
|
109
114
|
help=(
|
|
110
|
-
"Worker count for
|
|
111
|
-
"per physical core when psutil is installed, per
|
|
112
|
-
"otherwise) or 'logical'.
|
|
115
|
+
"Worker count for a parallel run: a number, 'auto' (the "
|
|
116
|
+
"default: one per physical core when psutil is installed, per "
|
|
117
|
+
"logical CPU otherwise) or 'logical'. Not with --serial."
|
|
113
118
|
),
|
|
114
119
|
),
|
|
115
120
|
)
|
|
@@ -121,19 +126,20 @@ def _resolve_workers(
|
|
|
121
126
|
fast: bool,
|
|
122
127
|
workers: str,
|
|
123
128
|
) -> str:
|
|
124
|
-
"""Validate ``--workers`` against ``--
|
|
129
|
+
"""Validate ``--workers`` against ``--serial`` and pytest-xdist's grammar.
|
|
125
130
|
|
|
126
131
|
Args:
|
|
127
132
|
ctx (typer.Context): The command context, used to tell an
|
|
128
133
|
explicit ``--workers`` from its default.
|
|
129
|
-
fast (bool): The ``--fast``
|
|
134
|
+
fast (bool): The ``--fast/--serial`` value (``False`` is
|
|
135
|
+
``--serial``).
|
|
130
136
|
workers (str): The ``--workers`` value.
|
|
131
137
|
|
|
132
138
|
Returns:
|
|
133
139
|
str: The value to hand to ``pytest -n``.
|
|
134
140
|
|
|
135
141
|
Raises:
|
|
136
|
-
typer.BadParameter: When ``--workers`` is given
|
|
142
|
+
typer.BadParameter: When ``--workers`` is given with ``--serial``,
|
|
137
143
|
or is neither a non-negative integer nor ``auto``/``logical``.
|
|
138
144
|
"""
|
|
139
145
|
explicit = getattr(ctx.get_parameter_source("workers"), "name", "") == (
|
|
@@ -141,7 +147,7 @@ def _resolve_workers(
|
|
|
141
147
|
)
|
|
142
148
|
if explicit and not fast:
|
|
143
149
|
raise typer.BadParameter(
|
|
144
|
-
"--workers only applies to a parallel run;
|
|
150
|
+
"--workers only applies to a parallel run; drop --serial.",
|
|
145
151
|
param_hint="--workers",
|
|
146
152
|
)
|
|
147
153
|
if workers in WORKER_KEYWORDS or workers.isdigit():
|
|
@@ -154,6 +160,21 @@ def _resolve_workers(
|
|
|
154
160
|
)
|
|
155
161
|
|
|
156
162
|
|
|
163
|
+
def _require_xdist(ctx: typer.Context) -> bool:
|
|
164
|
+
"""Tell whether the parallel run was asked for by name.
|
|
165
|
+
|
|
166
|
+
``--fast`` on the command line makes a missing pytest-xdist an error;
|
|
167
|
+
the default (no flag) only prefers parallel and degrades to serial.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
ctx (typer.Context): The command context.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
bool: ``True`` when ``--fast`` was typed.
|
|
174
|
+
"""
|
|
175
|
+
return getattr(ctx.get_parameter_source("fast"), "name", "") == "COMMANDLINE"
|
|
176
|
+
|
|
177
|
+
|
|
157
178
|
def register_commands(
|
|
158
179
|
app: typer.Typer,
|
|
159
180
|
*,
|
|
@@ -166,7 +187,7 @@ def register_commands(
|
|
|
166
187
|
help text are left untouched; only commands are added.
|
|
167
188
|
extra_steps (Sequence[CheckStep]): Steps the ``check`` command
|
|
168
189
|
runs after ``mypy`` and before ``pytest``, in every variant
|
|
169
|
-
(``--
|
|
190
|
+
(parallel or ``--serial``). This is how a framework adds its own
|
|
170
191
|
rules to the gate without this package knowing about it.
|
|
171
192
|
Empty by default.
|
|
172
193
|
|
|
@@ -286,27 +307,33 @@ def register_commands(
|
|
|
286
307
|
str | None,
|
|
287
308
|
typer.Argument(help="Optional pytest path filter."),
|
|
288
309
|
] = None,
|
|
289
|
-
fast: Annotated[bool, _fast_option()] =
|
|
310
|
+
fast: Annotated[bool, _fast_option()] = True,
|
|
290
311
|
workers: Annotated[str, _workers_option()] = lint_module.DEFAULT_FAST_WORKERS,
|
|
291
312
|
) -> None:
|
|
292
313
|
"""Run ``pytest`` (forwarding the optional path argument).
|
|
293
314
|
|
|
294
|
-
|
|
295
|
-
unless --workers says otherwise)
|
|
296
|
-
so --lf / --ff belong to serial runs. Without pytest-xdist in the
|
|
297
|
-
environment pytest runs in,
|
|
298
|
-
exit code
|
|
315
|
+
The suite runs in parallel by default: pytest-xdist workers (-n
|
|
316
|
+
auto unless --workers says otherwise), with the cache plugin off,
|
|
317
|
+
so --lf / --ff belong to --serial runs. Without pytest-xdist in the
|
|
318
|
+
environment pytest runs in, the default falls back to a serial run
|
|
319
|
+
and says how to install it; --fast makes that an error (exit code
|
|
320
|
+
127) instead.
|
|
299
321
|
|
|
300
|
-
A test that fails only
|
|
322
|
+
A test that fails only in parallel shares state with another
|
|
301
323
|
one: a file, a port, a module-level global, a fixed sleep racing
|
|
302
324
|
a busy machine. Before treating it as a regression, rerun it
|
|
303
325
|
alone, serially, with its node id as the target
|
|
304
|
-
(test tests/test_x.py::test_name). Passing alone means the
|
|
305
|
-
is the test's isolation, not the change under review.
|
|
326
|
+
(test --serial tests/test_x.py::test_name). Passing alone means the
|
|
327
|
+
defect is the test's isolation, not the change under review.
|
|
306
328
|
"""
|
|
307
329
|
resolved_workers = _resolve_workers(ctx, fast=fast, workers=workers)
|
|
308
330
|
raise typer.Exit(
|
|
309
|
-
lint_module.run_pytest(
|
|
331
|
+
lint_module.run_pytest(
|
|
332
|
+
target,
|
|
333
|
+
fast=fast,
|
|
334
|
+
workers=resolved_workers,
|
|
335
|
+
require_xdist=_require_xdist(ctx),
|
|
336
|
+
)
|
|
310
337
|
)
|
|
311
338
|
|
|
312
339
|
@app.command("check")
|
|
@@ -317,14 +344,14 @@ def register_commands(
|
|
|
317
344
|
typer.Argument(help="Path to inspect. Defaults to the current directory."),
|
|
318
345
|
] = ".",
|
|
319
346
|
strictness: Annotated[str | None, _strictness_option()] = None,
|
|
320
|
-
fast: Annotated[bool, _fast_option()] =
|
|
347
|
+
fast: Annotated[bool, _fast_option()] = True,
|
|
321
348
|
workers: Annotated[str, _workers_option()] = lint_module.DEFAULT_FAST_WORKERS,
|
|
322
349
|
) -> None:
|
|
323
350
|
"""Run the full quality gate (lint + fmt-check + type + test).
|
|
324
351
|
|
|
325
|
-
|
|
326
|
-
pytest-xdist check made before the first
|
|
327
|
-
registered run after type and before test.
|
|
352
|
+
The test step runs as ``test`` does — in parallel unless
|
|
353
|
+
``--serial`` — with the pytest-xdist check made before the first
|
|
354
|
+
step. Steps the host CLI registered run after type and before test.
|
|
328
355
|
"""
|
|
329
356
|
config = _resolve_config(target, strictness)
|
|
330
357
|
resolved_workers = _resolve_workers(ctx, fast=fast, workers=workers)
|
|
@@ -335,6 +362,7 @@ def register_commands(
|
|
|
335
362
|
fast=fast,
|
|
336
363
|
workers=resolved_workers,
|
|
337
364
|
extra_steps=extra_steps,
|
|
365
|
+
require_xdist=_require_xdist(ctx),
|
|
338
366
|
)
|
|
339
367
|
)
|
|
340
368
|
|
|
@@ -59,7 +59,7 @@ def log(monkeypatch: pytest.MonkeyPatch) -> list[str]:
|
|
|
59
59
|
|
|
60
60
|
monkeypatch.setattr(subprocess, "call", fake_call)
|
|
61
61
|
monkeypatch.setattr(shutil, "which", _on_path_only)
|
|
62
|
-
monkeypatch.setattr(lint, "
|
|
62
|
+
monkeypatch.setattr(lint, "_xdist_missing", lambda: None)
|
|
63
63
|
return entries
|
|
64
64
|
|
|
65
65
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tests for ``test
|
|
1
|
+
"""Tests for the parallel pytest run of ``test`` / ``check`` (the default).
|
|
2
2
|
|
|
3
3
|
Three things matter: the argv handed to pytest, the preflight that asks
|
|
4
4
|
*pytest's* interpreter whether pytest-xdist is importable, and the exit
|
|
@@ -163,6 +163,40 @@ def test_missing_xdist_names_the_fix_and_reports_127(
|
|
|
163
163
|
assert "Traceback" not in err
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def test_preferred_parallel_without_xdist_runs_serially_and_says_so(
|
|
167
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
168
|
+
calls: list[list[str]],
|
|
169
|
+
capsys: pytest.CaptureFixture[str],
|
|
170
|
+
) -> None:
|
|
171
|
+
"""The default only prefers parallel: no plugin is a slower run, not an error."""
|
|
172
|
+
monkeypatch.setattr(subprocess, "run", _probe_answering(1))
|
|
173
|
+
assert lint.run_pytest("tests", fast=True, require_xdist=False) == 0
|
|
174
|
+
assert calls[0][1:] == ["tests"]
|
|
175
|
+
err = capsys.readouterr().err
|
|
176
|
+
assert "runs serially" in err
|
|
177
|
+
assert "pytest-xdist" in err
|
|
178
|
+
assert "tempest-cli[tools]" in err
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_preferred_parallel_with_xdist_still_spreads(calls: list[list[str]]) -> None:
|
|
182
|
+
assert lint.run_pytest(None, fast=True, require_xdist=False) == 0
|
|
183
|
+
assert calls[0][1:] == ["-n", "auto", "-p", "no:cacheprovider"]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_full_check_preferred_parallel_falls_back_without_stopping(
|
|
187
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
188
|
+
calls: list[list[str]],
|
|
189
|
+
) -> None:
|
|
190
|
+
"""The gate runs every step and the pytest one serially."""
|
|
191
|
+
monkeypatch.setattr(subprocess, "run", _probe_answering(1))
|
|
192
|
+
code = lint.run_full_check(
|
|
193
|
+
"src", config=TempestConfig(), fast=True, require_xdist=False
|
|
194
|
+
)
|
|
195
|
+
assert code == 0
|
|
196
|
+
assert len(calls) == 4
|
|
197
|
+
assert "-n" not in calls[-1]
|
|
198
|
+
|
|
199
|
+
|
|
166
200
|
def test_an_unanswerable_probe_lets_pytest_speak(
|
|
167
201
|
monkeypatch: pytest.MonkeyPatch,
|
|
168
202
|
fake_pytest: Path,
|
|
@@ -311,13 +345,31 @@ def test_cli_test_fast_forwards_workers_and_target(
|
|
|
311
345
|
main_module.app, ["test", "tests/unit", "--fast", "--workers", "4"]
|
|
312
346
|
)
|
|
313
347
|
assert result.exit_code == 0, result.output
|
|
314
|
-
assert seen == [
|
|
348
|
+
assert seen == [
|
|
349
|
+
(("tests/unit",), {"fast": True, "workers": "4", "require_xdist": True})
|
|
350
|
+
]
|
|
315
351
|
|
|
316
352
|
|
|
317
|
-
def
|
|
353
|
+
def test_cli_runs_in_parallel_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
354
|
+
"""No flag: parallel on auto workers, degrading to serial without xdist."""
|
|
355
|
+
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
356
|
+
runner.invoke(main_module.app, ["test"])
|
|
357
|
+
assert seen == [
|
|
358
|
+
((None,), {"fast": True, "workers": "auto", "require_xdist": False})
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_cli_fast_by_name_requires_xdist(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
318
363
|
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
319
364
|
runner.invoke(main_module.app, ["test", "--fast"])
|
|
320
|
-
assert seen == [((None,), {"fast": True, "workers": "auto"})]
|
|
365
|
+
assert seen == [((None,), {"fast": True, "workers": "auto", "require_xdist": True})]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def test_cli_serial_opts_out(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
369
|
+
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
370
|
+
runner.invoke(main_module.app, ["test", "--serial", "tests/unit"])
|
|
371
|
+
assert seen[0][0] == ("tests/unit",)
|
|
372
|
+
assert seen[0][1]["fast"] is False
|
|
321
373
|
|
|
322
374
|
|
|
323
375
|
def test_a_folder_named_fast_is_still_a_target(
|
|
@@ -326,18 +378,29 @@ def test_a_folder_named_fast_is_still_a_target(
|
|
|
326
378
|
"""Why ``--fast`` is a flag: ``tempest test fast`` keeps its meaning."""
|
|
327
379
|
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
328
380
|
runner.invoke(main_module.app, ["test", "fast"])
|
|
329
|
-
assert seen == [
|
|
381
|
+
assert seen == [
|
|
382
|
+
(("fast",), {"fast": True, "workers": "auto", "require_xdist": False})
|
|
383
|
+
]
|
|
330
384
|
|
|
331
385
|
|
|
332
|
-
def
|
|
386
|
+
def test_cli_workers_with_serial_is_a_usage_error(
|
|
333
387
|
monkeypatch: pytest.MonkeyPatch,
|
|
334
388
|
) -> None:
|
|
335
389
|
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
336
|
-
result = runner.invoke(main_module.app, ["test", "--workers", "4"])
|
|
390
|
+
result = runner.invoke(main_module.app, ["test", "--serial", "--workers", "4"])
|
|
337
391
|
assert result.exit_code == 2
|
|
338
392
|
assert seen == []
|
|
339
393
|
|
|
340
394
|
|
|
395
|
+
def test_cli_workers_without_a_flag_tunes_the_default(
|
|
396
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
397
|
+
) -> None:
|
|
398
|
+
seen = _capture_runner(monkeypatch, "run_pytest")
|
|
399
|
+
result = runner.invoke(main_module.app, ["test", "--workers", "4"])
|
|
400
|
+
assert result.exit_code == 0, result.output
|
|
401
|
+
assert seen[0][1]["workers"] == "4"
|
|
402
|
+
|
|
403
|
+
|
|
341
404
|
@pytest.mark.parametrize("workers", ["many", "-1", "2.5", ""])
|
|
342
405
|
def test_cli_rejects_a_worker_count_xdist_would_refuse(
|
|
343
406
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -368,6 +431,16 @@ def test_cli_check_fast_reaches_the_gate(monkeypatch: pytest.MonkeyPatch) -> Non
|
|
|
368
431
|
assert args == ("src",)
|
|
369
432
|
assert kwargs["fast"] is True
|
|
370
433
|
assert kwargs["workers"] == "3"
|
|
434
|
+
assert kwargs["require_xdist"] is True
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_cli_check_is_parallel_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
438
|
+
seen = _capture_runner(monkeypatch, "run_full_check")
|
|
439
|
+
result = runner.invoke(main_module.app, ["check", "src"])
|
|
440
|
+
assert result.exit_code == 0, result.output
|
|
441
|
+
kwargs = seen[0][1]
|
|
442
|
+
assert kwargs["fast"] is True
|
|
443
|
+
assert kwargs["require_xdist"] is False
|
|
371
444
|
|
|
372
445
|
|
|
373
446
|
def test_cli_propagates_the_runner_exit_code(
|
|
@@ -384,5 +457,5 @@ def test_test_help_explains_the_parallel_only_failure() -> None:
|
|
|
384
457
|
)
|
|
385
458
|
assert command.callback is not None
|
|
386
459
|
doc = command.callback.__doc__ or ""
|
|
387
|
-
assert "fails only
|
|
388
|
-
assert "tests/test_x.py::test_name" in doc
|
|
460
|
+
assert "fails only in parallel" in doc
|
|
461
|
+
assert "test --serial tests/test_x.py::test_name" in doc
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.en-US.md
RENAMED
|
File without changes
|
{tempest_cli-0.5.1 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.pt-BR.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|