Fix concrete scriptwriter aborting on else branches under errexit

The bash scriptwriter emitted a per-line "(exit $_runner_exit_code)" to restore the previous command's ? for the user command to observe (the echoed " ..." header resets $? to 0). Under "set -o errexit" a standalone "(exit N)" with N != 0 aborts the script, and that is exactly what sits at the top of an else branch, where $? carries the failed if-condition's status. So any FF_CONCRETE job with an if/else authored as separate script lines aborted the moment the else branch was taken, before its body ran. A trailing "! cmd" aborted the same way, for the same reason.

Under errexit, $? can only be non-zero on entry to a line when the previous line ended in a condition context, which is precisely where the restore is fatal - so it was a no-op wherever it was safe and a job-killer wherever it wasn't. Drop it, so each line emits: echo header -> command [-> exit-code check], matching functions/script_legacy and the legacy shells/abstract.go. The trade-off: user commands now see $?=0 after the header, not the previous command's status. That "preservation" is unachievable under errexit anyway, since a non-zero (exit N) always aborts.

buildPwshScript is deliberately left alone: "$global:LASTEXITCODE = ..." is an assignment, not a failing command, so $ErrorActionPreference = "Stop" does not trip on it. This does leave bash and pwsh differing on whether a user command observes the previous command's status, which is now called out in the buildBashScript doc comment.

Adds executable scriptwriter tests covering the else and "! cmd" shapes plus negative cases that failure propagation is not weakened, static guards that the restore is not re-emitted in either the plain or ScriptSections path, and a shell integration test asserting the job succeeds with FF_CONCRETE both off and on. That integration test also asserts the flag actually took effect: a flag named in RUNNER_TEST_FEATURE_FLAGS is forced to the negation of its default and the job variable is ignored, which would otherwise silently collapse the off-vs-on pair into on-vs-on while still passing.

Two cases are added as documented skips: with FF_ENABLE_BASH_EXIT_CODE_CHECK on, the post-command exit-code check reads $? at the top of the else branch for the same reason the removed restore did, and still aborts. That is a pre-existing defect shared with the legacy generators (shells/bash.go CheckForErrors, driven per script line by shells/abstract.go writeCommands, and functions/script_legacy), the flag is off by default, and it is out of scope here. Tracked as #39634.

Note that elif is unaffected either way: the prologue lands after "elif ...; then", where $? is 0. It is covered as documentation, not as a regression guard.

Fixes #39610 (closed).

Edited by Axel von Bertoldi

Merge request reports

Loading