diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 712bf64..46d3cf0 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -122,6 +122,9 @@ jobs: bash scripts/tests/test-quick-update.sh --summary bash scripts/tests/test-managed-install.sh --summary bash scripts/tests/test-shell-packages.sh --summary + bash scripts/tests/test-runtime-status.sh --summary + bash scripts/tests/test-install-selections.sh --summary + bash scripts/tests/test-command-summaries.sh --summary - name: Run doctor and prompt budget checks run: | bash scripts/commands/doctor.sh --summary @@ -186,9 +189,13 @@ jobs: - name: Reject unsafe workflow settings run: | set -euo pipefail - ! grep -RInE 'runs-on:[[:space:]]*windows|pull_request_target|permissions:[[:space:]]*write-all|actions/checkout@v|actions/setup-python@v|curl[[:space:]].*\|[[:space:]]*(bash|sh)|wget[[:space:]].*\|[[:space:]]*(bash|sh)' .github/workflows - test "$(grep -Roh 'runs-on: ubuntu-latest' .github/workflows | wc -l)" -ge 1 - test "$(grep -Roh 'uses: actions/[^@]*@[0-9a-f]\{40\}' .github/workflows | wc -l)" -ge 1 + unsafe_patterns='runs-on:[[:space:]]*windows|pull_request_' + unsafe_patterns+='target|permissions:[[:space:]]*write-all|actions/checkout@' + unsafe_patterns+='v|actions/setup-python@' + unsafe_patterns+='v|curl[[:space:]].*\|[[:space:]]*(bash|sh)|wget[[:space:]].*\|[[:space:]]*(bash|sh)' + ! grep -RInE "$unsafe_patterns" .gitea/workflows + test "$(grep -Roh 'runs-on: ubuntu-latest' .gitea/workflows | wc -l)" -ge 1 + test "$(grep -Roh 'uses: actions/[^@]*@[0-9a-f]\{40\}' .gitea/workflows | wc -l)" -ge 1 - name: Check for tracked secrets run: | set -euo pipefail diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 712bf64..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - schedule: - - cron: '23 4 * * 1' - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -env: - PYTHONUNBUFFERED: '1' - PIP_DISABLE_PIP_VERSION_CHECK: '1' - -jobs: - capabilities: - name: Capabilities and shell quality - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Install shell tools - run: | - sudo apt-get update - sudo apt-get install --yes --no-install-recommends shellcheck jq - - name: Check capabilities - run: bash scripts/commands/check-capabilities.sh - - name: Validate Bash syntax - run: git ls-files -z '*.sh' | xargs -0 -r -n1 bash -n - - name: Lint Bash - run: shellcheck --severity=warning $(git ls-files '*.sh') - - python: - name: Python tests and compilation - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - - name: Compile Python sources - run: python -m compileall -q scripts shared - - name: Run Python tests - run: | - set -euo pipefail - for file in scripts/tests/test-*.py; do - echo "==> $file" - python "$file" - done - - name: Validate generated configuration - run: python scripts/checks/validate-config.py generated/codex-powershell/config.toml generated/claude-powershell/settings.json - - powershell-on-ubuntu: - name: PowerShell tests on Ubuntu - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Check PowerShell runtime - shell: bash - run: | - if command -v pwsh >/dev/null 2>&1; then - echo 'POWERSHELL_AVAILABLE=true' >> "$GITHUB_ENV" - exit 0 - fi - if sudo apt-get update && sudo apt-get install --yes --no-install-recommends powershell; then - echo 'POWERSHELL_AVAILABLE=true' >> "$GITHUB_ENV" - else - echo 'POWERSHELL_AVAILABLE=false' >> "$GITHUB_ENV" - echo '::warning::PowerShell is unavailable; PowerShell checks skipped for this runner.' - fi - - name: Run PowerShell tests - shell: bash - run: | - if [ "${POWERSHELL_AVAILABLE:-false}" != true ]; then exit 0; fi - pwsh -NoLogo -NoProfile -NonInteractive -File ./scripts/commands/check-capabilities.ps1 - for file in scripts/tests/test-*.ps1; do - echo "==> $file" - pwsh -NoLogo -NoProfile -NonInteractive -File "$file" -Summary - done - - build: - name: Build and inspect generated packages - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: [capabilities, python] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Build packages - env: - REQUIRE_CODEX_SCHEMA: 'false' - run: bash scripts/commands/build.sh --summary - - name: Run build validation - run: bash scripts/tests/test-build-validation.sh --summary - - name: Check generated files are reproducible - run: | - before=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum) - bash scripts/commands/build.sh --summary - after=$(find generated -type f -print0 | sort -z | xargs -0 sha256sum) - test "$before" = "$after" - - shell-integration: - name: Bash integration and installation guards - runs-on: ubuntu-latest - timeout-minutes: 25 - needs: [build] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Run integration tests - run: | - set -euo pipefail - bash scripts/tests/test-install-guards.sh --summary - bash scripts/tests/test-plugin-selection.sh --summary - bash scripts/tests/test-quick-update.sh --summary - bash scripts/tests/test-managed-install.sh --summary - bash scripts/tests/test-shell-packages.sh --summary - - name: Run doctor and prompt budget checks - run: | - bash scripts/commands/doctor.sh --summary - bash scripts/commands/prompt-budget.sh --summary - - name: Verify dry-run installation safety - run: | - bash scripts/commands/install.sh --summary --dry-run --client both --shell bash - bash scripts/commands/update.sh --summary --dry-run --client both --shell bash - - client-smoke: - name: Installed client configuration smoke tests - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: [build] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Build packages - run: bash scripts/commands/build.sh --summary - - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v22.16.0 - with: - node-version: '24' - - name: Install current client CLIs - run: npm install --global @openai/codex @anthropic-ai/claude-code - - name: Validate current Codex and Claude settings - run: | - set -euo pipefail - schema_home=$(mktemp -d) - cp generated/codex-powershell/config.toml "$schema_home/config.toml" - CODEX_HOME="$schema_home" codex --strict-config --help >/dev/null - claude --settings generated/claude-powershell/settings.json --help >/dev/null - - retrieval: - name: Qwen retrieval integration - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: [python] - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.12' - cache: pip - cache-dependency-path: shared/retrieval/requirements.txt - - name: Install retrieval dependencies - run: python -m pip install -r shared/retrieval/requirements.txt - - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: .cache/huggingface - key: qwen-retrieval-${{ runner.os }}-${{ hashFiles('shared/retrieval/requirements.txt') }} - - name: Run retrieval tests with real models - env: - QWEN_TEST_MODEL_CACHE: ${{ github.workspace }}/.cache/huggingface - HF_HUB_DISABLE_XET: '1' - run: python scripts/tests/test-retrieval-server.py - - security: - name: CI configuration security checks - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Reject unsafe workflow settings - run: | - set -euo pipefail - ! grep -RInE 'runs-on:[[:space:]]*windows|pull_request_target|permissions:[[:space:]]*write-all|actions/checkout@v|actions/setup-python@v|curl[[:space:]].*\|[[:space:]]*(bash|sh)|wget[[:space:]].*\|[[:space:]]*(bash|sh)' .github/workflows - test "$(grep -Roh 'runs-on: ubuntu-latest' .github/workflows | wc -l)" -ge 1 - test "$(grep -Roh 'uses: actions/[^@]*@[0-9a-f]\{40\}' .github/workflows | wc -l)" -ge 1 - - name: Check for tracked secrets - run: | - set -euo pipefail - if git ls-files | grep -E '(^|/)(\.env($|\.)|.*\.(pem|key))$'; then - echo 'Potential secret file or private key tracked' >&2 - exit 1 - fi - if git ls-files -z | xargs -0 -r grep -IlE 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY'; then - echo 'Potential private key material tracked' >&2 - exit 1 - fi diff --git a/docs/scripts.md b/docs/scripts.md index 2a9f39b..be99d15 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -36,6 +36,8 @@ Qwen retrieval uses `shared/retrieval/benchmark.py`; run it through `scripts/lib ## Checks and tests +Gitea Actions runs the repository checks from `.gitea/workflows/ci.yml`. + | Script | Purpose | | --- | --- | | `checks/validate-config.py` | Validate generated Codex TOML and Claude JSON. | @@ -51,7 +53,10 @@ Qwen retrieval uses `shared/retrieval/benchmark.py`; run it through `scripts/lib | `tests/test-retrieval-server.py` | Validate retrieval indexing, ranking, and optional real-model execution. | | `tests/test-semantic-retrieval.py` | Validate retrieval installation and removal. | | `tests/test-session-state.py` | Validate session-state helpers. | -| `tests/test-shell-packages.ps1`, `tests/test-shell-packages.sh` | Validate generated packages and shell entry points. | +| `tests/test-shell-packages.ps1`, `tests/test-shell-packages.sh` | Validate generated package contents and client settings. | +| `tests/test-runtime-status.ps1`, `tests/test-runtime-status.sh` | Validate build summaries and status-line output. | +| `tests/test-install-selections.ps1`, `tests/test-install-selections.sh` | Validate dry-run shell and client selections. | +| `tests/test-command-summaries.ps1`, `tests/test-command-summaries.sh` | Validate doctor, install, and update summary output. | | `tests/test-telemetry.py` | Validate telemetry output. | ## Internal helpers diff --git a/scripts/tests/test-command-summaries.ps1 b/scripts/tests/test-command-summaries.ps1 new file mode 100644 index 0000000..cd5c6a2 --- /dev/null +++ b/scripts/tests/test-command-summaries.ps1 @@ -0,0 +1,15 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +$doctorSummaryOutput = & (Join-Path $root 'scripts/commands/doctor.ps1') -Summary +if (@($doctorSummaryOutput | Where-Object { $_ -match '^PASS ' }).Count -ne 0) { throw 'Doctor summary mode must not print individual PASS lines.' } +if (@($doctorSummaryOutput | Where-Object { $_ -match '^Doctor: PASS \| \d+ checks$' }).Count -ne 1) { throw "Doctor summary mode must print one 'Doctor: PASS | N checks' line: $($doctorSummaryOutput -join '; ')" } +foreach ($entryPoint in @('install','update')) { + $preview = @(& (Join-Path $root "scripts/commands/$entryPoint.ps1") -Summary -DryRun -Client Both -Shell PowerShell) + if ($preview -match '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) ') { throw "$entryPoint summary leaked per-item output." } + if (@($preview | Where-Object { $_ -match ('^' + $entryPoint + ': PASS') }).Count -ne 1) { throw "$entryPoint summary is missing." } +} +$doctorSummaryOutput | Where-Object { $_ -match '^WARN ' } +if ($Summary) { Write-Output 'Tests: PASS | command summaries' } else { Write-Output 'PASS doctor, install, and update summaries' } +exit 0 diff --git a/scripts/tests/test-command-summaries.sh b/scripts/tests/test-command-summaries.sh new file mode 100644 index 0000000..b8ac526 --- /dev/null +++ b/scripts/tests/test-command-summaries.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +while [ $# -gt 0 ]; do + case "$1" in + --summary) summary=true; shift ;; + *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; + esac +done +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) +doctor_summary_output=$(bash "$root/scripts/commands/doctor.sh" --summary) +if printf '%s\n' "$doctor_summary_output" | grep -q '^PASS '; then + printf 'Doctor summary mode must not print individual PASS lines.\n' >&2 + exit 1 +fi +if ! printf '%s\n' "$doctor_summary_output" | grep -Eq '^Doctor: PASS \| [0-9]+ checks$'; then + printf "Doctor summary mode must print one 'Doctor: PASS | N checks' line: %s\n" "$doctor_summary_output" >&2 + exit 1 +fi +for entry_point in install update; do + preview=$(bash "$root/scripts/commands/$entry_point.sh" --summary --dry-run --client both --shell bash) + if printf '%s\n' "$preview" | grep -Eq '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) '; then + printf '%s summary leaked per-item output.\n' "$entry_point" >&2 + exit 1 + fi + printf '%s\n' "$preview" | grep -Eiq "^$entry_point: PASS" +done +printf '%s\n' "$doctor_summary_output" | grep '^WARN ' || true +if [ "$summary" = true ]; then printf 'Tests: PASS | command summaries\n'; else printf 'PASS doctor, install, and update summaries\n'; fi diff --git a/scripts/tests/test-install-selections.ps1 b/scripts/tests/test-install-selections.ps1 new file mode 100644 index 0000000..f75297d --- /dev/null +++ b/scripts/tests/test-install-selections.ps1 @@ -0,0 +1,18 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +foreach ($shellSelection in @('1','2')) { + foreach ($clientSelection in @('1','2','3')) { + $script:answers = [System.Collections.Generic.Queue[string]]::new() + $script:answers.Enqueue($shellSelection) + $script:answers.Enqueue($clientSelection) + function Read-Host { param($Prompt) $answers.Dequeue() } + $output = & (Join-Path $root 'scripts/commands/install.ps1') -DryRun 6>$null + if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' } + $shell = if ($shellSelection -eq '1') { 'powershell' } else { 'bash' } + if (($output -join "`n") -notmatch "-$shell") { throw 'Incorrect selected package.' } + } +} +if ($Summary) { Write-Output 'Tests: PASS | install selections' } else { Write-Output 'PASS six shell and client selections' } +exit 0 diff --git a/scripts/tests/test-install-selections.sh b/scripts/tests/test-install-selections.sh new file mode 100644 index 0000000..4872577 --- /dev/null +++ b/scripts/tests/test-install-selections.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +while [ $# -gt 0 ]; do + case "$1" in + --summary) summary=true; shift ;; + *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; + esac +done +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) +for shell_selection in 1 2; do + for client_selection in 1 2 3; do + output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/commands/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; } + printf '%s\n' "$output" | grep -E 'WARN|FAIL|WARNING|ERROR' || true + [[ "$output" == *'PASS install dry-run'* ]] + shell=powershell + [ "$shell_selection" != 2 ] || shell=bash + [[ "$output" == *"-$shell"* ]] + done +done +if [ "$summary" = true ]; then printf 'Tests: PASS | install selections\n'; else printf 'PASS six shell and client selections\n'; fi diff --git a/scripts/tests/test-retrieval-server.py b/scripts/tests/test-retrieval-server.py index aea6ce7..627a63e 100644 --- a/scripts/tests/test-retrieval-server.py +++ b/scripts/tests/test-retrieval-server.py @@ -58,14 +58,12 @@ class RetrievalServerTests(unittest.TestCase): self.assertFalse(huggingface_network_unavailable(OSError('model is missing'))) def test_ci_disables_xet_for_model_downloads(self): - for workflow in ('.github/workflows/ci.yml', '.gitea/workflows/ci.yml'): - content = (ROOT / workflow).read_text(encoding='utf-8') - self.assertIn("HF_HUB_DISABLE_XET: '1'", content) + content = (ROOT / '.gitea/workflows/ci.yml').read_text(encoding='utf-8') + self.assertIn("HF_HUB_DISABLE_XET: '1'", content) def test_client_smoke_builds_packages(self): - for workflow in ('.github/workflows/ci.yml', '.gitea/workflows/ci.yml'): - content = (ROOT / workflow).read_text(encoding='utf-8') - self.assertIn('name: Build packages\n run: bash scripts/commands/build.sh --summary', content) + content = (ROOT / '.gitea/workflows/ci.yml').read_text(encoding='utf-8') + self.assertIn('name: Build packages\n run: bash scripts/commands/build.sh --summary', content) def test_runtime_python_uses_adjacent_virtualenv(self): with TemporaryDirectory() as temporary: diff --git a/scripts/tests/test-runtime-status.ps1 b/scripts/tests/test-runtime-status.ps1 new file mode 100644 index 0000000..09417b2 --- /dev/null +++ b/scripts/tests/test-runtime-status.ps1 @@ -0,0 +1,18 @@ +[CmdletBinding()] +param([switch]$Summary) +$ErrorActionPreference = 'Stop' +$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent +$powerShellHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.ps1') -Raw +$bashHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.sh') -Raw +if ($powerShellHook -notmatch '\$HoldMs\s*=\s*250' -or $powerShellHook -notmatch '\$FadeMs\s*=\s*250' -or ([regex]::Matches($bashHook, 'default=250').Count -lt 2)) { throw 'Flashbang defaults must total 500 milliseconds on PowerShell and Bash.' } +$statusInput = '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"' + $root.Replace('\','\\') + '","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' +$branch = & git -C $root branch --show-current +$buildSummaryOutput = & (Join-Path $root 'scripts/commands/build.ps1') -Summary +if (@($buildSummaryOutput | Where-Object { $_ -eq 'Build: PASS | 4 packages' }).Count -ne 1) { throw "build.ps1 -Summary must still print the PASS line: $($buildSummaryOutput -join '; ')" } +$statusOutput = ($statusInput | & (Join-Path $root 'shared/statusline/statusline.ps1')) -join "`n" +$plainStatusOutput = [regex]::Replace($statusOutput, [char]27 + '\[[0-9;]*m', '') +$expectedStatusOutput = "Test Model $([char]0x00B7) Review auto $([char]0x00B7) Effort high $([char]0x00B7) TestRepo @ $branch`nCtx 200k $([char]0x00B7) Used 9% $([char]0x00B7) Tokens 16.7k" +if ($buildSummaryOutput.Count -ne 1) { throw 'Build summary must contain only one success line.' } +if ($plainStatusOutput -ne $expectedStatusOutput) { throw 'PowerShell status line output is incorrect.' } +if ($Summary) { Write-Output 'Tests: PASS | runtime status' } else { Write-Output 'PASS build summary and status lines' } +exit 0 diff --git a/scripts/tests/test-runtime-status.sh b/scripts/tests/test-runtime-status.sh new file mode 100644 index 0000000..063ed19 --- /dev/null +++ b/scripts/tests/test-runtime-status.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +summary=false +while [ $# -gt 0 ]; do + case "$1" in + --summary) summary=true; shift ;; + *) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;; + esac +done +root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) +grep -Eq '\$HoldMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1" +grep -Eq '\$FadeMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1" +[ "$(grep -c 'default=250' "$root/shared/hooks/flashbang.sh")" -ge 2 ] +branch=$(git -C "$root" branch --show-current) +build_summary_output=$(bash "$root/scripts/commands/build.sh" --summary) +if ! printf '%s\n' "$build_summary_output" | grep -qx 'Build: PASS | 4 packages'; then + printf 'build.sh --summary must still print the PASS line: %s\n' "$build_summary_output" >&2 + exit 1 +fi +[ "$build_summary_output" = 'Build: PASS | 4 packages' ] +status_output=$(printf '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"%s","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' "$root" | bash "$root/shared/statusline/statusline.sh") +plain_status_output=$(printf '%s' "$status_output" | sed -E $'s/\x1b\\[[0-9;]*m//g') +expected_status_output=$'Test Model \xc2\xb7 Review auto \xc2\xb7 Effort high \xc2\xb7 TestRepo @ '"$branch"$'\nCtx 200k \xc2\xb7 Used 9% \xc2\xb7 Tokens 16.7k' +[ "$plain_status_output" = "$expected_status_output" ] +if [ "$summary" = true ]; then printf 'Tests: PASS | runtime status\n'; else printf 'PASS build summary and status lines\n'; fi diff --git a/scripts/tests/test-shell-packages.ps1 b/scripts/tests/test-shell-packages.ps1 index 8f69db4..f5cc21a 100644 --- a/scripts/tests/test-shell-packages.ps1 +++ b/scripts/tests/test-shell-packages.ps1 @@ -2,18 +2,6 @@ param([switch]$Summary) $ErrorActionPreference = 'Stop' $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent -$powerShellHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.ps1') -Raw -$bashHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.sh') -Raw -if ($powerShellHook -notmatch '\$HoldMs\s*=\s*250' -or $powerShellHook -notmatch '\$FadeMs\s*=\s*250' -or ([regex]::Matches($bashHook, 'default=250').Count -lt 2)) { throw 'Flashbang defaults must total 500 milliseconds on PowerShell and Bash.' } -$statusInput = '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"' + $root.Replace('\','\\') + '","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' -$branch = & git -C $root branch --show-current -$buildSummaryOutput = & (Join-Path $root 'scripts/commands/build.ps1') -Summary -if (@($buildSummaryOutput | Where-Object { $_ -eq 'Build: PASS | 4 packages' }).Count -ne 1) { throw "build.ps1 -Summary must still print the PASS line: $($buildSummaryOutput -join '; ')" } -$statusOutput = ($statusInput | & (Join-Path $root 'shared/statusline/statusline.ps1')) -join "`n" -$plainStatusOutput = [regex]::Replace($statusOutput, [char]27 + '\[[0-9;]*m', '') -$expectedStatusOutput = "Test Model $([char]0x00B7) Review auto $([char]0x00B7) Effort high $([char]0x00B7) TestRepo @ $branch`nCtx 200k $([char]0x00B7) Used 9% $([char]0x00B7) Tokens 16.7k" -if ($buildSummaryOutput.Count -ne 1) { throw 'Build summary must contain only one success line.' } -if ($plainStatusOutput -ne $expectedStatusOutput) { throw 'PowerShell status line output is incorrect.' } $sourceAgentCount = @(Get-ChildItem (Join-Path $root 'shared/agents') -Directory).Count $sourceSkillCount = @(Get-ChildItem (Join-Path $root 'shared/skills') -Filter 'SKILL.md' -Recurse).Count $ruleSkillCount = @(Import-Csv -LiteralPath (Join-Path $root 'adapters/rule-skills.tsv') -Delimiter ([char]9)).Count @@ -68,27 +56,5 @@ foreach ($shell in @('powershell','bash')) { } } } -foreach ($shellSelection in @('1','2')) { - foreach ($clientSelection in @('1','2','3')) { - $script:answers = [System.Collections.Generic.Queue[string]]::new() - $script:answers.Enqueue($shellSelection) - $script:answers.Enqueue($clientSelection) - function Read-Host { param($Prompt) $answers.Dequeue() } - $output = & (Join-Path $root 'scripts/commands/install.ps1') -DryRun 6>$null - if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' } - $shell = if ($shellSelection -eq '1') { 'powershell' } else { 'bash' } - if (($output -join "`n") -notmatch "-$shell") { throw 'Incorrect selected package.' } - } -} -$doctorSummaryOutput = & (Join-Path $root 'scripts/commands/doctor.ps1') -Summary -if (@($doctorSummaryOutput | Where-Object { $_ -match '^PASS ' }).Count -ne 0) { throw 'Doctor summary mode must not print individual PASS lines.' } -if (@($doctorSummaryOutput | Where-Object { $_ -match '^Doctor: PASS \| \d+ checks$' }).Count -ne 1) { throw "Doctor summary mode must print one 'Doctor: PASS | N checks' line: $($doctorSummaryOutput -join '; ')" } -foreach ($entryPoint in @('install','update')) { - $preview = @(& (Join-Path $root "scripts/commands/$entryPoint.ps1") -Summary -DryRun -Client Both -Shell PowerShell) - if ($preview -match '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) ') { throw "$entryPoint summary leaked per-item output." } - if (@($preview | Where-Object { $_ -match ('^' + $entryPoint + ': PASS') }).Count -ne 1) { throw "$entryPoint summary is missing." } -} -$doctorSummaryOutput | Where-Object { $_ -match '^WARN ' } -if ($Summary) { Write-Output 'Tests: PASS | shell packages' } else { Write-Output 'PASS four shell packages and six PowerShell selections' } +if ($Summary) { Write-Output 'Tests: PASS | generated packages' } else { Write-Output 'PASS four generated client and shell packages' } exit 0 - diff --git a/scripts/tests/test-shell-packages.sh b/scripts/tests/test-shell-packages.sh index cf4d376..a09db78 100644 --- a/scripts/tests/test-shell-packages.sh +++ b/scripts/tests/test-shell-packages.sh @@ -8,20 +8,6 @@ while [ $# -gt 0 ]; do esac done root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) -grep -Eq '\$HoldMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1" -grep -Eq '\$FadeMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1" -[ "$(grep -c 'default=250' "$root/shared/hooks/flashbang.sh")" -ge 2 ] -branch=$(git -C "$root" branch --show-current) -build_summary_output=$(bash "$root/scripts/commands/build.sh" --summary) -if ! printf '%s\n' "$build_summary_output" | grep -qx 'Build: PASS | 4 packages'; then - printf 'build.sh --summary must still print the PASS line: %s\n' "$build_summary_output" >&2 - exit 1 -fi -[ "$build_summary_output" = 'Build: PASS | 4 packages' ] -status_output=$(printf '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"%s","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' "$root" | bash "$root/shared/statusline/statusline.sh") -plain_status_output=$(printf '%s' "$status_output" | sed -E $'s/\x1b\\[[0-9;]*m//g') -expected_status_output=$'Test Model \xc2\xb7 Review auto \xc2\xb7 Effort high \xc2\xb7 TestRepo @ '"$branch"$'\nCtx 200k \xc2\xb7 Used 9% \xc2\xb7 Tokens 16.7k' -[ "$plain_status_output" = "$expected_status_output" ] source_agent_count=$(find "$root/shared/agents" -mindepth 1 -maxdepth 1 -type d | wc -l) source_skill_count=$(find "$root/shared/skills" -name SKILL.md | wc -l) rule_skill_count=$(($(wc -l < "$root/adapters/rule-skills.tsv") - 1)) @@ -100,32 +86,4 @@ for shell in powershell bash; do done done done -for shell_selection in 1 2; do - for client_selection in 1 2 3; do - output=$(printf '%s\n%s\n' "$shell_selection" "$client_selection" | bash "$root/scripts/commands/install.sh" --dry-run 2>&1) || { printf '%s\n' "$output" >&2; exit 1; } - printf '%s\n' "$output" | grep -E 'WARN|FAIL|WARNING|ERROR' || true - [[ "$output" == *'PASS install dry-run'* ]] - shell=powershell - [ "$shell_selection" != 2 ] || shell=bash - [[ "$output" == *"-$shell"* ]] - done -done -doctor_summary_output=$(bash "$root/scripts/commands/doctor.sh" --summary) -if printf '%s\n' "$doctor_summary_output" | grep -q '^PASS '; then - printf 'Doctor summary mode must not print individual PASS lines.\n' >&2 - exit 1 -fi -if ! printf '%s\n' "$doctor_summary_output" | grep -Eq '^Doctor: PASS \| [0-9]+ checks$'; then - printf "Doctor summary mode must print one 'Doctor: PASS | N checks' line: %s\n" "$doctor_summary_output" >&2 - exit 1 -fi -for entry_point in install update; do - preview=$(bash "$root/scripts/commands/$entry_point.sh" --summary --dry-run --client both --shell bash) - if printf '%s\n' "$preview" | grep -Eq '^(SOURCE|CREATE|UPDATE|UNCHANGED|BACKUP|DRYRUN|PASS) '; then - printf '%s summary leaked per-item output.\n' "$entry_point" >&2 - exit 1 - fi - printf '%s\n' "$preview" | grep -Eiq "^$entry_point: PASS" -done -printf '%s\n' "$doctor_summary_output" | grep '^WARN ' || true -if [ "$summary" = true ]; then printf 'Tests: PASS | shell packages\n'; else printf 'PASS four shell packages and six Bash selections\n'; fi +if [ "$summary" = true ]; then printf 'Tests: PASS | generated packages\n'; else printf 'PASS four generated client and shell packages\n'; fi