Files
heretek-actions/tests/run-all.sh
T
John Smith 6ae3e16aba feat(lint): add lint-ultimate action wrapping MegaLinter with envelope
Tier 1.1 of the quality expansion design. Adds the lint-ultimate
composite action that runs MegaLinter (or Super-Linter) over the
repo and emits a per-linter checks[] plus per-finding findings[]
envelope, matching the standard .agent/output.json contract.

## Changes

### Action
- .github/actions/lint-ultimate/action.yml: composite action, runs
  oxsecurity/megalinter:v7 via Docker, uploads the JSON report and
  stdout as lint-ultimate-reports artifact, uploads the envelope as
  agent-output-lint for downstream consumers.
- .github/actions/lint-ultimate/build-envelope.sh: parses
  megalinter-report.json, maps each linter category to a check[]
  entry and each finding to a findings[] entry. Falls back to an
  empty envelope when the report is missing or unreadable.

### Library
- .github/actions/agent-envelope.sh: add add_finding() helper
  mirroring add_check(). Optional fields (rule, file, line, column,
  suggested_fix) default to null; numbers coerce when non-empty.

### Library fixes (pre-existing bugs surfaced by the new test)
- GITHUB_REPOSITORY default and owner/repo prefix strip now set -u
  safe. Previously the original --arg repo_name reference threw on
  unbound-variable in any non-GitHub runner.
- GITHUB_RUN_ID now passed as --argjson (numeric or null) instead of
  --arg with tonumber? in the filter. The prior form silently
  produced empty output when run_id was unset (jq 1.8.2 quirk where
  tonumber? on null returns Empty and jq emits no output).

### Schema
- .agent/schema.json: enum agent_action extended with all twelve new
  values from the quality-check expansion design (lint-ultimate,
  coverage, actionlint, codeql, lefthook-ci, test-matrix, reviewdog,
  quality-gate, semantic-release, dependabot, docker-ci). Future
  Tier 1/2/3 PRs pick the right value.

### Tests
- tests/: ship the previously untracked harness (run-action.sh,
  run-all.sh, README.md, fixtures for all CI actions) plus the
  new megalinter-passing and megalinter-failing fixtures.
- tests/unit/test-lint-ultimate-parser.sh: parser unit test that
  runs build-envelope.sh against a fabricated megalinter-report.json
  and asserts the envelope shape. No Docker required.
- tests/golden/lint-ultimate/README.md: documents that goldens are
  generated on first real-CI run (per tests/README.md first-run
  convention) and provides a no-Docker local check.

### Matrix
- tests/run-all.sh: megalinter-passing and megalinter-failing added
  to MATRIX (paired with lint-ultimate action).

### Docs
- README.md: lint-ultimate row added to the CI Composite Actions
  table; full example block added between python-ci and ---.

## Risk

- Pre-existing agent-envelope.sh changes affect all envelope-writing
  callers; the same fixes apply everywhere (none had a green test
  before). Behavior is unchanged when GITHUB_* env vars are set
  (real CI).
- No existing tests to regress against (tests/ was untracked).
  Unit test added in this PR exercises the new parser path.

Refs: docs/superpowers/specs/2026-07-30-code-quality-expansion-design.md
2026-07-30 14:38:20 -04:00

