check / check (push) Canceled after 0s
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
920 lines
30 KiB
Bash
Executable File
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 "$@"
|