fix: make Bash tests platform-explicit #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # The important one: prove that a clean machine can install multi-cli from | |
| # scratch and immediately run its core commands. This is the check that would | |
| # have caught the install failure (missing jq / broken launcher). | |
| install-smoke: | |
| name: install + run (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Force the installer's own jq-resolution path: both runner images ship | |
| # jq pre-installed, so we remove it first. The installer must put jq back | |
| # (apt-get on Linux, brew on macOS) or the whole CLI is dead on arrival. | |
| - name: Remove pre-installed jq so the installer must provide it | |
| run: | | |
| if command -v apt-get >/dev/null 2>&1; then | |
| sudo apt-get remove -y jq || true | |
| elif command -v brew >/dev/null 2>&1; then | |
| brew uninstall --ignore-dependencies --force jq || true | |
| fi | |
| hash -r | |
| if command -v jq >/dev/null 2>&1; then | |
| echo "WARNING: jq still present at $(command -v jq); installer will detect-and-skip." | |
| else | |
| echo "jq removed; installer must reinstall it." | |
| fi | |
| - name: Install multi-cli from the working tree | |
| run: bash scripts/install.sh --local | |
| - name: jq is available after install | |
| run: | | |
| command -v jq | |
| jq --version | |
| # The launcher lands in ~/.local/bin (see install.sh BIN_LINK default). | |
| - name: doctor / tools / new must all exit 0 | |
| run: | | |
| export PATH="$HOME/.local/bin:$PATH" | |
| multi-cli doctor | |
| multi-cli tools | |
| multi-cli new codex/ciprofile | |
| # Shell correctness for every .sh and the main launcher. Run at error | |
| # severity: the current tree has only style-level warnings (SC2155 "declare | |
| # and assign separately" x24, SC2115 x1 in `multi-cli`) and zero errors, so | |
| # error severity keeps CI green while still failing on real bugs (parse | |
| # errors, undefined behavior, CRLF/SC1017, bad redirects, etc.). | |
| shellcheck: | |
| name: shellcheck | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install shellcheck | |
| run: sudo apt-get update -y && sudo apt-get install -y shellcheck | |
| - name: Lint shell scripts | |
| run: shellcheck --severity=error multi-cli scripts/install.sh scripts/uninstall.sh | |
| # PowerShell static analysis for the Windows scripts. Run at Error severity | |
| # and exclude the rules that flag accepted design choices, not bugs: | |
| # PSAvoidUsingConvertToSecureStringWithPlainText (Error-severity, fires on | |
| # multi-cli.ps1's credential handling) and PSUseShouldProcessForStateChanging- | |
| # Functions. Excluding these keeps CI green while still catching genuine | |
| # parse/syntax failures and other error-severity rules. | |
| pwsh-lint: | |
| name: PSScriptAnalyzer | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | |
| Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser | |
| - name: Analyze PowerShell scripts | |
| shell: pwsh | |
| run: | | |
| $targets = @( | |
| 'scripts/install.ps1', | |
| 'scripts/uninstall.ps1', | |
| 'multi-cli.ps1' | |
| ) | |
| $excludedRules = @( | |
| 'PSAvoidUsingConvertToSecureStringWithPlainText', | |
| 'PSUseShouldProcessForStateChangingFunctions' | |
| ) | |
| $findings = foreach ($target in $targets) { | |
| Invoke-ScriptAnalyzer -Path $target -Severity Error -ExcludeRule $excludedRules | |
| } | |
| if ($findings) { | |
| $findings | Format-Table -AutoSize | Out-String | Write-Host | |
| throw "PSScriptAnalyzer reported $($findings.Count) error-severity finding(s)." | |
| } | |
| Write-Host "No error-severity findings." | |
| # The bash session-continuation suite (bats). Runs the REAL launcher against | |
| # real fixture trees on both Linux and macOS. bats-core and jq are fetched | |
| # fresh by the runner (never committed): jq via the system package manager, | |
| # bats-core via the system package manager when available, otherwise | |
| # tests/run-bats.sh clones the pinned v1.11.0 into tests/vendor on demand. | |
| bats: | |
| name: bats (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install bats + jq | |
| run: | | |
| if command -v apt-get >/dev/null 2>&1; then | |
| sudo apt-get update -y | |
| # libsecret-tools + gnome-keyring + dbus-x11: the auth/processSecret | |
| # tests exercise the real Linux Secret Service backend (secret-tool), | |
| # which needs both the CLI and a running keyring daemon. | |
| sudo apt-get install -y bats jq libsecret-tools gnome-keyring dbus-x11 acl | |
| elif command -v brew >/dev/null 2>&1; then | |
| brew install bats-core jq | |
| else | |
| echo "no supported package manager (apt-get/brew) on this runner" >&2 | |
| exit 1 | |
| fi | |
| bats --version | |
| jq --version | |
| - name: Validate adapter contracts | |
| run: bash scripts/validate-adapters.sh | |
| # macOS: `security` against the default login keychain can block on a GUI | |
| # unlock/access prompt on a headless runner, hanging the whole suite. | |
| # Give the run a dedicated ephemeral keychain with a known password, | |
| # unlocked, partition-listed, and made default so no prompt can appear. | |
| - name: Prepare an ephemeral CI keychain (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| KEYCHAIN="multicli-ci-$(uuidgen).keychain" | |
| KEYCHAIN_PASSWORD="multicli-ci" | |
| PREVIOUS_DEFAULT="$(security default-keychain -d user | tr -d '"' | xargs)" | |
| echo "MULTICLI_CI_KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV" | |
| echo "MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN=$PREVIOUS_DEFAULT" >> "$GITHUB_ENV" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN" | |
| security default-keychain -d user -s "$KEYCHAIN" | |
| # shellcheck disable=SC2046 | |
| security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"') | |
| - name: Run the bats suite | |
| run: | | |
| if [ "$(uname -s)" = "Linux" ]; then | |
| # Secret Service needs a session bus and an unlocked keyring. | |
| # Smoke-check the store first so a broken keyring setup fails here | |
| # with a clear error instead of obscure test failures. | |
| dbus-run-session -- bash -c ' | |
| set -euo pipefail | |
| printf "\n" | gnome-keyring-daemon --unlock --components=secrets >/dev/null | |
| printf "ci-smoke" | secret-tool store --label=ci-smoke service multicli-ci target smoke | |
| [ "$(secret-tool lookup service multicli-ci target smoke)" = "ci-smoke" ] | |
| secret-tool clear service multicli-ci target smoke | |
| exec bash tests/run-bats.sh | |
| ' | |
| else | |
| env MULTICLI_MACOS_KEYCHAIN="$HOME/Library/Keychains/$MULTICLI_CI_KEYCHAIN-db" bash tests/run-bats.sh | |
| fi | |
| - name: Run the real POSIX OS-user lifecycle | |
| timeout-minutes: 10 | |
| run: bash tests/e2e/posix-osuser.sh | |
| - name: Remove the ephemeral CI keychain (macOS) | |
| if: runner.os == 'macOS' && always() | |
| run: | | |
| if [ -n "${MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN:-}" ]; then | |
| security default-keychain -d user -s "$MULTICLI_PREVIOUS_DEFAULT_KEYCHAIN" | |
| fi | |
| if [ -n "${MULTICLI_CI_KEYCHAIN:-}" ]; then | |
| security delete-keychain "$MULTICLI_CI_KEYCHAIN" | |
| fi | |
| bash-coverage: | |
| name: bash coverage (ubuntu-latest) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install coverage dependencies | |
| run: | | |
| KCOV_VERSION=v43 | |
| KCOV_SHA256=4cbba86af11f72de0c7514e09d59c7927ed25df7cebdad087f6d3623213b95bf | |
| KCOV_ARCHIVE="$RUNNER_TEMP/kcov-source.tar.gz" | |
| KCOV_SOURCE="$RUNNER_TEMP/kcov-source" | |
| sudo apt-get update -y | |
| sudo apt-get install -y bats jq libsecret-tools gnome-keyring dbus-x11 acl \ | |
| binutils-dev build-essential cmake libcurl4-openssl-dev libdw-dev \ | |
| libelf-dev libiberty-dev libssl-dev ninja-build zlib1g-dev | |
| curl -fsSL "https://github.com/SimonKagstrom/kcov/archive/refs/tags/$KCOV_VERSION.tar.gz" -o "$KCOV_ARCHIVE" | |
| printf '%s %s\n' "$KCOV_SHA256" "$KCOV_ARCHIVE" | sha256sum --check --strict | |
| mkdir -p "$KCOV_SOURCE" | |
| tar -xzf "$KCOV_ARCHIVE" -C "$KCOV_SOURCE" --strip-components=1 | |
| cmake -S "$KCOV_SOURCE" -B "$KCOV_SOURCE/build" -G Ninja -DCMAKE_BUILD_TYPE=Release | |
| cmake --build "$KCOV_SOURCE/build" | |
| sudo cmake --install "$KCOV_SOURCE/build" | |
| kcov --version | |
| - name: Enforce Bash coverage | |
| env: | |
| COVERAGE_BASELINE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD^' }} | |
| run: | | |
| dbus-run-session -- bash -c ' | |
| set -euo pipefail | |
| printf "\n" | gnome-keyring-daemon --unlock --components=secrets >/dev/null | |
| exec env COVERAGE_BASELINE="$COVERAGE_BASELINE" bash tests/coverage/run-bash-coverage.sh | |
| ' | |
| # The PowerShell session-continuation suite (Pester). Uses the Pester 3.4 | |
| # shipped with Windows PowerShell 5.1 (the suite is 3.4-compatible). The | |
| # PowerShell launcher parses JSON natively, so this suite needs no jq. | |
| pester: | |
| name: pester (windows-latest) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Pester 3.4.0 (matches the 3.x-compatible suite) | |
| shell: powershell | |
| run: | | |
| Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | |
| Install-PackageProvider -Name NuGet -Force -Scope CurrentUser | |
| Install-Module -Name Pester -RequiredVersion 3.4.0 -Force -SkipPublisherCheck -Scope CurrentUser | |
| - name: Validate adapter contracts | |
| shell: powershell | |
| run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts/Validate-Adapters.ps1 | |
| - name: Run the Pester suite | |
| timeout-minutes: 30 | |
| shell: powershell | |
| run: powershell -NoProfile -ExecutionPolicy Bypass -File tests/run-pester.ps1 -CI | |
| - name: Enforce PowerShell module coverage | |
| shell: powershell | |
| env: | |
| COVERAGE_BASELINE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD^' }} | |
| run: powershell -NoProfile -ExecutionPolicy Bypass -File tests/coverage/Invoke-ModuleCoverage.ps1 -MinimumPercent 95 | |
| windows-install-smoke: | |
| name: install + run (windows-latest) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install from working tree | |
| shell: powershell | |
| run: powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install.ps1 -Local | |
| - name: Validate installed launcher | |
| shell: powershell | |
| run: | | |
| powershell -NoProfile -ExecutionPolicy Bypass -File multi-cli.ps1 doctor | |
| powershell -NoProfile -ExecutionPolicy Bypass -File multi-cli.ps1 tools |