pytest-embedded-wireskein 0.0.1__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.
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ .Python
6
+
7
+ # Build / packaging
8
+ build/
9
+ dist/
10
+ *.egg-info/
11
+ .eggs/
12
+ pip-wheel-metadata/
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ env/
18
+ .env
19
+
20
+ # Test / tooling caches
21
+ .pytest_cache/
22
+ .coverage
23
+ .coverage.*
24
+ htmlcov/
25
+ .mypy_cache/
26
+ .ruff_cache/
27
+ report.html
28
+
29
+ # IDE / OS
30
+ .DS_Store
31
+ .idea/
32
+ .vscode/
33
+
34
+ # Local dependency locks
35
+ uv.lock
36
+
@@ -0,0 +1,7 @@
1
+ # Changelog / 変更履歴
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.0.1
6
+ - (EN) First beta. The `ws_run` fixture gives each test a `wireskein.runlog.Recorder` writing into `<test_case_tempdir>/wireskein/`, next to `dut.log`. When the test body ends the run is verified: a failing check fails the test in its call phase (FAILED), `report.json` and `report.xml` are written, and the result lines are attached to the test report. `--wireskein-verify` / `wireskein_verify` = `fail` (default), `report` or `off`.
7
+ - (JA) 最初のβ版。`ws_run` fixture が test ごとに `wireskein.runlog.Recorder` を渡し、`dut.log` の隣の `<test_case_tempdir>/wireskein/` に記録する。test の本体が終わると照合する。検査が NG なら call の段で test を失敗(FAILED)にし、`report.json` と `report.xml` を書き、結果の行を test の報告に付ける。`--wireskein-verify` / `wireskein_verify` は `fail`(既定)、`report`、`off`。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Open Embedded Probe
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,111 @@
1
+ Metadata-Version: 2.5
2
+ Name: pytest-embedded-wireskein
3
+ Version: 0.0.1
4
+ Summary: pytest-embedded plugin: record logic-analyzer captures per test with WireSkein and fail the test when a waveform check fails
5
+ Project-URL: Homepage, https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein
6
+ Project-URL: Repository, https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein
7
+ Author: TANAKA Masayuki
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Requires-Python: >=3.13
15
+ Requires-Dist: pytest-embedded>=2.0
16
+ Requires-Dist: pytest>=8
17
+ Requires-Dist: wireskein>=0.0.1
18
+ Description-Content-Type: text/markdown
19
+
20
+ # pytest-embedded-wireskein
21
+
22
+ [日本語 README](https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein/blob/main/README.ja.md)
23
+
24
+ A [pytest-embedded](https://github.com/espressif/pytest-embedded) plugin that gives each test a [WireSkein](https://github.com/Open-Embedded-Probe/wireskein) recorder, `ws_run`. The test records the commands it sent, the logic-analyzer captures, and what each step should look like on the wire. When the test body ends, the plugin checks the captures against these expectations and fails the test if a check fails.
25
+
26
+ Status: **beta**.
27
+
28
+ ## Install
29
+
30
+ ```sh
31
+ pip install pytest-embedded-wireskein
32
+ ```
33
+
34
+ Python 3.13 or newer. This installs `wireskein` and `pytest-embedded`.
35
+
36
+ ## Use
37
+
38
+ ```python
39
+ from wireskein.runlog import square, level, only_moving
40
+
41
+ def test_pwm(dut, ws_run, probe): # `probe` is whatever drives your logic analyzer
42
+ with ws_run.section(1, "pwm"):
43
+ for duty in (64, 128, 0):
44
+ want = [square("PA1", 1000, duty / 255), only_moving(["PA1"])] if duty else [level("PA1", 0)]
45
+ with ws_run.section(2, f"duty={duty}", expect=want):
46
+ ws_run.command(f"PWM {duty}")
47
+ dut.write(f"PWM {duty}")
48
+ ws_run.reply(dut.expect(r"PWM duty=\d+").group(0).decode())
49
+ t = ws_run.armed() # right after arming the capture
50
+ data, rate = probe.capture() # bytes, one sample per byte, bit k = pin k
51
+ ws_run.capture(data, rate, ["PA1", "PA0"], t)
52
+ ```
53
+
54
+ When `test_pwm` returns, the plugin closes the run and verifies it. If a check fails, the test fails in its call phase (FAILED, not ERROR):
55
+
56
+ ```text
57
+ FAILED test_pwm.py::test_pwm - wireskein: 1 NG (5 ok, 1 ng, 0 unchecked (4 segments, 3 captures))
58
+ NG pwm/duty=128 square c0002.bin duty 0.6999 vs 0.5020
59
+ report: /tmp/pytest-embedded/2026-09-29_12-00-00-000000/test_pwm/wireskein/report.json
60
+ ```
61
+
62
+ The checks (`square`, `level`, `starts`, `ends`, `only_moving`, `pulses`, `i2c`, `spi`, `uart`) and the heading rules are described in the [WireSkein README](https://github.com/Open-Embedded-Probe/wireskein#checking-a-test-run).
63
+
64
+ ### Where the run is written
65
+
66
+ `ws_run` writes to `<test_case_tempdir>/wireskein/`, next to pytest-embedded's `dut.log`: `<root-logdir>/pytest-embedded/<time>/<test name>/wireskein/`.
67
+
68
+ | File | Content |
69
+ | --- | --- |
70
+ | `run.json` | Headings, commands, replies, notes, captures and expectations (WireSkein run format) |
71
+ | `c0001.bin`, ... | The captures |
72
+ | `report.json` | Every result with measured values, and the log |
73
+ | `report.xml` | The results as JUnit XML |
74
+
75
+ The same directory can be checked again later with `wireskein verify <dir>`. The test's `user_properties` carry `wireskein_report` (the path of `report.json`), and the result lines are added to the test report as a `wireskein` section (shown with `-rA` or on failure).
76
+
77
+ ### When the plugin verifies
78
+
79
+ - The run is verified after the test body, only if the test recorded a capture or an expectation.
80
+ - A check that fails makes the test fail.
81
+ - A check whose pins were not captured is unchecked and does not fail the test.
82
+ - If the test body already failed, the run is still recorded and verified, but the test's own failure is kept.
83
+
84
+ ### Options
85
+
86
+ | Option | ini | Default | Meaning |
87
+ | --- | --- | --- | --- |
88
+ | `--wireskein-verify=fail\|report\|off` | `wireskein_verify` | `fail` | `fail`: verify and fail on NG. `report`: verify and write the reports, never fail. `off`: record only |
89
+
90
+ ### Connecting a probe
91
+
92
+ This plugin knows nothing about probes or targets. A fixture that drives the logic analyzer (for example the one of a board-family plugin) connects to `ws_run` when both are installed:
93
+
94
+ - right after arming a capture: `t = ws_run.armed()`
95
+ - when the samples are read: `ws_run.capture(data, rate, bits, t, start_us=..., time_base_slipped=True)`. `bits` lists the target's pin names in bit order. Pass `time_base_slipped` only when the probe reports it.
96
+ - the console traffic: `ws_run.command(text)`, `ws_run.reply(text)`
97
+
98
+ ## Development
99
+
100
+ ```sh
101
+ uv sync
102
+ uv run pytest
103
+ ```
104
+
105
+ ## Release
106
+
107
+ The release works the same way as in [pytest-embedded-arduino-cli](https://github.com/tanakamasayuki/pytest-embedded-arduino-cli#release). Update `## Unreleased` in `CHANGELOG.md`, then run the `Release` workflow with the version (for example `0.0.2`). PyPI publishing uses Trusted Publishing.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,101 @@
1
+ # pytest-embedded-wireskein
2
+
3
+ [English README](https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein/blob/main/README.md)
4
+
5
+ [pytest-embedded](https://github.com/espressif/pytest-embedded) のプラグインです。test ごとに、[WireSkein](https://github.com/Open-Embedded-Probe/wireskein) の記録器 `ws_run` を渡します。
6
+
7
+ - test は、送ったコマンド、ロジックアナライザのキャプチャ、各ステップの線の上であるべき姿を記録します。
8
+ - test の本体が終わると、プラグインがキャプチャを期待と照らし合わせます。
9
+ - 検査が NG なら、その test を失敗にします。
10
+
11
+ 状態: **β 版**です。
12
+
13
+ ## 入れ方
14
+
15
+ ```sh
16
+ pip install pytest-embedded-wireskein
17
+ ```
18
+
19
+ Python 3.13 以上が要ります。`wireskein` と `pytest-embedded` も一緒に入ります。
20
+
21
+ ## 使い方
22
+
23
+ ```python
24
+ from wireskein.runlog import square, level, only_moving
25
+
26
+ def test_pwm(dut, ws_run, probe): # probe は、ロジックアナライザを動かす何かの fixture
27
+ with ws_run.section(1, "pwm"):
28
+ for duty in (64, 128, 0):
29
+ want = [square("PA1", 1000, duty / 255), only_moving(["PA1"])] if duty else [level("PA1", 0)]
30
+ with ws_run.section(2, f"duty={duty}", expect=want):
31
+ ws_run.command(f"PWM {duty}")
32
+ dut.write(f"PWM {duty}")
33
+ ws_run.reply(dut.expect(r"PWM duty=\d+").group(0).decode())
34
+ t = ws_run.armed() # キャプチャを開始した直後
35
+ data, rate = probe.capture() # bytes。1 サンプル 1 バイト、ビット k がピン k
36
+ ws_run.capture(data, rate, ["PA1", "PA0"], t)
37
+ ```
38
+
39
+ `test_pwm` が戻ると、プラグインは記録を閉じて照合します。NG があれば、test は call の段で失敗します(ERROR ではなく FAILED)。
40
+
41
+ ```text
42
+ FAILED test_pwm.py::test_pwm - wireskein: 1 NG (5 ok, 1 ng, 0 unchecked (4 segments, 3 captures))
43
+ NG pwm/duty=128 square c0002.bin duty 0.6999 vs 0.5020
44
+ report: /tmp/pytest-embedded/2026-09-29_12-00-00-000000/test_pwm/wireskein/report.json
45
+ ```
46
+
47
+ 検査の一覧(`square`、`level`、`starts`、`ends`、`only_moving`、`pulses`、`i2c`、`spi`、`uart`)と見出しの規則は、[WireSkein の README](https://github.com/Open-Embedded-Probe/wireskein/blob/main/README.ja.md) にあります。
48
+
49
+ ### 記録の置き場所
50
+
51
+ `ws_run` は、pytest-embedded の `dut.log` の隣の `<test_case_tempdir>/wireskein/` に書きます。フルパスは `<root-logdir>/pytest-embedded/<時刻>/<test 名>/wireskein/` です。
52
+
53
+ | ファイル | 中身 |
54
+ | --- | --- |
55
+ | `run.json` | 見出し、コマンド、応答、メモ、キャプチャ、期待(WireSkein の記録の形式) |
56
+ | `c0001.bin` など | キャプチャ |
57
+ | `report.json` | すべての結果と測定値、ログ |
58
+ | `report.xml` | 結果の JUnit XML |
59
+
60
+ 同じディレクトリは、後から `wireskein verify <ディレクトリ>` で照合し直せます。
61
+
62
+ - test の `user_properties` には、`wireskein_report`(`report.json` のパス)が入ります。
63
+ - 結果の行は、test の報告に `wireskein` の節として付きます(`-rA` や失敗のときに表示されます)。
64
+
65
+ ### 照合する条件
66
+
67
+ - test が、キャプチャか期待を 1 つでも記録したときだけ照合します。
68
+ - NG の検査があれば、test を失敗にします。
69
+ - ピンがキャプチャにない検査は未検査で、失敗にはしません。
70
+ - test の本体がすでに失敗していた場合も、記録と照合は行います。ただし、test 自身の失敗の内容はそのまま残します。
71
+
72
+ ### 設定
73
+
74
+ | オプション | ini | 既定 | 意味 |
75
+ | --- | --- | --- | --- |
76
+ | `--wireskein-verify=fail\|report\|off` | `wireskein_verify` | `fail` | `fail`: 照合して、NG なら失敗。`report`: 照合して報告だけ残す(失敗にしない)。`off`: 記録だけ |
77
+
78
+ ### プローブとのつなぎ方
79
+
80
+ このプラグインは、プローブもターゲットも知りません。ロジックアナライザを動かす fixture(例: ボードの家系ごとのプラグイン)が、両方入っているときに `ws_run` へつなぎます。
81
+
82
+ - キャプチャを開始した直後に `t = ws_run.armed()`
83
+ - 読み終えたら `ws_run.capture(data, rate, bits, t, start_us=..., time_base_slipped=True)`
84
+ - `bits` は、ビットの順に並べたターゲットのピン名です。
85
+ - `time_base_slipped` は、プローブが報告したときだけ渡します。
86
+ - コンソールの送受信は `ws_run.command(text)` と `ws_run.reply(text)`
87
+
88
+ ## 開発
89
+
90
+ ```sh
91
+ uv sync
92
+ uv run pytest
93
+ ```
94
+
95
+ ## リリース
96
+
97
+ [pytest-embedded-arduino-cli](https://github.com/tanakamasayuki/pytest-embedded-arduino-cli/blob/main/README.ja.md) と同じ手順です。`CHANGELOG.md` の `## Unreleased` を更新し、`Release` の workflow を版(例 `0.0.2`)を入れて実行します。PyPI への公開は Trusted Publishing で行います。
98
+
99
+ ## ライセンス
100
+
101
+ MIT
@@ -0,0 +1,92 @@
1
+ # pytest-embedded-wireskein
2
+
3
+ [日本語 README](https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein/blob/main/README.ja.md)
4
+
5
+ A [pytest-embedded](https://github.com/espressif/pytest-embedded) plugin that gives each test a [WireSkein](https://github.com/Open-Embedded-Probe/wireskein) recorder, `ws_run`. The test records the commands it sent, the logic-analyzer captures, and what each step should look like on the wire. When the test body ends, the plugin checks the captures against these expectations and fails the test if a check fails.
6
+
7
+ Status: **beta**.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pip install pytest-embedded-wireskein
13
+ ```
14
+
15
+ Python 3.13 or newer. This installs `wireskein` and `pytest-embedded`.
16
+
17
+ ## Use
18
+
19
+ ```python
20
+ from wireskein.runlog import square, level, only_moving
21
+
22
+ def test_pwm(dut, ws_run, probe): # `probe` is whatever drives your logic analyzer
23
+ with ws_run.section(1, "pwm"):
24
+ for duty in (64, 128, 0):
25
+ want = [square("PA1", 1000, duty / 255), only_moving(["PA1"])] if duty else [level("PA1", 0)]
26
+ with ws_run.section(2, f"duty={duty}", expect=want):
27
+ ws_run.command(f"PWM {duty}")
28
+ dut.write(f"PWM {duty}")
29
+ ws_run.reply(dut.expect(r"PWM duty=\d+").group(0).decode())
30
+ t = ws_run.armed() # right after arming the capture
31
+ data, rate = probe.capture() # bytes, one sample per byte, bit k = pin k
32
+ ws_run.capture(data, rate, ["PA1", "PA0"], t)
33
+ ```
34
+
35
+ When `test_pwm` returns, the plugin closes the run and verifies it. If a check fails, the test fails in its call phase (FAILED, not ERROR):
36
+
37
+ ```text
38
+ FAILED test_pwm.py::test_pwm - wireskein: 1 NG (5 ok, 1 ng, 0 unchecked (4 segments, 3 captures))
39
+ NG pwm/duty=128 square c0002.bin duty 0.6999 vs 0.5020
40
+ report: /tmp/pytest-embedded/2026-09-29_12-00-00-000000/test_pwm/wireskein/report.json
41
+ ```
42
+
43
+ The checks (`square`, `level`, `starts`, `ends`, `only_moving`, `pulses`, `i2c`, `spi`, `uart`) and the heading rules are described in the [WireSkein README](https://github.com/Open-Embedded-Probe/wireskein#checking-a-test-run).
44
+
45
+ ### Where the run is written
46
+
47
+ `ws_run` writes to `<test_case_tempdir>/wireskein/`, next to pytest-embedded's `dut.log`: `<root-logdir>/pytest-embedded/<time>/<test name>/wireskein/`.
48
+
49
+ | File | Content |
50
+ | --- | --- |
51
+ | `run.json` | Headings, commands, replies, notes, captures and expectations (WireSkein run format) |
52
+ | `c0001.bin`, ... | The captures |
53
+ | `report.json` | Every result with measured values, and the log |
54
+ | `report.xml` | The results as JUnit XML |
55
+
56
+ The same directory can be checked again later with `wireskein verify <dir>`. The test's `user_properties` carry `wireskein_report` (the path of `report.json`), and the result lines are added to the test report as a `wireskein` section (shown with `-rA` or on failure).
57
+
58
+ ### When the plugin verifies
59
+
60
+ - The run is verified after the test body, only if the test recorded a capture or an expectation.
61
+ - A check that fails makes the test fail.
62
+ - A check whose pins were not captured is unchecked and does not fail the test.
63
+ - If the test body already failed, the run is still recorded and verified, but the test's own failure is kept.
64
+
65
+ ### Options
66
+
67
+ | Option | ini | Default | Meaning |
68
+ | --- | --- | --- | --- |
69
+ | `--wireskein-verify=fail\|report\|off` | `wireskein_verify` | `fail` | `fail`: verify and fail on NG. `report`: verify and write the reports, never fail. `off`: record only |
70
+
71
+ ### Connecting a probe
72
+
73
+ This plugin knows nothing about probes or targets. A fixture that drives the logic analyzer (for example the one of a board-family plugin) connects to `ws_run` when both are installed:
74
+
75
+ - right after arming a capture: `t = ws_run.armed()`
76
+ - when the samples are read: `ws_run.capture(data, rate, bits, t, start_us=..., time_base_slipped=True)`. `bits` lists the target's pin names in bit order. Pass `time_base_slipped` only when the probe reports it.
77
+ - the console traffic: `ws_run.command(text)`, `ws_run.reply(text)`
78
+
79
+ ## Development
80
+
81
+ ```sh
82
+ uv sync
83
+ uv run pytest
84
+ ```
85
+
86
+ ## Release
87
+
88
+ The release works the same way as in [pytest-embedded-arduino-cli](https://github.com/tanakamasayuki/pytest-embedded-arduino-cli#release). Update `## Unreleased` in `CHANGELOG.md`, then run the `Release` workflow with the version (for example `0.0.2`). PyPI publishing uses Trusted Publishing.
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pytest-embedded-wireskein"
7
+ version = "0.0.1"
8
+ description = "pytest-embedded plugin: record logic-analyzer captures per test with WireSkein and fail the test when a waveform check fails"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.13"
12
+ authors = [
13
+ { name = "TANAKA Masayuki" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Framework :: Pytest",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Testing",
20
+ ]
21
+ dependencies = [
22
+ "pytest>=8",
23
+ "pytest-embedded>=2.0",
24
+ "wireskein>=0.0.1",
25
+ ]
26
+
27
+ [project.entry-points.pytest11]
28
+ embedded-wireskein = "pytest_embedded_wireskein.plugin"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein"
32
+ Repository = "https://github.com/Open-Embedded-Probe/pytest-embedded-wireskein"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/pytest_embedded_wireskein"]
36
+
37
+ [tool.hatch.build.targets.sdist]
38
+ only-include = ["src/pytest_embedded_wireskein", "tests", "README.md", "README.ja.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+ addopts = "-p pytester"
43
+ filterwarnings = [
44
+ "ignore:record_xml_attribute is an experimental feature:pytest.PytestExperimentalApiWarning",
45
+ ]
46
+
47
+ [dependency-groups]
48
+ dev = ["pytest>=8"]
@@ -0,0 +1,5 @@
1
+ """pytest-embedded plugin that records WireSkein runs per test and verifies them."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.0.1"
@@ -0,0 +1,91 @@
1
+ """`ws_run`: a WireSkein recorder per test, verified when the test body ends.
2
+
3
+ The run is written next to pytest-embedded's dut.log, in
4
+ <test_case_tempdir>/wireskein/ (run.json, the captures, report.json,
5
+ report.xml). A failing check fails the test in its call phase, so pytest
6
+ reports FAILED, not ERROR. The plugin knows nothing about the probe or the
7
+ target: whatever fixture drives the probe feeds `ws_run` (armed / capture /
8
+ command / reply).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ import pytest
16
+ from wireskein import verify as ws_verify
17
+ from wireskein.runlog import Recorder
18
+
19
+ MODES = ("fail", "report", "off")
20
+ DIR_NAME = "wireskein"
21
+
22
+ _recorder_key = pytest.StashKey[Recorder]()
23
+ _done_key = pytest.StashKey[bool]()
24
+
25
+
26
+ def pytest_addoption(parser: pytest.Parser) -> None:
27
+ group = parser.getgroup("embedded-wireskein")
28
+ group.addoption(
29
+ "--wireskein-verify",
30
+ choices=MODES,
31
+ default=None,
32
+ help="after each test using ws_run: fail = verify and fail the test on NG (default), "
33
+ "report = verify and keep the reports only, off = record only",
34
+ )
35
+ parser.addini("wireskein_verify", help="fail | report | off (see --wireskein-verify)", default="fail")
36
+
37
+
38
+ def pytest_configure(config: pytest.Config) -> None:
39
+ _mode(config) # a wrong value is a usage error before any test runs
40
+
41
+
42
+ def _mode(config: pytest.Config) -> str:
43
+ mode = config.getoption("wireskein_verify") or config.getini("wireskein_verify")
44
+ if mode not in MODES:
45
+ raise pytest.UsageError(f"wireskein_verify must be one of {', '.join(MODES)}, not {mode!r}")
46
+ return mode
47
+
48
+
49
+ @pytest.fixture
50
+ def ws_run(request: pytest.FixtureRequest, test_case_tempdir: str) -> Recorder:
51
+ """A wireskein.runlog.Recorder for this test, recording into
52
+ <test_case_tempdir>/wireskein/. Headings (`section`), commands, replies,
53
+ captures and expectations go through it; the plugin verifies the run when
54
+ the test body ends."""
55
+ rec = Recorder(Path(test_case_tempdir) / DIR_NAME, test=request.node.nodeid)
56
+ request.node.stash[_recorder_key] = rec
57
+ yield rec
58
+ # the call phase did not run (a setup error): still leave the record
59
+ _finish(request.node, verdict=False)
60
+
61
+
62
+ @pytest.hookimpl(wrapper=True)
63
+ def pytest_runtest_call(item: pytest.Item):
64
+ try:
65
+ result = yield
66
+ except BaseException:
67
+ _finish(item, verdict=False) # the test failed on its own: record, do not override
68
+ raise
69
+ _finish(item, verdict=True)
70
+ return result
71
+
72
+
73
+ def _finish(item: pytest.Item, verdict: bool) -> None:
74
+ rec = item.stash.get(_recorder_key, None)
75
+ if rec is None or item.stash.get(_done_key, False):
76
+ return
77
+ item.stash[_done_key] = True
78
+ path = rec.close()
79
+ mode = _mode(item.config)
80
+ if mode == "off" or not (rec.doc["expect"] or rec.doc["captures"]):
81
+ return
82
+ report = ws_verify.verify(path.parent)
83
+ (path.parent / "report.json").write_text(ws_verify.dumps(report))
84
+ (path.parent / "report.xml").write_text(ws_verify.junit(report))
85
+ item.user_properties.append(("wireskein_report", str(path.parent / "report.json")))
86
+ text = "\n".join([ws_verify.summary_line(report), *ws_verify.lines(report), f"report: {path.parent / 'report.json'}"])
87
+ item.add_report_section("call", "wireskein", text)
88
+ if verdict and mode == "fail" and report["summary"]["ng"]:
89
+ ng = [line for line in ws_verify.lines(report) if line.startswith("NG")]
90
+ pytest.fail(f"wireskein: {report['summary']['ng']} NG ({ws_verify.summary_line(report)})\n"
91
+ + "\n".join(ng) + f"\nreport: {path.parent / 'report.json'}", pytrace=False)
@@ -0,0 +1,86 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ # A test file using ws_run with a synthetic capture: P0 low for 1000 samples.
7
+ TEST_FILE = '''
8
+ from wireskein.runlog import level
9
+
10
+ def test_{name}(ws_run):
11
+ with ws_run.section(1, "t", expect=[level("P0", {want})]):
12
+ ws_run.command("PING")
13
+ ws_run.reply("PONG")
14
+ t = ws_run.armed()
15
+ ws_run.capture(bytes(1000), 1e6, ["P0"], t)
16
+ '''
17
+
18
+
19
+ def run(pytester: pytest.Pytester, *args, name="x", want=0, body=None):
20
+ pytester.makepyfile(body or TEST_FILE.format(name=name, want=want))
21
+ logdir = pytester.path / "logs"
22
+ return pytester.runpytest("--root-logdir", str(logdir), *args), logdir
23
+
24
+
25
+ def run_dir(logdir: Path, name: str) -> Path:
26
+ (d,) = logdir.glob(f"pytest-embedded/*/test_{name}/wireskein")
27
+ return d
28
+
29
+
30
+ def test_ok_run_is_recorded_next_to_the_dut_log(pytester):
31
+ result, logdir = run(pytester)
32
+ result.assert_outcomes(passed=1)
33
+ d = run_dir(logdir, "x")
34
+ assert {p.name for p in d.iterdir()} == {"run.json", "c0001.bin", "report.json", "report.xml"}
35
+ doc = json.loads((d / "run.json").read_text())
36
+ assert doc["meta"]["test"].endswith("::test_x")
37
+ assert [e["src"] for e in doc["log"]] == ["marker", "host", "dut", "marker"]
38
+ assert json.loads((d / "report.json").read_text())["summary"]["ok"] == 1
39
+
40
+
41
+ def test_ng_fails_the_call_phase(pytester):
42
+ result, logdir = run(pytester, want=1)
43
+ result.assert_outcomes(failed=1) # FAILED, not ERROR
44
+ result.stdout.fnmatch_lines(["*wireskein: 1 NG*", "*NG t level c0001.bin not constant 1*", "*report: *report.json*"])
45
+
46
+
47
+ def test_report_mode_keeps_the_reports_without_failing(pytester):
48
+ result, logdir = run(pytester, "--wireskein-verify=report", want=1)
49
+ result.assert_outcomes(passed=1)
50
+ assert json.loads((run_dir(logdir, "x") / "report.json").read_text())["summary"]["ng"] == 1
51
+
52
+
53
+ def test_off_mode_only_records(pytester):
54
+ pytester.makeini("[pytest]\nwireskein_verify = off\n")
55
+ result, logdir = run(pytester, want=1)
56
+ result.assert_outcomes(passed=1)
57
+ assert {p.name for p in run_dir(logdir, "x").iterdir()} == {"run.json", "c0001.bin"}
58
+
59
+
60
+ def test_a_failing_test_body_is_not_overridden(pytester):
61
+ body = TEST_FILE.format(name="x", want=1) + " assert False, 'the body failed'\n"
62
+ result, logdir = run(pytester, body=body)
63
+ result.assert_outcomes(failed=1)
64
+ result.stdout.fnmatch_lines(["*the body failed*"])
65
+ result.stdout.no_fnmatch_line("*wireskein: 1 NG*")
66
+ assert (run_dir(logdir, "x") / "report.json").exists() # still verified and recorded
67
+
68
+
69
+ def test_unchecked_does_not_fail(pytester):
70
+ body = TEST_FILE.format(name="x", want=0).replace('level("P0", 0)', 'level("NOT_CAPTURED", 0)')
71
+ result, _ = run(pytester, body=body)
72
+ result.assert_outcomes(passed=1)
73
+
74
+
75
+ def test_unused_recorder_writes_no_report(pytester):
76
+ body = "def test_x(ws_run):\n pass\n"
77
+ result, logdir = run(pytester, body=body)
78
+ result.assert_outcomes(passed=1)
79
+ assert {p.name for p in run_dir(logdir, "x").iterdir()} == {"run.json"}
80
+
81
+
82
+ def test_bad_mode_is_a_usage_error(pytester):
83
+ pytester.makeini("[pytest]\nwireskein_verify = maybe\n")
84
+ result, _ = run(pytester)
85
+ assert result.ret != 0
86
+ result.stderr.fnmatch_lines(["*wireskein_verify must be one of fail, report, off*"])