213 lines
11 KiB
Bash
213 lines
11 KiB
Bash
#!/bin/sh
|
|
# Negative controls for the repository gates. A gate not seen red is unproven.
|
|
set -eu
|
|
|
|
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
TMP=$(mktemp -d "${TMPDIR:-/tmp}/rules-swift-gates.XXXXXX")
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
|
|
if grep -Eq '\\x[[:xdigit:]]{2}' \
|
|
"$ROOT/scripts/leak-gate.sh" "$ROOT/.githooks/commit-msg"; then
|
|
echo "gate test failed: byte matchers must use portable octal escapes" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/priority-label"
|
|
printf '%s\n' '{"issue":{"number":1,"body":"## Priority\n\nhigh","labels":[]}}' \
|
|
> "$TMP/priority-label/add.json"
|
|
printf '%s\n' '{"issue":{"number":1,"body":"### Priority\n\nnormal","labels":[{"id":7,"name":"priority: high"}]}}' \
|
|
> "$TMP/priority-label/remove.json"
|
|
printf '%s\n' '{"issue":{"number":1,"body":"### Priority\n\nhigh","labels":[{"id":7,"name":"priority: high"}]}}' \
|
|
> "$TMP/priority-label/unchanged.json"
|
|
test "$(FORGEJO_EVENT_PATH="$TMP/priority-label/add.json" PRIORITY_LABEL_DRY_RUN=1 \
|
|
"$ROOT/scripts/sync-priority-label.sh")" = add
|
|
test "$(FORGEJO_EVENT_PATH="$TMP/priority-label/remove.json" PRIORITY_LABEL_DRY_RUN=1 \
|
|
"$ROOT/scripts/sync-priority-label.sh")" = remove:7
|
|
test "$(FORGEJO_EVENT_PATH="$TMP/priority-label/unchanged.json" PRIORITY_LABEL_DRY_RUN=1 \
|
|
"$ROOT/scripts/sync-priority-label.sh")" = unchanged
|
|
|
|
expect_hook() {
|
|
expected=$1
|
|
label=$2
|
|
content=$3
|
|
file="$TMP/message"
|
|
printf '%b\n' "$content" > "$file"
|
|
if "$ROOT/.githooks/commit-msg" "$file" >/dev/null 2>&1; then
|
|
actual=0
|
|
else
|
|
actual=1
|
|
fi
|
|
if [ "$actual" -ne "$expected" ]; then
|
|
echo "gate test failed: $label expected failure=$expected, got failure=$actual" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
expect_hook 0 "valid Conventional Commit" 'docs(readme): clarify setup'
|
|
expect_hook 1 "invalid type" 'banana: invalid type'
|
|
expect_hook 1 "long subject" 'docs(readme): explain the complete repository verification contract in excessive detail'
|
|
expect_hook 1 "automated co-author" 'docs(readme): clarify setup\n\nCo-Authored-By: Codex'
|
|
expect_hook 1 "assisted attribution" 'docs(readme): clarify setup\n\nAssisted-by: ChatGPT'
|
|
expect_hook 1 "em dash" 'docs(readme): clarify setup \342\200\224 now'
|
|
expect_hook 1 "trailing period" 'docs(readme): clarify setup.'
|
|
expect_hook 1 "breaking footer absent" 'feat(api)!: change response format'
|
|
expect_hook 0 "breaking change complete" 'feat(api)!: change response format\n\nBREAKING CHANGE: migrate callers to the new response value'
|
|
expect_hook 1 "breaking marker absent" 'feat(api): change response format\n\nBREAKING CHANGE: migrate callers to the new response value'
|
|
expect_hook 1 "long body line" 'docs(readme): clarify setup\n\nThis explanation is deliberately longer than seventy-two characters so wrapping enforcement is proven.'
|
|
expect_hook 0 "comment text ignored" 'docs(readme): clarify setup\n# Co-Authored-By: Codex \342\200\224 ignored comment'
|
|
expect_hook 1 "signature emoji" 'docs(readme): clarify setup \360\237\244\226'
|
|
expect_hook 1 "hidden watermark" 'docs(readme): clarify setup\342\200\213'
|
|
|
|
printf 'docs(test): planted trailer\n\nCo-Authored-By: Codex\n' > "$TMP/message"
|
|
if "$ROOT/.githooks/commit-msg" "$TMP/message" > "$TMP/hook-output" 2>&1; then
|
|
echo "gate test failed: planted attribution trailer passed" >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'Rule 5.1' "$TMP/hook-output"
|
|
|
|
mkdir -p "$TMP/broken-links"
|
|
printf '[missing](missing-file)\n\n[reference]: absent/reference.md\n' > "$TMP/broken-links/README.md"
|
|
if RULES_ROOT="$TMP/broken-links" "$ROOT/scripts/check-links.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: link gate accepted missing inline and reference targets" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/leak"
|
|
printf 'bad \342\200\224 dash\n' > "$TMP/leak/note.md"
|
|
if RULES_ROOT="$TMP/leak" "$ROOT/scripts/leak-gate.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: leak gate accepted an em dash" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/signature-leak"
|
|
printf 'bad \360\237\244\226 signature\n' > "$TMP/signature-leak/note.txt"
|
|
if RULES_ROOT="$TMP/signature-leak" "$ROOT/scripts/leak-gate.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: leak gate accepted a signature emoji" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p "$TMP/signature-rule"
|
|
printf 'forbidden pattern: \360\237\244\226\n' > "$TMP/signature-rule/commits.md"
|
|
RULES_ROOT="$TMP/signature-rule" "$ROOT/scripts/leak-gate.sh" >/dev/null
|
|
|
|
mkdir -p "$TMP/namespacing"
|
|
printf 'public struct One {}\npublic struct Two {}\n' > "$TMP/namespacing/Two.swift"
|
|
if RULES_ROOT="$TMP/namespacing" "$ROOT/scripts/check-namespacing.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: namespacing gate accepted two top-level types" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/file-names"
|
|
printf 'bad name\n' > "$TMP/file-names/Bad_Name.txt"
|
|
printf 'bad name\n' > "$TMP/file-names/bad name.txt"
|
|
printf 'bad name\n' > "$TMP/file-names/bad&name.txt"
|
|
non_ascii=$(printf 'bad-\305\276.txt')
|
|
printf 'bad name\n' > "$TMP/file-names/$non_ascii"
|
|
if RULES_ROOT="$TMP/file-names" "$ROOT/scripts/check-file-names.sh" > "$TMP/file-name-output" 2>&1; then
|
|
echo "gate test failed: file-name gate accepted invalid fixture names" >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'uppercase name' "$TMP/file-name-output"
|
|
grep -q 'underscore separator' "$TMP/file-name-output"
|
|
grep -q 'invalid separator or special character' "$TMP/file-name-output"
|
|
grep -q 'non-ASCII name' "$TMP/file-name-output"
|
|
|
|
# Positive control: a known-clean fixture must pass. Without this, a gate that
|
|
# flags everything (the 2026-07-12 locale bug: bash 3.2's case bracket ranges
|
|
# collate [A-Z] against the ambient UTF-8 locale, matching all-lowercase names
|
|
# too) still satisfies every must-fail expectation above and ships silently.
|
|
mkdir -p "$TMP/file-names-clean"
|
|
printf 'ok\n' > "$TMP/file-names-clean/clean-name.md"
|
|
printf 'ok\n' > "$TMP/file-names-clean/README.md"
|
|
if ! RULES_ROOT="$TMP/file-names-clean" "$ROOT/scripts/check-file-names.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: file-name gate rejected a clean fixture" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/rule-structure"
|
|
printf '# Binding Rule\n\nMUST do the thing.\n' > "$TMP/rule-structure/example.md"
|
|
if RULES_ROOT="$TMP/rule-structure" "$ROOT/scripts/check-rule-structure.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: rule-structure gate accepted a binding rule with no proof section" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/core-source" "$TMP/core-target"
|
|
printf '# Core Rule\n' > "$TMP/core-source/example.md"
|
|
printf '# Core Rule\n' > "$TMP/core-target/example.md"
|
|
CORE_SOURCE_DIR="$TMP/core-source" CORE_TARGET_DIR="$TMP/core-target" \
|
|
"$ROOT/scripts/check-core-sync.sh" >/dev/null
|
|
printf '\nDrift.\n' >> "$TMP/core-target/example.md"
|
|
if CORE_SOURCE_DIR="$TMP/core-source" CORE_TARGET_DIR="$TMP/core-target" \
|
|
"$ROOT/scripts/check-core-sync.sh" >/dev/null 2>&1; then
|
|
echo "gate test failed: core-sync gate accepted a divergent vendored file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$TMP/version"
|
|
git -C "$TMP/version" init -q
|
|
printf '1.0.0\n' > "$TMP/version/VERSION"
|
|
printf '# Changelog\n' > "$TMP/version/CHANGELOG.md"
|
|
printf '# Rule\n' > "$TMP/version/example.md"
|
|
printf 'name: freshness\n' > "$TMP/version/rules-freshness.yml"
|
|
git -C "$TMP/version" add VERSION CHANGELOG.md example.md rules-freshness.yml
|
|
git -C "$TMP/version" -c user.name=Gate -c user.email=gate@example.invalid commit -qm 'docs(rule): seed fixture'
|
|
printf '\nChanged.\n' >> "$TMP/version/example.md"
|
|
if RULES_ROOT="$TMP/version" "$ROOT/scripts/check-version-bumped.sh" --working-tree >/dev/null 2>&1; then
|
|
echo "gate test failed: version gate accepted a rule edit without VERSION" >&2
|
|
exit 1
|
|
fi
|
|
printf '1.0.1\n' > "$TMP/version/VERSION"
|
|
RULES_ROOT="$TMP/version" "$ROOT/scripts/check-version-bumped.sh" --working-tree >/dev/null
|
|
if RULES_ROOT="$TMP/version" "$ROOT/scripts/check-changelog-touched.sh" --working-tree >/dev/null 2>&1; then
|
|
echo "gate test failed: changelog gate accepted a rule edit without CHANGELOG.md" >&2
|
|
exit 1
|
|
fi
|
|
printf '\n- Changed the rule.\n' >> "$TMP/version/CHANGELOG.md"
|
|
RULES_ROOT="$TMP/version" "$ROOT/scripts/check-changelog-touched.sh" --working-tree >/dev/null
|
|
git -C "$TMP/version" add VERSION CHANGELOG.md example.md
|
|
git -C "$TMP/version" -c user.name=Gate -c user.email=gate@example.invalid commit -qm 'docs(rule): change fixture'
|
|
printf 'on: push\n' >> "$TMP/version/rules-freshness.yml"
|
|
if RULES_ROOT="$TMP/version" "$ROOT/scripts/check-version-bumped.sh" --working-tree >/dev/null 2>&1; then
|
|
echo "gate test failed: version gate ignored the managed freshness workflow" >&2
|
|
exit 1
|
|
fi
|
|
if RULES_ROOT="$TMP/version" "$ROOT/scripts/check-changelog-touched.sh" --working-tree >/dev/null 2>&1; then
|
|
echo "gate test failed: changelog gate ignored the managed freshness workflow" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' '[{"number":1,"body":"### Priority\n\nhigh\n\nBlocked on #2. See `missing/file.md` at README.md:10","labels":[]}]' > "$TMP/issues.json"
|
|
if ISSUES_JSON="$TMP/issues.json" LABELS_JSON="$ROOT/.github/labels.json" "$ROOT/scripts/check-issue-body-staleness.sh" > "$TMP/issues-output" 2>&1; then
|
|
echo "gate test failed: issue-body gate accepted stale fixture data" >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'missing dated Status' "$TMP/issues-output"
|
|
grep -q 'line-number anchor' "$TMP/issues-output"
|
|
grep -q 'priority body/label drift' "$TMP/issues-output"
|
|
grep -q 'phantom backtick path' "$TMP/issues-output"
|
|
grep -q 'blocker reference is not open' "$TMP/issues-output"
|
|
printf '%s\n' '[{"number":1,"body":"### Status (YYYY-MM-DD)\n\n2026-07-11: open.\n\n### Priority\n\nnormal","labels":[]}]' > "$TMP/issues.json"
|
|
ISSUES_JSON="$TMP/issues.json" LABELS_JSON="$ROOT/.github/labels.json" "$ROOT/scripts/check-issue-body-staleness.sh" >/dev/null
|
|
|
|
printf '%s\n' '[]' > "$TMP/labels.json"
|
|
if ISSUES_JSON="$TMP/issues.json" LABELS_JSON="$TMP/labels.json" "$ROOT/scripts/check-issue-body-staleness.sh" > "$TMP/labels-output" 2>&1; then
|
|
echo "gate test failed: tracker gate accepted a missing repository label set" >&2
|
|
exit 1
|
|
fi
|
|
grep -q 'repository label drift' "$TMP/labels-output"
|
|
|
|
mkdir -p "$TMP/consumer"
|
|
git -C "$TMP/consumer" init -q
|
|
git -C "$TMP/consumer" remote add origin https://codeberg.org/example/consumer.git
|
|
mkdir -p "$TMP/consumer/.forgejo/workflows"
|
|
printf '# Managed by rules-swift legacy workflow\n' > "$TMP/consumer/.forgejo/workflows/rules-conformance.yml"
|
|
"$ROOT/scripts/sync-rules.sh" "$TMP/consumer" >/dev/null
|
|
test -f "$TMP/consumer/.forgejo/workflows/rules-freshness.yml"
|
|
test ! -e "$TMP/consumer/.forgejo/workflows/rules-conformance.yml"
|
|
grep -q 'does not claim' "$TMP/consumer/.forgejo/workflows/rules-freshness.yml"
|
|
grep -q 'runs-on: ubuntu-latest' "$TMP/consumer/.forgejo/workflows/rules-freshness.yml"
|
|
printf '# Consumer-owned semantic checks\n' > "$TMP/consumer/.forgejo/workflows/rules-conformance.yml"
|
|
"$ROOT/scripts/sync-rules.sh" "$TMP/consumer" >/dev/null
|
|
grep -q 'Consumer-owned semantic checks' "$TMP/consumer/.forgejo/workflows/rules-conformance.yml"
|
|
|
|
echo "gate fault-injection tests clean (14 message cases; priority label, link, leak, namespacing, file-name, rule-structure, core-sync, version, changelog, issue-body negatives; freshness migration)"
|