test.sh 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/env bash
  2. # test.sh <path to a Go app repo with content/> (e.g. ~/src/posts)
  3. #
  4. # Runs release end to end in a throwaway sandbox: a local bare remote
  5. # instead of Gogs, and stand-in sudo/systemctl that record what they were
  6. # asked to do. Nothing on the real server is touched.
  7. set -euo pipefail
  8. here=$(cd "$(dirname "$0")" && pwd)
  9. src=$(cd "${1:?usage: test.sh <go app repo>}" && pwd)
  10. T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
  11. pass=0; fail=0
  12. ok() { echo "ok $*"; pass=$((pass+1)); }
  13. bad() { echo "FAIL $*"; fail=$((fail+1)); }
  14. check() { local what=$1; shift; if "$@"; then ok "$what"; else bad "$what"; fi; }
  15. # Remote: bajdev/posts (go app with content) and bajdev/site (static).
  16. mkdir -p "$T/remote/bajdev" "$T/apps/posts/content" "$T/vhosts/site.example" "$T/bin"
  17. git clone --quiet --bare "$src" "$T/remote/bajdev/posts.git"
  18. # Start from no deploy tags, whatever the source repo already has.
  19. git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/*' | xargs -r git -C "$T/remote/bajdev/posts.git" tag -d >/dev/null
  20. git init --quiet -b main "$T/site" && mkdir "$T/site/public" && echo hi > "$T/site/public/index.html"
  21. git -C "$T/site" add . && git -C "$T/site" -c user.name=t -c user.email=t@t commit --quiet -m init
  22. git clone --quiet --bare "$T/site" "$T/remote/bajdev/site.git"
  23. echo stale > "$T/apps/posts/content/removed-post.md"
  24. cat > "$T/sites.json" <<EOF
  25. {
  26. "bajdev/posts": { "kind": "go", "name": "posts", "content": "content" },
  27. "bajdev/site": { "kind": "static", "host": "site.example" }
  28. }
  29. EOF
  30. # Stand-ins: sudo just runs the command; systemctl logs and reports active
  31. # unless SYSTEMCTL_FAIL is set.
  32. printf '#!/bin/sh\n[ "$1" = -n ] && shift\nexec "$@"\n' > "$T/bin/sudo"
  33. printf '#!/bin/sh\necho "$*" >> "%s/systemctl.log"\n[ "$1" = is-active ] && [ -n "$SYSTEMCTL_FAIL" ] && exit 3\nexit 0\n' "$T" > "$T/bin/systemctl"
  34. chmod +x "$T/bin/sudo" "$T/bin/systemctl"
  35. export PATH="$T/bin:$PATH" RELEASE_CONF="$T/sites.json" RELEASE_GIT_BASE="$T/remote/" \
  36. RELEASE_WORK="$T/work" RELEASE_APPS="$T/apps" RELEASE_VHOSTS="$T/vhosts" RELEASE_LOG="$T/deploys.log" \
  37. GIT_AUTHOR_NAME=deploy GIT_AUTHOR_EMAIL=deploy@test GIT_COMMITTER_NAME=deploy GIT_COMMITTER_EMAIL=deploy@test
  38. R="$here/release"
  39. head=$(git -C "$src" rev-parse main)
  40. # 1. A normal release of the Go app.
  41. out=$(DEPLOY_BY="PR merged by bajdev" "$R" bajdev/posts "$head" 2>&1) || { bad "release posts: $out"; }
  42. check "binary built" test -x "$T/apps/posts/posts"
  43. check "no .new left behind" test ! -e "$T/apps/posts/posts.new"
  44. check "content synced" test -f "$T/apps/posts/content/2026-10-24-hello-world.md"
  45. check "deleted posts removed" test ! -e "$T/apps/posts/content/removed-post.md"
  46. check "service restarted" grep -qx "restart goapp@posts" "$T/systemctl.log"
  47. tag1=$(git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/posts/*' | tail -1)
  48. check "deploy tag pushed" test -n "$tag1"
  49. check "tag is on the commit" test "$(git -C "$T/remote/bajdev/posts.git" rev-parse "$tag1^{commit}")" = "$head"
  50. check "log line written" grep -q "posts $head $tag1 PR merged by bajdev" "$T/deploys.log"
  51. # 2. Rollback to that tag (a second later, so the new tag name differs).
  52. sleep 1
  53. out=$(DEPLOY_BY="rollback by bajdev" "$R" bajdev/posts "$tag1" 2>&1) || bad "rollback: $out"
  54. check "rollback logged" grep -q "rollback by bajdev" "$T/deploys.log"
  55. check "rollback tagged" test "$(git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/posts/*' | wc -l)" -eq 2
  56. # 3. Static site.
  57. out=$("$R" bajdev/site main 2>&1) || bad "release site: $out"
  58. check "static files synced" test -f "$T/vhosts/site.example/index.html"
  59. check "static tag pushed" test -n "$(git -C "$T/remote/bajdev/site.git" tag -l 'deploy/site.example/*')"
  60. # 4. Refusals.
  61. refused() { # <message the right guard prints> <args...>
  62. local want=$1; shift; local out
  63. if out=$("$R" "$@" 2>&1); then return 1; fi
  64. [[ $out == *"$want"* ]]
  65. }
  66. check "unknown repo refused" refused "unknown repo" bajdev/nope main
  67. check "../ repo name refused" refused "bad repo name" ../x main
  68. check "dot-leading name refused" refused "bad repo name" bajdev/.git main
  69. check "option-like ref refused" refused "bad ref" bajdev/posts --upload-pack=x
  70. check "wrong arg count refused" refused "usage:" bajdev/posts
  71. # 5. A service that fails to start: no tag, no log line.
  72. tags_before=$(git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/posts/*' | wc -l)
  73. lines_before=$(wc -l < "$T/deploys.log")
  74. sleep 1
  75. check "failed start exits non-zero" bash -c '! SYSTEMCTL_FAIL=1 "$0" bajdev/posts main >/dev/null 2>&1' "$R"
  76. check "failed start not tagged" test "$(git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/posts/*' | wc -l)" -eq "$tags_before"
  77. check "failed start not logged" test "$(wc -l < "$T/deploys.log")" -eq "$lines_before"
  78. # 6. What deployd sends: origin/main, after main has moved on since the work
  79. # clone was made (another PR merged meanwhile). The release must include it.
  80. up=$T/upstream
  81. git clone --quiet "$T/remote/bajdev/posts.git" "$up"
  82. printf -- '---\ntitle: Merged by another PR\n---\n\nHi.\n' > "$up/content/2026-12-01-other-pr.md"
  83. git -C "$up" add content && git -C "$up" commit --quiet -m "Another PR" && git -C "$up" push --quiet origin HEAD:main
  84. tip=$(git -C "$T/remote/bajdev/posts.git" rev-parse main)
  85. sleep 1
  86. out=$(DEPLOY_BY="PR merged by bajdev (head 0bfb14e)" "$R" bajdev/posts origin/main 2>&1) || bad "release origin/main: $out"
  87. check "origin/main includes the other PR" test -f "$T/apps/posts/content/2026-12-01-other-pr.md"
  88. tagN=$(git -C "$T/remote/bajdev/posts.git" tag -l 'deploy/posts/*' | tail -1)
  89. check "tag is on main's new tip" test "$(git -C "$T/remote/bajdev/posts.git" rev-parse "$tagN^{commit}")" = "$tip"
  90. check "log records main's tip" grep -q "posts $tip $tagN PR merged by bajdev (head 0bfb14e)" "$T/deploys.log"
  91. echo "$pass passed, $fail failed"
  92. [[ $fail -eq 0 ]]