Skip to content

Commit a519072

Browse files
nadilasclaude
andcommitted
feat: add TinyGo stdlib audit script
Create scripts/audit-tinygo-stdlib.sh that: - Iterates over 20 pkg_* fixture directories - Runs tinygo build -target=wasip2 on each - Captures pass/fail/partial status with error details - Generates compat-db/tinygo-stdlib.json (machine-readable) - Generates compat-db/tinygo-stdlib-compat-table.md (--table mode) - Supports --build, --table, --json, --test, --all modes - Follows patterns from audit-componentize-js-warpgrid.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0135b95 commit a519072

File tree

1 file changed

+389
-0
lines changed

1 file changed

+389
-0
lines changed

scripts/audit-tinygo-stdlib.sh

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
1+
#!/usr/bin/env bash
2+
# audit-tinygo-stdlib.sh — Audit Go stdlib package compatibility with TinyGo wasip2
3+
#
4+
# US-302: Audit Go stdlib compatibility for wasip2 target
5+
#
6+
# Usage:
7+
# scripts/audit-tinygo-stdlib.sh [--build|--table|--json|--test|--all]
8+
#
9+
# Modes:
10+
# --build Run TinyGo wasip2 builds for all 20 stdlib packages
11+
# --table Generate human-readable markdown compatibility table
12+
# --json Print compatibility results as JSON
13+
# --test Run Go test suite (validates file structure + JSON schema)
14+
# --all Run build + table + test (default)
15+
#
16+
# Exit codes:
17+
# 0 — Success (individual package failures are expected and recorded)
18+
# 1 — Script error or prerequisites missing
19+
# 2 — Go tests failed
20+
21+
set -euo pipefail
22+
23+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
25+
FIXTURE_DIR="${PROJECT_ROOT}/tests/fixtures/go-stdlib-compat"
26+
COMPAT_DB="${PROJECT_ROOT}/compat-db"
27+
JSON_OUTPUT="${COMPAT_DB}/tinygo-stdlib.json"
28+
TABLE_OUTPUT="${COMPAT_DB}/tinygo-stdlib-compat-table.md"
29+
TINYGO_BIN="${PROJECT_ROOT}/build/tinygo/bin/tinygo"
30+
TMP_DIR="/tmp/tinygo-stdlib-audit"
31+
32+
MODE="${1:---all}"
33+
34+
log() { echo "==> $*" >&2; }
35+
err() { echo "ERROR: $*" >&2; }
36+
37+
# Map directory name to Go import path
38+
dir_to_import_path() {
39+
local dir="$1"
40+
case "${dir}" in
41+
pkg_fmt) echo "fmt" ;;
42+
pkg_strings) echo "strings" ;;
43+
pkg_strconv) echo "strconv" ;;
44+
pkg_encoding_json) echo "encoding/json" ;;
45+
pkg_encoding_base64) echo "encoding/base64" ;;
46+
pkg_crypto_sha256) echo "crypto/sha256" ;;
47+
pkg_crypto_tls) echo "crypto/tls" ;;
48+
pkg_math) echo "math" ;;
49+
pkg_sort) echo "sort" ;;
50+
pkg_bytes) echo "bytes" ;;
51+
pkg_io) echo "io" ;;
52+
pkg_os) echo "os" ;;
53+
pkg_net) echo "net" ;;
54+
pkg_net_http) echo "net/http" ;;
55+
pkg_database_sql) echo "database/sql" ;;
56+
pkg_context) echo "context" ;;
57+
pkg_sync) echo "sync" ;;
58+
pkg_time) echo "time" ;;
59+
pkg_regexp) echo "regexp" ;;
60+
pkg_log) echo "log" ;;
61+
*) echo "${dir}" ;;
62+
esac
63+
}
64+
65+
# Map directory name to human-friendly package name
66+
dir_to_name() {
67+
local dir="$1"
68+
dir_to_import_path "${dir}"
69+
}
70+
71+
find_tinygo() {
72+
if [ -x "${TINYGO_BIN}" ]; then
73+
echo "${TINYGO_BIN}"
74+
elif command -v tinygo &>/dev/null; then
75+
command -v tinygo
76+
else
77+
echo ""
78+
fi
79+
}
80+
81+
get_tinygo_version() {
82+
local bin="$1"
83+
"${bin}" version 2>&1 | head -1 || echo "unknown"
84+
}
85+
86+
# Escape a string for safe JSON embedding
87+
json_escape() {
88+
local s="$1"
89+
# Use python if available, otherwise sed
90+
if command -v python3 &>/dev/null; then
91+
python3 -c "import json,sys; print(json.dumps(sys.stdin.read()), end='')" <<< "${s}"
92+
else
93+
# Basic escaping: backslash, quotes, newlines
94+
echo -n "\"$(echo "${s}" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')\""
95+
fi
96+
}
97+
98+
run_build() {
99+
local tinygo_bin
100+
tinygo_bin="$(find_tinygo)"
101+
102+
if [ -z "${tinygo_bin}" ]; then
103+
err "TinyGo not found. Install via: scripts/build-tinygo.sh"
104+
err "Looked in: ${TINYGO_BIN} and system PATH"
105+
exit 1
106+
fi
107+
108+
local tinygo_version
109+
tinygo_version="$(get_tinygo_version "${tinygo_bin}")"
110+
local go_version
111+
go_version="$(go version 2>&1 | sed 's/go version go//' | cut -d' ' -f1)"
112+
local tested_at
113+
tested_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
114+
115+
log "TinyGo: ${tinygo_version}"
116+
log "Go: ${go_version}"
117+
log "Target: wasip2"
118+
log "Fixtures: ${FIXTURE_DIR}"
119+
echo ""
120+
121+
mkdir -p "${TMP_DIR}" "${COMPAT_DB}"
122+
123+
# Collect results as JSON array entries
124+
local packages_json=""
125+
local pkg_count=0
126+
local pass_count=0
127+
local fail_count=0
128+
local partial_count=0
129+
130+
# Sort directories for deterministic output
131+
for pkg_dir in $(find "${FIXTURE_DIR}" -maxdepth 1 -type d -name 'pkg_*' | sort); do
132+
local dir_name
133+
dir_name="$(basename "${pkg_dir}")"
134+
local import_path
135+
import_path="$(dir_to_import_path "${dir_name}")"
136+
local pkg_name
137+
pkg_name="$(dir_to_name "${dir_name}")"
138+
139+
pkg_count=$((pkg_count + 1))
140+
local wasm_out="${TMP_DIR}/${dir_name}.wasm"
141+
142+
printf " [%2d/20] %-20s " "${pkg_count}" "${pkg_name}"
143+
144+
# Run TinyGo build, capture stderr
145+
local compile_output=""
146+
local compile_status=""
147+
local exit_code=0
148+
149+
compile_output=$("${tinygo_bin}" build -target=wasip2 -o "${wasm_out}" "${pkg_dir}/main.go" 2>&1) || exit_code=$?
150+
151+
if [ ${exit_code} -eq 0 ]; then
152+
compile_status="pass"
153+
pass_count=$((pass_count + 1))
154+
echo "PASS"
155+
else
156+
# Check if it's a partial failure (compiled but with warnings)
157+
if [ -f "${wasm_out}" ]; then
158+
compile_status="partial"
159+
partial_count=$((partial_count + 1))
160+
echo "PARTIAL"
161+
else
162+
compile_status="fail"
163+
fail_count=$((fail_count + 1))
164+
echo "FAIL"
165+
fi
166+
fi
167+
168+
# Extract error details
169+
local errors_json="[]"
170+
local error_count=0
171+
local missing_symbols_json="[]"
172+
local notes=""
173+
174+
if [ "${compile_status}" != "pass" ] && [ -n "${compile_output}" ]; then
175+
# Count error lines
176+
error_count=$(echo "${compile_output}" | grep -c 'error:\|Error\|cannot\|undefined\|not declared\|missing' 2>/dev/null || echo "0")
177+
178+
# Collect individual error messages (first 20 lines)
179+
local error_lines=""
180+
while IFS= read -r line; do
181+
if [ -n "${line}" ]; then
182+
local escaped
183+
escaped=$(echo "${line}" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/ /g')
184+
if [ -n "${error_lines}" ]; then
185+
error_lines="${error_lines}, "
186+
fi
187+
error_lines="${error_lines}\"${escaped}\""
188+
fi
189+
done < <(echo "${compile_output}" | head -20)
190+
if [ -n "${error_lines}" ]; then
191+
errors_json="[${error_lines}]"
192+
fi
193+
194+
# Extract missing/undefined symbols
195+
local symbols=""
196+
while IFS= read -r sym; do
197+
if [ -n "${sym}" ]; then
198+
local escaped_sym
199+
escaped_sym=$(echo "${sym}" | sed 's/\\/\\\\/g; s/"/\\"/g')
200+
if [ -n "${symbols}" ]; then
201+
symbols="${symbols}, "
202+
fi
203+
symbols="${symbols}\"${escaped_sym}\""
204+
fi
205+
done < <(echo "${compile_output}" | grep -oP 'undefined: \K[a-zA-Z0-9_.]+' 2>/dev/null || true)
206+
if [ -n "${symbols}" ]; then
207+
missing_symbols_json="[${symbols}]"
208+
fi
209+
210+
notes="TinyGo wasip2 compilation failed with ${error_count} error(s)"
211+
else
212+
notes="Compiles successfully with TinyGo wasip2"
213+
fi
214+
215+
# Build JSON entry
216+
local entry
217+
entry=$(cat <<ENTRYEOF
218+
{
219+
"name": "${pkg_name}",
220+
"importPath": "${import_path}",
221+
"compileStatus": "${compile_status}",
222+
"errors": ${errors_json},
223+
"errorCount": ${error_count},
224+
"missingSymbols": ${missing_symbols_json},
225+
"notes": "${notes}"
226+
}
227+
ENTRYEOF
228+
)
229+
230+
if [ -n "${packages_json}" ]; then
231+
packages_json="${packages_json},
232+
${entry}"
233+
else
234+
packages_json="${entry}"
235+
fi
236+
done
237+
238+
# Extract compiler version number
239+
local compiler_version
240+
compiler_version=$(echo "${tinygo_version}" | grep -oP '\d+\.\d+\.\d+' | head -1 || echo "unknown")
241+
242+
# Write final JSON
243+
cat > "${JSON_OUTPUT}" <<JSONEOF
244+
{
245+
"compiler": "tinygo",
246+
"compilerVersion": "${compiler_version}",
247+
"target": "wasip2",
248+
"testedAt": "${tested_at}",
249+
"goVersion": "${go_version}",
250+
"userStory": "US-302",
251+
"packageCount": ${pkg_count},
252+
"packages": [
253+
${packages_json}
254+
]
255+
}
256+
JSONEOF
257+
258+
echo ""
259+
log "Results written to ${JSON_OUTPUT}"
260+
log "Summary: ${pass_count} pass, ${fail_count} fail, ${partial_count} partial (${pkg_count} total)"
261+
262+
# Validate count
263+
if [ "${pkg_count}" -ne 20 ]; then
264+
err "Expected 20 packages, found ${pkg_count}"
265+
exit 1
266+
fi
267+
268+
# Clean up temp files
269+
rm -rf "${TMP_DIR}"
270+
}
271+
272+
generate_table() {
273+
if [ ! -f "${JSON_OUTPUT}" ]; then
274+
err "JSON results not found: ${JSON_OUTPUT}"
275+
err "Run with --build first to generate results."
276+
exit 1
277+
fi
278+
279+
log "Generating compatibility table..."
280+
281+
# Use jq to generate the markdown table
282+
if ! command -v jq &>/dev/null; then
283+
err "jq not found. Install jq to generate markdown tables."
284+
exit 1
285+
fi
286+
287+
local compiler_version target tested_at go_version pkg_count
288+
compiler_version=$(jq -r '.compilerVersion' "${JSON_OUTPUT}")
289+
target=$(jq -r '.target' "${JSON_OUTPUT}")
290+
tested_at=$(jq -r '.testedAt' "${JSON_OUTPUT}")
291+
go_version=$(jq -r '.goVersion' "${JSON_OUTPUT}")
292+
pkg_count=$(jq -r '.packageCount' "${JSON_OUTPUT}")
293+
294+
local pass_count fail_count partial_count
295+
pass_count=$(jq '[.packages[] | select(.compileStatus == "pass")] | length' "${JSON_OUTPUT}")
296+
fail_count=$(jq '[.packages[] | select(.compileStatus == "fail")] | length' "${JSON_OUTPUT}")
297+
partial_count=$(jq '[.packages[] | select(.compileStatus == "partial")] | length' "${JSON_OUTPUT}")
298+
299+
{
300+
echo "# TinyGo wasip2 Standard Library Compatibility"
301+
echo ""
302+
echo "**Compiler**: TinyGo ${compiler_version}"
303+
echo "**Target**: ${target}"
304+
echo "**Go Version**: ${go_version}"
305+
echo "**Tested**: ${tested_at}"
306+
echo "**User Story**: US-302"
307+
echo ""
308+
echo "## Summary"
309+
echo ""
310+
echo "- **Pass**: ${pass_count}/${pkg_count} packages compile successfully"
311+
echo "- **Fail**: ${fail_count}/${pkg_count} packages fail to compile"
312+
echo "- **Partial**: ${partial_count}/${pkg_count} packages compile with warnings"
313+
echo ""
314+
echo "## Compatibility Table"
315+
echo ""
316+
echo "| Package | Import Path | Status | Errors | Notes |"
317+
echo "|---------|-------------|--------|--------|-------|"
318+
319+
# Generate table rows using jq
320+
jq -r '.packages[] | "| \(.name) | \(.importPath) | \(.compileStatus) | \(.errorCount) | \(.notes) |"' "${JSON_OUTPUT}"
321+
322+
echo ""
323+
echo "---"
324+
echo ""
325+
echo "*Generated by \`scripts/audit-tinygo-stdlib.sh --table\`*"
326+
} > "${TABLE_OUTPUT}"
327+
328+
log "Table written to ${TABLE_OUTPUT}"
329+
}
330+
331+
show_json() {
332+
if [ ! -f "${JSON_OUTPUT}" ]; then
333+
err "JSON results not found: ${JSON_OUTPUT}"
334+
err "Run with --build first to generate results."
335+
exit 1
336+
fi
337+
338+
if command -v jq &>/dev/null; then
339+
jq '.' "${JSON_OUTPUT}"
340+
else
341+
cat "${JSON_OUTPUT}"
342+
fi
343+
}
344+
345+
run_tests() {
346+
log "Running Go test suite..."
347+
348+
cd "${FIXTURE_DIR}"
349+
350+
if ! go vet ./... 2>&1; then
351+
err "go vet failed"
352+
exit 2
353+
fi
354+
355+
if ! go test -v -count=1 -timeout=120s . 2>&1; then
356+
err "Go tests failed"
357+
exit 2
358+
fi
359+
360+
log "All Go tests passed."
361+
}
362+
363+
case "${MODE}" in
364+
--build) run_build ;;
365+
--table) generate_table ;;
366+
--json) show_json ;;
367+
--test) run_tests ;;
368+
--all) run_build; generate_table; run_tests ;;
369+
--help|-h)
370+
echo "Usage: $0 [--build|--table|--json|--test|--all]"
371+
echo ""
372+
echo "Modes:"
373+
echo " --build Run TinyGo wasip2 builds for all 20 stdlib packages"
374+
echo " --table Generate human-readable markdown compatibility table"
375+
echo " --json Print compatibility results as JSON"
376+
echo " --test Run Go test suite (validates file structure + JSON schema)"
377+
echo " --all Run build + table + test (default)"
378+
echo ""
379+
echo "Prerequisites:"
380+
echo " - Go 1.22+ (go command in PATH)"
381+
echo " - TinyGo 0.40+ (build/tinygo/bin/tinygo or system PATH)"
382+
echo " - jq (for --table mode)"
383+
;;
384+
*)
385+
echo "Unknown option: ${MODE}"
386+
echo "Run $0 --help for usage"
387+
exit 1
388+
;;
389+
esac

0 commit comments

Comments
 (0)