Skip to content

examples: common.ps1 ParserError made every PowerShell example a silent false green #102

Description

@mobileskyfi

Found while running the full matrix for #101: run 30312099123, Examples smoke (windows/x86_64 · long-term).

What happened

examples/common.ps1:39:

throw "quickchr exited with code $LASTEXITCODE: $($Rest -join ' ')"

PowerShell parses $NAME: as a scope-qualified variable reference ($env:PATH, $script:foo), so $LASTEXITCODE: is a ParserError — and a ParserError takes out the entire file, not just that line:

ParserError: examples\common.ps1:39
  39 |   throw "quickchr exited with code $LASTEXITCODE: $($Rest -join …
     |                                    ~~~~~~~~~~~~~~
     | Variable reference is not valid. ':' was not followed by a valid
     | variable name character. Consider using ${} to delimit the name.

Every helper it defines was therefore undefined for the caller:

Get-ExampleName: quickstart.ps1:5   The term 'Get-ExampleName' is not recognized…
Register-Cleanup: quickstart.ps1:6  The term 'Register-Cleanup' is not recognized…
Invoke-QcCleanup: quickstart.ps1:19 The term 'Invoke-QcCleanup' is not recognized…
Invoke-Qc:        quickstart.ps1:10 …

The tell in the output is -> starting (stable channel, host-native arch)... — a double space where the machine name should be, because Get-ExampleName returned nothing.

Why nothing caught it

The smoke test reported (pass). It asserts code === 0 and out.length > 0; the script exited 0 despite every helper failing, and it had written one line to stdout, so both assertions held:

(pass) examples smoke > quickstart-ps1 (ps1) runs clean [7676.99ms]

PSScriptAnalyzer passed too. lint-powershell.yml runs Invoke-ScriptAnalyzer -Settings examples/PSScriptAnalyzerSettings.psd1, and with a Severity = @('Error','Warning') filter it does not surface ParserErrors — so a file that cannot be parsed at all sails through the gate that exists to check exactly that.

Broken since 3f57daf and green ever since. It only became visible because the examples smoke harness started streaming child stderr instead of buffering it and discarding it on a passing test (#101).

Fixed in #101

  • ${LASTEXITCODE} in common.ps1, with a comment explaining that the braces are load-bearing rather than stylistic. It is the only occurrence in examples/**/*.ps1.
  • lint-powershell.yml now runs [System.Management.Automation.Language.Parser]::ParseFile over every example .ps1 before PSScriptAnalyzer — PowerShell's own parser, the same one that runs the scripts — and fails on any parse error. That closes the class permanently, not just this instance.

Left open deliberately

The smoke harness still cannot tell "the PowerShell example worked" from "the PowerShell example failed but exited 0". The parse gate stops this specific cause, but a .ps1 that fails at runtime and returns 0 would still report green. Worth deciding whether the .ps1 examples should set $ErrorActionPreference = 'Stop' plus an explicit non-zero exit, or whether the smoke test should assert on expected stdout markers rather than just out.length > 0. Filing that here rather than widening #101.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:ciCI workflows, publish, verification matrixarea:examplesexamples/ runnable scripts + coveragebugSomething isn't workingplatform:windowsMikrosoft Windows specific issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions