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.
Files changed (31) hide show
  1. lr_lumentest_sdk-0.6.1/LICENSE +21 -0
  2. lr_lumentest_sdk-0.6.1/Makefile +120 -0
  3. lr_lumentest_sdk-0.6.1/PKG-INFO +125 -0
  4. lr_lumentest_sdk-0.6.1/README.md +98 -0
  5. lr_lumentest_sdk-0.6.1/pyproject.toml +71 -0
  6. lr_lumentest_sdk-0.6.1/setup.cfg +4 -0
  7. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/PKG-INFO +125 -0
  8. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/SOURCES.txt +29 -0
  9. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/dependency_links.txt +1 -0
  10. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/entry_points.txt +2 -0
  11. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/requires.txt +9 -0
  12. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/scm_file_list.json +15 -0
  13. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/scm_version.json +8 -0
  14. lr_lumentest_sdk-0.6.1/src/lr_lumentest_sdk.egg-info/top_level.txt +1 -0
  15. lr_lumentest_sdk-0.6.1/src/lumentest_station/__init__.py +21 -0
  16. lr_lumentest_sdk-0.6.1/src/lumentest_station/_version.py +24 -0
  17. lr_lumentest_sdk-0.6.1/src/lumentest_station/decorators.py +65 -0
  18. lr_lumentest_sdk-0.6.1/src/lumentest_station/limits.py +120 -0
  19. lr_lumentest_sdk-0.6.1/src/lumentest_station/manifest.py +557 -0
  20. lr_lumentest_sdk-0.6.1/src/lumentest_station/run_tool.py +390 -0
  21. lr_lumentest_sdk-0.6.1/src/lumentest_station/sequence.py +348 -0
  22. lr_lumentest_sdk-0.6.1/src/lumentest_station/step_executor.py +277 -0
  23. lr_lumentest_sdk-0.6.1/src/lumentest_station/step_main.py +413 -0
  24. lr_lumentest_sdk-0.6.1/src/lumentest_station/test_context.py +168 -0
  25. lr_lumentest_sdk-0.6.1/tests/_helpers.py +42 -0
  26. lr_lumentest_sdk-0.6.1/tests/conftest.py +36 -0
  27. lr_lumentest_sdk-0.6.1/tests/unit/test_allowlist_closed.py +31 -0
  28. lr_lumentest_sdk-0.6.1/tests/unit/test_no_undeclared_dependency.py +29 -0
  29. lr_lumentest_sdk-0.6.1/tests/unit/test_public_surface.py +38 -0
  30. lr_lumentest_sdk-0.6.1/tests/unit/test_staging_tree_matches_allowlist.py +22 -0
  31. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ [console_scripts]
2
+ lumentest-step = lumentest_station.step_main:main
@@ -0,0 +1,9 @@
1
+ pydantic>=2.8
2
+ pyserial>=3.5
3
+
4
+ [dev]
5
+ build>=1.2.1
6
+ twine>=6.1
7
+ pytest>=8.4
8
+ pytest-cov>=5.0
9
+ black>=25.9
@@ -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,8 @@
1
+ {
2
+ "tag": "0.6.1",
3
+ "distance": 0,
4
+ "node": "g16f681c",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-15"
8
+ }
@@ -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
+ ]