#!/bin/sh # test/commit-upstream-policy-files.sh: test # script/write-upstream-policy-files and script/commit-upstream-policy-files # 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" WRITE_UPSTREAM_POLICY_FILES="$SCRIPT_DIR/write-upstream-policy-files" COMMIT_UPSTREAM_POLICY_FILES="$SCRIPT_DIR/commit-upstream-policy-files" 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/write-upstream-policy-files") serve_file script/write-upstream-policy-files ;; "$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 "$WRITE_UPSTREAM_POLICY_FILES" "$COMMIT_UPSTREAM_POLICY_FILES" \ "$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 : 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 "$WRITE_UPSTREAM_POLICY_FILES" "$COMMIT_UPSTREAM_POLICY_FILES" \ "$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 : 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 : 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_commit_upstream_policy_files() { status=0 (cd "$dir/repo" && PATH="$dir/bin:$PATH" TMPDIR="$dir/tmp" \ script/commit-upstream-policy-files "$@") > "$dir/output" 2>&1 || status=$? } # run_write_upstream_policy_files : run the script in this # source tree, from outside the repo it updates. run_write_upstream_policy_files() { status=0 (cd "$dir" && PATH="$dir/bin:$PATH" TMPDIR="$dir/tmp" \ "$WRITE_UPSTREAM_POLICY_FILES" "$@") > "$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 : 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files expect_success expect_output "nothing to commit" expect_nothing_written } test_target_lacking_write_upstream_policy_files_gets_both_scripts() { start_scenario "a repo lacking script/write-upstream-policy-files gets it, and the current script/commit-upstream-policy-files, in the commit" rm "$dir/repo/script/write-upstream-policy-files" echo "# An older version." >> "$dir/repo/script/commit-upstream-policy-files" commit_and_push_fixture .prettierrc run_commit_upstream_policy_files expect_success expect_same_as_canonical script/write-upstream-policy-files expect_same_as_canonical script/commit-upstream-policy-files expect_committed_executable script/write-upstream-policy-files expect_committed_executable script/commit-upstream-policy-files expect_new_commit_not_pushed [ "$(files_in_commit)" = ".prettierrc script/commit-upstream-policy-files script/write-upstream-policy-files " ] || 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/write-upstream-policy-files left the old file in place under a # new name. The old file is an earlier canonical version, so # script/write-upstream-policy-files replaces it. test_commit_upstream_policy_files_replaces_itself() { start_scenario "script/commit-upstream-policy-files replaces its own file and still exits 0" { cat "$dir/repo/script/commit-upstream-policy-files" 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/commit-upstream-policy-files" commit_canonical cp "$COMMIT_UPSTREAM_POLICY_FILES" \ "$dir/canonical/script/commit-upstream-policy-files" commit_canonical mv "$dir/merged" "$dir/repo/script/commit-upstream-policy-files" chmod 755 "$dir/repo/script/commit-upstream-policy-files" commit_and_push_fixture run_commit_upstream_policy_files expect_success expect_output "the old file ran to its end" expect_same_as_canonical script/commit-upstream-policy-files expect_executable script/commit-upstream-policy-files expect_new_commit_not_pushed } test_write_upstream_policy_files_with_path_commits_nothing() { start_scenario "script/write-upstream-policy-files with a path writes into that clone and commits nothing" rm "$dir/repo/script/write-upstream-policy-files" \ "$dir/repo/script/commit-upstream-policy-files" commit_and_push_fixture .prettierrc run_write_upstream_policy_files "$dir/repo" expect_success expect_file .prettierrc << 'EOF' { "tabWidth": 4 } EOF expect_same_as_canonical script/write-upstream-policy-files expect_same_as_canonical script/commit-upstream-policy-files expect_executable script/write-upstream-policy-files expect_executable script/commit-upstream-policy-files expect_no_commit [ "$(head -n 1 "$dir/output")" = "$HASH" ] || fail "the first line of output is not the hash" expect_output "script/write-upstream-policy-files" [ -z "$(ls -A "$dir/tmp")" ] || fail "a temporary directory is left" } test_write_upstream_policy_files_without_script_directory() { start_scenario "script/write-upstream-policy-files with a path writes both scripts into a clone with no script directory" commit_and_push_fixture .prettierrc remove_from_fixture script run_write_upstream_policy_files "$dir/repo" expect_success expect_same_as_canonical script/write-upstream-policy-files expect_same_as_canonical script/commit-upstream-policy-files expect_executable script/write-upstream-policy-files expect_executable script/commit-upstream-policy-files expect_no_commit } test_write_upstream_policy_files_without_argument_updates_own_repo() { start_scenario "script/write-upstream-policy-files with no argument updates the repo it sits in" commit_and_push_fixture .prettierrc status=0 (cd "$dir" && PATH="$dir/bin:$PATH" \ repo/script/write-upstream-policy-files) \ > "$dir/output" 2>&1 || status=$? expect_success expect_file .prettierrc << 'EOF' { "tabWidth": 4 } EOF expect_no_commit } test_write_upstream_policy_files_bad_target() { start_scenario "script/write-upstream-policy-files refuses a path that is not the top of a clone" commit_and_push_fixture .prettierrc run_write_upstream_policy_files "$dir/repo/script" expect_failure expect_output "not the top of a git clone" run_write_upstream_policy_files "$dir/bin" expect_failure expect_output "not the top of a git clone" run_write_upstream_policy_files "$dir/no-such-directory" expect_failure expect_output "no such directory" run_write_upstream_policy_files "$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/write-upstream-policy-files" \ "/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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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/write-upstream-policy-files is not overwritten" rm "$dir/repo/script/write-upstream-policy-files" commit_and_push_fixture .prettierrc echo "our own script" > "$dir/repo/script/write-upstream-policy-files" run_write_upstream_policy_files "$dir/repo" expect_failure expect_output "script/write-upstream-policy-files has uncommitted changes" expect_file script/write-upstream-policy-files << '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_write_upstream_policy_files "$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/write-upstream-policy-files \ script/commit-upstream-policy-files; 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files 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_commit_upstream_policy_files_takes_no_argument() { start_scenario "script/commit-upstream-policy-files refuses an argument" commit_and_push_fixture .prettierrc run_commit_upstream_policy_files "$HASH" expect_failure expect_output "usage:" expect_nothing_written } # expect_former_canonical_line_replaced : 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_commit_upstream_policy_files 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/write-upstream-policy-files line an earlier canonical commit held is replaced" expect_former_canonical_line_replaced \ script/write-upstream-policy-files "echo an older version" expect_same_as_canonical script/write-upstream-policy-files } # 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_commit_upstream_policy_files 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_write_upstream_policy_files_gets_both_scripts test_commit_upstream_policy_files_replaces_itself test_write_upstream_policy_files_with_path_commits_nothing test_write_upstream_policy_files_without_script_directory test_write_upstream_policy_files_without_argument_updates_own_repo test_write_upstream_policy_files_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_commit_upstream_policy_files_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 "commit-upstream-policy-files tests passed" } main "$@"