@@ -380,6 +380,135 @@ func TestAncestorPriorityIsNotAChoice(t *testing.T) {
380380 }
381381}
382382
383+ // Leaves in sibling regions select the same transition out of the composite
384+ // state enclosing them, which fires once: so does the choice among the
385+ // transitions out of it.
386+ func TestSharedAncestorChoiceIsReportedOnce (t * testing.T ) {
387+ src := `package test {
388+ state Machine {
389+ attribute level : Integer = 8;
390+ entry; then work;
391+ state work parallel {
392+ state a { entry; then a1; state a1; }
393+ state b { entry; then b1; state b1; }
394+ }
395+ state low;
396+ state high;
397+ transition first work accept Go if level > 5 then low;
398+ transition first work accept Go if level > 7 then high;
399+ transition first work accept Go if 1 / (level - 8) > 0 then high;
400+ }
401+ }`
402+ idx , _ , ctx := buildRuntime (t , "<test>" , parseAndBuild (t , src ))
403+ sym := findSymbolByName (idx .DocumentRoot ("<test>" ), "Machine" , ast .DefState )
404+ if sym == nil {
405+ t .Fatal ("state machine not found" )
406+ }
407+ _ , visited , err := ctx .ExecuteStateWithEvents (sym , []string {"Go" })
408+ if err != nil {
409+ t .Fatalf ("execute: %v" , err )
410+ }
411+ if strings .Join (visited , "," ) != "work,a1,b1,low" {
412+ t .Fatalf ("visited %v, want both regions entered, then the first transition out of work" , visited )
413+ }
414+ want := "choice state work on accept Go: transitions 1->low, 2->high (unordered; took 1->low)"
415+ if got := ctx .Choices (); len (got ) != 1 || got [0 ].String () != want {
416+ t .Fatalf ("choices = %v, want exactly [%s]" , got , want )
417+ }
418+ if got := ctx .UnevaluableGuards (); len (got ) != 1 || got [0 ].Alternative != "3->high" {
419+ t .Fatalf ("unevaluable guards = %v, want the third transition out of work, once" , got )
420+ }
421+ }
422+
423+ // A transition out of a composite state loses to one a nested state takes on the
424+ // same event, so the alternatives found out of the composite state were never
425+ // the run's to choose among: nothing about them is reported.
426+ func TestAncestorChoiceSuppressedByNestedTransitionIsNotReported (t * testing.T ) {
427+ src := `package test {
428+ state Machine {
429+ attribute level : Integer = 8;
430+ entry; then work;
431+ state work parallel {
432+ state a {
433+ entry; then a1;
434+ state a1;
435+ state a2;
436+ transition first a1 accept Go then a2;
437+ }
438+ state b { entry; then b1; state b1; }
439+ }
440+ state low;
441+ state high;
442+ transition first work accept Go if level > 5 then low;
443+ transition first work accept Go if level > 7 then high;
444+ transition first work accept Go if 1 / (level - 8) > 0 then high;
445+ }
446+ }`
447+ idx , _ , ctx := buildRuntime (t , "<test>" , parseAndBuild (t , src ))
448+ sym := findSymbolByName (idx .DocumentRoot ("<test>" ), "Machine" , ast .DefState )
449+ if sym == nil {
450+ t .Fatal ("state machine not found" )
451+ }
452+ _ , visited , err := ctx .ExecuteStateWithEvents (sym , []string {"Go" })
453+ if err != nil {
454+ t .Fatalf ("execute: %v" , err )
455+ }
456+ if strings .Join (visited , "," ) != "work,a1,b1,a2" {
457+ t .Fatalf ("visited %v, want the nested transition to fire and work to stay active" , visited )
458+ }
459+ if got := ctx .Notes (); len (got ) != 0 {
460+ t .Fatalf ("the outranked transitions out of work were reported: %v" , got )
461+ }
462+ }
463+
464+ // A step that fails after one token already went still made an ordering choice:
465+ // the failing token could have gone first, and the diagnostics of a failed run
466+ // must say what the run did before it failed.
467+ func TestTokenOrderIsReportedWhenALaterTokenFails (t * testing.T ) {
468+ src := `package test {
469+ private import ScalarValues::*;
470+ action race {
471+ attribute x : Integer = 0;
472+ attribute n : Integer = 0;
473+ first start;
474+ fork split;
475+ action safe { assign x := 1; }
476+ action failing { assign x := 1 / n; }
477+ join sync;
478+ done;
479+ succession first start then split;
480+ succession first split then failing;
481+ succession first split then safe;
482+ succession first safe then sync;
483+ succession first failing then sync;
484+ succession first sync then done;
485+ }
486+ }`
487+ idx , _ , ctx := buildRuntime (t , "<test>" , parseAndBuild (t , src ))
488+ sym := findSymbolByName (idx .DocumentRoot ("<test>" ), "race" , ast .DefAction )
489+ if sym == nil {
490+ t .Fatal ("action not found" )
491+ }
492+ _ , err := ctx .ExecuteAction (sym )
493+ if err == nil || ! strings .Contains (err .Error (), "division by zero" ) {
494+ t .Fatalf ("err = %v, want the failing token's error" , err )
495+ }
496+ var tokenOrders []ChoicePoint
497+ for _ , c := range ctx .Choices () {
498+ if c .Kind == ChoiceTokenOrder {
499+ tokenOrders = append (tokenOrders , c )
500+ }
501+ }
502+ if len (tokenOrders ) != 1 {
503+ t .Fatalf ("token-order choices = %v, want the one step both tokens took part in" , tokenOrders )
504+ }
505+ got := tokenOrders [0 ]
506+ if len (got .Alternatives ) != 2 || ! strings .HasSuffix (got .Alternatives [0 ], "@failing" ) ||
507+ ! strings .HasSuffix (got .Alternatives [1 ], "@safe" ) || got .Taken != 1 {
508+ t .Fatalf ("choice = %s, want failing and safe as the alternatives, safe taken first" , got )
509+ }
510+ }
511+
383512// Two tokens writing one feature in one step is a choice point naming both
384513// writes and the one that stood, whether or not the values differ: which
385514// performance wrote last is the executor's order either way.
0 commit comments