Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
57 changes: 57 additions & 0 deletions mdl-examples/bug-tests/304-describer-empty-else-branch.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- ============================================================================
-- Bug #304: Empty `else` branch emitted by DESCRIBE MICROFLOW
-- ============================================================================
--
-- Symptom (before fix):
-- `describe microflow` printed an `else` line for every IF whose false
-- branch jumped straight to the merge point (the shape produced by an
-- `if X then ... end if;` with no else body and a non-terminal true branch).
-- Output looked like
-- if $X > 0 then
-- ...
-- else
-- end if;
-- The empty else block re-parsed as a real branch, breaking
-- describe → exec → describe fixpoint and creating cosmetic BSON drift.
--
-- After fix:
-- Split traversal in cmd_microflows_show_helpers.go skips the `else`
-- keyword when the false branch destination is the matching merge point.
-- Explicit else bodies (with at least one activity) are still emitted.
--
-- Usage:
-- mxcli exec mdl-examples/bug-tests/304-describer-empty-else-branch.mdl -p app.mpr
-- mxcli -p app.mpr -c "describe microflow BugTest304.MF_IfWithoutElse"
-- The output must NOT contain an `else` keyword and MUST be a fixpoint
-- under describe → exec → describe.
-- ============================================================================

create module BugTest304;

create microflow BugTest304.MF_IfWithoutElse (
$value: integer
)
begin
if $value > 0 then
log info node 'BugTest304' 'positive value';
end if;

log info node 'BugTest304' 'always logged';
end;
/

-- Negative control: explicit else body MUST still be preserved.
create microflow BugTest304.MF_IfWithElse (
$value: integer
)
returns string as $label
begin
declare $label string = empty;

if $value > 0 then
return 'positive';
else
return 'non-positive';
end if;
end;
/
7 changes: 6 additions & 1 deletion mdl/executor/cmd_microflows_show_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,12 @@ func traverseFlow(
traverseFlowUntilMerge(ctx, trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
}

if falseFlow != nil {
// Emit the ELSE branch only if it has statements. When the false
// flow jumps straight to the merge (the MDL was `if X then ... end if`
// with no else), emitting `else` with no body produces an empty
// branch that normalizes away on re-parse.
falseHasBody := falseFlow != nil && falseFlow.DestinationID != mergeID
if falseHasBody {
*lines = append(*lines, indentStr+"else")
visitedFalseBranch := make(map[model.ID]bool)
for id := range visited {
Expand Down
56 changes: 56 additions & 0 deletions mdl/executor/cmd_microflows_traverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,62 @@ func TestTraverseFlow_IfElse(t *testing.T) {
}
}

// TestTraverseFlow_IfWithoutElse verifies that when the FALSE branch jumps
// straight to the merge point (as emitted by the builder for `if X then ... end if`),
// the describer does not print an empty `else` — the previous behavior produced
// `if X then ... else end if;` which re-parses as an IF with an empty else body
// that is indistinguishable from the original.
func TestTraverseFlow_IfWithoutElse(t *testing.T) {
e := newTestExecutor()

activityMap := map[model.ID]microflows.MicroflowObject{
mkID("start"): &microflows.StartEvent{BaseMicroflowObject: mkObj("start")},
mkID("split"): &microflows.ExclusiveSplit{
BaseMicroflowObject: mkObj("split"),
SplitCondition: &microflows.ExpressionSplitCondition{Expression: "$x > 0"},
},
mkID("true_act"): &microflows.ActionActivity{
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj("true_act")},
Action: &microflows.LogMessageAction{LogLevel: "Info", LogNodeName: "'App'", MessageTemplate: &model.Text{Translations: map[string]string{"en_US": "positive"}}},
},
mkID("merge"): &microflows.ExclusiveMerge{BaseMicroflowObject: mkObj("merge")},
mkID("end"): &microflows.EndEvent{BaseMicroflowObject: mkObj("end")},
}

flowsByOrigin := map[model.ID][]*microflows.SequenceFlow{
mkID("start"): {mkFlow("start", "split")},
mkID("split"): {
mkBranchFlow("split", "true_act", &microflows.ExpressionCase{Expression: "true"}),
mkBranchFlow("split", "merge", &microflows.ExpressionCase{Expression: "false"}),
},
mkID("true_act"): {mkFlow("true_act", "merge")},
mkID("merge"): {mkFlow("merge", "end")},
}

splitMergeMap := map[model.ID]model.ID{
mkID("split"): mkID("merge"),
}

var lines []string
visited := make(map[model.ID]bool)
e.traverseFlow(mkID("start"), activityMap, flowsByOrigin, splitMergeMap, visited, nil, nil, &lines, 1, nil, 0, nil)

for _, line := range lines {
if contains(line, "else") {
t.Errorf("expected no else branch in output, got: %v", lines)
}
}
foundENDIF := false
for _, line := range lines {
if contains(line, "end if;") {
foundENDIF = true
}
}
if !foundENDIF {
t.Errorf("expected end if in output: %v", lines)
}
}

// =============================================================================
// collectErrorHandlerStatements
// =============================================================================
Expand Down