Skip to content

fix: enter history states by default without running the incoming effect twice - #693

Merged
fgmacedo merged 2 commits into
mainfrom
fgmacedo/uml-w2a_history
Oct 15, 2026
Merged

fgmacedo merged 2 commits into
mainfrom
fgmacedo/uml-w2a_history

Conversation

@fgmacedo

@fgmacedo fgmacedo commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Why

The engine set the default history content to the incoming transition unconditionally (engines/base.py, _add_history_states_to_enter). Two bugs came from that one line:

  • B1: entering a HistoryState with no stored value and no default transition left the machine with an empty configuration, silently. Its effective targets were empty too, so the transition domain was None and ancestors of the history's parent were never entered.
  • B2: entering through history ran the incoming transition's on= actions twice (once as transition content, once as "default history content" on entry of the parent), with a stored value and also with a default transition.

Refs #692

What changes

  • Default history content is only the history's own transition, and only when nothing is stored (SCXML). The incoming transition's actions run once.
  • With neither stored history nor a default transition, the history's parent is entered by default (UML default entry). get_effective_target_states returns the parent in that case, so the transition domain, exit set and ancestor entry are computed as for a transition to the parent. A missing default stays valid at definition time: the existing docs example declares none, and SCXML documents always have one.
  • Default entry of a compound now records the initial transition it chose and runs that transition's content. Before, the content ran only when the compound had exactly one initial transition; W3C test579 (an SCXML <initial> targeting a history, where the factory also adds an initial transition to the first child) only passed because of the double run removed above.
  • docs/states.md describes first-visit behavior, with a doctest.

Evidence

  • 28 new tests in tests/test_statechart_history.py (TestHistoryDefaultEntry, shallow and deep, sync and async via sm_runner): empty-history default entry, entry of the parent's ancestors, entry from a sibling, incoming effect once with a stored value and with a default, default content skipped when a value is stored. All failed on main before the fix.
  • PSSM cases unblocked (tests/uml/pssm/, after rebasing on feat: accept x- extension keys in native documents and run the OMG PSSM 1.0 test suite #697): History_001-A, History_002-A (B2) and History_002-C (B1) now pass on both engines and run unmarked. History_002-B still fails, so it stays in the xfail map: the incoming effect no longer runs twice (B2 is gone from its trace), but the library groups the completion transitions of its two regions into one microstep, while every expected trace runs each region's completion in its own microstep. Its reason now lists only that blocker (unclassified). No other entry changed.
  • Full suite on the rebased branch: 2962 passed, 26 skipped, 126 xfailed; coverage 100% (branch). W3C SCXML suite unchanged.
  • Adversarial review, 1 round by a fresh subagent: tried history inside parallel states, internal transitions from the parent to its own history, history default targeting another history, deep history across regions, error.execution raised by the incoming effect, sync/async divergence, and ran every W3C file on both main and this branch (identical pass/fail sets). No material findings; every scenario it found differing from main was a main bug (empty configuration or doubled effect).
  • Adversarial review of the xfail update, 1 round by a fresh subagent: ran the PSSM cases with --runxfail before and after the fix; the failing set differs by exactly the three removed cases on both engines, every remaining entry still fails on both, and History_002-B's traces confirm the doubled effect is gone and only the region interleaving remains. No material findings.

Not verified

  • The factory still adds an initial transition to the first child when a compound's initial targets a history state; left as is, since the engine no longer depends on it.

@fgmacedo fgmacedo added this to the 3.3.0 milestone Oct 14, 2026
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (8be9e4b) to head (71f1047).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #693   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           53        53           
  Lines         5901      5900    -1     
  Branches       940       939    -1     
=========================================
- Hits          5901      5900    -1     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…ect twice

A history state with no stored value and no default transition left the
machine with an empty configuration; it now enters its parent by default.
Entering through history no longer runs the incoming transition's actions a
second time: only the history's own default transition is default history
content, and only when nothing is stored.

Refs #692

Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
History_001-A, History_002-A and History_002-C pass on both engines now.
History_002-B no longer runs the incoming effect twice; it still fails on
the interleaving of two regions' completions, so only that blocker stays.

Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
@fgmacedo
fgmacedo force-pushed the fgmacedo/uml-w2a_history branch from 7cd2597 to 71f1047 Compare October 15, 2026 00:52
@sonarqubecloud

Copy link
Copy Markdown

@fgmacedo
fgmacedo merged commit 75c9181 into main Oct 15, 2026
15 checks passed
@fgmacedo
fgmacedo deleted the fgmacedo/uml-w2a_history branch October 15, 2026 01:36
fgmacedo added a commit that referenced this pull request Oct 16, 2026
Lay out the 3.3.0 notes in the structure later PRs extend and document
the history default entry fix (#693), the done.invoke error reporting
fix (#698) and the PSSM-based UML test cases (#697).

Refs #692
Refs #713

Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant