Skip to content

Commit 62c2e53

Browse files
committed
Adds smoke-test and github action to run it
1 parent 8f467a5 commit 62c2e53

2 files changed

Lines changed: 320 additions & 0 deletions

File tree

.github/scripts/run-smoke.sh

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Assumptions:
5+
# - Frontend has already been built (vite build) if needed
6+
# - 'go run server.go' launches the application
7+
# - Success condition: HTTPS port responds with any content OR log line 'Starting API services...'
8+
9+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
10+
LOG_DIR="$ROOT_DIR/smoke-logs"
11+
mkdir -p "$LOG_DIR"
12+
LOG_FILE="$LOG_DIR/server.log"
13+
RESULT_FILE="$LOG_DIR/result.txt"
14+
STATUS_JSON="$LOG_DIR/status.json"
15+
PORT="8443"
16+
TIMEOUT_SECONDS=${SMOKE_TIMEOUT_SECONDS:-45}
17+
GRACE_KILL_SECONDS=5
18+
19+
# Start server in background
20+
(
21+
cd "$ROOT_DIR"
22+
echo "[smoke] launching server..." | tee -a "$LOG_FILE"
23+
# Disable long-running / heavy operations if possible via env flags
24+
export SKIP_STEAMCMD=true
25+
export LOG_LEVEL=20 # INFO
26+
# Run server
27+
go run server.go 2>&1 | tee -a "$LOG_FILE" &
28+
echo $! > "$LOG_DIR/server.pid"
29+
) || true
30+
31+
PID=$(cat "$LOG_DIR/server.pid" 2>/dev/null || echo "")
32+
if [[ -z "$PID" ]]; then
33+
echo "{\"status\":\"failed\",\"reason\":\"No PID captured\"}" > "$STATUS_JSON"
34+
exit 0
35+
fi
36+
37+
echo "[smoke] server pid: $PID"
38+
39+
# Wait for readiness
40+
START_TIME=$(date +%s)
41+
READY=0
42+
while true; do
43+
NOW=$(date +%s)
44+
ELAPSED=$((NOW-START_TIME))
45+
if (( ELAPSED > TIMEOUT_SECONDS )); then
46+
echo "[smoke] timeout after $ELAPSED seconds" | tee -a "$LOG_FILE"
47+
break
48+
fi
49+
# Check log line
50+
if grep -q "Starting API services" "$LOG_FILE"; then
51+
READY=1
52+
echo "[smoke] readiness via log line" | tee -a "$LOG_FILE"
53+
break
54+
fi
55+
# Try HTTPS curl (ignore cert issues)
56+
if curl -sk --max-time 2 https://localhost:$PORT/ >/dev/null 2>&1; then
57+
READY=1
58+
echo "[smoke] readiness via https response" | tee -a "$LOG_FILE"
59+
break
60+
fi
61+
sleep 2
62+
done
63+
64+
if [[ $READY -eq 1 ]]; then
65+
STATUS="passed"
66+
else
67+
STATUS="failed"
68+
fi
69+
70+
echo "[smoke] stopping server (status=$STATUS)" | tee -a "$LOG_FILE"
71+
if kill "$PID" 2>/dev/null; then
72+
# allow graceful shutdown
73+
for i in $(seq 1 $GRACE_KILL_SECONDS); do
74+
if kill -0 "$PID" 2>/dev/null; then
75+
sleep 1
76+
else
77+
break
78+
fi
79+
done
80+
if kill -0 "$PID" 2>/dev/null; then
81+
echo "[smoke] force killing $PID" | tee -a "$LOG_FILE"
82+
kill -9 "$PID" || true
83+
fi
84+
fi
85+
86+
echo "$STATUS" > "$RESULT_FILE"
87+
cat > "$STATUS_JSON" <<EOF
88+
{ "status": "$STATUS", "timeoutSeconds": $TIMEOUT_SECONDS }
89+
EOF
90+
91+
echo "[smoke] done" | tee -a "$LOG_FILE"
92+
93+
# Always exit 0 so workflow can collect artifacts; status is in files.
94+
exit 0
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
name: PR Cross-OS Smoke Test
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
11+
jobs:
12+
smoke:
13+
name: Smoke (Runner ${{ matrix.os }})
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest, windows-latest]
19+
env:
20+
CGO_ENABLED: 0
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Setup Go
26+
uses: actions/setup-go@v5
27+
with:
28+
go-version: '1.22'
29+
check-latest: true
30+
31+
- name: Setup Node
32+
uses: actions/setup-node@v4
33+
with:
34+
node-version: '20'
35+
cache: 'npm'
36+
cache-dependency-path: 'frontend/package-lock.json'
37+
continue-on-error: true
38+
39+
- name: Install frontend deps
40+
working-directory: frontend
41+
run: |
42+
if [ -f package.json ]; then npm ci || npm install; fi
43+
shell: bash
44+
45+
- name: Build frontend
46+
working-directory: frontend
47+
run: |
48+
if [ -f package.json ]; then npm run build || echo 'frontend build failed (continuing)'; fi
49+
shell: bash
50+
51+
- name: Smoke test
52+
run: bash ./.github/scripts/run-smoke.sh
53+
shell: bash
54+
55+
- name: Collect results
56+
if: always()
57+
id: collect
58+
run: |
59+
echo "status=$(cat smoke-logs/result.txt 2>/dev/null || echo 'missing')" >> $GITHUB_OUTPUT
60+
echo "status_json=$(cat smoke-logs/status.json 2>/dev/null || echo '{}')" >> $GITHUB_OUTPUT
61+
shell: bash
62+
63+
- name: Upload logs
64+
if: always()
65+
uses: actions/upload-artifact@v4
66+
with:
67+
name: smoke-${{ matrix.os }}
68+
path: |
69+
smoke-logs/server.log
70+
smoke-logs/status.json
71+
smoke-logs/result.txt
72+
if-no-files-found: warn
73+
74+
smoke-linux-distros:
75+
name: Smoke (Container ${{ matrix.distro }})
76+
runs-on: ubuntu-latest
77+
strategy:
78+
fail-fast: false
79+
matrix:
80+
include:
81+
- distro: debian
82+
image: debian:trixie
83+
pkg_update: apt-get update
84+
install: apt-get install -y --no-install-recommends ca-certificates curl git build-essential wget nodejs npm bash
85+
needs_go: true
86+
- distro: arch
87+
image: archlinux:base
88+
pkg_update: pacman -Sy --noconfirm
89+
install: pacman -S --noconfirm ca-certificates curl git wget tar nodejs npm go base-devel bash
90+
needs_go: false
91+
container:
92+
image: ${{ matrix.image }}
93+
env:
94+
GO_VERSION: '1.22.6'
95+
CGO_ENABLED: 0
96+
steps:
97+
- name: Prepare packages
98+
run: |
99+
set -e
100+
${{ matrix.pkg_update }}
101+
${{ matrix.install }}
102+
- name: Install Go (if needed)
103+
if: matrix.needs_go == true
104+
run: |
105+
set -e
106+
ARCH=$(uname -m)
107+
case "$ARCH" in
108+
x86_64) GOARCH=amd64 ;;
109+
aarch64) GOARCH=arm64 ;;
110+
*) echo "Unsupported arch: $ARCH"; exit 1 ;;
111+
esac
112+
curl -fsSL https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz -o /tmp/go.tgz
113+
rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go.tgz
114+
echo "/usr/local/go/bin" >> $GITHUB_PATH
115+
/usr/local/go/bin/go version
116+
- name: Add Go to PATH (arch already has go)
117+
if: matrix.needs_go == false
118+
run: which go || echo "go not found" || true
119+
- name: Checkout
120+
uses: actions/checkout@v4
121+
- name: Node version
122+
run: node --version && npm --version || true
123+
- name: Install frontend deps
124+
working-directory: frontend
125+
run: |
126+
if [ -f package.json ]; then npm ci || npm install; fi
127+
- name: Build frontend
128+
working-directory: frontend
129+
run: |
130+
if [ -f package.json ]; then npm run build || echo 'frontend build failed (continuing)'; fi
131+
- name: Run smoke test
132+
run: bash ./.github/scripts/run-smoke.sh
133+
- name: Collect results
134+
if: always()
135+
id: collect
136+
run: |
137+
echo "status=$(cat smoke-logs/result.txt 2>/dev/null || echo 'missing')" >> $GITHUB_OUTPUT
138+
echo "status_json=$(cat smoke-logs/status.json 2>/dev/null || echo '{}')" >> $GITHUB_OUTPUT
139+
- name: Upload logs
140+
if: always()
141+
uses: actions/upload-artifact@v4
142+
with:
143+
name: smoke-${{ matrix.distro }}
144+
path: |
145+
smoke-logs/server.log
146+
smoke-logs/status.json
147+
smoke-logs/result.txt
148+
if-no-files-found: warn
149+
150+
summarize:
151+
name: Summarize & Comment
152+
needs: [smoke, smoke-linux-distros]
153+
runs-on: ubuntu-latest
154+
permissions:
155+
pull-requests: write
156+
contents: read
157+
steps:
158+
- name: Download artifacts
159+
uses: actions/download-artifact@v4
160+
with:
161+
path: artifacts
162+
163+
- name: Build summary
164+
id: summary
165+
run: |
166+
set -e
167+
echo '### Cross-OS Smoke Test Results' > summary.md
168+
echo '' >> summary.md
169+
STATUS_TABLE="| OS | Status | Details |\n|----|--------|---------|"
170+
for dir in artifacts/*; do
171+
osname=$(basename "$dir" | sed 's/^smoke-//')
172+
resultFile=$(find "$dir" -name result.txt -maxdepth 2 -type f 2>/dev/null | head -n1)
173+
logFile=$(find "$dir" -name server.log -maxdepth 2 -type f 2>/dev/null | head -n1)
174+
status="missing"
175+
if [ -f "$resultFile" ]; then status=$(cat "$resultFile"); fi
176+
detailsLink="(logs unavailable)"
177+
if [ -f "$logFile" ]; then
178+
head -n5 "$logFile" > "$dir/head.txt" || true
179+
tail -n5 "$logFile" > "$dir/tail.txt" || true
180+
detailsLink="See below"
181+
fi
182+
STATUS_TABLE+="\n| $osname | $status | $detailsLink |"
183+
done
184+
echo "$STATUS_TABLE" >> summary.md
185+
echo '' >> summary.md
186+
echo '<details><summary>Log excerpts</summary>' >> summary.md
187+
for dir in artifacts/*; do
188+
osname=$(basename "$dir" | sed 's/^smoke-//')
189+
logFile=$(find "$dir" -name server.log -maxdepth 2 -type f 2>/dev/null | head -n1)
190+
if [ -f "$logFile" ]; then
191+
echo "\n#### $osname" >> summary.md
192+
echo '\n<details><summary>First 5 lines</summary>' >> summary.md
193+
echo '\n```' >> summary.md
194+
cat "$dir/head.txt" >> summary.md
195+
echo '```' >> summary.md
196+
echo '</details>' >> summary.md
197+
echo '\n<details><summary>Last 5 lines</summary>' >> summary.md
198+
echo '\n```' >> summary.md
199+
cat "$dir/tail.txt" >> summary.md
200+
echo '```' >> summary.md
201+
echo '</details>' >> summary.md
202+
fi
203+
done
204+
echo '\n</details>' >> summary.md
205+
SUMMARY_BODY=$(cat summary.md)
206+
SUMMARY_BODY_ESCAPED=$(printf '%s' "$SUMMARY_BODY" | sed -e ':a' -e 'N' -e '$!ba' -e 's/%/%25/g' -e 's/\n/%0A/g' -e 's/\r/%0D/g')
207+
echo "body=$SUMMARY_BODY_ESCAPED" >> $GITHUB_OUTPUT
208+
209+
- name: Create or update PR comment
210+
uses: actions/github-script@v7
211+
with:
212+
script: |
213+
const markerStart = '<!-- smoke-test-report-start -->';
214+
const markerEnd = '<!-- smoke-test-report-end -->';
215+
const body = process.env.SUMMARY_BODY || `\n${{ steps.summary.outputs.body }}`;
216+
const {owner, repo, number} = context.issue;
217+
const { data: comments } = await github.rest.issues.listComments({owner, repo, issue_number: number, per_page: 100});
218+
const existing = comments.find(c => c.body && c.body.includes(markerStart));
219+
const finalBody = `${markerStart}\n${body}\n${markerEnd}`;
220+
if (existing) {
221+
await github.rest.issues.updateComment({owner, repo, comment_id: existing.id, body: finalBody});
222+
} else {
223+
await github.rest.issues.createComment({owner, repo, issue_number: number, body: finalBody});
224+
}
225+
env:
226+
SUMMARY_BODY: ${{ steps.summary.outputs.body }}

0 commit comments

Comments
 (0)