Repository navigation
feat: accept x- extension keys in native documents and run the OMG PSSM 1.0 test suite #697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
be2614e
4908150
3662fd7
f7460f7
57b325a
041fd96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,10 @@ | |
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| # Must match the exclude-newer span recorded in uv.lock, or `uv sync --locked` fails. | ||
| UV_EXCLUDE_NEWER: "7 days" | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
|
|
@@ -27,14 +31,14 @@ | |
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
| - name: Setup Graphviz | ||
| uses: ts-graphviz/setup-graphviz@v2 | ||
| uses: ts-graphviz/setup-graphviz@b1de5da23ed0a6d14e0aeee8ed52fdd87af2363c # v2 | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v10.2.0 | ||
| uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 | ||
| with: | ||
| enable-cache: true | ||
| cache-suffix: "python${{ matrix.python-version }}" | ||
| - name: Install the project | ||
| run: uv sync --all-extras --dev | ||
| run: uv sync --locked --all-extras --dev | ||
|
Check warning on line 41 in .github/workflows/python-package.yml
|
||
| #---------------------------------------------- | ||
| # run ruff | ||
| #---------------------------------------------- | ||
|
|
@@ -54,7 +58,7 @@ | |
| # upload coverage | ||
| #---------------------------------------------- | ||
| - name: Upload coverage to Codecov | ||
| uses: codecov/codecov-action@v7 | ||
| uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 | ||
| if: matrix.python-version == 3.15 | ||
| with: | ||
| token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos | ||
|
|
@@ -64,3 +68,32 @@ | |
| flags: unittests | ||
| name: codecov-umbrella | ||
| verbose: true | ||
|
|
||
| pssm-drift: | ||
| # The XMI may not be stored or cached anywhere, see tests/uml/pssm/README.md. | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v7 | ||
| with: | ||
| python-version: "3.15" | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 | ||
| with: | ||
| enable-cache: true | ||
| cache-suffix: "pssm-drift" | ||
| - name: Install the project | ||
| run: uv sync --locked --all-extras --dev | ||
|
Check warning on line 88 in .github/workflows/python-package.yml
|
||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not applied yet: PyYAML 6.0.3 publishes no cp315 wheels, so |
||
| - name: Download the PSSM test suite XMI | ||
| run: | | ||
| xmi_dir="$(mktemp -d)" | ||
| curl --proto "=https" --fail --silent --show-error --location --retry 3 \ | ||
| --output "$xmi_dir/PSSM_TestSuite.xmi" \ | ||
| https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi | ||
| echo "PSSM_XMI=$xmi_dir/PSSM_TestSuite.xmi" >> "$GITHUB_ENV" | ||
| - name: Compare the committed cases with the converter output | ||
| env: | ||
| PSSM_REQUIRED: "1" | ||
| run: uv run --locked pytest tests/uml/pssm/test_pssm_drift.py --timeout 300 | ||
|
Check warning on line 99 in .github/workflows/python-package.yml
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,10 @@ | |
| tags: [ 'v?*.*.*' ] | ||
| name: release | ||
|
|
||
| env: | ||
| # Must match the exclude-newer span recorded in uv.lock, or `uv sync --locked` fails. | ||
| UV_EXCLUDE_NEWER: "7 days" | ||
|
|
||
| jobs: | ||
| release-build: | ||
| name: Build release artifacts | ||
|
|
@@ -19,15 +23,15 @@ | |
| python-version: '3.15' | ||
|
|
||
| - name: Setup Graphviz | ||
| uses: ts-graphviz/setup-graphviz@v2 | ||
| uses: ts-graphviz/setup-graphviz@b1de5da23ed0a6d14e0aeee8ed52fdd87af2363c # v2 | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v10.2.0 | ||
| uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 | ||
| with: | ||
| enable-cache: true | ||
|
|
||
| - name: Install the project | ||
| run: uv sync --all-extras --dev | ||
| run: uv sync --locked --all-extras --dev | ||
|
Check warning on line 34 in .github/workflows/release.yml
|
||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not applied yet: PyYAML 6.0.3 publishes no cp315 wheels, so |
||
|
|
||
| - name: Test | ||
| run: | | ||
|
|
@@ -63,4 +67,4 @@ | |
| path: dist/ | ||
|
|
||
| - name: Publish package distributions to PyPI | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # python-statemachine 3.3.0 | ||
|
|
||
| *Unreleased* | ||
|
|
||
| ## What's new in 3.3.0 | ||
|
|
||
| ### Extension keys in native documents | ||
|
|
||
| A JSON or YAML document can now carry top-level keys prefixed with `x-`, such as | ||
| `x-review: {owner: ops}`. The loader and the JSON Schema accept them and ignore their | ||
| content, so other tools can keep their data in the same file as the statechart. Other | ||
| unknown keys are still rejected. See [](../io/formats.md#extension-keys). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ Requires Python 3.10+. | |
| ```{toctree} | ||
| :maxdepth: 2 | ||
|
|
||
| 3.3.0 | ||
| 3.2.2 | ||
| 3.2.1 | ||
| 3.2.0 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # UML state machine cases based on PSSM | ||
|
|
||
| The cases in `cases/` are generated by `convert.py` from the test suite of the OMG | ||
| Precise Semantics of UML State Machines (PSSM) 1.0 specification: | ||
|
|
||
| - Source: <https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi> | ||
| - OMG document number: ptc/18-11-06 | ||
| - sha256: `c355b249c356774377a46b60345019d827af1ce417bde88e533aa5f39206ae07` | ||
|
|
||
| The PSSM specification and its test suite are copyright © Object Management Group, Inc. | ||
| and the organizations named in the specification's copyright notice. UML and OMG are | ||
| trademarks of the Object Management Group, used here only to name the source. | ||
|
|
||
| The repository holds the converter, the harness and the generated cases, never the XMI | ||
| itself: the specification's license does not allow posting copies of it on a network | ||
| computer, so the XMI is downloaded when needed and never committed, stored or cached. The | ||
| cases are *based on* PSSM; passing them is not a conformance claim. | ||
|
|
||
| ## Layout | ||
|
|
||
| - `convert.py`: XMI to native YAML, standard library XML parser, deterministic output. | ||
| - `cases/<Category>/<id>.yaml`: one generated case per PSSM test. Do not edit. | ||
| - `overlays/<id>.yaml`: hand-written patches the converter applies, for behaviors with logic | ||
| (counters, signals sent from inside the machine) and for CallEvent tests. `cases/` is | ||
| always exactly the converter output for the XMI plus these overlays. In the generated | ||
| statechart, an overlay's native actions run before its trace callback. | ||
| - `conftest.py`: the expected failures, per engine, with a reason code, and `LOWERED`: the | ||
| cases that pass only because the converter lowers a construct the library cannot spell | ||
| yet (for example, a junction flattened into guarded transitions). Those passes do not | ||
| show native support for the construct. Every case with a non-empty `requires` is in one | ||
| of the two. | ||
| - `test_pssm_cases.py`: runs every case on the sync and the async engine. | ||
| - `test_pssm_drift.py`: regenerates the cases and compares them with `cases/`. | ||
|
|
||
| ## Case format | ||
|
|
||
| Each case is a native statechart document: `statemachine.io.load(path)` loads it as is. The | ||
| test lives in the document's `x-pssm` [extension key](../../../docs/io/formats.md#extension-keys), | ||
| which the loader ignores: | ||
|
|
||
| - `requires`: UML constructs the case uses that the native format cannot spell yet | ||
| (`choice`, `junction`, `join`, `fork`, `terminate`, `entry_point`, `exit_point`, | ||
| `defer`, `call_event`, `redefinition`, `do_activity_accept`). The statechart is the | ||
| closest loadable approximation. | ||
| - `expected_traces`: the accepted traces, taken from the suite's executable oracle. | ||
| - `stimulus`: what the tester does, in order, starting with `Start`: `{event, kwargs}` | ||
| sends a signal, `{call, kwargs}` calls an operation, `{trace}` is a token the tester | ||
| traces itself, `trace_result` formats the value a call returned. | ||
| - `behaviors`: the trace tokens each callback appends; `{name}` placeholders are filled | ||
| with the event data. `on_invoke_<state>` entries are doActivities; in them, | ||
| `{send: {event}}` sends an event to the machine and `{accept}` parks the doActivity until | ||
| its state is left. | ||
|
|
||
| The callbacks the statechart names come from the harness listener, which reads | ||
| `behaviors`; loaded without it, the machine runs but traces nothing. The harness loads the | ||
| cases untrusted and validated against the JSON Schema. | ||
|
|
||
| The converter lowers UML completion transitions to what the library has today: from a | ||
| composite state to `done.state.<id>`, from a state with a doActivity to | ||
| `done.invoke.<id>`, from a simple state to an eventless transition. | ||
|
|
||
| ## Running | ||
|
|
||
| ```bash | ||
| timeout 120 uv run pytest tests/uml/pssm -n 4 | ||
| ``` | ||
|
|
||
| ## Regenerating | ||
|
|
||
| Download the XMI to a git-ignored location (any directory outside the repository, or the | ||
| repository root, where `PSSM_TestSuite.xmi` is ignored), then: | ||
|
|
||
| ```bash | ||
| export PSSM_XMI=/path/to/PSSM_TestSuite.xmi | ||
| uv run python -m tests.uml.pssm.convert --out tests/uml/pssm/cases | ||
| uv run pytest tests/uml/pssm/test_pssm_drift.py | ||
| ``` | ||
|
|
||
| The converter checks the sha256 above before reading the file. Without `PSSM_XMI` the | ||
| drift test is skipped; `PSSM_REQUIRED=1` turns the skip into a failure, which is how the | ||
| `pssm-drift` CI job runs it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not applied yet: PyYAML 6.0.3 publishes no cp315 wheels, so
--no-buildfails on the 3.15 jobs. Tracked in #684.