diff --git a/changes/unreleased/migrate-sender-and-wait-notes.fixed.md b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md new file mode 100644 index 0000000000..5b3cad33ed --- /dev/null +++ b/changes/unreleased/migrate-sender-and-wait-notes.fixed.md @@ -0,0 +1 @@ +- **The migrator's ledger counts a send action as a sender only once it is written, and a button of the tool's UI prototype as posting the signals it specializes.** An accept of a signal is noted as waiting for a message only the tool's UI posts when no written send sends it or a signal specializing it — a send left as a placeholder, which performs nothing, no longer hides the note — and a button posting a specialization of the accepted signal now raises it. A send whose port is not the sender's keeps the note on its arguments beside the one on its port, and an entry, exit or transition effect is noted as waiting for the clock only for a wait the writer writes, not for a time event or duration constraint it leaves as a placeholder, nor for one in a behavior named by a call written as a placeholder or never firing, which invokes nothing. diff --git a/docs/reference/sysml-v1-migration.md b/docs/reference/sysml-v1-migration.md index 5e83d38e66..dc6509d1d5 100644 --- a/docs/reference/sysml-v1-migration.md +++ b/docs/reference/sysml-v1-migration.md @@ -255,7 +255,7 @@ returned over the service yet. | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity with no nodes | an empty action: `entry action x;` in a state, `do action x { }` on a transition, whose target follows on the next line | mapped (the note says the action is empty) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an Activity whose every action node is refused | the action, holding the flow and a comment for each refused node; the behavior runs nothing | approximated (each node: **unmapped**) | | `entry`, `doActivity`, `exit` behavior or transition `effect` that is an OpaqueBehavior in a language the mapping cannot write | the action, holding the body as a comment | approximated | -| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes, an accept of a time event, or a call of an activity, or of an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted) | approximated | +| `entry` or `exit` behavior or transition `effect` that waits for the clock — an Activity with a duration constraint on itself or on one of its nodes that is written as a wait, an accept of a time event whose time is written, or a call it writes invoking an activity, or an operation whose method is an activity, that has one | the action as written; the note names the wait, since a v2 entry or exit action or transition effect is performed whole at the instant it is triggered and a run stops at the wait with a typed error (the `doActivity` may wait, and is not noted; a constraint or time event left as a placeholder is no wait, and is not noted either, nor is one in a behavior named by a call written as a placeholder or never firing, which invokes nothing) | approximated | | Transition `effect` referring to a behavior owned elsewhere | `do action : Def` on the transition, the target following on the next line; the behavior's own `action def` is written once where it is owned | mapped | | `entry`, `doActivity`, `exit` behavior or transition `effect` referring to a behavior that is not written, or is written as something no state runs (a StateMachine, for one) | comment in the state's body or before the transition (a `/* */` comment is admitted only where a member may appear, not between the transition's clauses); the state or transition is written without it | approximated (the state or transition: "its … is not run"; a behavior not written: **unmapped**) | | Transition `effect` with `in` parameters | the accepted signal is named, `accept sig : Sig`, and each parameter typed by the signal (or a general of it), or the sole untyped one, is bound to it: `in p : Sig = sig;`; a parameter of another type takes no value | mapped (an unbound parameter: approximated) | @@ -272,7 +272,7 @@ returned over the service yet. | State `stateInvariant` | comment in the state's body quoting the constraint; the state is written with a body so the comment has a place | **unmapped** — no v2 form | | Initial transition with a trigger or guard | the region's `entry; then s;`; each trigger and the guard are dropped and reported apart from the transition | approximated (the trigger, the guard: unmapped) | | SignalEvent, ChangeEvent, relative TimeEvent | written where a trigger refers to them, as `accept Sig`, `accept when `, `accept after [SI::s]` | mapped / approximated | -| SignalEvent whose signal no send or broadcast action of the document sends (neither it nor a signal specializing it, which the accept takes too), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated | +| SignalEvent whose signal no written send action of the document sends (neither it nor a signal specializing it, which the accept takes too; a send left as a placeholder performs nothing and is no sender), and a button of the tool's UI prototype posts (a «SimulationProfile» `SignalInstance` on a «Button» class naming the signal, or a signal specializing it, as its `element`) | the accept as written, on the transition or accept action; the trigger's note says that only the prototype posts the signal, which the migration does not write, so the accept waits for a message nothing in the model posts and a run stops there as a deadlock. A signal nothing at all posts is an ordinary accept, of a message from outside the model in the tool and here alike | approximated | | Absolute TimeEvent a trigger refers to | `accept at ` on the transition or accept action, the instant an attribute of the `state def`/`action def` typed `Time::TimeInstantValue` when `when` is a number with a time unit or an expression that resolves, read on the simulation clock, which starts at 0 | approximated (the clock's origin is the run's, not the calendar's) | | Event (of any kind) no trigger refers to | — | skipped, counted as a model element nothing refers to | | SignalEvent whose signal is not written, TimeEvent whose `when` is not a number with a time unit | comment; the transition that refers to it drops the trigger | **unmapped** — the reason names the signal or the time | diff --git a/internal/translate/migrate/activity.go b/internal/translate/migrate/activity.go index 6562d60e57..b4feb4b2d1 100644 --- a/internal/translate/migrate/activity.go +++ b/internal/translate/migrate/activity.go @@ -840,24 +840,53 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { if len(dcs) == 0 { return "", false } - dc := dcs[0] for _, other := range dcs[1:] { - a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dc)+" is written as its wait") + a.m.add(other, Unmapped, "", "a second duration constraint on "+describe(e)+"; only "+describe(dcs[0])+" is written as its wait") + } + w := a.m.waitOf(e) + switch { + case w.ok: + a.m.add(w.dc, Approximated, a.m.v2Name(a.def), w.note) + return w.expr, true + case w.noInterval: + a.m.unmapped(w.dc, w.why) + default: + a.unmappedWait(w.dc, e, w.why) + } + return "", false +} + +// wait is what the duration constraint dc on an element is written as: the +// delay before the element in seconds with the note on it when ok, else why +// none is written — the constraint has no interval, or its bounds admit no one wait. +type wait struct { + dc *sysmlv1.Element + expr, note string + why string + ok bool + noInterval bool +} + +// waitOf decides the wait the first duration constraint on e is written as; a +// zero wait when e has none. The decision is shared by the writer and by the +// search for the clock waits of a behavior performed at an instant. +func (m *migration) waitOf(e *sysmlv1.Element) wait { + dcs := m.bounded[e] + if len(dcs) == 0 { + return wait{} } + dc := dcs[0] spec := firstOwned(dc, "specification") if spec == nil || spec.Type != "DurationInterval" && spec.Type != "Interval" { - a.m.unmapped(dc, "the duration constraint has no interval") - return "", false + return wait{dc: dc, why: "the duration constraint has no interval", noInterval: true} } - lo, lok, lnote := a.m.durationExpr(a.m.model.Ref(spec, "min"), e) - hi, hok, hnote := a.m.durationExpr(a.m.model.Ref(spec, "max"), e) - if bound, bnote, ok := a.m.singleValue(spec, lo, lok, hok); ok { - a.m.add(dc, Approximated, a.m.v2Name(a.def), joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e))) - return inSeconds(bound), true + lo, lok, lnote := m.durationExpr(m.model.Ref(spec, "min"), e) + hi, hok, hnote := m.durationExpr(m.model.Ref(spec, "max"), e) + if bound, bnote, ok := m.singleValue(spec, lo, lok, hok); ok { + return wait{dc: dc, expr: inSeconds(bound), note: joinNotes(bnote, "so the wait is a fixed "+bound+" s before "+describe(e)), ok: true} } if !lok || !hok { - a.unmappedWait(dc, e, a.m.openInterval(spec, lo, lok, lnote, hi, hok, hnote)) - return "", false + return wait{dc: dc, why: m.openInterval(spec, lo, lok, lnote, hi, hok, hnote)} } note := joinNotes(lnote, hnote) var expr string @@ -865,8 +894,7 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { hf, herr := strconv.ParseFloat(hi, 64) switch { case lerr == nil && herr == nil && lf > hf: - a.unmappedWait(dc, e, "the interval's min "+lo+" exceeds its max "+hi) - return "", false + return wait{dc: dc, why: "the interval's min " + lo + " exceeds its max " + hi} case lo == hi: expr = lo note = joinNotes(note, "written as a fixed wait of "+lo+" s before "+describe(e)) @@ -874,8 +902,7 @@ func (a *activity) waitFor(e *sysmlv1.Element) (string, bool) { expr = "RandomFunctions::uniform(" + lo + ", " + hi + ")" note = joinNotes(note, "written as a wait drawn uniformly over ["+lo+", "+hi+"] s before "+describe(e)+"; a tool's fixed min or max mode is a run setting, not the model's") } - a.m.add(dc, Approximated, a.m.v2Name(a.def), note) - return inSeconds(expr), true + return wait{dc: dc, expr: inSeconds(expr), note: note, ok: true} } func (a *activity) unmappedWait(dc, e *sysmlv1.Element, note string) { @@ -2338,7 +2365,7 @@ func (a *activity) readFeature(n *sysmlv1.Element, name string) { } a.names[r] = pname mult, mnote := a.m.multiplicity(r) - a.m.w.line("out " + writeName(pname) + shaped(mult, r, true) + " = " + expr + ";") + a.m.w.line("out " + writeName(pname) + shaped(mult, r, true, false) + " = " + expr + ";") a.m.add(r, verdictFor(mnote), a.m.v2Name(n)+"."+pname, mnote) a.m.add(n, Mapped, name, "") }) @@ -2405,11 +2432,11 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) { path, hasPath := a.portPath(port) switch { case !a.m.written(port): - note = "the port " + qualifiedName(port) + " has no v2 declaration; the signal is sent to the sender" + note = joinNotes(note, "the port "+qualifiedName(port)+" has no v2 declaration; the signal is sent to the sender") case hasPath: line += " via " + a.m.respellThis(a.self()+"."+path, a.act) default: - note = "the port " + qualifiedName(port) + " is no port of the object the sender acts on; the signal is sent to the sender" + note = joinNotes(note, "the port "+qualifiedName(port)+" is no port of the object the sender acts on; the signal is sent to the sender") } } else if t := firstOwned(n, "target"); t != nil { obj, _, ok := a.objectOf(t) @@ -2424,6 +2451,7 @@ func (a *activity) sendSignal(n *sysmlv1.Element, name string) { } } a.m.w.line(line + ";") + a.m.senders.sent[sig] = true }) a.m.add(n, verdictFor(note), name, note) } @@ -2486,7 +2514,7 @@ func (a *activity) trigger(t, n *sysmlv1.Element) (clause, note string, ok bool) tnote := "" if sig := a.m.model.Ref(ev, "signal"); ev.Type == "SignalEvent" && sig != nil { clause, tnote = a.m.actionRoute(clause, t, a.selfType(), a.act, a.viaPrefix(), sig) - tnote = joinNotes(tnote, a.m.unsentNote(sig)) + a.m.uiAccept(sig, t, n) if len(n.Owned("result")) > 0 { a.payload[n.Owned("result")[0]] = sig } diff --git a/internal/translate/migrate/migrate.go b/internal/translate/migrate/migrate.go index 35bd4db2e7..b3490170b3 100644 --- a/internal/translate/migrate/migrate.go +++ b/internal/translate/migrate/migrate.go @@ -157,7 +157,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { opUsage: map[*sysmlv1.Element]string{}, deciding: map[*sysmlv1.Element]bool{}, bounded: map[*sysmlv1.Element][]*sysmlv1.Element{}, - senders: senders{actions: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}}, + senders: senders{sent: map[*sysmlv1.Element]bool{}, buttons: map[*sysmlv1.Element]int{}}, allocated: map[*sysmlv1.Element][]*sysmlv1.Element{}, triggered: map[*sysmlv1.Element]bool{}, snapshots: map[*sysmlv1.Element]snapshotTyping{}, @@ -259,6 +259,7 @@ func FromModelOptions(name string, model *sysmlv1.Model, opts Options) *Result { m.views(nil) m.flushFlows() m.placeholderEnds() + m.uiOnlyAccepts() m.unwrittenEvents() m.w.fill() m.diagrams() diff --git a/internal/translate/migrate/senders.go b/internal/translate/migrate/senders.go index 8037a8cd35..7c89fb37de 100644 --- a/internal/translate/migrate/senders.go +++ b/internal/translate/migrate/senders.go @@ -1,27 +1,33 @@ package migrate import ( + "sort" "strings" "github.com/Open-MBEE/OpenSysML/internal/translate/xmi/sysmlv1" ) -// senders indexes what posts each signal: the send and broadcast actions of the -// model, and the signal instances of the tool's UI prototype, which a button -// posts when it is pressed in the tool and the migration does not write. +// senders indexes what posts each signal: the send actions the migration +// wrote, and the signal instances of the tool's UI prototype, which a button +// posts when it is pressed in the tool and the migration does not write. The +// accepts of a signal a button posts are noted once every send is written, since +// a send action is a sender only when it is written, not refused as a placeholder. type senders struct { - actions map[*sysmlv1.Element]bool + sent map[*sysmlv1.Element]bool buttons map[*sysmlv1.Element]int + accepts []uiAccept } -// recordSender notes e as a sender of a signal, when it is one. +// uiAccept is an accept of sig: the elements reported for it, which are noted +// when no send action of the document turns out to send sig. +type uiAccept struct { + sig *sysmlv1.Element + of []*sysmlv1.Element +} + +// recordSender notes e as a button of the tool's UI prototype posting a signal, +// when it is one. func (m *migration) recordSender(e *sysmlv1.Element) { - switch e.Type { - case "SendSignalAction", "BroadcastSignalAction": - if sig := m.model.Ref(e, "signal"); sig != nil { - m.senders.actions[sig] = true - } - } for _, s := range e.Stereotypes { if !simulationProvenance.applies(s, "SignalInstance") { continue @@ -34,26 +40,75 @@ func (m *migration) recordSender(e *sysmlv1.Element) { } } +// uiAccept records that the elements of are an accept of sig, to be noted by +// uiOnlyAccepts when a button of the tool's UI prototype posts sig or a signal +// specializing it and no send action is written for either. +func (m *migration) uiAccept(sig *sysmlv1.Element, of ...*sysmlv1.Element) { + if sig == nil { + return + } + if n, _ := m.buttonsPosting(sig); n > 0 { + m.senders.accepts = append(m.senders.accepts, uiAccept{sig: sig, of: of}) + } +} + +// uiOnlyAccepts notes, once every send action is written, each recorded accept +// whose signal only the tool's UI prototype posts. +func (m *migration) uiOnlyAccepts() { + for _, acc := range m.senders.accepts { + if m.sentByAction(acc.sig) { + continue + } + note := m.unsentNote(acc.sig) + for _, e := range acc.of { + m.annotate(e, note, true) + } + } +} + // unsentNote says that the tool's UI prototype is what posts sig: no send action -// of the document sends it, only a button of the prototype, which the migration -// does not write, so an accept of it waits for a message nothing in the model -// posts. "" when a send action names it or nothing at all does: a signal nothing -// posts arrives from outside the model, in the tool and here alike. +// of the document sends it or a signal specializing it, only a button of the +// prototype, which the migration does not write, so an accept of it waits for a +// message nothing in the model posts. A signal nothing posts arrives from outside +// the model, in the tool and here alike, and gets no note. func (m *migration) unsentNote(sig *sysmlv1.Element) string { - if sig == nil || m.senders.buttons[sig] == 0 || m.sentByAction(sig) { - return "" + n, special := m.buttonsPosting(sig) + what := describe(sig) + if len(special) > 0 { + names := make([]string, len(special)) + for i, s := range special { + names[i] = describe(s) + } + what += " or a signal specializing it (" + strings.Join(names, ", ") + ")" } - return "no send action of the document sends " + describe(sig) + ", which only " + count(m.senders.buttons[sig], "button") + + return "no send action of the document sends " + what + ", which only " + count(n, "button") + " of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts" } -// sentByAction reports whether a send action of the model sends sig or a +// buttonsPosting counts the buttons of the tool's UI prototype posting sig or a +// signal specializing it, which an accept of sig takes too, and lists those +// specializing signals by id. +func (m *migration) buttonsPosting(sig *sysmlv1.Element) (n int, special []*sysmlv1.Element) { + for posted, buttons := range m.senders.buttons { + switch { + case posted == sig: + n += buttons + case m.inherits(posted, sig): + n += buttons + special = append(special, posted) + } + } + sort.Slice(special, func(i, j int) bool { return special[i].ID < special[j].ID }) + return n, special +} + +// sentByAction reports whether a send action the migration wrote sends sig or a // signal specializing it, either of which an accept of sig takes. func (m *migration) sentByAction(sig *sysmlv1.Element) bool { - if m.senders.actions[sig] { + if m.senders.sent[sig] { return true } - for sent := range m.senders.actions { + for sent := range m.senders.sent { if m.inherits(sent, sig) { return true } @@ -92,10 +147,8 @@ func uiFrameName(e *sysmlv1.Element) string { return describe(e) } -// clockWait names the first wait on the clock the behavior b, or a behavior an -// action of it calls, is written with: a duration constraint bounding it or one -// of its nodes, written as a wait before it, or an accept of a time event. "" -// when none. +// clockWait names the first wait on the clock written for the behavior b or a +// behavior a call of it invokes; "" when none is written (placeholders are no wait). func (m *migration) clockWait(b *sysmlv1.Element) string { return m.clockWaitIn(b, map[*sysmlv1.Element]bool{}) } @@ -105,45 +158,99 @@ func (m *migration) clockWaitIn(b *sysmlv1.Element, seen map[*sysmlv1.Element]bo return "" } seen[b] = true - if dc := m.waitBound(b); dc != nil { - return "the duration constraint " + describe(dc) + " on " + describe(b) + if w := m.waitOf(b); w.ok { + return "the duration constraint " + describe(w.dc) + " on " + describe(b) } - found := "" - m.walkActions(b, func(n *sysmlv1.Element) { - if found != "" { - return - } - switch n.Type { - case "AcceptEventAction": - for _, t := range n.Owned("trigger") { - if ev := m.model.Ref(t, "event"); ev != nil && ev.Type == "TimeEvent" { - found = "the accept of a time event " + describe(n) + " in " + describe(b) - return - } - } - case "CallBehaviorAction": - found = m.clockWaitIn(m.model.Ref(n, "behavior"), seen) - case "CallOperationAction": - if op := m.model.Ref(n, "operation"); op != nil { - found = m.clockWaitIn(m.bodyMethod(op), seen) - } + def := b + if op := m.methodOf[b]; op != nil { + def = op + } + return m.graphWait(m.scanGraph(b, def), b, seen) +} + +// scanGraph links an activity graph as the writer does, without writing, far +// enough to tell the nodes written as placeholders and the pins nothing fills. +func (m *migration) scanGraph(act, def *sysmlv1.Element) *activity { + a := m.newActivity(act, def) + a.link() + a.resolveData() + a.deaden() + return a +} + +// graphWait names the first wait written for the graph a of the behavior b, in +// writer order; a node whose pin nothing fills is written without its wait. +func (m *migration) graphWait(a *activity, b *sysmlv1.Element, seen map[*sysmlv1.Element]bool) string { + for _, n := range a.nodes { + if a.starvedPin(n) != nil { + continue + } + if w := m.waitOf(n); w.ok { + return "the duration constraint " + describe(w.dc) + " on " + describe(n) + " in " + describe(b) } - if dc := m.waitBound(n); found == "" && dc != nil { - found = "the duration constraint " + describe(dc) + " on " + describe(n) + " in " + describe(b) + found := "" + switch { + case n.Type == "AcceptEventAction" && m.acceptsTime(n): + found = "the accept of a time event " + describe(n) + " in " + describe(b) + case isStructured(n): + found = m.graphWait(m.scanGraph(n, n), b, seen) + case a.invokes(n): + found = m.clockWaitIn(a.callee(n), seen) } - }) - return found + if found != "" { + return found + } + } + return "" } -// waitBound is the duration constraint on e that is written as a wait before -// it — the first with an interval, as waitFor writes it; nil when none is. -func (m *migration) waitBound(e *sysmlv1.Element) *sysmlv1.Element { - for _, dc := range m.bounded[e] { - if spec := firstOwned(dc, "specification"); spec != nil && (spec.Type == "DurationInterval" || spec.Type == "Interval") { - return dc +// invokes reports whether the call n is written invoking its callee: not as a +// placeholder, a library function, or the empty step of a usage with no receiver. +func (a *activity) invokes(n *sysmlv1.Element) bool { + if a.dead[n] { + return false + } + switch n.Type { + case "CallBehaviorAction": + return a.m.primitiveCalled(n) == nil + case "CallOperationAction": + op := a.m.model.Ref(n, "operation") + t := firstOwned(n, "target") + if !a.m.asUsage[op] || t == nil { + return true + } + if port := a.m.model.Ref(n, "onPort"); port != nil { + _, _, ok := a.portReceiver(port, t, op) + return ok } + _, _, ok := a.receiverOf(t, op) + return ok + } + return false +} + +// callee is the activity the call n invokes: its behavior, or the operation's method. +func (a *activity) callee(n *sysmlv1.Element) *sysmlv1.Element { + if n.Type == "CallOperationAction" { + return a.m.bodyMethod(a.m.model.Ref(n, "operation")) + } + return a.m.model.Ref(n, "behavior") +} + +// acceptsTime reports whether the accept action n is written accepting a time +// event: its first trigger, the one written, names a time event whose time the +// writer spells. +func (m *migration) acceptsTime(n *sysmlv1.Element) bool { + triggers := n.Owned("trigger") + if len(triggers) == 0 { + return false + } + ev := m.model.Ref(triggers[0], "event") + if ev == nil || ev.Type != "TimeEvent" { + return false } - return nil + _, _, ok := m.acceptClause(ev, n.Parent, "") + return ok } // instantWaitNote says that a behavior performed at an instant — a state's entry diff --git a/internal/translate/migrate/states.go b/internal/translate/migrate/states.go index 855fc1899f..e037f5e279 100644 --- a/internal/translate/migrate/states.go +++ b/internal/translate/migrate/states.go @@ -2704,7 +2704,7 @@ func (s *stateRegion) transitionAccepts(t *sysmlv1.Element, triggers []*sysmlv1. } note = joinNotes(note, rnote) if ev := s.m.model.Ref(tr, "event"); ev != nil && ev.Type == "SignalEvent" { - note = joinNotes(note, s.m.unsentNote(s.m.model.Ref(ev, "signal"))) + s.m.uiAccept(s.m.model.Ref(ev, "signal"), tr) } s.m.add(tr, verdictFor(note), "", joinNotes(note, rinfo)) accepts = append(accepts, routes...) diff --git a/tests/migrate/instant_waits_test.go b/tests/migrate/instant_waits_test.go index 37e6dcdbcf..49f54905dd 100644 --- a/tests/migrate/instant_waits_test.go +++ b/tests/migrate/instant_waits_test.go @@ -12,7 +12,11 @@ import ( // clock — by a duration constraint on itself or one of its nodes, a call of an // activity or an operation whose method has one, or an accept of a time event — // is ledgered as an approximation naming the wait: a run stops there. A do -// activity may wait, and gets no such note. +// activity may wait, and gets no such note. Nor does a behavior whose only +// waits the writer leaves as placeholders — a time event whose time it cannot +// spell, a duration constraint whose interval no one wait stands for — since +// nothing written waits; nor one whose waits lie in a behavior named by a call +// written as a placeholder or never firing, which invokes nothing. func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { r := migrateXMI(t, "instant_waits") wantClean(t, "t.sysml", r) @@ -26,4 +30,21 @@ func TestInstantBehaviorsWaitingForTheClockAreLedgered(t *testing.T) { t.Errorf("the do action Blink is noted as waiting for the clock: %s", e.Note) } } + wantNote(t, r, "_sahWait", migrate.Approximated, "the time event's time is not written") + wantNote(t, r, "_dtHold", migrate.Unmapped, "the interval has no max, so the interval is open above") + for _, e := range entriesFor(r, "_sendAckHold") { + if strings.Contains(e.Note, "waits for the clock") { + t.Errorf("the transition effect Send Ack Hold, whose waits are placeholders, is noted as waiting for the clock: %s", e.Note) + } + } + wantNote(t, r, "_sasCall", migrate.Approximated, "the behavior acts on a Relay through its parameter context, which is left unbound") + wantNote(t, r, "_sapCall", migrate.Approximated, "the action never fires: its input pin 'target' must hold a value, but no object flow feeds it") + for _, id := range []string{"_sendAckSettle", "_sendAckPause"} { + wantNote(t, r, id, migrate.Mapped, "written as the do action of") + for _, e := range entriesFor(r, id) { + if strings.Contains(e.Note, "waits for the clock") { + t.Errorf("the transition effect %s, whose call of a waiting behavior invokes nothing, is noted as waiting for the clock: %s", id, e.Note) + } + } + } } diff --git a/tests/migrate/signal_absent_argument_test.go b/tests/migrate/signal_absent_argument_test.go index c85e35bd4a..b01cc5ff7f 100644 --- a/tests/migrate/signal_absent_argument_test.go +++ b/tests/migrate/signal_absent_argument_test.go @@ -73,3 +73,45 @@ func TestSendOfAbsentArgumentToRequiredAttributeIsLedgered(t *testing.T) { wantNote(t, r, "_send", migrate.Approximated, "the argument pin target admits no value, which the signal's target, declared holding one, cannot: a run reaching the send with none stops at it") wantLine(t, r.Notation, "send new Point(target, note) via tx;") } + +// misroutedSend is a beacon whose activity Hail counts its hails, so it acts on +// the beacon, and sends a Ping, which declares no attribute, passing an argument +// pin anyway, on a port of the unrelated block Relay. +const misroutedSend = ` + + + + + + ` + integerHref + ` + + + + + + JavaScript + hails = hails + 1; + + + + + + + + + + + + ` + +const misroutedSendApplications = ` + + ` + +// A send on a port that is no port of the sender's object keeps the note on its +// arguments beside the one on its port: both say what the send as written drops. +func TestSendOnAnotherObjectsPortKeepsItsArgumentNote(t *testing.T) { + r := migrateDocument(t, misroutedSend, misroutedSendApplications) + wantNote(t, r, "_hailSend", migrate.Approximated, "the signal has no attribute for the argument pin strength, which is not sent; the port Relay::rx is no port of the object the sender acts on; the signal is sent to the sender") + wantLine(t, r.Notation, "send new Ping();") +} diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt index 7134891e25..0f03606c4b 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.report.txt +++ b/tests/migrate/testdata/xmi/instant_waits.golden.report.txt @@ -1,25 +1,53 @@ # SysML v1 to v2 migration report: instant_waits.xmi -# migrated 57 element(s): 48 mapped, 9 approximated, 0 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) +# migrated 105 element(s): 88 mapped, 13 approximated, 4 unmapped (0 skipped as profile, library or notation-only content, 2 as model elements nothing refers to) -## approximated (9) +## unmapped (4) +TimeEvent _someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) +DurationConstraint Stimulus::Hold::dtHold _dtHold (the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it) +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapE1 (the edge leads to (_sapCall), which never fires: no value reaches its input pin 'target') +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapE2 (the edge leaves (_sapCall), which never fires, so no token travels it) + +## approximated (13) Activity Stimulus::Blink _blink -> Stimulus::Blink (also run as the do action of 'On') Activity Stimulus::Cool Down _cool -> Stimulus::'Cool Down' (also run as the exit action of 'On') DurationConstraint Stimulus::Cool Down::dtCool _dtCool -> Stimulus::'Cool Down' (written as a wait drawn uniformly over [1.0, 3.0] s before 'Cool Down'; a tool's fixed min or max mode is a run setting, not the model's) DurationConstraint Stimulus::Send Ack On::dtOn _dtOn -> Stimulus::'Send Ack On' (written as a wait drawn uniformly over [1.0, 3.0] s before 'Turn On'; a tool's fixed min or max mode is a run setting, not the model's) StateMachine Stimulus::Stimulus Behavior _sm -> Stimulus::'Stimulus Behavior' (acts on its owner Stimulus, which it takes as its parameter context, since its this is the def's own occurrence; the classifier behavior is run by every object of Stimulus as its usage stimulus Behavior) Activity Stimulus::Stimulus Behavior::::::Send Ack _sendAck -> Stimulus::'Stimulus Behavior'::unnamed::'Send Ack' (written as the do action of (_tOn); it waits for the clock (the duration constraint 'dtOn' on 'Turn On' in 'Send Ack On'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) -Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed2::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) +AcceptEventAction Stimulus::Stimulus Behavior::::::Send Ack Hold::someday _sahWait -> someday (the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called) +Activity Stimulus::Stimulus Behavior::::::Send Ack Off _sendAckOff -> Stimulus::'Stimulus Behavior'::unnamed5::'Send Ack Off' (written as the do action of (_tOff); it waits for the clock (the accept of a time event 'delayed' in 'Delaying'), which a v2 transition effect, performed whole at the instant it is triggered, may not: a run stops at the wait) +CallOperationAction Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapCall -> call (the call runs in the caller's context: no flow feeds its target pin; the action never fires: its input pin 'target' must hold a value, but no object flow feeds it and it holds no value; no succession leads to it or leaves it, where v1 would wait on it forever) +InputPin Stimulus::Stimulus Behavior::::::Send Ack Pause::::target _sapTarget (the target pin is not written; the call runs in the caller's context) +CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasCall -> call (the behavior acts on a Relay through its parameter context, which is left unbound: the caller is a Stimulus, which is no Relay and has no part that is one; v1 runs Relay::Settle on the caller's object, which lacks the ports it goes through, so the action carries the token and performs nothing) State Stimulus::Stimulus Behavior::::On _on -> On (its entry action Stimulus::Warm Up: it waits for the clock (the accept of a time event 'warmed' in 'Warm Up'), which a v2 entry action, performed whole at the instant it is triggered, may not: a run stops at the wait; its exit action Stimulus::Cool Down: it waits for the clock (the duration constraint 'dtCool' on 'Cool Down'), which a v2 exit action, performed whole at the instant it is triggered, may not: a run stops at the wait) Activity Stimulus::Warm Up _warm -> Stimulus::'Warm Up' (also run as the entry action of 'On') -## mapped (48) +## mapped (88) SignalEvent _cmdOnEv (written where a trigger refers to it, as accept 'Cmd On') +SignalEvent _cmdHoldEv (written where a trigger refers to it, as accept 'Cmd Hold') +SignalEvent _cmdSettleEv (written where a trigger refers to it, as accept 'Cmd Settle') +SignalEvent _cmdPauseEv (written where a trigger refers to it, as accept 'Cmd Pause') SignalEvent _cmdOffEv (written where a trigger refers to it, as accept 'Cmd Off') +TimeEvent _settled (written where a trigger refers to it, as accept after 1.0 [SI::s]) TimeEvent _warmed (written where a trigger refers to it, as accept after 2.0 [SI::s]) TimeEvent _blinked (written where a trigger refers to it, as accept after 1.0 [SI::s]) +Signal Cmd Hold _cmdHold -> 'Cmd Hold' Signal Cmd Off _cmdOff -> 'Cmd Off' Signal Cmd On _cmdOn -> 'Cmd On' +Signal Cmd Pause _cmdPause -> 'Cmd Pause' +Signal Cmd Settle _cmdSettle -> 'Cmd Settle' Model Model _m (the root model's members are written at the top level) +«Block» Class Relay _relay -> Relay +Activity Relay::Settle _settle -> Relay::Settle (acts on its owner Relay, which it takes as its parameter context, since its this is the def's own occurrence) +ActivityFinalNode Relay::Settle:: _settleFinal -> final +ControlFlow Relay::Settle:: _settleE2 +ControlFlow Relay::Settle:: _settleE3 +InitialNode Relay::Settle:: _settleInit -> start +ReadStructuralFeatureAction Relay::Settle::read gain _readGain -> 'read gain' +OutputPin Relay::Settle::read gain::result _readGainR -> Relay::Settle::'read gain'.result +AcceptEventAction Relay::Settle::settled _settleWait -> settled +Trigger Relay::Settle::settled:: _settleTr +Property Relay::gain _gain -> Relay::gain «Block» Class Stimulus _stim -> Stimulus ActivityFinalNode Stimulus::Blink:: _blinkFinal -> final ControlFlow Stimulus::Blink:: _blinkE2 @@ -35,6 +63,9 @@ ControlFlow Stimulus::Delaying:: _delayE2 InitialNode Stimulus::Delaying:: _delayInit -> start AcceptEventAction Stimulus::Delaying::delayed _delayWait -> delayed Trigger Stimulus::Delaying::delayed:: _delayTr +Activity Stimulus::Hold _hold -> Stimulus::Hold +ActivityFinalNode Stimulus::Hold:: _holdFinal -> final +InitialNode Stimulus::Hold:: _holdInit -> start Activity Stimulus::Send Ack On _ackOn -> Stimulus::'Send Ack On' ActivityFinalNode Stimulus::Send Ack On:: _ackFinal -> final ControlFlow Stimulus::Send Ack On:: _ackE2 @@ -44,13 +75,32 @@ Region Stimulus::Stimulus Behavior:: _r (the one region is written as th Pseudostate Stimulus::Stimulus Behavior:::: _init (written as the entry of the region) Transition Stimulus::Stimulus Behavior:::: _tStart Transition Stimulus::Stimulus Behavior:::: _tOn +Transition Stimulus::Stimulus Behavior:::: _tHold +Transition Stimulus::Stimulus Behavior:::: _tSettle +Transition Stimulus::Stimulus Behavior:::: _tPause Transition Stimulus::Stimulus Behavior:::: _tOff Trigger Stimulus::Stimulus Behavior:::::: _trOn +Trigger Stimulus::Stimulus Behavior:::::: _trHold +Trigger Stimulus::Stimulus Behavior:::::: _trSettle +Trigger Stimulus::Stimulus Behavior:::::: _trPause Trigger Stimulus::Stimulus Behavior:::::: _trOff +Activity Stimulus::Stimulus Behavior::::::Send Ack Hold _sendAckHold -> Stimulus::'Stimulus Behavior'::unnamed2::'Send Ack Hold' (written as the do action of (_tHold)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahFinal -> final +CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahCall -> call +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahE2 +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahE3 +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Hold:: _sahInit -> start ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoFinal -> final CallOperationAction Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoCall -> call ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoE2 InitialNode Stimulus::Stimulus Behavior::::::Send Ack Off:: _saoInit -> start +Activity Stimulus::Stimulus Behavior::::::Send Ack Pause _sendAckPause -> Stimulus::'Stimulus Behavior'::unnamed4::'Send Ack Pause' (written as the do action of (_tPause)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapFinal -> final +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Pause:: _sapInit -> start +Activity Stimulus::Stimulus Behavior::::::Send Ack Settle _sendAckSettle -> Stimulus::'Stimulus Behavior'::unnamed3::'Send Ack Settle' (written as the do action of (_tSettle)) +ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasFinal -> final +ControlFlow Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasE2 +InitialNode Stimulus::Stimulus Behavior::::::Send Ack Settle:: _sasInit -> start ActivityFinalNode Stimulus::Stimulus Behavior::::::Send Ack:: _saFinal -> final CallBehaviorAction Stimulus::Stimulus Behavior::::::Send Ack:: _saCall -> call ControlFlow Stimulus::Stimulus Behavior::::::Send Ack:: _saE2 diff --git a/tests/migrate/testdata/xmi/instant_waits.golden.sysml b/tests/migrate/testdata/xmi/instant_waits.golden.sysml index 012c9d28d6..049056025d 100644 --- a/tests/migrate/testdata/xmi/instant_waits.golden.sysml +++ b/tests/migrate/testdata/xmi/instant_waits.golden.sysml @@ -1,6 +1,30 @@ item def 'Cmd On'; item def 'Cmd Off'; +item def 'Cmd Hold'; +item def 'Cmd Settle'; +item def 'Cmd Pause'; +part def Relay { + attribute gain : ScalarValues::Real default = 1.0; + action def Settle { + in ref context : Relay[1]; + first start then 'read gain'; + action 'read gain' { + out result[1] = context.gain; + } + first 'read gain' then settled; + action settled accept after 1.0 [SI::s]; + first settled then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about final; + } +} part def Stimulus { + action def Hold { + /* duration constraint on 'Hold' not migrated — the interval has no max, so the interval is open above and no one wait of at least 1.0 s stands for it */ + first start then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about final; + } action def 'Send Ack On' { first start then wait; action wait accept after RandomFunctions::uniform(1.0, 3.0) [SI::s]; @@ -59,6 +83,38 @@ part def Stimulus { metadata MigrationMetadata::SynthesizedName about call, final; } then On; + transition first Standby accept 'Cmd Hold' + do action 'Send Ack Hold' { + first start then call; + action call : Hold; + first call then someday; + action someday { + /* not migrated: AcceptEventAction 'someday' — the time event's time is not written: the instant "someday" is neither a number with a time unit nor an expression: names someday, which nothing visible from Stimulus::Stimulus Behavior::::::Send Ack Hold is called */ + } + first someday then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; + transition first Standby accept 'Cmd Settle' + do action 'Send Ack Settle' { + first start then call; + action call { + /* not migrated: CallBehaviorAction (_sasCall) — the behavior acts on a Relay through its parameter context, which is left unbound: the caller is a Stimulus, which is no Relay and has no part that is one; v1 runs Relay::Settle on the caller's object, which lacks the ports it goes through, so the action carries the token and performs nothing */ + } + first call then final; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; + transition first Standby accept 'Cmd Pause' + do action 'Send Ack Pause' { + /* not migrated: ControlFlow (_sapE1) — the edge leads to (_sapCall), which never fires: no value reaches its input pin 'target' */ + action call : Delay; + action final terminate; + metadata MigrationMetadata::SynthesizedName about call, final; + } + then Standby; transition first On accept 'Cmd Off' do action 'Send Ack Off' { first start then call; diff --git a/tests/migrate/testdata/xmi/instant_waits.xmi b/tests/migrate/testdata/xmi/instant_waits.xmi index 2a867bf02a..7d2995e849 100644 --- a/tests/migrate/testdata/xmi/instant_waits.xmi +++ b/tests/migrate/testdata/xmi/instant_waits.xmi @@ -23,7 +23,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -93,6 +135,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -108,4 +187,5 @@ + diff --git a/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt b/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt index 3cdbf9e109..d8e545068c 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt +++ b/tests/migrate/testdata/xmi/ui_stimuli.golden.report.txt @@ -1,18 +1,23 @@ # SysML v1 to v2 migration report: ui_stimuli.xmi -# migrated 41 element(s): 35 mapped, 6 approximated, 0 unmapped (3 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) +# migrated 53 element(s): 44 mapped, 9 approximated, 0 unmapped (5 skipped as profile, library or notation-only content, 0 as model elements nothing refers to) -## approximated (6) +## approximated (9) AcceptEventAction Await Start::started _awAccept -> started (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) Trigger Await Start::started:: _awTr (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) StateMachine Controller::Controller Behavior _sm -> Controller::'Controller Behavior' (the classifier behavior is run by every object of Controller as its usage controller Behavior) Trigger Controller::Controller Behavior:::::: _trStart (no send action of the document sends 'Start', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) +Trigger Controller::Controller Behavior:::::: _trPause (no send action of the document sends 'Pause' or a signal specializing it ('Long Pause'), which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) +Trigger Controller::Controller Behavior:::::: _trResume (no send action of the document sends 'Resume', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts) Activity Controller::Run _run -> Controller::Run (also run as the do action of 'Running') +SendSignalAction Controller::Run::send resume _sendResume -> 'send resume' (the send passes no argument for the attribute delay of Resume, which must hold a value; v1 sends the signal without it, which v2 does not admit, so the action carries the token and performs nothing) «SimulationConfig» Class Interactive _cfg -> Interactive («SimulationConfig» UI = 'Operate the controller' is the tool's UI prototype, through which a user of the tool's run posts signals and reads values; it has no v2 form, so a run here takes no input from it; the classifier behavior of Controller is a state machine, which a run performs as no action; the configuration only holds 'Controller') -## mapped (35) +## mapped (44) SignalEvent _startEv (written where a trigger refers to it, as accept Start) SignalEvent _stopEv (written where a trigger refers to it, as accept Stop) SignalEvent _resetEv (written where a trigger refers to it, as accept Reset) +SignalEvent _pauseEv (written where a trigger refers to it, as accept Pause) +SignalEvent _resumeEv (written where a trigger refers to it, as accept Resume) SignalEvent _finishEv (written where a trigger refers to it, as accept Finish) Activity Await Start _await -> 'Await Start' ActivityFinalNode Await Start:: _awFinal -> final @@ -25,6 +30,8 @@ Transition Controller::Controller Behavior:::: _t0 Transition Controller::Controller Behavior:::: _tStart Transition Controller::Controller Behavior:::: _tStop Transition Controller::Controller Behavior:::: _tReset +Transition Controller::Controller Behavior:::: _tPause +Transition Controller::Controller Behavior:::: _tResume Transition Controller::Controller Behavior:::: _tFinish Trigger Controller::Controller Behavior:::::: _trStop Trigger Controller::Controller Behavior:::::: _trReset @@ -34,6 +41,7 @@ State Controller::Controller Behavior::::Idle _idle -> Idle State Controller::Controller Behavior::::Running _running -> Running ActivityFinalNode Controller::Run:: _runFinal -> final ControlFlow Controller::Run:: _runE3 +ControlFlow Controller::Run:: _runE4 ControlFlow Controller::Run:: _runE2 InitialNode Controller::Run:: _runInit -> start SendSignalAction Controller::Run::send hard reset _sendHardReset -> 'send hard reset' @@ -41,12 +49,18 @@ SendSignalAction Controller::Run::send stop _sendStop -> 'send stop' Signal Finish _finish -> Finish Package Gui _gui -> Gui Signal Hard Reset _hardReset -> 'Hard Reset' +Signal Long Pause _longPause -> 'Long Pause' Model Model _m (the root model's members are written at the top level) +Signal Pause _pause -> Pause Signal Reset _reset -> Reset +Signal Resume _resume -> Resume +Property Resume::delay _resumeDelay -> Resume::delay Signal Start _start -> Start Signal Stop _stop -> Stop -## skipped (3) +## skipped (5) «Button» Class Gui:: _button («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) «Button» Class Gui:: _resetButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) +«Button» Class Gui:: _pauseButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) +«Button» Class Gui:: _resumeButton («Button» marks the modeling tool's UI prototyping mockup, which has no model meaning) «Frame» Component Gui::Console _frame («Frame» marks the modeling tool's UI prototyping mockup, which has no model meaning) diff --git a/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml b/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml index d7afa9fe79..5575b2f592 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml +++ b/tests/migrate/testdata/xmi/ui_stimuli.golden.sysml @@ -3,6 +3,11 @@ item def Stop; item def Finish; item def Reset; item def 'Hard Reset' :> Reset; +item def Pause; +item def 'Long Pause' :> Pause; +item def Resume { + attribute delay : ScalarValues::Real; +} part def Controller { action def Run { first start then 'send stop'; @@ -13,7 +18,11 @@ part def Controller { action 'send hard reset' { send new 'Hard Reset'(); } - first 'send hard reset' then final; + first 'send hard reset' then 'send resume'; + action 'send resume' { + /* not migrated: SendSignalAction 'send resume' — the send passes no argument for the attribute delay of Resume, which must hold a value; v1 sends the signal without it, which v2 does not admit, so the action carries the token and performs nothing */ + } + first 'send resume' then final; action final terminate; metadata MigrationMetadata::SynthesizedName about final; } @@ -26,6 +35,8 @@ part def Controller { transition first Idle accept Start then Running; transition first Running accept Stop then Idle; transition first Running accept Reset then Idle; + transition first Running accept Pause then Idle; + transition first Idle accept Resume then Running; transition first Idle accept Finish then done; } exhibit state 'controller Behavior' : 'Controller Behavior'; diff --git a/tests/migrate/testdata/xmi/ui_stimuli.xmi b/tests/migrate/testdata/xmi/ui_stimuli.xmi index f65bb78858..2b87ef3f9c 100644 --- a/tests/migrate/testdata/xmi/ui_stimuli.xmi +++ b/tests/migrate/testdata/xmi/ui_stimuli.xmi @@ -12,7 +12,19 @@ + + + + + + + + + + + + @@ -22,10 +34,12 @@ + - + + @@ -43,6 +57,12 @@ + + + + + + @@ -59,11 +79,13 @@ - + + + @@ -73,5 +95,9 @@ + + + + diff --git a/tests/migrate/ui_stimuli_test.go b/tests/migrate/ui_stimuli_test.go index 4192eb8a9b..bf45826cab 100644 --- a/tests/migrate/ui_stimuli_test.go +++ b/tests/migrate/ui_stimuli_test.go @@ -12,7 +12,9 @@ import ( // a configuration whose UI is that prototype says a run here takes no input // from it. A signal a send action sends — itself or a signal specializing it, // which the accept takes too — or one nothing at all posts, is an ordinary -// accept. +// accept. A button posting a signal specializing the accepted one counts as +// posting it, and a send action written as a placeholder, which performs +// nothing, is no sender. func TestSignalsOnlyTheToolsUIPostsAreLedgered(t *testing.T) { r := migrateXMI(t, "ui_stimuli") wantClean(t, "t.sysml", r) @@ -22,6 +24,9 @@ func TestSignalsOnlyTheToolsUIPostsAreLedgered(t *testing.T) { wantNote(t, r, "_trStop", migrate.Mapped, "") wantNote(t, r, "_trReset", migrate.Mapped, "") wantNote(t, r, "_trFinish", migrate.Mapped, "") + wantNote(t, r, "_trPause", migrate.Approximated, "no send action of the document sends 'Pause' or a signal specializing it ('Long Pause'), which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts") + wantNote(t, r, "_sendResume", migrate.Approximated, "the send passes no argument for the attribute delay of Resume, which must hold a value") + wantNote(t, r, "_trResume", migrate.Approximated, "no send action of the document sends 'Resume', which only 1 button of the tool's UI prototype posts, which the migration does not write; an accept of it waits for a message nothing in the model posts") wantNote(t, r, "_cfg", migrate.Approximated, "«SimulationConfig» UI = 'Operate the controller' is the tool's UI prototype, through which a user of the tool's run posts signals and reads values; it has no v2 form, so a run here takes no input from it") wantLine(t, r.Notation, "UI = Gui::Console") }