Files
prompts/test/update-policy.sh
T
clawbot ed60414378
check / check (push) Canceled after 0s
Rename script/fetch-policy to script/commit-current-upstream-policy-docs
sneak, on the PR: the script does more than fetch, and short names are
not wanted. The make target, the test names, the variables and the
messages take the new name. Behaviour is unchanged.

Model: opus-5-5
2026-11-11 00:00:28 +00:00

920 lines
30 KiB
Bash
Executable File

#!/bin/sh
# test/update-policy.sh: test script/commit-current-upstream-policy-docs
# and script/update-policy on throwaway repos. Each scenario gets one
# temp directory holding a repo, its local bare origin, a local git
# repository of the canonical files in place of prompts, and a stub curl
# that serves them, so nothing touches the network.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd -P)/script"
COMMIT_CURRENT_UPSTREAM_POLICY_DOCS="$SCRIPT_DIR/commit-current-upstream-policy-docs"
UPDATE_POLICY="$SCRIPT_DIR/update-policy"
dir=""
trap 'rm -rf "$dir"' EXIT
fail() {
echo "FAIL: $scenario: $*" >&2
cat "$dir/output" >&2
exit 1
}
# The stub stops with curl's HTTP error status on any URL containing
# FAIL_URL, and answers with no commit hash when BAD_BRANCH_JSON is set.
write_stub_curl() {
mkdir "$dir/bin"
cat > "$dir/bin/curl" << 'EOF'
#!/bin/sh
previous=""
fail_flag=""
for argument in "$@"; do
[ "$previous" = "--output" ] && output="$argument"
[ "$argument" = "--fail" ] && fail_flag=yes
previous="$argument"
done
url="$previous"
canonical="$(dirname "$0")/../canonical"
site="https://git.eeqj.de/sneak/prompts"
first="af257bef63f162a681bddc80d9b941c47a44b748"
not_found() {
[ -n "$fail_flag" ] && exit 22
echo "404 page not found" > "$output"
exit 0
}
serve_file() {
[ -f "$canonical/$1" ] || not_found
cat "$canonical/$1" > "$output"
}
case "$url" in
*"${FAIL_URL:-no url contains this}"*)
not_found
;;
"https://git.eeqj.de/api/v1/repos/sneak/prompts/branches/main")
if [ -n "${BAD_BRANCH_JSON:-}" ]; then
echo '{"message":"no such branch"}' > "$output"
else
printf '{"name":"main","commit":{"id":"%s"},"x":{"id":"%s"}}\n' \
"$HASH" 0000000000000000000000000000000000000000 > "$output"
fi
;;
"$site/raw/branch/main/script/commit-current-upstream-policy-docs")
serve_file script/commit-current-upstream-policy-docs
;;
"$site/compare/$first...$HASH.patch")
git -C "$canonical" format-patch --stdout --root "$HASH" > "$output"
;;
"$site/raw/commit/$HASH/"*)
serve_file "${url#"$site/raw/commit/$HASH/"}"
;;
*)
not_found
;;
esac
EOF
chmod +x "$dir/bin/curl"
}
write_canonical_files() {
mkdir -p "$dir/canonical/prompts" "$dir/canonical/script"
cp "$COMMIT_CURRENT_UPSTREAM_POLICY_DOCS" "$UPDATE_POLICY" \
"$dir/canonical/script/"
cat > "$dir/canonical/.gitignore" << 'EOF'
*.log
*.tmp
# Own entries go below this comment.
EOF
cat > "$dir/canonical/.dockerignore" << 'EOF'
**/*.log
**/*.tmp
# Own entries go below this comment.
EOF
cat > "$dir/canonical/.editorconfig" << 'EOF'
root = true
[*]
indent_size = 4
# Own sections go below this comment.
EOF
cat > "$dir/canonical/.golangci.yml" << 'EOF'
linters:
settings:
depguard:
rules:
test-support:
deny:
- pkg: net/http/httptest
desc: canonical
- pkg: net/http/cgi
desc: added canonically
other:
deny:
- pkg: example.com/other
EOF
echo '{ "tabWidth": 4 }' > "$dir/canonical/.prettierrc"
echo "new policy" > "$dir/canonical/prompts/REPO_POLICIES.md"
echo "new api policy" > "$dir/canonical/prompts/API_POLICIES.md"
for name in CODE_STYLEGUIDE CODE_STYLEGUIDE_GO CODE_STYLEGUIDE_JS \
CODE_STYLEGUIDE_PYTHON EXISTING_REPO_CHECKLIST \
GO_HTTP_SERVER_CONVENTIONS LLM_PROSE_TELLS NEW_REPO_CHECKLIST; do
echo "new $name" > "$dir/canonical/prompts/$name.md"
done
mkdir -p "$dir/canonical/.gitea/workflows"
echo "name: check" > "$dir/canonical/.gitea/workflows/check.yml"
}
# commit_canonical: commit the canonical files as they stand. The stub
# curl serves only the newest commit; every other commit gets a 404.
commit_canonical() {
git -C "$dir/canonical" add .gitignore .dockerignore .editorconfig \
.golangci.yml .prettierrc .gitea prompts script
git -C "$dir/canonical" commit -q -m "Canonical"
HASH="$(git -C "$dir/canonical" rev-parse HEAD)"
}
# write_outdated_files: this repo's files, one canonical version behind,
# with the repo's own entries in the places the script keeps them. The
# comment above the closing comment in .gitignore is replaced, not kept.
write_outdated_files() {
cat > "$dir/repo/.gitignore" << 'EOF'
*.log
# An outdated comment.
# Own entries go below this comment.
/myapp
EOF
cat > "$dir/repo/.dockerignore" << 'EOF'
**/*.log
# Own entries go below this comment.
/myapp
EOF
cat > "$dir/repo/.editorconfig" << 'EOF'
root = true
[*]
# Own sections go below this comment.
[*.py]
indent_size = 2
EOF
cat > "$dir/repo/.golangci.yml" << 'EOF'
linters:
settings:
depguard:
rules:
test-support:
deny:
- pkg: net/http/httptest
desc: canonical
# Our own test helpers.
- pkg: example.com/app/internal/apptest
desc: own entry
other:
deny:
- pkg: example.com/other
EOF
echo '{ "tabWidth": 2 }' > "$dir/repo/.prettierrc"
echo "old policy" > "$dir/repo/REPO_POLICIES.md"
}
# start_scenario <name>: make a new temp directory holding a repo with
# a Go source file and the outdated files, not yet committed, and an
# empty origin.
start_scenario() {
scenario="$1"
rm -rf "$dir"
dir="$(mktemp -d)"
: > "$dir/output"
mkdir "$dir/tmp"
write_stub_curl
write_canonical_files
export GIT_CONFIG_GLOBAL="$dir/gitconfig"
git config --global user.name "Test"
git config --global user.email "test@example.com"
export FAIL_URL=""
export BAD_BRANCH_JSON=""
git init -q -b main "$dir/canonical"
export HASH
commit_canonical
git init -q --bare -b main "$dir/origin.git"
git init -q -b main "$dir/repo"
git -C "$dir/repo" remote add origin "$dir/origin.git"
mkdir "$dir/repo/script"
cp "$COMMIT_CURRENT_UPSTREAM_POLICY_DOCS" "$UPDATE_POLICY" \
"$dir/repo/script/"
printf '#!/bin/sh\necho example\n' > "$dir/repo/script/projectname"
chmod +x "$dir/repo/script/projectname"
mkdir -p "$dir/repo/cmd/app"
echo "package main" > "$dir/repo/cmd/app/main.go"
write_outdated_files
}
# commit_and_push_fixture <paths...>: commit those paths as they stand
# and every other shared file as the canonical file has it, so the
# script changes only those paths. Record the commit as $before.
commit_and_push_fixture() {
(
cd "$dir/repo"
git add script cmd "$@"
git clean -q -f -x
for file in $(git -C "$dir/canonical" ls-files); do
case "$file" in
script/*) continue ;;
prompts/REPO_POLICIES.md) path="REPO_POLICIES.md" ;;
prompts/*) path="docs/${file#prompts/}" ;;
*) path="$file" ;;
esac
if [ -z "$(git ls-files -- "$path")" ]; then
mkdir -p "$(dirname "$path")"
cp "$dir/canonical/$file" "$path"
git add "$path"
fi
done
git commit -q -m "Fixture"
git push -q origin HEAD
)
before="$(git -C "$dir/repo" rev-parse HEAD)"
}
# remove_from_fixture <paths...>: delete those paths from the repo and
# its origin, and record the commit as $before.
remove_from_fixture() {
(
cd "$dir/repo"
git rm -q -r "$@"
git commit -q -m "Remove from fixture"
git push -q origin HEAD
)
before="$(git -C "$dir/repo" rev-parse HEAD)"
}
run_update_policy() {
status=0
(cd "$dir/repo" && PATH="$dir/bin:$PATH" TMPDIR="$dir/tmp" \
script/update-policy "$@") > "$dir/output" 2>&1 || status=$?
}
# run_commit_current_upstream_policy_docs <arguments>: run the script in
# this source tree, from outside the repo it updates.
run_commit_current_upstream_policy_docs() {
status=0
(cd "$dir" && PATH="$dir/bin:$PATH" TMPDIR="$dir/tmp" \
"$COMMIT_CURRENT_UPSTREAM_POLICY_DOCS" "$@") > "$dir/output" 2>&1 ||
status=$?
}
head_of_repo() {
git -C "$dir/repo" rev-parse HEAD
}
head_of_origin() {
git -C "$dir/origin.git" rev-parse refs/heads/main
}
files_in_commit() {
git -C "$dir/repo" show --name-only --format= HEAD | tr '\n' ' '
}
expect_success() {
[ "$status" -eq 0 ] || fail "exited $status"
}
expect_failure() {
[ "$status" -ne 0 ] || fail "exited 0"
}
expect_output() {
grep -q -F -e "$1" "$dir/output" || fail "output lacks: $1"
}
expect_output_without() {
if grep -q -F -e "$1" "$dir/output"; then
fail "output has: $1"
fi
}
# expect_file <path>: the file must equal standard input.
expect_file() {
cat > "$dir/expected"
diff "$dir/expected" "$dir/repo/$1" >&2 || fail "$1 is wrong"
}
expect_same_as_canonical() {
diff "$dir/canonical/$1" "$dir/repo/$1" >&2 || fail "$1 is not canonical"
}
expect_absent() {
[ ! -e "$dir/repo/$1" ] || fail "$1 exists"
}
expect_executable() {
[ "$(stat -c %a "$dir/repo/$1")" = 755 ] || fail "$1 is not mode 755"
}
expect_committed_executable() {
git -C "$dir/repo" ls-files -s -- "$1" | grep -q '^100755 ' ||
fail "$1 is not committed as executable"
}
expect_new_commit_not_pushed() {
[ "$(head_of_repo)" != "$before" ] || fail "no commit was made"
[ "$(head_of_origin)" = "$before" ] || fail "the commit was pushed"
}
expect_no_commit() {
[ "$(head_of_repo)" = "$before" ] || fail "the repo has a new commit"
[ "$(head_of_origin)" = "$before" ] || fail "origin has a new commit"
}
expect_nothing_written() {
[ -z "$(git -C "$dir/repo" status --porcelain)" ] ||
fail "the working tree changed"
expect_no_commit
}
test_own_entries_kept() {
start_scenario "own entries kept in all four files"
commit_and_push_fixture .gitignore .dockerignore .editorconfig \
.golangci.yml .prettierrc REPO_POLICIES.md
run_update_policy
expect_success
expect_file .gitignore << 'EOF'
*.log
*.tmp
# Own entries go below this comment.
/myapp
EOF
expect_file .dockerignore << 'EOF'
**/*.log
**/*.tmp
# Own entries go below this comment.
/myapp
EOF
expect_file .editorconfig << 'EOF'
root = true
[*]
indent_size = 4
# Own sections go below this comment.
[*.py]
indent_size = 2
EOF
expect_file .golangci.yml << 'EOF'
linters:
settings:
depguard:
rules:
test-support:
deny:
- pkg: net/http/httptest
desc: canonical
- pkg: net/http/cgi
desc: added canonically
# Our own test helpers.
- pkg: example.com/app/internal/apptest
desc: own entry
other:
deny:
- pkg: example.com/other
EOF
expect_file .prettierrc << 'EOF'
{ "tabWidth": 4 }
EOF
expect_file REPO_POLICIES.md << 'EOF'
new policy
EOF
expect_new_commit_not_pushed
git -C "$dir/repo" log -1 --format=%s | grep -q -F -e "$HASH" ||
fail "the commit message does not name the head of main"
[ "$(files_in_commit)" = ".dockerignore .editorconfig .gitignore .golangci.yml .prettierrc REPO_POLICIES.md " ] ||
fail "the commit holds other files: $(files_in_commit)"
[ -z "$(ls -A "$dir/tmp")" ] || fail "a temporary directory is left"
}
test_files_the_repo_lacks_are_written_whole() {
start_scenario "files the repo lacks are written whole"
mkdir "$dir/repo/docs"
mv "$dir/repo/REPO_POLICIES.md" "$dir/repo/docs/REPO_POLICIES.md"
echo "print()" > "$dir/repo/tool.py"
echo "export {}" > "$dir/repo/web.ts"
commit_and_push_fixture docs/REPO_POLICIES.md tool.py web.ts
remove_from_fixture REPO_POLICIES.md .gitignore .golangci.yml .gitea \
docs/API_POLICIES.md docs/CODE_STYLEGUIDE_GO.md \
docs/CODE_STYLEGUIDE_JS.md docs/CODE_STYLEGUIDE_PYTHON.md
run_update_policy
expect_success
expect_same_as_canonical .gitignore
expect_same_as_canonical .golangci.yml
expect_same_as_canonical .gitea/workflows/check.yml
expect_file docs/REPO_POLICIES.md << 'EOF'
new policy
EOF
expect_absent REPO_POLICIES.md
expect_file docs/API_POLICIES.md << 'EOF'
new api policy
EOF
expect_absent API_POLICIES.md
expect_file docs/CODE_STYLEGUIDE_GO.md << 'EOF'
new CODE_STYLEGUIDE_GO
EOF
expect_file docs/CODE_STYLEGUIDE_JS.md << 'EOF'
new CODE_STYLEGUIDE_JS
EOF
expect_file docs/CODE_STYLEGUIDE_PYTHON.md << 'EOF'
new CODE_STYLEGUIDE_PYTHON
EOF
expect_new_commit_not_pushed
}
test_language_files_need_a_source_file() {
start_scenario "a repo with no source file of a language gets none of its files"
commit_and_push_fixture .prettierrc
remove_from_fixture cmd .golangci.yml docs/CODE_STYLEGUIDE_GO.md \
docs/GO_HTTP_SERVER_CONVENTIONS.md docs/CODE_STYLEGUIDE_JS.md \
docs/CODE_STYLEGUIDE_PYTHON.md
run_update_policy
expect_success
expect_absent .golangci.yml
expect_absent docs/CODE_STYLEGUIDE_GO.md
expect_absent docs/GO_HTTP_SERVER_CONVENTIONS.md
expect_absent docs/CODE_STYLEGUIDE_JS.md
expect_absent docs/CODE_STYLEGUIDE_PYTHON.md
expect_new_commit_not_pushed
}
test_javascript_source_brings_its_styleguide() {
for extension in js mjs; do
start_scenario "a repo whose only JavaScript source is a .$extension file gets the JavaScript style guide"
echo "export {}" > "$dir/repo/web.$extension"
commit_and_push_fixture "web.$extension"
remove_from_fixture docs/CODE_STYLEGUIDE_JS.md
run_update_policy
expect_success
expect_file docs/CODE_STYLEGUIDE_JS.md << 'EOF'
new CODE_STYLEGUIDE_JS
EOF
expect_new_commit_not_pushed
done
}
test_nothing_changed() {
start_scenario "nothing changed"
cp "$dir/canonical/.prettierrc" "$dir/repo/.prettierrc"
commit_and_push_fixture .prettierrc
run_update_policy
expect_success
expect_output "nothing to commit"
expect_nothing_written
}
test_target_lacking_commit_current_upstream_policy_docs_gets_both_scripts() {
start_scenario "a repo lacking script/commit-current-upstream-policy-docs gets it, and the current script/update-policy, in the commit"
rm "$dir/repo/script/commit-current-upstream-policy-docs"
echo "# An older version." >> "$dir/repo/script/update-policy"
commit_and_push_fixture .prettierrc
run_update_policy
expect_success
expect_same_as_canonical script/commit-current-upstream-policy-docs
expect_same_as_canonical script/update-policy
expect_committed_executable script/commit-current-upstream-policy-docs
expect_committed_executable script/update-policy
expect_new_commit_not_pushed
[ "$(files_in_commit)" = ".prettierrc script/commit-current-upstream-policy-docs script/update-policy " ] ||
fail "the commit holds other files: $(files_in_commit)"
}
# The shell reads a script in blocks, so the padding makes it read the
# last line only after main has returned. It finds that line only if
# script/commit-current-upstream-policy-docs left the old file in place
# under a new name. The old file is an earlier canonical version, so
# script/commit-current-upstream-policy-docs replaces it.
test_update_policy_replaces_itself() {
start_scenario "script/update-policy replaces its own file and still exits 0"
{
cat "$dir/repo/script/update-policy"
i=0
while [ "$i" -lt 2000 ]; do
echo "# Padding."
i=$((i + 1))
done
echo "echo 'the old file ran to its end'"
} > "$dir/merged"
cp "$dir/merged" "$dir/canonical/script/update-policy"
commit_canonical
cp "$UPDATE_POLICY" "$dir/canonical/script/update-policy"
commit_canonical
mv "$dir/merged" "$dir/repo/script/update-policy"
chmod 755 "$dir/repo/script/update-policy"
commit_and_push_fixture
run_update_policy
expect_success
expect_output "the old file ran to its end"
expect_same_as_canonical script/update-policy
expect_executable script/update-policy
expect_new_commit_not_pushed
}
test_commit_current_upstream_policy_docs_with_path_commits_nothing() {
start_scenario "script/commit-current-upstream-policy-docs with a path writes into that clone and commits nothing"
rm "$dir/repo/script/commit-current-upstream-policy-docs" \
"$dir/repo/script/update-policy"
commit_and_push_fixture .prettierrc
run_commit_current_upstream_policy_docs "$dir/repo"
expect_success
expect_file .prettierrc << 'EOF'
{ "tabWidth": 4 }
EOF
expect_same_as_canonical script/commit-current-upstream-policy-docs
expect_same_as_canonical script/update-policy
expect_executable script/commit-current-upstream-policy-docs
expect_executable script/update-policy
expect_no_commit
[ "$(head -n 1 "$dir/output")" = "$HASH" ] ||
fail "the first line of output is not the hash"
expect_output "script/commit-current-upstream-policy-docs"
[ -z "$(ls -A "$dir/tmp")" ] || fail "a temporary directory is left"
}
test_commit_current_upstream_policy_docs_without_script_directory() {
start_scenario "script/commit-current-upstream-policy-docs with a path writes both scripts into a clone with no script directory"
commit_and_push_fixture .prettierrc
remove_from_fixture script
run_commit_current_upstream_policy_docs "$dir/repo"
expect_success
expect_same_as_canonical script/commit-current-upstream-policy-docs
expect_same_as_canonical script/update-policy
expect_executable script/commit-current-upstream-policy-docs
expect_executable script/update-policy
expect_no_commit
}
test_commit_current_upstream_policy_docs_without_argument_updates_own_repo() {
start_scenario "script/commit-current-upstream-policy-docs with no argument updates the repo it sits in"
commit_and_push_fixture .prettierrc
status=0
(cd "$dir" && PATH="$dir/bin:$PATH" \
repo/script/commit-current-upstream-policy-docs) \
> "$dir/output" 2>&1 || status=$?
expect_success
expect_file .prettierrc << 'EOF'
{ "tabWidth": 4 }
EOF
expect_no_commit
}
test_commit_current_upstream_policy_docs_bad_target() {
start_scenario "script/commit-current-upstream-policy-docs refuses a path that is not the top of a clone"
commit_and_push_fixture .prettierrc
run_commit_current_upstream_policy_docs "$dir/repo/script"
expect_failure
expect_output "not the top of a git clone"
run_commit_current_upstream_policy_docs "$dir/bin"
expect_failure
expect_output "not the top of a git clone"
run_commit_current_upstream_policy_docs "$dir/no-such-directory"
expect_failure
expect_output "no such directory"
run_commit_current_upstream_policy_docs "$dir/repo" extra
expect_failure
expect_output "usage:"
expect_nothing_written
}
test_failed_fetch_stops() {
for url in \
"/api/v1/repos/sneak/prompts/branches/main" \
"/raw/branch/main/script/commit-current-upstream-policy-docs" \
"/compare/" \
"/raw/commit/"; do
start_scenario "a failed fetch of $url stops with nothing written"
commit_and_push_fixture .gitignore .prettierrc REPO_POLICIES.md
FAIL_URL="$url"
run_update_policy
expect_failure
expect_output "cannot fetch"
expect_nothing_written
done
}
test_one_missing_file_stops_all() {
start_scenario "one file missing from prompts stops before any file is written"
rm "$dir/canonical/prompts/REPO_POLICIES.md"
commit_and_push_fixture .prettierrc REPO_POLICIES.md
run_update_policy
expect_failure
expect_output "cannot fetch"
expect_nothing_written
}
test_branch_response_without_hash_stops() {
start_scenario "a branch response with no commit hash stops"
commit_and_push_fixture .prettierrc
BAD_BRANCH_JSON=yes
run_update_policy
expect_failure
expect_output "no commit hash"
expect_nothing_written
}
# Each of these files, with the repo's own entries above the closing
# comment. The comment added with them must not be named.
test_own_entry_above_closing_comment() {
for file in .gitignore .dockerignore .editorconfig; do
start_scenario "own entries above the closing comment of $file stop"
{
printf '/myapp\n/.tool/\n\n# A comment of our own.\n'
cat "$dir/repo/$file"
} > "$dir/merged"
mv "$dir/merged" "$dir/repo/$file"
commit_and_push_fixture .gitignore .dockerignore .editorconfig \
.prettierrc REPO_POLICIES.md
run_update_policy
expect_failure
expect_output "$file has entries"
expect_output "/myapp"
expect_output "/.tool/"
expect_output_without "A comment of our own."
expect_nothing_written
done
}
test_uncommitted_change_stops() {
start_scenario "a carried file with uncommitted changes stops"
commit_and_push_fixture .prettierrc REPO_POLICIES.md
echo "uncommitted" >> "$dir/repo/REPO_POLICIES.md"
run_update_policy
expect_failure
expect_output "REPO_POLICIES.md has uncommitted changes"
expect_file REPO_POLICIES.md << 'EOF'
old policy
uncommitted
EOF
expect_no_commit
}
test_untracked_script_stops() {
start_scenario "an untracked script/commit-current-upstream-policy-docs is not overwritten"
rm "$dir/repo/script/commit-current-upstream-policy-docs"
commit_and_push_fixture .prettierrc
echo "our own script" > "$dir/repo/script/commit-current-upstream-policy-docs"
run_commit_current_upstream_policy_docs "$dir/repo"
expect_failure
expect_output "script/commit-current-upstream-policy-docs has uncommitted changes"
expect_file script/commit-current-upstream-policy-docs << 'EOF'
our own script
EOF
expect_no_commit
}
test_ignored_file_stops() {
start_scenario "an ignored file at a listed path is not overwritten"
commit_and_push_fixture .prettierrc
remove_from_fixture docs/API_POLICIES.md
echo "docs/API_POLICIES.md" >> "$dir/repo/.git/info/exclude"
echo "our own notes" > "$dir/repo/docs/API_POLICIES.md"
run_commit_current_upstream_policy_docs "$dir/repo"
expect_failure
expect_output "docs/API_POLICIES.md is ignored by git"
expect_file docs/API_POLICIES.md << 'EOF'
our own notes
EOF
expect_no_commit
}
test_own_script_stops() {
for script in script/commit-current-upstream-policy-docs \
script/update-policy; do
start_scenario "a tracked $script with a line the canonical file has never held stops"
echo "echo our own script" >> "$dir/repo/$script"
commit_and_push_fixture .prettierrc
run_update_policy
expect_failure
expect_output "$script has lines"
expect_output "echo our own script"
expect_nothing_written
done
}
test_gitignore_lacks_canonical_last_line() {
start_scenario ".gitignore lacking the canonical last line keeps the repo's own line below the canonical content"
printf '*.log\n\n# A comment of our own.\n/myapp\n' > "$dir/repo/.gitignore"
commit_and_push_fixture .gitignore .prettierrc
run_update_policy
expect_success
expect_file .gitignore << 'EOF'
*.log
*.tmp
# Own entries go below this comment.
/myapp
EOF
expect_new_commit_not_pushed
}
test_dockerignore_lacks_canonical_last_line() {
start_scenario ".dockerignore lacking the canonical last line drops a line an earlier canonical commit held"
echo "**/*.log" > "$dir/repo/.dockerignore"
expect_former_canonical_line_replaced .dockerignore ".git"
expect_same_as_canonical .dockerignore
}
test_editorconfig_lacks_canonical_last_line() {
start_scenario ".editorconfig lacking the canonical last line with a line of its own stops"
printf 'root = true\n[*.py]\nindent_size = 2\n' > "$dir/repo/.editorconfig"
commit_and_push_fixture .editorconfig .prettierrc
run_update_policy
expect_failure
expect_output ".editorconfig lacks this line"
expect_output "[*.py]"
expect_output "indent_size = 2"
expect_nothing_written
}
test_editorconfig_lacks_canonical_last_line_no_own_line() {
start_scenario ".editorconfig lacking the canonical last line with no line of its own is replaced"
printf 'root = true\n[*]\n' > "$dir/repo/.editorconfig"
expect_former_canonical_line_replaced .editorconfig "indent_size = 8"
expect_same_as_canonical .editorconfig
}
test_fetched_golangci_without_deny_list() {
start_scenario "a fetched .golangci.yml with no deny list stops"
cat > "$dir/canonical/.golangci.yml" << 'EOF'
linters:
settings:
depguard:
rules:
test-support:
files:
- "**/*.go"
other:
deny:
- pkg: example.com/other
EOF
commit_and_push_fixture .golangci.yml .prettierrc
run_update_policy
expect_failure
expect_output "no deny list in its test-support rule"
expect_nothing_written
}
test_own_golangci_setting_stops() {
start_scenario "a .golangci.yml setting the canonical file lacks stops"
cat >> "$dir/repo/.golangci.yml" << 'EOF'
# A comment of our own.
forbidigo:
forbid:
- pattern: model.Operator
EOF
commit_and_push_fixture .golangci.yml .prettierrc
run_update_policy
expect_failure
expect_output ".golangci.yml has lines"
expect_output "forbidigo:"
expect_output "- pattern: model.Operator"
expect_output_without "A comment of our own."
expect_nothing_written
}
test_source_repo_stops() {
start_scenario "the repo the policy files come from stops"
mkdir "$dir/repo/prompts"
echo "policy" > "$dir/repo/prompts/REPO_POLICIES.md"
commit_and_push_fixture .prettierrc prompts/REPO_POLICIES.md
run_update_policy
expect_failure
expect_output "this repo is the source of the policy files"
expect_nothing_written
}
test_staged_file_stays_out_of_commit() {
start_scenario "a file staged before the script starts stays out of its commit"
commit_and_push_fixture .prettierrc
echo "notes" > "$dir/repo/notes.txt"
git -C "$dir/repo" add notes.txt
run_update_policy
expect_success
[ "$(files_in_commit)" = ".prettierrc " ] ||
fail "the commit holds more than the policy file"
[ "$(git -C "$dir/repo" diff --cached --name-only)" = "notes.txt" ] ||
fail "notes.txt is no longer staged"
}
test_update_policy_takes_no_argument() {
start_scenario "script/update-policy refuses an argument"
commit_and_push_fixture .prettierrc
run_update_policy "$HASH"
expect_failure
expect_output "usage:"
expect_nothing_written
}
# expect_former_canonical_line_replaced <file> <line>: an earlier
# canonical commit held the line and the newest does not. This repo's
# file still has it, and the script replaces it.
expect_former_canonical_line_replaced() {
cp "$dir/canonical/$1" "$dir/newest"
{ echo "$2"; cat "$dir/newest"; } > "$dir/canonical/$1"
commit_canonical
cp "$dir/newest" "$dir/canonical/$1"
commit_canonical
{ echo "$2"; cat "$dir/repo/$1"; } > "$dir/merged"
mv "$dir/merged" "$dir/repo/$1"
commit_and_push_fixture "$1" .prettierrc
run_update_policy
expect_success
if grep -q -F -x -e "$2" "$dir/repo/$1"; then
fail "$1 still has: $2"
fi
expect_new_commit_not_pushed
}
test_former_canonical_golangci_line_replaced() {
start_scenario "a .golangci.yml line an earlier canonical commit held is replaced"
expect_former_canonical_line_replaced .golangci.yml "version: 1"
}
test_former_canonical_gitignore_line_replaced() {
start_scenario "a .gitignore line above the closing comment that an earlier canonical commit held is replaced"
expect_former_canonical_line_replaced .gitignore "*.old"
}
test_former_canonical_script_line_replaced() {
start_scenario "a script/commit-current-upstream-policy-docs line an earlier canonical commit held is replaced"
expect_former_canonical_line_replaced \
script/commit-current-upstream-policy-docs "echo an older version"
expect_same_as_canonical script/commit-current-upstream-policy-docs
}
# A line another file's history removed does not count as held by this
# one.
test_line_removed_from_another_file_still_stops() {
start_scenario "a line only another canonical file once held stops"
{ echo "*.old"; cat "$dir/canonical/.dockerignore"; } > "$dir/merged"
cp "$dir/merged" "$dir/canonical/.dockerignore"
commit_canonical
tail -n +2 "$dir/merged" > "$dir/canonical/.dockerignore"
commit_canonical
{ echo "*.old"; cat "$dir/repo/.gitignore"; } > "$dir/merged"
mv "$dir/merged" "$dir/repo/.gitignore"
commit_and_push_fixture .gitignore .prettierrc
run_update_policy
expect_failure
expect_output ".gitignore has entries"
expect_output "*.old"
expect_nothing_written
}
main() {
test_own_entries_kept
test_files_the_repo_lacks_are_written_whole
test_language_files_need_a_source_file
test_javascript_source_brings_its_styleguide
test_nothing_changed
test_target_lacking_commit_current_upstream_policy_docs_gets_both_scripts
test_update_policy_replaces_itself
test_commit_current_upstream_policy_docs_with_path_commits_nothing
test_commit_current_upstream_policy_docs_without_script_directory
test_commit_current_upstream_policy_docs_without_argument_updates_own_repo
test_commit_current_upstream_policy_docs_bad_target
test_failed_fetch_stops
test_one_missing_file_stops_all
test_branch_response_without_hash_stops
test_own_entry_above_closing_comment
test_uncommitted_change_stops
test_untracked_script_stops
test_ignored_file_stops
test_own_script_stops
test_gitignore_lacks_canonical_last_line
test_dockerignore_lacks_canonical_last_line
test_editorconfig_lacks_canonical_last_line
test_editorconfig_lacks_canonical_last_line_no_own_line
test_fetched_golangci_without_deny_list
test_own_golangci_setting_stops
test_source_repo_stops
test_staged_file_stays_out_of_commit
test_update_policy_takes_no_argument
test_former_canonical_golangci_line_replaced
test_former_canonical_gitignore_line_replaced
test_former_canonical_script_line_replaced
test_line_removed_from_another_file_still_stops
echo "update-policy tests passed"
}
main "$@"