diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9c25da..9cc10bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,16 @@ jobs: set -o pipefail swift test 2>&1 | tee /tmp/swift-test.log + - name: Run acceptance smoke tests (capture log) + run: | + set -o pipefail + ./scripts/acceptance-tests.sh 2>&1 | tee /tmp/acceptance.log + + - name: Convert acceptance output to JUnit + if: always() + run: | + ./scripts/acceptance-to-junit.py /tmp/acceptance.log /tmp/acceptance.junit.xml + - name: Convert unit test output to JUnit if: always() run: | @@ -49,6 +59,15 @@ jobs: reporter: java-junit fail-on-error: false + - name: Publish acceptance report + if: always() + uses: dorny/test-reporter@v1 + with: + name: Acceptance Smoke Tests + path: /tmp/acceptance.junit.xml + reporter: java-junit + fail-on-error: false + - name: Job summary if: always() run: | @@ -57,4 +76,7 @@ jobs: echo ""; echo "### Unit tests executed"; grep -E '^◇ Test ' /tmp/swift-test.log | sed -E 's/^◇ Test (.*) started\.$/- \1/' || true; + echo ""; + echo "### Acceptance checks"; + grep -E '^\[acceptance\] ok:' /tmp/acceptance.log | sed -E 's/^\[acceptance\] ok: /- ✅ /' || true; } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 33f14e2..2e25ec8 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -37,6 +37,16 @@ jobs: set -o pipefail swift test 2>&1 | tee /tmp/swift-test.log + - name: Run acceptance smoke tests (capture log) + run: | + set -o pipefail + ./scripts/acceptance-tests.sh 2>&1 | tee /tmp/acceptance.log + + - name: Convert acceptance output to JUnit + if: always() + run: | + ./scripts/acceptance-to-junit.py /tmp/acceptance.log /tmp/acceptance.junit.xml + - name: Convert unit test output to JUnit if: always() run: | @@ -51,6 +61,15 @@ jobs: reporter: java-junit fail-on-error: false + - name: Publish acceptance report + if: always() + uses: dorny/test-reporter@v1 + with: + name: Acceptance Smoke Tests + path: /tmp/acceptance.junit.xml + reporter: java-junit + fail-on-error: false + - name: Job summary if: always() run: | @@ -59,4 +78,7 @@ jobs: echo ""; echo "### Unit tests executed"; grep -E '^◇ Test ' /tmp/swift-test.log | sed -E 's/^◇ Test (.*) started\.$/- \1/' || true; + echo ""; + echo "### Acceptance checks"; + grep -E '^\[acceptance\] ok:' /tmp/acceptance.log | sed -E 's/^\[acceptance\] ok: /- ✅ /' || true; } >> "$GITHUB_STEP_SUMMARY" diff --git a/Sources/gitw/main.swift b/Sources/gitw/main.swift index c4fb94b..c06744f 100644 --- a/Sources/gitw/main.swift +++ b/Sources/gitw/main.swift @@ -112,7 +112,8 @@ do { } catch let e as GitwError { switch e { case .usage(let msg): - die(msg, code: 0) + // Usage here indicates an error in invocation; fail closed. + die(msg, code: 2) default: die("gitw: \(e)") } diff --git a/scripts/acceptance-tests.sh b/scripts/acceptance-tests.sh new file mode 100755 index 0000000..a24a680 --- /dev/null +++ b/scripts/acceptance-tests.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +swift build + +BIN="$ROOT_DIR/.build/debug/gitw" +if [[ ! -x "$BIN" ]]; then + echo "error: gitw binary not found at: $BIN" >&2 + exit 1 +fi + +echo "[acceptance] gitw binary: $BIN" + +# 1) Missing --as should fail closed +set +e +OUT="$($BIN whoami 2>&1)" +CODE=$? +set -e +if [[ $CODE -eq 0 ]]; then + echo "[acceptance] expected whoami without --as to fail, got exit 0" >&2 + echo "$OUT" >&2 + exit 1 +fi +if ! echo "$OUT" | grep -qi "Missing --as"; then + echo "[acceptance] expected 'Missing --as' message" >&2 + echo "$OUT" >&2 + exit 1 +fi + +echo "[acceptance] ok: missing --as fails" + +# 2) Missing --as for git invocation should fail closed +set +e +OUT="$($BIN status 2>&1)" +CODE=$? +set -e +if [[ $CODE -eq 0 ]]; then + echo "[acceptance] expected git invocation without --as to fail, got exit 0" >&2 + echo "$OUT" >&2 + exit 1 +fi +if ! echo "$OUT" | grep -qi "Missing --as"; then + echo "[acceptance] expected 'Missing --as' message (git invocation)" >&2 + echo "$OUT" >&2 + exit 1 +fi + +echo "[acceptance] ok: git invocation missing --as fails" diff --git a/scripts/acceptance-to-junit.py b/scripts/acceptance-to-junit.py new file mode 100755 index 0000000..0a0303e --- /dev/null +++ b/scripts/acceptance-to-junit.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Convert acceptance smoke test output to a minimal JUnit XML. + +We parse lines like: + [acceptance] ok: + +Usage: + scripts/acceptance-to-junit.py +""" + +from __future__ import annotations + +import sys +import time +import xml.etree.ElementTree as ET + + +def main() -> int: + if len(sys.argv) != 3: + print("usage: acceptance-to-junit.py ", file=sys.stderr) + return 2 + + inp, outp = sys.argv[1], sys.argv[2] + + checks: list[str] = [] + with open(inp, "r", encoding="utf-8", errors="replace") as f: + for line in f: + line = line.strip("\n") + if line.startswith("[acceptance] ok:"): + checks.append(line.split("[acceptance] ok:", 1)[1].strip()) + + suite = ET.Element("testsuite") + suite.set("name", "acceptance") + suite.set("timestamp", time.strftime("%Y-%m-%dT%H:%M:%S")) + suite.set("tests", str(len(checks))) + suite.set("failures", "0") + + for name in checks: + tc = ET.SubElement(suite, "testcase") + tc.set("name", name) + tc.set("classname", "gitw") + tc.set("time", "0") + + tree = ET.ElementTree(suite) + ET.indent(tree, space=" ") + with open(outp, "wb") as f: + tree.write(f, encoding="utf-8", xml_declaration=True) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())