doeff-hy-pytest 0.5.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.
- doeff_hy_pytest-0.5.0/.gitignore +142 -0
- doeff_hy_pytest-0.5.0/LICENSE +21 -0
- doeff_hy_pytest-0.5.0/PKG-INFO +163 -0
- doeff_hy_pytest-0.5.0/README.md +151 -0
- doeff_hy_pytest-0.5.0/pyproject.toml +30 -0
- doeff_hy_pytest-0.5.0/src/doeff_hy_pytest/__init__.py +25 -0
- doeff_hy_pytest-0.5.0/src/doeff_hy_pytest/budget.py +1698 -0
- doeff_hy_pytest-0.5.0/src/doeff_hy_pytest/py.typed +0 -0
- doeff_hy_pytest-0.5.0/src/doeff_hy_pytest/python_calls.py +147 -0
- doeff_hy_pytest-0.5.0/tests/test_budget.py +1481 -0
- doeff_hy_pytest-0.5.0/tests/test_plugin_distribution.py +55 -0
- doeff_hy_pytest-0.5.0/tests/test_python_calls.py +362 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
# macOS debug symbols for the compiled extension above: build output that
|
|
7
|
+
# embeds the builder's absolute paths, so it must not be distributed either.
|
|
8
|
+
*.dSYM/
|
|
9
|
+
.Python
|
|
10
|
+
env/
|
|
11
|
+
venv/
|
|
12
|
+
.venv/
|
|
13
|
+
ENV/
|
|
14
|
+
env.bak/
|
|
15
|
+
venv.bak/
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
|
|
19
|
+
# Distribution / packaging
|
|
20
|
+
.Python
|
|
21
|
+
build/
|
|
22
|
+
develop-eggs/
|
|
23
|
+
dist/
|
|
24
|
+
downloads/
|
|
25
|
+
eggs/
|
|
26
|
+
.eggs/
|
|
27
|
+
lib/
|
|
28
|
+
lib64/
|
|
29
|
+
parts/
|
|
30
|
+
sdist/
|
|
31
|
+
var/
|
|
32
|
+
wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
MANIFEST
|
|
37
|
+
|
|
38
|
+
# Testing
|
|
39
|
+
.pytest_cache/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
.hypothesis/
|
|
45
|
+
htmlcov/
|
|
46
|
+
.tox/
|
|
47
|
+
|
|
48
|
+
# IDE
|
|
49
|
+
# Generated by Eclipse buildship and machine-specific: embeds the local Java home
|
|
50
|
+
# and toolchain paths. The gradle build does not read it.
|
|
51
|
+
ide-plugins/pycharm/.settings/org.eclipse.buildship.core.prefs
|
|
52
|
+
.idea/
|
|
53
|
+
.vscode/
|
|
54
|
+
*.swp
|
|
55
|
+
*.swo
|
|
56
|
+
*~
|
|
57
|
+
.DS_Store
|
|
58
|
+
|
|
59
|
+
# UV
|
|
60
|
+
!uv.lock
|
|
61
|
+
.venv/
|
|
62
|
+
|
|
63
|
+
# Ruff
|
|
64
|
+
.ruff_cache/
|
|
65
|
+
|
|
66
|
+
# MyPy
|
|
67
|
+
.mypy_cache/
|
|
68
|
+
.dmypy.json
|
|
69
|
+
dmypy.json
|
|
70
|
+
|
|
71
|
+
# Generated logs, traces, and local databases
|
|
72
|
+
.doeff-lint.jsonl
|
|
73
|
+
.doeff-flow/
|
|
74
|
+
*.jsonl
|
|
75
|
+
# 旧い版の coordinator の置き場の log(版上げの下見の検 test_deployed_state_upgrade.hy が読む fixture — 生成された log ではない)
|
|
76
|
+
!packages/doeff-cluster/tests/fixtures/coordinator_state_*/wal/wal.jsonl
|
|
77
|
+
# codex の app-server の実物の行を録った物(行の分類の検 packages/doeff-codex/tests/test_lines.hy が読む fixture — 生成された log ではない)
|
|
78
|
+
!packages/doeff-codex/tests/recorded/codex-*/*.jsonl
|
|
79
|
+
*.db
|
|
80
|
+
*.db-journal
|
|
81
|
+
*.db-shm
|
|
82
|
+
*.db-wal
|
|
83
|
+
*.sqlite
|
|
84
|
+
*.sqlite-journal
|
|
85
|
+
*.sqlite-shm
|
|
86
|
+
*.sqlite-wal
|
|
87
|
+
*.sqlite3
|
|
88
|
+
*.sqlite3-journal
|
|
89
|
+
*.sqlite3-shm
|
|
90
|
+
*.sqlite3-wal
|
|
91
|
+
None
|
|
92
|
+
Untitled
|
|
93
|
+
|
|
94
|
+
# Local agent and browser automation state
|
|
95
|
+
.agent-home/
|
|
96
|
+
.playwright-mcp/
|
|
97
|
+
.claude/settings.local.json
|
|
98
|
+
.claude/skills/install-vscode-plugin/
|
|
99
|
+
.agents/skills/install-vscode-plugin/
|
|
100
|
+
|
|
101
|
+
# Accidental desktop helper bundle captured at repository root
|
|
102
|
+
/server.js
|
|
103
|
+
|
|
104
|
+
# Rust build artifacts
|
|
105
|
+
rust/**/target/
|
|
106
|
+
packages/**/target/
|
|
107
|
+
|
|
108
|
+
# Local deploy keys
|
|
109
|
+
proboscis_doeff_deploy
|
|
110
|
+
proboscis_doeff_deploy.pub
|
|
111
|
+
|
|
112
|
+
# Worktrees and orchestration
|
|
113
|
+
.git-worktrees/
|
|
114
|
+
.orch/
|
|
115
|
+
.sisyphus/
|
|
116
|
+
VAULT
|
|
117
|
+
|
|
118
|
+
# Override global gitignore for examples
|
|
119
|
+
!examples/
|
|
120
|
+
ORCH_PROMPT.md
|
|
121
|
+
.worktrees/
|
|
122
|
+
|
|
123
|
+
# Local workspace artifacts
|
|
124
|
+
# Repo-root scratch directory only. A `python/` directory inside a package
|
|
125
|
+
# (e.g. packages/doeff-indexer/python/) is NOT covered by this rule.
|
|
126
|
+
/python/
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# 設計記録は repo に入れる — global の除外(`artifacts` の dir・`*.html`・`*.svg`)を打ち消す
|
|
130
|
+
# (打ち消しは dir を先に戻さないと中身に効かない)
|
|
131
|
+
!docs/design/*/artifacts/
|
|
132
|
+
!docs/design/**
|
|
133
|
+
# 書き込みロックだけは入れない(道具が作る一時 file — 打ち消しの後に置く)
|
|
134
|
+
docs/design/*/.records.lock
|
|
135
|
+
|
|
136
|
+
# Repository-owned Zenn article visuals (override global image ignores)
|
|
137
|
+
!/images/zenn-use-cases-v0/*.png
|
|
138
|
+
# 記事が参照する採用画像は generated/ の下に在る(上の行は直下だけに効き、2026-09-16 の 47 枚は入らなかった)
|
|
139
|
+
!/images/zenn-use-cases-v0/generated/
|
|
140
|
+
!/images/zenn-use-cases-v0/generated/*.png
|
|
141
|
+
# 手で書いた図の正本(global の `*.svg` の除外を打ち消す)
|
|
142
|
+
!/publications/zenn-use-cases-v0/visuals/*.svg
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 proboscis contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do 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,163 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: doeff-hy-pytest
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: pytest plugin for doeff-hy tests: a Python 3.14 fix for Hy and optional per-test time limits
|
|
5
|
+
Project-URL: Homepage, https://github.com/proboscis/doeff/tree/main/packages/doeff-hy-pytest
|
|
6
|
+
Project-URL: Repository, https://github.com/proboscis/doeff
|
|
7
|
+
Project-URL: Issues, https://github.com/proboscis/doeff/issues
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# doeff-hy-pytest
|
|
14
|
+
|
|
15
|
+
The pytest plugin for tests written with
|
|
16
|
+
[doeff-hy](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy).
|
|
17
|
+
|
|
18
|
+
**This README is for** developers who run pytest on Hy code that uses doeff. After
|
|
19
|
+
reading it you know what the plugin does without any setting, and how to turn on its
|
|
20
|
+
optional time limits for tests.
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
pytest loads the plugin by itself (entry point `pytest11`, name `doeff_hy`). It does
|
|
25
|
+
two things:
|
|
26
|
+
|
|
27
|
+
| Job | When it acts | Setting needed |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| Fixes a Hy problem on Python 3.14 | At pytest start-up, before any conftest imports Hy | None |
|
|
30
|
+
| Time limits for Hy tests | During each test in a `.hy` file | Yes; off by default |
|
|
31
|
+
|
|
32
|
+
**The Python 3.14 fix.** Hy replaces `ast.unparse`. On Python 3.14, when pytest reads
|
|
33
|
+
fixture annotations as strings, that replacement recurses without end. The plugin
|
|
34
|
+
installs a corrected replacement (`doeff_hy.ast_unparse.install()`) as soon as pytest
|
|
35
|
+
starts, so it is in place even if a conftest or another plugin imports Hy first.
|
|
36
|
+
|
|
37
|
+
**Why this is a separate package.** pytest marks every module of a distribution that
|
|
38
|
+
declares a `pytest11` entry point for assertion rewriting. If doeff-hy itself declared
|
|
39
|
+
the entry point, pytest would try to rewrite doeff-hy's `.hy` files as Python and the
|
|
40
|
+
import would fail with a `SyntaxError`. This package contains no `.hy` files, so the
|
|
41
|
+
mark is harmless here.
|
|
42
|
+
|
|
43
|
+
The plugin does **not** collect `.hy` test files. The doeff-hy README shows the
|
|
44
|
+
`conftest.py` hook that collects them:
|
|
45
|
+
[Testing with deftest](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy#testing-with-deftest).
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
doeff-hy depends on this package, so it is already installed when doeff or doeff-hy
|
|
50
|
+
is:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install doeff # installs doeff-hy and doeff-hy-pytest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You do not list it yourself. The exception: if you install doeff-hy from a git
|
|
57
|
+
revision with uv, add a `[tool.uv.sources]` entry for doeff-hy-pytest from the same
|
|
58
|
+
revision.
|
|
59
|
+
|
|
60
|
+
To check that pytest loaded it, look for `doeff-hy-pytest` in the `plugins:` line of
|
|
61
|
+
`pytest` output, or in the plugins that `pytest -VV` lists:
|
|
62
|
+
|
|
63
|
+
```bash run
|
|
64
|
+
pytest -VV # doeff-hy-pytest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Time limits for Hy tests
|
|
68
|
+
|
|
69
|
+
Tests that run programs against in-memory or scripted handlers should be fast. The
|
|
70
|
+
limits tell you when one of them becomes slow. They apply only to tests defined in
|
|
71
|
+
`.hy` files (`deftest` and plain test functions in the same file); Python test files
|
|
72
|
+
are not measured. With none of the three limits set, the plugin measures nothing.
|
|
73
|
+
|
|
74
|
+
### The three limits
|
|
75
|
+
|
|
76
|
+
| Setting | What it limits | Unit | How it is measured |
|
|
77
|
+
| --- | --- | --- | --- |
|
|
78
|
+
| `doeff_test_call_budget_steps` | Running one test (pytest's `call` phase; fixture setup and teardown are not included) | doeff-vm steps (a positive integer, or `default` = 100,000) | Steps the VM executed. The count does not depend on machine load; CPU seconds are reported but not judged |
|
|
79
|
+
| `doeff_test_call_budget_by_marker` | Running one test that carries a given pytest mark | Wall-clock seconds, one line per mark: `mark=seconds` | For tests that do real I/O (child processes, sockets), whose cost does not show up as VM steps. A test that matches a line is judged by seconds even when a step limit is set; if several lines match, the longest limit applies |
|
|
80
|
+
| `doeff_test_collect_budget_seconds` | Importing one `.hy` test file | CPU seconds | Only when the file is imported through the `pytest_doeff_import_hy_module` hook, which the collector of [doeff-adr](https://github.com/proboscis/doeff/tree/main/packages/doeff-adr) calls. With the plain `conftest.py` collector from the doeff-hy README, nothing is measured |
|
|
81
|
+
|
|
82
|
+
A test that matches no `by_marker` line is judged by steps if a step limit is set, and
|
|
83
|
+
is not measured otherwise. A test that executes 0 steps (it only reads data and never
|
|
84
|
+
runs the VM) is within the limit.
|
|
85
|
+
|
|
86
|
+
Measured time excludes work that is not the test's own cost: compiling source to
|
|
87
|
+
bytecode when no cache exists, the first import of another module (a one-time cost of
|
|
88
|
+
a shared dependency), and garbage collection. Each is subtracted on the same clock
|
|
89
|
+
that is being judged.
|
|
90
|
+
|
|
91
|
+
### What happens when a test goes over
|
|
92
|
+
|
|
93
|
+
| Setting | Values |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `doeff_test_budget_mode` | `report` (default): warn and list the slow tests at the end. `fail`: the slow test fails |
|
|
96
|
+
| `doeff_test_budget_registry` | Directories (relative to the pytest rootdir, one per line) that list tests allowed to go over. One file per test: `<first 12 hex chars of sha256(key)>.txt`; line 1 is the key (the pytest node id, or the file path relative to rootdir for the collection limit), the following lines give the reason. When a test goes over, the message names the file to add in the first directory |
|
|
97
|
+
|
|
98
|
+
In `fail` mode, a listed test that finishes in at most half of its limit also fails,
|
|
99
|
+
as a stale entry, and the message names the file to delete. Between half the limit and
|
|
100
|
+
the limit, the summary only reports that the entry can be removed.
|
|
101
|
+
|
|
102
|
+
### Example
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
[tool.pytest.ini_options]
|
|
106
|
+
doeff_test_call_budget_steps = "default"
|
|
107
|
+
doeff_test_call_budget_by_marker = ["real_world=10"]
|
|
108
|
+
doeff_test_collect_budget_seconds = 1.0
|
|
109
|
+
doeff_test_budget_mode = "fail"
|
|
110
|
+
doeff_test_budget_registry = ["tests/over-budget"]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
With this configuration, each Hy test may run 100,000 VM steps; a test marked
|
|
114
|
+
`real_world` may take 10 seconds of wall-clock time instead; a test over its limit
|
|
115
|
+
fails unless `tests/over-budget` lists it.
|
|
116
|
+
|
|
117
|
+
### More settings
|
|
118
|
+
|
|
119
|
+
| Setting or API | What it does |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `step_budget_multiplier` | While a step-limited test runs, the scheduler stops it once it reaches *limit × multiplier* steps (default 20), so a test that never finishes fails instead of hanging |
|
|
122
|
+
| `doeff_test_call_steps_counted` | `call` (default): count all steps of the test. `world-running`: for tests that run a simulated world recorded in `doeff_core_effects.world_phases`, count only the steps of the world's running phase |
|
|
123
|
+
| `doeff_hy_pytest.budget.declare_step_budget(test, steps, reason)` | Gives one test its own step limit with a one-line reason, next to the test's definition. Over the declared value fails; at most half of it fails as stale |
|
|
124
|
+
| `--doeff-python-calls` | Command-line flag. Counts Python calls (`sys.monitoring` CALL events) during tests that declared a step limit, and prints one summary line. Reports only; never fails a test |
|
|
125
|
+
|
|
126
|
+
The step limits need a doeff-vm build that exposes step counts
|
|
127
|
+
(`doeff_vm.doeff_vm.vm_work_counts`). If it is missing, pytest stops at start-up with
|
|
128
|
+
a `UsageError` instead of silently falling back to seconds.
|
|
129
|
+
|
|
130
|
+
### Output
|
|
131
|
+
|
|
132
|
+
With `-v`, the summary prints one line per measured test: steps, handler calls, and
|
|
133
|
+
CPU seconds. Use these numbers to pick a step limit.
|
|
134
|
+
|
|
135
|
+
The report and failure messages are currently written in Japanese.
|
|
136
|
+
|
|
137
|
+
## How it fits with the other doeff packages
|
|
138
|
+
|
|
139
|
+
doeff is published as five core packages. `pip install doeff` installs all of them.
|
|
140
|
+
|
|
141
|
+
| Package | Role |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| [doeff](https://github.com/proboscis/doeff) | The Python API: `@do`, handler installers, `run`, the `doeff` CLI |
|
|
144
|
+
| [doeff-vm](https://github.com/proboscis/doeff/tree/main/packages/doeff-vm) | The Rust VM that executes programs |
|
|
145
|
+
| [doeff-core-effects](https://github.com/proboscis/doeff/tree/main/packages/doeff-core-effects) | The standard effects and their handlers |
|
|
146
|
+
| [doeff-hy](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy) | Macros for writing doeff code in Hy, a Lisp that compiles to Python |
|
|
147
|
+
| **doeff-hy-pytest** | This package: the pytest plugin for tests written with doeff-hy |
|
|
148
|
+
|
|
149
|
+
Arrows point from a package to a package it depends on:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
doeff ──► doeff-vm
|
|
153
|
+
│
|
|
154
|
+
└─────► doeff-core-effects ──► doeff-hy ──► doeff-hy-pytest
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
doeff-hy also depends on doeff and doeff-core-effects, so the five packages are
|
|
158
|
+
released together with matching versions. doeff-hy-pytest declares no dependencies of
|
|
159
|
+
its own; it imports doeff-hy, which is always installed alongside it.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT. See [LICENSE](https://github.com/proboscis/doeff/blob/main/LICENSE).
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# doeff-hy-pytest
|
|
2
|
+
|
|
3
|
+
The pytest plugin for tests written with
|
|
4
|
+
[doeff-hy](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy).
|
|
5
|
+
|
|
6
|
+
**This README is for** developers who run pytest on Hy code that uses doeff. After
|
|
7
|
+
reading it you know what the plugin does without any setting, and how to turn on its
|
|
8
|
+
optional time limits for tests.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
pytest loads the plugin by itself (entry point `pytest11`, name `doeff_hy`). It does
|
|
13
|
+
two things:
|
|
14
|
+
|
|
15
|
+
| Job | When it acts | Setting needed |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Fixes a Hy problem on Python 3.14 | At pytest start-up, before any conftest imports Hy | None |
|
|
18
|
+
| Time limits for Hy tests | During each test in a `.hy` file | Yes; off by default |
|
|
19
|
+
|
|
20
|
+
**The Python 3.14 fix.** Hy replaces `ast.unparse`. On Python 3.14, when pytest reads
|
|
21
|
+
fixture annotations as strings, that replacement recurses without end. The plugin
|
|
22
|
+
installs a corrected replacement (`doeff_hy.ast_unparse.install()`) as soon as pytest
|
|
23
|
+
starts, so it is in place even if a conftest or another plugin imports Hy first.
|
|
24
|
+
|
|
25
|
+
**Why this is a separate package.** pytest marks every module of a distribution that
|
|
26
|
+
declares a `pytest11` entry point for assertion rewriting. If doeff-hy itself declared
|
|
27
|
+
the entry point, pytest would try to rewrite doeff-hy's `.hy` files as Python and the
|
|
28
|
+
import would fail with a `SyntaxError`. This package contains no `.hy` files, so the
|
|
29
|
+
mark is harmless here.
|
|
30
|
+
|
|
31
|
+
The plugin does **not** collect `.hy` test files. The doeff-hy README shows the
|
|
32
|
+
`conftest.py` hook that collects them:
|
|
33
|
+
[Testing with deftest](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy#testing-with-deftest).
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
doeff-hy depends on this package, so it is already installed when doeff or doeff-hy
|
|
38
|
+
is:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install doeff # installs doeff-hy and doeff-hy-pytest
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You do not list it yourself. The exception: if you install doeff-hy from a git
|
|
45
|
+
revision with uv, add a `[tool.uv.sources]` entry for doeff-hy-pytest from the same
|
|
46
|
+
revision.
|
|
47
|
+
|
|
48
|
+
To check that pytest loaded it, look for `doeff-hy-pytest` in the `plugins:` line of
|
|
49
|
+
`pytest` output, or in the plugins that `pytest -VV` lists:
|
|
50
|
+
|
|
51
|
+
```bash run
|
|
52
|
+
pytest -VV # doeff-hy-pytest
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Time limits for Hy tests
|
|
56
|
+
|
|
57
|
+
Tests that run programs against in-memory or scripted handlers should be fast. The
|
|
58
|
+
limits tell you when one of them becomes slow. They apply only to tests defined in
|
|
59
|
+
`.hy` files (`deftest` and plain test functions in the same file); Python test files
|
|
60
|
+
are not measured. With none of the three limits set, the plugin measures nothing.
|
|
61
|
+
|
|
62
|
+
### The three limits
|
|
63
|
+
|
|
64
|
+
| Setting | What it limits | Unit | How it is measured |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `doeff_test_call_budget_steps` | Running one test (pytest's `call` phase; fixture setup and teardown are not included) | doeff-vm steps (a positive integer, or `default` = 100,000) | Steps the VM executed. The count does not depend on machine load; CPU seconds are reported but not judged |
|
|
67
|
+
| `doeff_test_call_budget_by_marker` | Running one test that carries a given pytest mark | Wall-clock seconds, one line per mark: `mark=seconds` | For tests that do real I/O (child processes, sockets), whose cost does not show up as VM steps. A test that matches a line is judged by seconds even when a step limit is set; if several lines match, the longest limit applies |
|
|
68
|
+
| `doeff_test_collect_budget_seconds` | Importing one `.hy` test file | CPU seconds | Only when the file is imported through the `pytest_doeff_import_hy_module` hook, which the collector of [doeff-adr](https://github.com/proboscis/doeff/tree/main/packages/doeff-adr) calls. With the plain `conftest.py` collector from the doeff-hy README, nothing is measured |
|
|
69
|
+
|
|
70
|
+
A test that matches no `by_marker` line is judged by steps if a step limit is set, and
|
|
71
|
+
is not measured otherwise. A test that executes 0 steps (it only reads data and never
|
|
72
|
+
runs the VM) is within the limit.
|
|
73
|
+
|
|
74
|
+
Measured time excludes work that is not the test's own cost: compiling source to
|
|
75
|
+
bytecode when no cache exists, the first import of another module (a one-time cost of
|
|
76
|
+
a shared dependency), and garbage collection. Each is subtracted on the same clock
|
|
77
|
+
that is being judged.
|
|
78
|
+
|
|
79
|
+
### What happens when a test goes over
|
|
80
|
+
|
|
81
|
+
| Setting | Values |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `doeff_test_budget_mode` | `report` (default): warn and list the slow tests at the end. `fail`: the slow test fails |
|
|
84
|
+
| `doeff_test_budget_registry` | Directories (relative to the pytest rootdir, one per line) that list tests allowed to go over. One file per test: `<first 12 hex chars of sha256(key)>.txt`; line 1 is the key (the pytest node id, or the file path relative to rootdir for the collection limit), the following lines give the reason. When a test goes over, the message names the file to add in the first directory |
|
|
85
|
+
|
|
86
|
+
In `fail` mode, a listed test that finishes in at most half of its limit also fails,
|
|
87
|
+
as a stale entry, and the message names the file to delete. Between half the limit and
|
|
88
|
+
the limit, the summary only reports that the entry can be removed.
|
|
89
|
+
|
|
90
|
+
### Example
|
|
91
|
+
|
|
92
|
+
```toml
|
|
93
|
+
[tool.pytest.ini_options]
|
|
94
|
+
doeff_test_call_budget_steps = "default"
|
|
95
|
+
doeff_test_call_budget_by_marker = ["real_world=10"]
|
|
96
|
+
doeff_test_collect_budget_seconds = 1.0
|
|
97
|
+
doeff_test_budget_mode = "fail"
|
|
98
|
+
doeff_test_budget_registry = ["tests/over-budget"]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With this configuration, each Hy test may run 100,000 VM steps; a test marked
|
|
102
|
+
`real_world` may take 10 seconds of wall-clock time instead; a test over its limit
|
|
103
|
+
fails unless `tests/over-budget` lists it.
|
|
104
|
+
|
|
105
|
+
### More settings
|
|
106
|
+
|
|
107
|
+
| Setting or API | What it does |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `step_budget_multiplier` | While a step-limited test runs, the scheduler stops it once it reaches *limit × multiplier* steps (default 20), so a test that never finishes fails instead of hanging |
|
|
110
|
+
| `doeff_test_call_steps_counted` | `call` (default): count all steps of the test. `world-running`: for tests that run a simulated world recorded in `doeff_core_effects.world_phases`, count only the steps of the world's running phase |
|
|
111
|
+
| `doeff_hy_pytest.budget.declare_step_budget(test, steps, reason)` | Gives one test its own step limit with a one-line reason, next to the test's definition. Over the declared value fails; at most half of it fails as stale |
|
|
112
|
+
| `--doeff-python-calls` | Command-line flag. Counts Python calls (`sys.monitoring` CALL events) during tests that declared a step limit, and prints one summary line. Reports only; never fails a test |
|
|
113
|
+
|
|
114
|
+
The step limits need a doeff-vm build that exposes step counts
|
|
115
|
+
(`doeff_vm.doeff_vm.vm_work_counts`). If it is missing, pytest stops at start-up with
|
|
116
|
+
a `UsageError` instead of silently falling back to seconds.
|
|
117
|
+
|
|
118
|
+
### Output
|
|
119
|
+
|
|
120
|
+
With `-v`, the summary prints one line per measured test: steps, handler calls, and
|
|
121
|
+
CPU seconds. Use these numbers to pick a step limit.
|
|
122
|
+
|
|
123
|
+
The report and failure messages are currently written in Japanese.
|
|
124
|
+
|
|
125
|
+
## How it fits with the other doeff packages
|
|
126
|
+
|
|
127
|
+
doeff is published as five core packages. `pip install doeff` installs all of them.
|
|
128
|
+
|
|
129
|
+
| Package | Role |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| [doeff](https://github.com/proboscis/doeff) | The Python API: `@do`, handler installers, `run`, the `doeff` CLI |
|
|
132
|
+
| [doeff-vm](https://github.com/proboscis/doeff/tree/main/packages/doeff-vm) | The Rust VM that executes programs |
|
|
133
|
+
| [doeff-core-effects](https://github.com/proboscis/doeff/tree/main/packages/doeff-core-effects) | The standard effects and their handlers |
|
|
134
|
+
| [doeff-hy](https://github.com/proboscis/doeff/tree/main/packages/doeff-hy) | Macros for writing doeff code in Hy, a Lisp that compiles to Python |
|
|
135
|
+
| **doeff-hy-pytest** | This package: the pytest plugin for tests written with doeff-hy |
|
|
136
|
+
|
|
137
|
+
Arrows point from a package to a package it depends on:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
doeff ──► doeff-vm
|
|
141
|
+
│
|
|
142
|
+
└─────► doeff-core-effects ──► doeff-hy ──► doeff-hy-pytest
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
doeff-hy also depends on doeff and doeff-core-effects, so the five packages are
|
|
146
|
+
released together with matching versions. doeff-hy-pytest declares no dependencies of
|
|
147
|
+
its own; it imports doeff-hy, which is always installed alongside it.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT. See [LICENSE](https://github.com/proboscis/doeff/blob/main/LICENSE).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# license の SPDX の式と license-files(PEP 639)を既定で core metadata に書くのは hatchling 1.27 から(既定の版 2.4)。
|
|
3
|
+
requires = ["hatchling>=1.27"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "doeff-hy-pytest"
|
|
8
|
+
version = "0.5.0"
|
|
9
|
+
description = "pytest plugin for doeff-hy tests: a Python 3.14 fix for Hy and optional per-test time limits"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
# 依存を宣言しない: この plugin は doeff-hy に引かれて入る(doeff-hy → doeff-hy-pytest の向きだけ)。
|
|
15
|
+
# 当て物の本体は doeff_hy.ast_unparse にあり、plugin はそれを import して当てるだけ。
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://github.com/proboscis/doeff/tree/main/packages/doeff-hy-pytest"
|
|
20
|
+
Repository = "https://github.com/proboscis/doeff"
|
|
21
|
+
Issues = "https://github.com/proboscis/doeff/issues"
|
|
22
|
+
|
|
23
|
+
# pytest は pytest11 の entry point を持つ配布物の**全 module** に assert の書き換えの印を付ける。doeff-hy 自身がこの
|
|
24
|
+
# entry point を持つと doeff_hy の .hy(macros.hy など)まで Python として書き換えようとして import が落ちる(wheel で
|
|
25
|
+
# 入れた repo だけ — editable の install では印が付かない)。だから plugin は .hy を持たないこの配布物に置く。
|
|
26
|
+
[project.entry-points.pytest11]
|
|
27
|
+
doeff_hy = "doeff_hy_pytest"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/doeff_hy_pytest"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""doeff-hy の pytest plugin(entry point `pytest11` の名 `doeff_hy`)。
|
|
2
|
+
|
|
3
|
+
pytest は起動時に plugin と conftest を読み、fixture の注記を Python 3.14 では
|
|
4
|
+
`annotation_format=Format.STRING` で読む。Hy がそれより前に(他の plugin や conftest から)
|
|
5
|
+
import されていると、Hy の `ast.unparse` の差し替えがそこで止まらない再帰になる
|
|
6
|
+
(doeff_hy/ast_unparse.py)。`import doeff_hy` を待たず、pytest の起動の時点で置き換えるための plugin。
|
|
7
|
+
|
|
8
|
+
もう 1 つの役目は Hy の検の時間の上限(doeff_hy_pytest/budget.py — 設定が無ければ何もしない)。hook は
|
|
9
|
+
この module から輸出して、entry point の 1 つ(`doeff_hy`)のまま載せる。
|
|
10
|
+
|
|
11
|
+
この module が doeff-hy とは別の配布物にある理由は pyproject.toml の註(pytest の assert の書き換えの印が
|
|
12
|
+
配布物の全 module に付き、doeff_hy の .hy を壊す)。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from doeff_hy.ast_unparse import install
|
|
16
|
+
|
|
17
|
+
from doeff_hy_pytest.budget import pytest_addoption as pytest_addoption
|
|
18
|
+
from doeff_hy_pytest.budget import pytest_configure as pytest_configure
|
|
19
|
+
from doeff_hy_pytest.budget import pytest_doeff_import_hy_module as pytest_doeff_import_hy_module
|
|
20
|
+
from doeff_hy_pytest.budget import pytest_runtest_call as pytest_runtest_call
|
|
21
|
+
from doeff_hy_pytest.budget import pytest_runtest_makereport as pytest_runtest_makereport
|
|
22
|
+
from doeff_hy_pytest.budget import pytest_terminal_summary as pytest_terminal_summary
|
|
23
|
+
from doeff_hy_pytest.budget import pytest_unconfigure as pytest_unconfigure
|
|
24
|
+
|
|
25
|
+
install()
|