Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,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@v10.2.0

Check failure on line 79 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfW&open=AaE4oWGq-FcIkQBG0sfW&pullRequest=697
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
enable-cache: true
cache-suffix: "pssm-drift"
- name: Install the project
run: uv sync --all-extras --dev

Check warning on line 84 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfY&open=AaE4oWGq-FcIkQBG0sfY&pullRequest=697

Check warning on line 84 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--no-build" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfX&open=AaE4oWGq-FcIkQBG0sfX&pullRequest=697
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
- name: Download the PSSM test suite XMI
run: |
xmi_dir="$(mktemp -d)"
curl --fail --silent --show-error --location --retry 3 \
--output "$xmi_dir/PSSM_TestSuite.xmi" \
https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi

Check warning on line 90 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Not enforcing HTTPS here might allow for redirections to insecure websites. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfZ&open=AaE4oWGq-FcIkQBG0sfZ&pullRequest=697
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
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 pytest tests/uml/pssm/test_pssm_drift.py --timeout 300

Check warning on line 95 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--no-build" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfa&open=AaE4oWGq-FcIkQBG0sfa&pullRequest=697

Check warning on line 95 in .github/workflows/python-package.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=fgmacedo_python-statemachine&issues=AaE4oWGq-FcIkQBG0sfb&open=AaE4oWGq-FcIkQBG0sfb&pullRequest=697

Copy link
Copy Markdown
Owner Author

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-build fails on the 3.15 jobs. Tracked in #684, which adds the flag once the wheels land.

Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ tmp/
# Local specs
specs/

# Local copy of the OMG PSSM test suite, never committed (see tests/uml/pssm/README.md)
PSSM_TestSuite.xmi

# Environment files (secrets)
.env
.env.*
Expand Down
18 changes: 18 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ Before you submit a pull request, check that it meets these guidelines:
3. Consider adding yourself to the contributor's list.
4. The pull request should work for all supported Python versions.

## UML cases based on PSSM

`tests/uml/pssm/` runs cases generated from the test suite of the OMG Precise Semantics of
UML State Machines (PSSM) 1.0 specification, on both engines. Each case is a native YAML
statechart plus the stimulus and the traces the suite expects. Cases the library cannot
pass yet are listed in `tests/uml/pssm/conftest.py`, each with a reason code; since
`xfail_strict` is on, a change that makes one of them pass must remove it from that list.
The same file lists in `LOWERED` the cases that pass only because the converter lowers a
construct the library cannot spell yet; when that construct gets a native spelling, the
converter stops lowering it and those cases are reclassified.