173 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# tests/run-all.sh — Run all action/fixture pairs and report a summary
#
# Usage:
# tests/run-all.sh # run everything, parallel
# tests/run-all.sh --serial # run sequentially
# tests/run-all.sh --dry-run # print the matrix, don't execute
# tests/run-all.sh --only rust-ci # run only matching actions
# tests/run-all.sh --jobs 4 # parallel worker count (default: nproc)
#
# Exit codes:
# 0 — every pair passed (or matched golden / first-run snapshot)
# 1 — one or more pairs failed
# 2 — bad usage / environment
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FIXTURES_DIR="${REPO_ROOT}/tests/fixtures"
RESULTS_DIR="${REPO_ROOT}/tests/work/results"
SERIAL=false
DRY_RUN=false
ONLY=""
JOBS="$(nproc 2>/dev/null || echo 4)"
while [ $# -gt 0 ]; do
case "$1" in
--serial) SERIAL=true ;;
--dry-run) DRY_RUN=true ;;
--only) ONLY="${2:-}"; shift ;;
--jobs) JOBS="${2:-4}"; shift ;;
-h|--help)
sed -n '2,15p' "$0"
exit 0
;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
shift
done
# Action matrix: maps fixture glob → action + extra args.
# Format: "<fixture-glob>|<action>|<action-args>"
# fixture-glob — shell glob under tests/fixtures
# action — composite action name (must exist under .github/actions/)
# action-args — extra args forwarded to run-action.sh
MATRIX=(
"rust-*|rust-ci|"
"js-*|js-ci|"
"python-*|python-ci|"
"docker-*|release|release-type=docker dockerfile=./Dockerfile"
"megalinter-passing|lint-ultimate|"
"megalinter-failing|lint-ultimate|"
)
# Expand matrix into concrete pairs
PAIRS=()
for entry in "${MATRIX[@]}"; do
glob="${entry%%|*}"
rest="${entry#*|}"
action="${rest%%|*}"
args="${rest#*|}"
if [ -n "${ONLY}" ] && [ "${action}" != "${ONLY}" ]; then
continue
fi
# shellcheck disable=SC2206
matches=(${FIXTURES_DIR}/${glob}/)
if [ ${#matches[@]} -eq 0 ] || [ ! -d "${matches[0]}" ]; then
continue
fi
for fixture_path in "${matches[@]}"; do
fixture="$(basename "${fixture_path}")"
PAIRS+=("${fixture}|${action}|${args}")
done
done
if [ ${#PAIRS[@]} -eq 0 ]; then
echo "no fixture/action pairs to run" >&2
exit 2
fi
echo "==> ${#PAIRS[@]} pair(s) queued, jobs=${JOBS} mode=$( [ "${SERIAL}" = true ] && echo serial || echo parallel )"
echo
mkdir -p "${RESULTS_DIR}"
# Run one pair, capture exit code into a marker file.
run_pair() {
local pair="$1"
local fixture="${pair%%|*}"
local rest="${pair#*|}"
local action="${rest%%|*}"
local args="${rest#*|}"
local marker="${RESULTS_DIR}/${action}__${fixture}.exit"
local log="${RESULTS_DIR}/${action}__${fixture}.log"
echo "--- ${action} / ${fixture} ---"
# shellcheck disable=SC2086
if "${REPO_ROOT}/tests/run-action.sh" "${fixture}" "${action}" ${args} >"${log}" 2>&1; then
echo "0" > "${marker}"
else
echo "$?" > "${marker}"
fi
}
export -f run_pair
export REPO_ROOT FIXTURES_DIR RESULTS_DIR
# Dry-run: print matrix and exit
if [ "${DRY_RUN}" = true ]; then
for pair in "${PAIRS[@]}"; do
fixture="${pair%%|*}"
rest="${pair#*|}"
action="${rest%%|*}"
echo " ${action} / ${fixture}"
done
exit 0
fi
if [ "${SERIAL}" = true ]; then
for pair in "${PAIRS[@]}"; do
run_pair "${pair}"
done
else
printf '%s\n' "${PAIRS[@]}" | xargs -n1 -P "${JOBS}" -I{} bash -c 'run_pair "$@"' _ {}
fi
# Tally results
PASS=0
FAIL=0
FAILED_PAIRS=()
for pair in "${PAIRS[@]}"; do
fixture="${pair%%|*}"
rest="${pair#*|}"
action="${rest%%|*}"
marker="${RESULTS_DIR}/${action}__${fixture}.exit"
if [ ! -f "${marker}" ]; then
FAIL=$((FAIL + 1))
FAILED_PAIRS+=("${action} / ${fixture} (no marker)")
continue
fi
code="$(cat "${marker}")"
if [ "${code}" = "0" ]; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
FAILED_PAIRS+=("${action} / ${fixture} (exit ${code})")
fi
done
echo
echo "==> ${PASS} passed, ${FAIL} failed"
if [ ${FAIL} -gt 0 ]; then
echo
echo "Failures:"
for f in "${FAILED_PAIRS[@]}"; do
echo " ✗ ${f}"
done
echo
echo "Inspect logs under: ${RESULTS_DIR}/"
exit 1
fi
exit 0