install_next_test.sh RawHistoryFolder
1#!/bin/sh
2set -eu
3
4ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
5PASS=0
6FAIL=0
7SHA=0123456789abcdef0123456789abcdef01234567
8
9fail() {
10 echo "not ok - $1"
11 FAIL=$((FAIL + 1))
12}
13
14pass() {
15 echo "ok - $1"
16 PASS=$((PASS + 1))
17}
18
19run_case() {
20 shift
21 case_dir=$(mktemp -d)
22 mkdir -p "$case_dir/bin" "$case_dir/home" "$case_dir/install"
23
24 cat >"$case_dir/bin/uname" <<'EOF'
25#!/bin/sh
26[ "$1" = "-s" ] && echo Linux || echo x86_64
27EOF
28 cat >"$case_dir/bin/id" <<'EOF'
29#!/bin/sh
30[ "$1" = "-u" ] && { echo 1000; exit 0; }
31exec /usr/bin/id "$@"
32EOF
33 cat >"$case_dir/bin/curl" <<EOF
34#!/bin/sh
35printf '%s\n' "\$*" >>'$case_dir/curl.log'
36case "\$*" in
37 *api.github.com/repos/bnema/gordon/commits/next*) printf '%s\n' '{"sha":"$SHA"}' ;;
38 *codeload.github.com/bnema/gordon/tar.gz/$SHA*) : >'$case_dir/source.tar.gz' ;;
39 *git.bnema.dev/gordon/releases/latest*) printf '%s' "\${MOCK_LATEST_URL-https://git.bnema.dev/gordon/releases/tag/v9.9.9}" ;;
40 *) exit 90 ;;
41esac
42EOF
43 cat >"$case_dir/bin/tar" <<EOF
44#!/bin/sh
45printf '%s\n' "\$*" >>'$case_dir/tar.log'
46case "\$1" in
47 -tzf) printf '%s\n' 'gordon-$SHA/' 'gordon-$SHA/go.mod' 'gordon-$SHA/main.go' ;;
48 -tvzf) printf '%s\n' 'drwxr-xr-x root/root 0 date gordon-$SHA/' '-rw-r--r-- root/root 0 date gordon-$SHA/go.mod' '-rw-r--r-- root/root 0 date gordon-$SHA/main.go' ;;
49 -xzf)
50 while [ "\$#" -gt 0 ]; do
51 if [ "\$1" = -C ]; then
52 shift
53 mkdir -p "\$1/gordon-$SHA"
54 printf 'module example\n\ngo 1.27\n' >"\$1/gordon-$SHA/go.mod"
55 : >"\$1/gordon-$SHA/main.go"
56 exit 0
57 fi
58 shift
59 done
60 ;;
61esac
62EOF
63 cat >"$case_dir/bin/go" <<EOF
64#!/bin/sh
65printf '%s\n' "GOOS=\${GOOS-} GOARCH=\${GOARCH-} GOTOOLCHAIN=\${GOTOOLCHAIN-} \$*" >>'$case_dir/go.log'
66if [ "\$1 \$2" = 'env GOVERSION' ]; then echo go1.27.1; exit 0; fi
67if [ "\$1" = build ]; then
68 while [ "\$#" -gt 0 ]; do [ "\$1" = -o ] && { shift; : >"\$1"; chmod 755 "\$1"; exit 0; }; shift; done
69fi
70exit 91
71EOF
72 cat >"$case_dir/bin/install" <<EOF
73#!/bin/sh
74printf '%s\n' "\$*" >>'$case_dir/install.log'
75cp "\$3" "\$4"
76EOF
77 cat >"$case_dir/bin/mv" <<EOF
78#!/bin/sh
79printf '%s\n' "\$*" >>'$case_dir/mv.log'
80[ "\$1" = -f ] && shift
81/bin/mv "\$1" "\$2"
82EOF
83 chmod +x "$case_dir/bin/"*
84 mkdir -p "$case_dir/tmp"
85
86 if (PATH="$case_dir/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=0 "$@") >"$case_dir/out" 2>&1; then
87 status=0
88 else
89 status=$?
90 fi
91}
92
93test_next_build() {
94 run_case next-build env GORDON_CHANNEL=next sh "$ROOT/install.sh"
95 [ "$status" -eq 0 ] || { cat "$case_dir/out"; fail "next channel builds pinned source"; return; }
96 if grep -q "commits/next" "$case_dir/curl.log" &&
97 grep -q "codeload.github.com/bnema/gordon/tar.gz/$SHA" "$case_dir/curl.log" &&
98 grep -q -- "-X main.version=next-$SHA" "$case_dir/go.log" &&
99 grep -q "GOOS=linux GOARCH=amd64 GOTOOLCHAIN=local" "$case_dir/go.log" &&
100 grep -q "UNVERIFIED DEVELOPMENT BUILD" "$case_dir/out" &&
101 [ -x "$case_dir/home/.local/bin/gordon" ]; then
102 pass "next channel builds pinned source"
103 else
104 fail "next channel builds pinned source"
105 fi
106}
107
108test_conflicts() {
109 run_case version-conflict env GORDON_CHANNEL=next GORDON_VERSION=v1.2.3 sh "$ROOT/install.sh"
110 if [ "$status" -ne 0 ] && grep -q "GORDON_VERSION cannot be used" "$case_dir/out"; then
111 pass "next rejects GORDON_VERSION"
112 else
113 fail "next rejects GORDON_VERSION"
114 fi
115
116 run_case prerelease-conflict env GORDON_CHANNEL=next GORDON_PRERELEASE=1 sh "$ROOT/install.sh"
117 if [ "$status" -ne 0 ] && grep -q "GORDON_PRERELEASE cannot be used" "$case_dir/out"; then
118 pass "next rejects GORDON_PRERELEASE"
119 else
120 fail "next rejects GORDON_PRERELEASE"
121 fi
122}
123
124test_go_version() {
125 run_case old-go env GORDON_CHANNEL=next MOCK_GO_VERSION=unused sh "$ROOT/install.sh"
126 # Replace the mock after setup, then run once more in the same fixture.
127 cat >"$case_dir/bin/go" <<'EOF'
128#!/bin/sh
129[ "$1 $2" = 'env GOVERSION' ] && { echo go1.26.9; exit 0; }
130exit 91
131EOF
132 chmod +x "$case_dir/bin/go"
133 if (PATH="$case_dir/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=0 GORDON_CHANNEL=next sh "$ROOT/install.sh") >"$case_dir/out" 2>&1; then status=0; else status=$?; fi
134 if [ "$status" -ne 0 ] && grep -q "requires Go 1.27 or newer" "$case_dir/out"; then
135 pass "next enforces go.mod Go version"
136 else
137 fail "next enforces go.mod Go version"
138 fi
139}
140
141test_default_and_path_modes() {
142 run_case default-dir env GORDON_CHANNEL=next sh "$ROOT/install.sh"
143 if [ "$status" -eq 0 ] && [ -x "$case_dir/home/.local/bin/gordon" ] &&
144 grep -q "export PATH='${case_dir}/home/.local/bin':\$PATH" "$case_dir/out"; then
145 pass "default install is user-local and prints current-shell instruction"
146 else
147 fail "default install is user-local and prints current-shell instruction"
148 fi
149
150 run_case path-present env GORDON_CHANNEL=next sh "$ROOT/install.sh"
151 if (PATH="$case_dir/bin:$case_dir/home/.local/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=0 GORDON_CHANNEL=next sh "$ROOT/install.sh") >"$case_dir/out" 2>&1; then status=0; else status=$?; fi
152 if [ "$status" -eq 0 ] && ! grep -q "Add Gordon to the current shell" "$case_dir/out"; then
153 pass "existing PATH needs no update"
154 else
155 fail "existing PATH needs no update"
156 fi
157
158 run_case forced-no env GORDON_CHANNEL=next GORDON_UPDATE_PATH=0 sh "$ROOT/install.sh"
159 if [ "$status" -eq 0 ] && [ ! -e "$case_dir/home/.bashrc" ]; then
160 pass "forced no does not modify shell config"
161 else
162 fail "forced no does not modify shell config"
163 fi
164
165 run_case invalid-update env GORDON_CHANNEL=next GORDON_UPDATE_PATH=maybe sh "$ROOT/install.sh"
166 if [ "$status" -ne 0 ] && grep -q "must be 0 or 1" "$case_dir/out"; then
167 pass "invalid PATH mode is rejected"
168 else
169 fail "invalid PATH mode is rejected"
170 fi
171
172 run_case unsafe-dir env GORDON_CHANNEL=next GORDON_INSTALL_DIR="/tmp/unsafe
173path" sh "$ROOT/install.sh"
174 if [ "$status" -ne 0 ] && grep -q "unsafe control character" "$case_dir/out"; then
175 pass "control characters in install directory are rejected"
176 else
177 fail "control characters in install directory are rejected"
178 fi
179
180 run_case trailing-newline env GORDON_CHANNEL=next GORDON_INSTALL_DIR="/tmp/unsafe
181" sh "$ROOT/install.sh"
182 if [ "$status" -ne 0 ] && grep -q "unsafe control character" "$case_dir/out"; then
183 pass "trailing newline in install directory is rejected"
184 else
185 fail "trailing newline in install directory is rejected"
186 fi
187}
188
189test_shell_updates() {
190 for shell in bash zsh fish; do
191 run_case "shell-$shell" env GORDON_CHANNEL=next GORDON_UPDATE_PATH=1 SHELL="/usr/bin/$shell" sh "$ROOT/install.sh"
192 case "$shell" in
193 bash) config=$case_dir/home/.bashrc; expected="export PATH='${case_dir}/home/.local/bin':\$PATH" ;;
194 zsh) config=$case_dir/home/.zshrc; expected="export PATH='${case_dir}/home/.local/bin':\$PATH" ;;
195 fish) config=$case_dir/home/.config/fish/config.fish; expected="fish_add_path '${case_dir}/home/.local/bin'" ;;
196 esac
197 if [ "$status" -eq 0 ] && grep -Fq "$expected" "$config"; then
198 pass "$shell PATH update"
199 else
200 fail "$shell PATH update"
201 fi
202 done
203
204 run_case idempotent env GORDON_CHANNEL=next GORDON_UPDATE_PATH=1 SHELL=/bin/bash sh "$ROOT/install.sh"
205 first_case=$case_dir
206 if (PATH="$case_dir/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=1 GORDON_CHANNEL=next sh "$ROOT/install.sh") >>"$case_dir/out" 2>&1 &&
207 [ "$(grep -c '^# >>> Gordon installer PATH >>>$' "$case_dir/home/.bashrc")" -eq 1 ]; then
208 pass "PATH marker block is idempotent"
209 else
210 cat "$first_case/out"
211 fail "PATH marker block is idempotent"
212 fi
213
214 run_case unsupported env GORDON_CHANNEL=next GORDON_UPDATE_PATH=1 SHELL=/bin/ksh sh "$ROOT/install.sh"
215 if [ "$status" -eq 0 ] && grep -q "unsupported or missing SHELL" "$case_dir/out"; then
216 pass "unsupported shell gets manual instructions"
217 else
218 fail "unsupported shell gets manual instructions"
219 fi
220}
221
222test_explicit_global_override() {
223 run_case home-bin env GORDON_CHANNEL=next sh "$ROOT/install.sh"
224 if (PATH="$case_dir/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=1 GORDON_INSTALL_DIR="$case_dir/home/bin" GORDON_CHANNEL=next sh "$ROOT/install.sh") >"$case_dir/out" 2>&1; then status=0; else status=$?; fi
225 if [ "$status" -eq 0 ] && [ -x "$case_dir/home/bin/gordon" ] &&
226 grep -Fq "export PATH='${case_dir}/home/bin':\$PATH" "$case_dir/home/.bashrc"; then
227 pass "HOME bin override is installed and added to PATH"
228 else
229 fail "HOME bin override is installed and added to PATH"
230 fi
231
232 run_case global env GORDON_CHANNEL=next sh "$ROOT/install.sh"
233 if (PATH="$case_dir/bin:/usr/bin:/bin" HOME="$case_dir/home" SHELL=/bin/bash TMPDIR="$case_dir/tmp" GORDON_UPDATE_PATH=0 GORDON_INSTALL_DIR="$case_dir/install" GORDON_CHANNEL=next sh "$ROOT/install.sh") >"$case_dir/out" 2>&1; then status=0; else status=$?; fi
234 if [ "$status" -eq 0 ] && [ -x "$case_dir/install/gordon" ]; then
235 pass "explicit global install override is preserved"
236 else
237 fail "explicit global install override is preserved"
238 fi
239
240 run_case sudo-default env SUDO_USER=test-user GORDON_CHANNEL=next sh "$ROOT/install.sh"
241 if [ "$status" -ne 0 ] && grep -q "Refusing a default user-local install" "$case_dir/out" && [ ! -e "$case_dir/home/.local/bin/gordon" ]; then
242 pass "sudo default install is refused"
243 else
244 fail "sudo default install is refused"
245 fi
246}
247
248test_stable_latest_without_api() {
249 run_case stable-latest env sh "$ROOT/install.sh"
250 # The mock refuses the checksum download; resolution is what is tested.
251 if grep -q "Resolved version: v9.9.9" "$case_dir/out" &&
252 grep -q "git.bnema.dev/gordon/releases/download/v9.9.9/checksums.txt" "$case_dir/curl.log" &&
253 ! grep -q "/-/github/api" "$case_dir/curl.log" &&
254 ! grep -q "api.github.com" "$case_dir/curl.log"; then
255 pass "stable latest resolves from the release redirect without the API"
256 else
257 cat "$case_dir/out"
258 fail "stable latest resolves from the release redirect without the API"
259 fi
260
261 run_case stable-latest-bad env MOCK_LATEST_URL='https://git.bnema.dev/gordon/releases' sh "$ROOT/install.sh"
262 if [ "$status" -ne 0 ] && grep -q "Could not determine latest stable version" "$case_dir/out" &&
263 ! grep -q "releases/download" "$case_dir/curl.log"; then
264 pass "unexpected latest redirect is rejected"
265 else
266 cat "$case_dir/out"
267 fail "unexpected latest redirect is rejected"
268 fi
269
270 run_case stable-latest-unsafe env MOCK_LATEST_URL='https://git.bnema.dev/gordon/releases/tag/v1.0.0;rm' sh "$ROOT/install.sh"
271 if [ "$status" -ne 0 ] && grep -q "Could not determine latest stable version" "$case_dir/out"; then
272 pass "unsafe latest tag is rejected"
273 else
274 cat "$case_dir/out"
275 fail "unsafe latest tag is rejected"
276 fi
277}
278
279test_stable_latest_without_api
280test_next_build
281test_conflicts
282test_go_version
283test_default_and_path_modes
284test_shell_updates
285test_explicit_global_override
286printf '%s passed, %s failed\n' "$PASS" "$FAIL"
287[ "$FAIL" -eq 0 ]