lr-lumentest-sdk 0.6.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.
- lr_lumentest_sdk-0.6.1/LICENSE +21 -0
- lr_lumentest_sdk-0.6.1/Makefile +120 -0
- lr_lumentest_sdk-0.6.1/PKG-INFO +125 -0
- lr_lumentest_sdk-0.6.1/README.md +98 -0
- lr_lumentest_sdk-0.6.1/pyproject.toml +71 -0
- lr_lumentest_sdk-0.6.1/setup.cfg +4 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/PKG-INFO +125 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/SOURCES.txt +29 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/dependency_links.txt +1 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/entry_points.txt +2 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/requires.txt +9 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/scm_file_list.json +15 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/scm_version.json +8 -0
- lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/top_level.txt +1 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/__init__.py +21 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/_version.py +24 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/decorators.py +65 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/limits.py +120 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/manifest.py +557 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/run_tool.py +390 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/sequence.py +348 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/step_executor.py +277 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/step_main.py +413 -0
- lr_lumentest_sdk-0.6.1/src/lumentest_station/test_context.py +168 -0
- lr_lumentest_sdk-0.6.1/tests/_helpers.py +42 -0
- lr_lumentest_sdk-0.6.1/tests/conftest.py +36 -0
- lr_lumentest_sdk-0.6.1/tests/unit/test_allowlist_closed.py +31 -0
- lr_lumentest_sdk-0.6.1/tests/unit/test_no_undeclared_dependency.py +29 -0
- lr_lumentest_sdk-0.6.1/tests/unit/test_public_surface.py +38 -0
- lr_lumentest_sdk-0.6.1/tests/unit/test_staging_tree_matches_allowlist.py +22 -0
- lr_lumentest_sdk-0.6.1/tests/unit/test_sync_matches_station_source.py +23 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LumenRadio AB
|
|
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,120 @@
|
|
|
1
|
+
VENV := .venv
|
|
2
|
+
PY := $(VENV)/bin/python
|
|
3
|
+
PIP := $(VENV)/bin/pip
|
|
4
|
+
|
|
5
|
+
# Resolve black config from the repo root (one directory up).
|
|
6
|
+
# This avoids duplicating line-length / target-version across the sub-packages.
|
|
7
|
+
ROOT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
|
8
|
+
|
|
9
|
+
# setuptools_scm version, resolved on the host from the installed _version.py.
|
|
10
|
+
# Injected via SETUPTOOLS_SCM_PRETEND_VERSION_FOR_* so the sdist build skips the
|
|
11
|
+
# VCS file-finder (which root=".." would otherwise use to pull in the whole
|
|
12
|
+
# monorepo), and so in-container builds (no git / no repo-root mount) get it too.
|
|
13
|
+
DIST_ENV := SETUPTOOLS_SCM_PRETEND_VERSION_FOR_LR_LUMENTEST_SDK
|
|
14
|
+
SCM_VERSION = $(shell $(PY) -c "import lumentest_station._version as v; print(v.version)" 2>/dev/null)
|
|
15
|
+
|
|
16
|
+
# Only the sdist and wheel are valid PyPI distributions.
|
|
17
|
+
DIST_FILES := dist/*.whl dist/*.tar.gz
|
|
18
|
+
|
|
19
|
+
PYPI_REPOSITORY ?= https://upload.pypi.org/legacy/
|
|
20
|
+
TEST_PYPI_REPOSITORY ?= https://test.pypi.org/legacy/
|
|
21
|
+
|
|
22
|
+
# The PyPI token lives in the releasing developer's shell, exported by hand
|
|
23
|
+
# before running `publish`/`publish-test` — the same arrangement lr-gladiator
|
|
24
|
+
# and lr-qrm use. No CI job holds it, so a release reaches PyPI only when a
|
|
25
|
+
# person runs the target.
|
|
26
|
+
#
|
|
27
|
+
# setuptools_scm derives SCM_VERSION from the `lumentest-*` tag: any commit
|
|
28
|
+
# past the tag carries a `.devN+g<sha>` suffix. PyPI rejects a local (`+…`)
|
|
29
|
+
# segment outright, and a `.dev` upload permanently spends a version number
|
|
30
|
+
# the index can never reuse — so both upload targets refuse anything but a
|
|
31
|
+
# clean tag.
|
|
32
|
+
CHECK_RELEASE_VERSION = case "$(SCM_VERSION)" in *dev*|*+*|"") echo "error: refusing to upload $(SCM_VERSION) — build from a clean 'lumentest-X.Y.Z' tag"; exit 2 ;; esac
|
|
33
|
+
|
|
34
|
+
# The staging tree this package ships is generated, not authored: each module
|
|
35
|
+
# has exactly one canonical home, under service/src/lumentest_station/. This
|
|
36
|
+
# list is an allowlist — sdk/tests/ asserts that its closure (every
|
|
37
|
+
# lumentest_station.X import reachable from these files) never reaches outside
|
|
38
|
+
# it, so a station-side change that adds a new intra-package import fails the
|
|
39
|
+
# SDK's own test suite instead of silently shipping a broken wheel.
|
|
40
|
+
STATION_SRC := ../service/src/lumentest_station
|
|
41
|
+
SDK_MODULES := __init__.py decorators.py limits.py manifest.py run_tool.py \
|
|
42
|
+
sequence.py step_executor.py step_main.py test_context.py
|
|
43
|
+
|
|
44
|
+
SRC_DIR := src/lumentest_station
|
|
45
|
+
|
|
46
|
+
.DEFAULT_GOAL := install
|
|
47
|
+
|
|
48
|
+
.PHONY: install dev sync test test-only format dist check publish publish-test shell clean
|
|
49
|
+
|
|
50
|
+
$(VENV)/bin/activate:
|
|
51
|
+
python3.12 -m venv $(VENV)
|
|
52
|
+
$(PIP) install --upgrade pip
|
|
53
|
+
|
|
54
|
+
# Regenerates the staging tree from scratch every time: a module dropped from
|
|
55
|
+
# SDK_MODULES (or renamed upstream) must disappear from the wheel too, not
|
|
56
|
+
# linger from a previous sync. _version.py is never copied here — setuptools_scm
|
|
57
|
+
# writes it into $(SRC_DIR) itself at build/install time.
|
|
58
|
+
sync:
|
|
59
|
+
rm -rf $(SRC_DIR)
|
|
60
|
+
mkdir -p $(SRC_DIR)
|
|
61
|
+
@for f in $(SDK_MODULES); do \
|
|
62
|
+
test -f $(STATION_SRC)/$$f || { echo "error: $(STATION_SRC)/$$f not found — sdk/Makefile's SDK_MODULES names a module the station does not have"; exit 2; }; \
|
|
63
|
+
cp $(STATION_SRC)/$$f $(SRC_DIR)/$$f; \
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
install: $(VENV)/bin/activate sync
|
|
67
|
+
$(PIP) install .
|
|
68
|
+
|
|
69
|
+
dev: $(VENV)/bin/activate sync
|
|
70
|
+
$(PIP) install -e ".[dev]"
|
|
71
|
+
|
|
72
|
+
# `test` is the whole job: sync the venv, then run pytest. `test-only` is the
|
|
73
|
+
# pytest half on its own, for a driver that has already done the `dev` pass and
|
|
74
|
+
# wants to run several sub-trees at once -- the editable installs `dev` performs
|
|
75
|
+
# write in-tree build artefacts, so they must not overlap; pytest runs may.
|
|
76
|
+
test: dev test-only
|
|
77
|
+
|
|
78
|
+
test-only:
|
|
79
|
+
$(VENV)/bin/pytest \
|
|
80
|
+
--junit-xml=test-results.xml \
|
|
81
|
+
tests/
|
|
82
|
+
|
|
83
|
+
# src/ is generated from already-formatted upstream sources; formatting it here
|
|
84
|
+
# would edit a tree `sync` throws away on the next run.
|
|
85
|
+
format: dev
|
|
86
|
+
$(VENV)/bin/black --config $(ROOT_DIR)/pyproject.toml tests/
|
|
87
|
+
|
|
88
|
+
# Clearing the previous sdist and wheel is what keeps `$(DIST_FILES)` meaning
|
|
89
|
+
# "this build": every artefact accumulates under its own version, and an upload
|
|
90
|
+
# globbing the directory would carry a stale version along with the current one.
|
|
91
|
+
dist: dev
|
|
92
|
+
rm -f dist/*.whl dist/*.tar.gz
|
|
93
|
+
$(DIST_ENV)="$(SCM_VERSION)" $(PY) -m build
|
|
94
|
+
|
|
95
|
+
check: dist
|
|
96
|
+
$(VENV)/bin/twine check $(DIST_FILES)
|
|
97
|
+
|
|
98
|
+
# Both upload targets pass credentials through TWINE_USERNAME/TWINE_PASSWORD,
|
|
99
|
+
# which twine reads natively, and silence the upload line with `@`. `-u`/`-p`
|
|
100
|
+
# would put the token in the echoed recipe line and in the upload process's
|
|
101
|
+
# argv, where `ps` can read it — visible in a developer's scrollback and, since
|
|
102
|
+
# this target also runs as a CI job, in a captured job log.
|
|
103
|
+
publish-test: check
|
|
104
|
+
@$(CHECK_RELEASE_VERSION)
|
|
105
|
+
@if [ -z "$$TEST_PYPI_TOKEN" ]; then echo "error: TEST_PYPI_TOKEN is required — create one at https://test.pypi.org/manage/account/token/"; exit 2; fi
|
|
106
|
+
@TWINE_USERNAME=__token__ TWINE_PASSWORD="$$TEST_PYPI_TOKEN" $(VENV)/bin/twine upload --repository-url $(TEST_PYPI_REPOSITORY) $(DIST_FILES)
|
|
107
|
+
|
|
108
|
+
publish: check
|
|
109
|
+
@$(CHECK_RELEASE_VERSION)
|
|
110
|
+
@if [ -z "$$PYPI_TOKEN" ]; then echo "error: PYPI_TOKEN is required — create one at https://pypi.org/manage/account/token/"; exit 2; fi
|
|
111
|
+
@TWINE_USERNAME=__token__ TWINE_PASSWORD="$$PYPI_TOKEN" $(VENV)/bin/twine upload --repository-url $(PYPI_REPOSITORY) $(DIST_FILES)
|
|
112
|
+
|
|
113
|
+
shell:
|
|
114
|
+
@echo "source $(VENV)/bin/activate"
|
|
115
|
+
|
|
116
|
+
clean:
|
|
117
|
+
rm -rf $(VENV) dist/ build/ htmlcov/ test-results.xml .coverage $(SRC_DIR)
|
|
118
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null; true
|
|
119
|
+
find . -name "*.pyc" -delete 2>/dev/null; true
|
|
120
|
+
find . -name "*.egg-info" -type d -exec rm -rf {} + 2>/dev/null; true
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lr-lumentest-sdk
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: Step-authoring SDK for LumenTest test sequences: write and run steps against the same lumentest_station API the station imports, from a standalone .venv with no monorepo checkout.
|
|
5
|
+
Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/lumenradio/production/lumentest
|
|
8
|
+
Project-URL: Changelog, https://gitlab.com/lumenradio/production/lumentest/-/blob/main/CHANGELOG.md
|
|
9
|
+
Keywords: LumenRadio,LumenTest,production test,test station,step SDK
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Software Development :: Testing
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydantic>=2.8
|
|
19
|
+
Requires-Dist: pyserial>=3.5
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2.1; extra == "dev"
|
|
22
|
+
Requires-Dist: twine>=6.1; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=8.4; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
25
|
+
Requires-Dist: black>=25.9; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# lr-lumentest-sdk
|
|
29
|
+
|
|
30
|
+
Step-authoring SDK for LumenRadio LumenTest test sequences.
|
|
31
|
+
|
|
32
|
+
This package is for an embedded developer writing or updating test steps and
|
|
33
|
+
sequences for a LumenTest production station, who wants a `.venv` and an
|
|
34
|
+
editor with working imports, type hints and autocomplete for the step API —
|
|
35
|
+
without checking out or installing the station itself. It ships the exact
|
|
36
|
+
same `lumentest_station` source files the station runs, so a step written and
|
|
37
|
+
tested against this package behaves identically when the station loads it.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install lr-lumentest-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> **This package and `lumentest-station` both provide the `lumentest_station`
|
|
46
|
+
> import name and must never be installed into the same environment.** A
|
|
47
|
+
> station developer already has the real package — install `lr-lumentest-sdk`
|
|
48
|
+
> only in a separate `.venv` dedicated to authoring steps and sequences.
|
|
49
|
+
|
|
50
|
+
## Public API
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from lumentest_station import (
|
|
54
|
+
step_options, # decorator: attaches step metadata (type, timeout, limits section)
|
|
55
|
+
sequence_options, # decorator: attaches sequence metadata (name, serial handling)
|
|
56
|
+
run_tool, # run an external tool as a supervised subprocess
|
|
57
|
+
StepFailed, # raise to fail the step; optionally carrying the ToolResult that failed
|
|
58
|
+
ToolResult, # what run_tool() returns: exit_code, stdout, stderr, timed_out
|
|
59
|
+
Measurement, # a single limits-checked measurement to report
|
|
60
|
+
step_main, # run this step standalone from the command line
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Running a single step
|
|
65
|
+
|
|
66
|
+
A step module can run itself directly, with no separate harness:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from lumentest_station import step_options, step_main
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@step_options("PROGRAMMING", display_name="Flash Firmware")
|
|
73
|
+
def flash_firmware(test):
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
raise SystemExit(step_main(flash_firmware))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python my_step.py --limits limits.toml --manifest artifact-manifest.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a step file with no `__main__` guard, use the `lumentest-step` console
|
|
86
|
+
script instead:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
lumentest-step validate my_step.py
|
|
90
|
+
lumentest-step run my_step.py --limits limits.toml --manifest artifact-manifest.json --env NRF_TOOLS=/opt/nrf --results-dir ./results
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`validate` is a static check only — it never imports the file, so it needs no
|
|
94
|
+
hardware and no environment. It enforces the two rules the station's loader
|
|
95
|
+
enforces: exactly one `@step_options`-decorated top-level function, and no
|
|
96
|
+
direct `subprocess` use (steps call `run_tool` instead). `run` performs the
|
|
97
|
+
same check, then imports and executes the step.
|
|
98
|
+
|
|
99
|
+
Options for `run`, and for `step_main` under a `__main__` guard:
|
|
100
|
+
|
|
101
|
+
| Option | Purpose |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `--limits PATH` | TOML file of measurement limits; repeatable, later files win. Needed by any step that returns measurements. |
|
|
104
|
+
| `--manifest PATH` | A package directory, or its `artifact-manifest.json`, so `manifest.resolve(...)` works against a real package |
|
|
105
|
+
| `--env NAME=VALUE` | Set an environment variable for the step's run, repeatable. Production steps read station configuration from the environment, so this stands in for the station's `[env]` config. |
|
|
106
|
+
| `--dut`, `--article`, `--revision`, `--station` | The identity fields the step sees on its `test` context |
|
|
107
|
+
| `--timeout SECONDS` | Override the step's own `@step_options(timeout=...)` |
|
|
108
|
+
| `--results-dir DIR` | Where captured `stdout.txt`, `stderr.txt` and `traceback.txt` land |
|
|
109
|
+
|
|
110
|
+
The exit status is `0` when the step passed, `1` when it failed (an explicit
|
|
111
|
+
`StepFailed`, or an out-of-limit measurement), and `2` on an error — an
|
|
112
|
+
unexpected exception, a timeout, or a file the loader rejects.
|
|
113
|
+
|
|
114
|
+
A station does far more than run one step: it uploads to QRM, validates serial
|
|
115
|
+
numbers, curates the `PATH` a tool is resolved against, and runs a whole
|
|
116
|
+
sequence in order. None of that is reproduced here. A green standalone run says
|
|
117
|
+
the step's own logic holds, not that the sequence will pass on a station.
|
|
118
|
+
|
|
119
|
+
## Full authoring guide
|
|
120
|
+
|
|
121
|
+
For the complete authoring reference — sequence structure, limits files,
|
|
122
|
+
manifests, the step SDK's allowed third-party imports, and the full
|
|
123
|
+
`lumentest-step` command reference — see
|
|
124
|
+
[`doc/step-sdk.md`](https://gitlab.com/lumenradio/production/lumentest/-/blob/main/doc/step-sdk.md)
|
|
125
|
+
in the LumenTest repository.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# lr-lumentest-sdk
|
|
2
|
+
|
|
3
|
+
Step-authoring SDK for LumenRadio LumenTest test sequences.
|
|
4
|
+
|
|
5
|
+
This package is for an embedded developer writing or updating test steps and
|
|
6
|
+
sequences for a LumenTest production station, who wants a `.venv` and an
|
|
7
|
+
editor with working imports, type hints and autocomplete for the step API —
|
|
8
|
+
without checking out or installing the station itself. It ships the exact
|
|
9
|
+
same `lumentest_station` source files the station runs, so a step written and
|
|
10
|
+
tested against this package behaves identically when the station loads it.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install lr-lumentest-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **This package and `lumentest-station` both provide the `lumentest_station`
|
|
19
|
+
> import name and must never be installed into the same environment.** A
|
|
20
|
+
> station developer already has the real package — install `lr-lumentest-sdk`
|
|
21
|
+
> only in a separate `.venv` dedicated to authoring steps and sequences.
|
|
22
|
+
|
|
23
|
+
## Public API
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from lumentest_station import (
|
|
27
|
+
step_options, # decorator: attaches step metadata (type, timeout, limits section)
|
|
28
|
+
sequence_options, # decorator: attaches sequence metadata (name, serial handling)
|
|
29
|
+
run_tool, # run an external tool as a supervised subprocess
|
|
30
|
+
StepFailed, # raise to fail the step; optionally carrying the ToolResult that failed
|
|
31
|
+
ToolResult, # what run_tool() returns: exit_code, stdout, stderr, timed_out
|
|
32
|
+
Measurement, # a single limits-checked measurement to report
|
|
33
|
+
step_main, # run this step standalone from the command line
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Running a single step
|
|
38
|
+
|
|
39
|
+
A step module can run itself directly, with no separate harness:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from lumentest_station import step_options, step_main
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@step_options("PROGRAMMING", display_name="Flash Firmware")
|
|
46
|
+
def flash_firmware(test):
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
raise SystemExit(step_main(flash_firmware))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python my_step.py --limits limits.toml --manifest artifact-manifest.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For a step file with no `__main__` guard, use the `lumentest-step` console
|
|
59
|
+
script instead:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
lumentest-step validate my_step.py
|
|
63
|
+
lumentest-step run my_step.py --limits limits.toml --manifest artifact-manifest.json --env NRF_TOOLS=/opt/nrf --results-dir ./results
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`validate` is a static check only — it never imports the file, so it needs no
|
|
67
|
+
hardware and no environment. It enforces the two rules the station's loader
|
|
68
|
+
enforces: exactly one `@step_options`-decorated top-level function, and no
|
|
69
|
+
direct `subprocess` use (steps call `run_tool` instead). `run` performs the
|
|
70
|
+
same check, then imports and executes the step.
|
|
71
|
+
|
|
72
|
+
Options for `run`, and for `step_main` under a `__main__` guard:
|
|
73
|
+
|
|
74
|
+
| Option | Purpose |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `--limits PATH` | TOML file of measurement limits; repeatable, later files win. Needed by any step that returns measurements. |
|
|
77
|
+
| `--manifest PATH` | A package directory, or its `artifact-manifest.json`, so `manifest.resolve(...)` works against a real package |
|
|
78
|
+
| `--env NAME=VALUE` | Set an environment variable for the step's run, repeatable. Production steps read station configuration from the environment, so this stands in for the station's `[env]` config. |
|
|
79
|
+
| `--dut`, `--article`, `--revision`, `--station` | The identity fields the step sees on its `test` context |
|
|
80
|
+
| `--timeout SECONDS` | Override the step's own `@step_options(timeout=...)` |
|
|
81
|
+
| `--results-dir DIR` | Where captured `stdout.txt`, `stderr.txt` and `traceback.txt` land |
|
|
82
|
+
|
|
83
|
+
The exit status is `0` when the step passed, `1` when it failed (an explicit
|
|
84
|
+
`StepFailed`, or an out-of-limit measurement), and `2` on an error — an
|
|
85
|
+
unexpected exception, a timeout, or a file the loader rejects.
|
|
86
|
+
|
|
87
|
+
A station does far more than run one step: it uploads to QRM, validates serial
|
|
88
|
+
numbers, curates the `PATH` a tool is resolved against, and runs a whole
|
|
89
|
+
sequence in order. None of that is reproduced here. A green standalone run says
|
|
90
|
+
the step's own logic holds, not that the sequence will pass on a station.
|
|
91
|
+
|
|
92
|
+
## Full authoring guide
|
|
93
|
+
|
|
94
|
+
For the complete authoring reference — sequence structure, limits files,
|
|
95
|
+
manifests, the step SDK's allowed third-party imports, and the full
|
|
96
|
+
`lumentest-step` command reference — see
|
|
97
|
+
[`doc/step-sdk.md`](https://gitlab.com/lumenradio/production/lumentest/-/blob/main/doc/step-sdk.md)
|
|
98
|
+
in the LumenTest repository.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "setuptools_scm>=8", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lr-lumentest-sdk"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Step-authoring SDK for LumenTest test sequences: write and run steps against the same lumentest_station API the station imports, from a standalone .venv with no monorepo checkout."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Jonas Estberger", email = "jonas.estberger@lumenradio.com" }
|
|
15
|
+
]
|
|
16
|
+
# pydantic backs the manifest/limits models a step imports transitively.
|
|
17
|
+
# pyserial is the pinned step-SDK library step code is allowed to import (see
|
|
18
|
+
# the "Step SDK" table in instructions.md) — it is a direct dependency here,
|
|
19
|
+
# not just a transitive one, so a developer's venv matches exactly what the
|
|
20
|
+
# station gives a step at run time.
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pydantic>=2.8",
|
|
23
|
+
"pyserial>=3.5",
|
|
24
|
+
]
|
|
25
|
+
keywords = ["LumenRadio", "LumenTest", "production test", "test station", "step SDK"]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Environment :: Console",
|
|
31
|
+
"Topic :: Software Development :: Testing",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"build>=1.2.1",
|
|
37
|
+
"twine>=6.1",
|
|
38
|
+
"pytest>=8.4",
|
|
39
|
+
"pytest-cov>=5.0",
|
|
40
|
+
"black>=25.9",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
lumentest-step = "lumentest_station.step_main:main"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://gitlab.com/lumenradio/production/lumentest"
|
|
48
|
+
Changelog = "https://gitlab.com/lumenradio/production/lumentest/-/blob/main/CHANGELOG.md"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools]
|
|
51
|
+
package-dir = {"" = "src"}
|
|
52
|
+
include-package-data = true
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
|
|
57
|
+
# Single source of truth: the monorepo `lumentest-*` git tag (see service
|
|
58
|
+
# pyproject for the rationale). root=".." points at the repo root so the version
|
|
59
|
+
# resolves. NB: the `dist` make target sets SETUPTOOLS_SCM_PRETEND_VERSION_FOR_*
|
|
60
|
+
# so the sdist build skips the VCS file-finder — otherwise root=".." would pull
|
|
61
|
+
# the whole monorepo into the sdist.
|
|
62
|
+
[tool.setuptools_scm]
|
|
63
|
+
root = ".."
|
|
64
|
+
tag_regex = "^lumentest-(?P<version>[0-9].+)$"
|
|
65
|
+
git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "lumentest-*"]
|
|
66
|
+
version_file = "src/lumentest_station/_version.py"
|
|
67
|
+
fallback_version = "0.0.0"
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
addopts = "-ra"
|
|
71
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lr-lumentest-sdk
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: Step-authoring SDK for LumenTest test sequences: write and run steps against the same lumentest_station API the station imports, from a standalone .venv with no monorepo checkout.
|
|
5
|
+
Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/lumenradio/production/lumentest
|
|
8
|
+
Project-URL: Changelog, https://gitlab.com/lumenradio/production/lumentest/-/blob/main/CHANGELOG.md
|
|
9
|
+
Keywords: LumenRadio,LumenTest,production test,test station,step SDK
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Software Development :: Testing
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydantic>=2.8
|
|
19
|
+
Requires-Dist: pyserial>=3.5
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2.1; extra == "dev"
|
|
22
|
+
Requires-Dist: twine>=6.1; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=8.4; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
25
|
+
Requires-Dist: black>=25.9; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# lr-lumentest-sdk
|
|
29
|
+
|
|
30
|
+
Step-authoring SDK for LumenRadio LumenTest test sequences.
|
|
31
|
+
|
|
32
|
+
This package is for an embedded developer writing or updating test steps and
|
|
33
|
+
sequences for a LumenTest production station, who wants a `.venv` and an
|
|
34
|
+
editor with working imports, type hints and autocomplete for the step API —
|
|
35
|
+
without checking out or installing the station itself. It ships the exact
|
|
36
|
+
same `lumentest_station` source files the station runs, so a step written and
|
|
37
|
+
tested against this package behaves identically when the station loads it.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install lr-lumentest-sdk
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> **This package and `lumentest-station` both provide the `lumentest_station`
|
|
46
|
+
> import name and must never be installed into the same environment.** A
|
|
47
|
+
> station developer already has the real package — install `lr-lumentest-sdk`
|
|
48
|
+
> only in a separate `.venv` dedicated to authoring steps and sequences.
|
|
49
|
+
|
|
50
|
+
## Public API
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from lumentest_station import (
|
|
54
|
+
step_options, # decorator: attaches step metadata (type, timeout, limits section)
|
|
55
|
+
sequence_options, # decorator: attaches sequence metadata (name, serial handling)
|
|
56
|
+
run_tool, # run an external tool as a supervised subprocess
|
|
57
|
+
StepFailed, # raise to fail the step; optionally carrying the ToolResult that failed
|
|
58
|
+
ToolResult, # what run_tool() returns: exit_code, stdout, stderr, timed_out
|
|
59
|
+
Measurement, # a single limits-checked measurement to report
|
|
60
|
+
step_main, # run this step standalone from the command line
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Running a single step
|
|
65
|
+
|
|
66
|
+
A step module can run itself directly, with no separate harness:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from lumentest_station import step_options, step_main
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@step_options("PROGRAMMING", display_name="Flash Firmware")
|
|
73
|
+
def flash_firmware(test):
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
raise SystemExit(step_main(flash_firmware))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python my_step.py --limits limits.toml --manifest artifact-manifest.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a step file with no `__main__` guard, use the `lumentest-step` console
|
|
86
|
+
script instead:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
lumentest-step validate my_step.py
|
|
90
|
+
lumentest-step run my_step.py --limits limits.toml --manifest artifact-manifest.json --env NRF_TOOLS=/opt/nrf --results-dir ./results
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`validate` is a static check only — it never imports the file, so it needs no
|
|
94
|
+
hardware and no environment. It enforces the two rules the station's loader
|
|
95
|
+
enforces: exactly one `@step_options`-decorated top-level function, and no
|
|
96
|
+
direct `subprocess` use (steps call `run_tool` instead). `run` performs the
|
|
97
|
+
same check, then imports and executes the step.
|
|
98
|
+
|
|
99
|
+
Options for `run`, and for `step_main` under a `__main__` guard:
|
|
100
|
+
|
|
101
|
+
| Option | Purpose |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `--limits PATH` | TOML file of measurement limits; repeatable, later files win. Needed by any step that returns measurements. |
|
|
104
|
+
| `--manifest PATH` | A package directory, or its `artifact-manifest.json`, so `manifest.resolve(...)` works against a real package |
|
|
105
|
+
| `--env NAME=VALUE` | Set an environment variable for the step's run, repeatable. Production steps read station configuration from the environment, so this stands in for the station's `[env]` config. |
|
|
106
|
+
| `--dut`, `--article`, `--revision`, `--station` | The identity fields the step sees on its `test` context |
|
|
107
|
+
| `--timeout SECONDS` | Override the step's own `@step_options(timeout=...)` |
|
|
108
|
+
| `--results-dir DIR` | Where captured `stdout.txt`, `stderr.txt` and `traceback.txt` land |
|
|
109
|
+
|
|
110
|
+
The exit status is `0` when the step passed, `1` when it failed (an explicit
|
|
111
|
+
`StepFailed`, or an out-of-limit measurement), and `2` on an error — an
|
|
112
|
+
unexpected exception, a timeout, or a file the loader rejects.
|
|
113
|
+
|
|
114
|
+
A station does far more than run one step: it uploads to QRM, validates serial
|
|
115
|
+
numbers, curates the `PATH` a tool is resolved against, and runs a whole
|
|
116
|
+
sequence in order. None of that is reproduced here. A green standalone run says
|
|
117
|
+
the step's own logic holds, not that the sequence will pass on a station.
|
|
118
|
+
|
|
119
|
+
## Full authoring guide
|
|
120
|
+
|
|
121
|
+
For the complete authoring reference — sequence structure, limits files,
|
|
122
|
+
manifests, the step SDK's allowed third-party imports, and the full
|
|
123
|
+
`lumentest-step` command reference — see
|
|
124
|
+
[`doc/step-sdk.md`](https://gitlab.com/lumenradio/production/lumentest/-/blob/main/doc/step-sdk.md)
|
|
125
|
+
in the LumenTest repository.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
Makefile
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/lr_lumentest_sdk.egg-info/PKG-INFO
|
|
6
|
+
src/lr_lumentest_sdk.egg-info/SOURCES.txt
|
|
7
|
+
src/lr_lumentest_sdk.egg-info/dependency_links.txt
|
|
8
|
+
src/lr_lumentest_sdk.egg-info/entry_points.txt
|
|
9
|
+
src/lr_lumentest_sdk.egg-info/requires.txt
|
|
10
|
+
src/lr_lumentest_sdk.egg-info/scm_file_list.json
|
|
11
|
+
src/lr_lumentest_sdk.egg-info/scm_version.json
|
|
12
|
+
src/lr_lumentest_sdk.egg-info/top_level.txt
|
|
13
|
+
src/lumentest_station/__init__.py
|
|
14
|
+
src/lumentest_station/_version.py
|
|
15
|
+
src/lumentest_station/decorators.py
|
|
16
|
+
src/lumentest_station/limits.py
|
|
17
|
+
src/lumentest_station/manifest.py
|
|
18
|
+
src/lumentest_station/run_tool.py
|
|
19
|
+
src/lumentest_station/sequence.py
|
|
20
|
+
src/lumentest_station/step_executor.py
|
|
21
|
+
src/lumentest_station/step_main.py
|
|
22
|
+
src/lumentest_station/test_context.py
|
|
23
|
+
tests/_helpers.py
|
|
24
|
+
tests/conftest.py
|
|
25
|
+
tests/unit/test_allowlist_closed.py
|
|
26
|
+
tests/unit/test_no_undeclared_dependency.py
|
|
27
|
+
tests/unit/test_public_surface.py
|
|
28
|
+
tests/unit/test_staging_tree_matches_allowlist.py
|
|
29
|
+
tests/unit/test_sync_matches_station_source.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"LICENSE",
|
|
4
|
+
"Makefile",
|
|
5
|
+
"README.md",
|
|
6
|
+
"pyproject.toml",
|
|
7
|
+
"tests/_helpers.py",
|
|
8
|
+
"tests/conftest.py",
|
|
9
|
+
"tests/unit/test_allowlist_closed.py",
|
|
10
|
+
"tests/unit/test_no_undeclared_dependency.py",
|
|
11
|
+
"tests/unit/test_public_surface.py",
|
|
12
|
+
"tests/unit/test_staging_tree_matches_allowlist.py",
|
|
13
|
+
"tests/unit/test_sync_matches_station_source.py"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lumentest_station
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""LumenRadio Station — public API surface for step and sequence authors."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from lumentest_station._version import __version__
|
|
5
|
+
except ImportError: # pragma: no cover - _version.py is generated by setuptools_scm
|
|
6
|
+
__version__ = "0.0.0+unknown"
|
|
7
|
+
|
|
8
|
+
from lumentest_station.decorators import step_options, sequence_options
|
|
9
|
+
from lumentest_station.run_tool import run_tool, StepFailed, ToolResult
|
|
10
|
+
from lumentest_station.limits import Measurement
|
|
11
|
+
from lumentest_station.step_main import step_main
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"step_options",
|
|
15
|
+
"sequence_options",
|
|
16
|
+
"run_tool",
|
|
17
|
+
"StepFailed",
|
|
18
|
+
"ToolResult",
|
|
19
|
+
"Measurement",
|
|
20
|
+
"step_main",
|
|
21
|
+
]
|