The cases are generated by `tests/uml/pssm/convert.py` and must not be edited by hand. The
OMG XMI is never committed: to regenerate the cases, download it, point `PSSM_XMI` at it
and run the converter, as described in
[tests/uml/pssm/README.md](https://github.com/fgmacedo/python-statemachine/blob/main/tests/uml/pssm/README.md).
The `pssm-drift` CI job downloads the XMI and fails if the committed cases differ from the
converter output.

## Releasing a New Version

This project follows [trunk based development](https://trunkbaseddevelopment.com/): `main` is
Expand Down
Empty file added tests/uml/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions tests/uml/pssm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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 YAML mapping:

- `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.
- `statechart`: a native document, loaded untrusted with `statemachine.io.load`.

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.
Empty file added tests/uml/pssm/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions tests/uml/pssm/cases/Behavior/Behavior_001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires: []
expected_traces:
- S1(entry)
stimulus:
- event: Start
behaviors:
pssm_S1_entry:
- S1(entry)
statechart:
name: Behavior_001
states:
S1:
initial: true
enter:
- pssm_S1_entry
transitions:
- event: Start
target: FinalState1
FinalState1:
final: true
21 changes: 21 additions & 0 deletions tests/uml/pssm/cases/Behavior/Behavior_002.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires: []
expected_traces:
- S1(exit)
stimulus:
- event: Start
behaviors:
pssm_S1_exit:
- S1(exit)
statechart:
name: Behavior_002
states:
S1:
initial: true
exit:
- pssm_S1_exit
transitions:
- event: Start
target: FinalState1
FinalState1:
final: true
40 changes: 40 additions & 0 deletions tests/uml/pssm/cases/Behavior/Behavior_003-A.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires:
- do_activity_accept
expected_traces:
- S1(entry)::S1(doActivityPartI)
- S1(entry)
stimulus:
- event: Start
- event: AnotherSignal
behaviors:
pssm_S1_entry:
- S1(entry)
on_invoke_S1:
- S1(doActivityPartI)
- accept: Continue
pssm_T3_effect:
- T3(effect)
statechart:
name: Behavior_003-A
states:
wait:
initial: true
transitions:
- event: Start
target: S1
S1:
enter:
- pssm_S1_entry
transitions:
- event: done.invoke.S1
target: error
'on':
- pssm_T3_effect
- event: AnotherSignal
target: FS1
FS1:
final: true
error:
transitions:
- target: FS1
30 changes: 30 additions & 0 deletions tests/uml/pssm/cases/Behavior/Behavior_003-B.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires:
- do_activity_accept
expected_traces:
- S1(entry)::S1(doActivityPartI)::S1(doActivityPartII)
stimulus:
- event: Start
- event: Continue
behaviors:
pssm_S1_entry:
- S1(entry)
on_invoke_S1:
- S1(doActivityPartI)
- accept: Continue
statechart:
name: Behavior_003-B
states:
wait:
initial: true
transitions:
- event: Start
target: S1
S1:
enter:
- pssm_S1_entry
transitions:
- event: done.invoke.S1
target: FinalState1
FinalState1:
final: true
36 changes: 36 additions & 0 deletions tests/uml/pssm/cases/Behavior/Behavior_004.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires:
- do_activity_accept
expected_traces:
- S1(entry)::S1(doActivityPartI)::T3(effect)::S1(doActivityPartII)
stimulus:
- event: Start
- event: AnotherSignal
- event: Continue
behaviors:
pssm_S1_entry:
- S1(entry)
on_invoke_S1:
- S1(doActivityPartI)
- accept: Continue
pssm_T3_effect:
- T3(effect)
statechart:
name: Behavior_004
states:
wait:
initial: true
transitions:
- event: Start
target: S1
S1:
enter:
- pssm_S1_entry
transitions:
- event: done.invoke.S1
target: FS1
- event: AnotherSignal
'on':
- pssm_T3_effect
FS1:
final: true
38 changes: 38 additions & 0 deletions tests/uml/pssm/cases/Choice/Choice_001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires:
- choice
expected_traces:
- T4(effect)::T4(effect)::T4(effect)::T4(effect)
stimulus:
- event: Start
behaviors:
pssm_T4_effect:
- T4(effect)
statechart:
name: Choice_001
datamodel:
value: '0'
states:
Increment:
enter:
- assign:
location: value
expr: value + 1
transitions:
- target: FS1
cond: value == 5
- target: Increment
cond: value < 5
'on':
- pssm_T4_effect
wait:
initial: true
transitions:
- event: Start
target: Increment
'on':
- assign:
location: value
expr: '0'
FS1:
final: true
41 changes: 41 additions & 0 deletions tests/uml/pssm/cases/Choice/Choice_002.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by tests/uml/pssm/convert.py from the OMG PSSM 1.0 test suite (ptc/18-11-06, https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi). Do not edit.
requires:
- choice
expected_traces:
- T3(effect)
- T4(effect)
- T5(effect)
stimulus:
- event: Start
behaviors:
pssm_T3_effect:
- T3(effect)
pssm_T4_effect:
- T4(effect)
pssm_T5_effect:
- T5(effect)
statechart:
name: Choice_002
states:
wait:
initial: true
transitions:
- event: Start
target: S1
cond: 'True'
'on':
- pssm_T3_effect
- event: Start
target: S1
cond: 'True'
'on':
- pssm_T4_effect
- event: Start
target: S1
'on':
- pssm_T5_effect
FS1:
final: true
S1:
transitions:
- target: FS1
Loading
Loading