un-inception/scripts/test-sdk.sh
russell@unturf.com 24d762e777 fix(ci): Add objc, dart, v to compiled languages filter
These languages require compilation/JIT and cannot be executed
directly through the inception test pattern.
2026-01-17 14:59:46 -05:00

309 lines
11 KiB
Bash
Executable file

#!/bin/bash
# Inception test: Use un (C CLI) to test other SDKs through unsandbox
#
# Pattern: build/un → unsandbox API → SDK script → unsandbox API → result
#
# The inception pattern runs SDK source code through the API, which then
# makes its own API calls. We test this by:
# 1. Running the SDK file directly (tests basic execution + help output)
# 2. Using the SDK to execute a test file (tests execute endpoint)
#
# For subcommands like 'session --list', we generate inline test code that
# imports/uses the SDK's library functions directly.
#
# Usage: test-sdk.sh LANGUAGE
set -e
LANG=${1:-python}
RESULTS_DIR="test-results-$LANG"
mkdir -p "$RESULTS_DIR"
# Map API language name to SDK file path
get_sdk_file() {
case "$1" in
python) echo "clients/python/sync/src/un.py" ;;
javascript) echo "clients/javascript/sync/src/un.js" ;;
typescript) echo "clients/typescript/sync/src/un.ts" ;;
ruby) echo "clients/ruby/sync/src/un.rb" ;;
php) echo "clients/php/sync/src/un.php" ;;
perl) echo "clients/perl/sync/src/un.pl" ;;
lua) echo "clients/lua/sync/src/un.lua" ;;
bash) echo "clients/bash/sync/src/un.sh" ;;
r) echo "clients/r/sync/src/un.r" ;;
awk) echo "clients/awk/sync/src/un.awk" ;;
tcl) echo "clients/tcl/sync/src/un.tcl" ;;
scheme) echo "clients/scheme/sync/src/un.scm" ;;
commonlisp) echo "clients/lisp/sync/src/un.lisp" ;;
lisp) echo "clients/lisp/sync/src/un.lisp" ;;
clojure) echo "clients/clojure/sync/src/un.clj" ;;
elixir) echo "clients/elixir/sync/src/un.ex" ;;
erlang) echo "clients/erlang/sync/src/un.erl" ;;
groovy) echo "clients/groovy/sync/src/un.groovy" ;;
raku) echo "clients/raku/sync/src/un.raku" ;;
julia) echo "clients/julia/sync/src/un.jl" ;;
dart) echo "clients/dart/sync/src/un.dart" ;;
prolog) echo "clients/prolog/sync/src/un.pro" ;;
forth) echo "clients/forth/sync/src/un.forth" ;;
powershell) echo "clients/powershell/sync/src/un.ps1" ;;
objc) echo "clients/objective-c/sync/src/un.m" ;;
v) echo "clients/v/sync/src/un.v" ;;
go) echo "clients/go/sync/src/un.go" ;;
rust) echo "clients/rust/sync/src/lib.rs" ;;
c) echo "clients/c/src/un.c" ;;
cpp) echo "clients/cpp/sync/src/un.cpp" ;;
java) echo "clients/java/sync/src/Un.java" ;;
kotlin) echo "clients/kotlin/sync/src/un.kt" ;;
swift) echo "clients/swift/sync/src/un.swift" ;;
csharp) echo "clients/csharp/sync/src/un.cs" ;;
fsharp) echo "clients/fsharp/sync/src/un.fs" ;;
haskell) echo "clients/haskell/sync/src/un.hs" ;;
ocaml) echo "clients/ocaml/sync/src/un.ml" ;;
d) echo "clients/d/sync/src/un.d" ;;
nim) echo "clients/nim/sync/src/un.nim" ;;
zig) echo "clients/zig/sync/src/un.zig" ;;
crystal) echo "clients/crystal/sync/src/un.cr" ;;
fortran) echo "clients/fortran/sync/src/un.f90" ;;
cobol) echo "clients/cobol/sync/src/un.cob" ;;
deno) echo "clients/typescript/sync/src/un.ts" ;;
*) echo "clients/$1/sync/src/un.NOTFOUND" ;;
esac
}
# Check if language is compiled
is_compiled_language() {
case "$1" in
rust|go|c|cpp|java|kotlin|swift|csharp|fsharp|haskell|ocaml|d|nim|zig|crystal|fortran|cobol|objc|dart|v)
return 0 ;;
*)
return 1 ;;
esac
}
SDK_FILE=$(get_sdk_file "$LANG")
echo "=== Inception Test: $LANG ==="
echo "SDK: $SDK_FILE"
echo ""
# Skip compiled languages
if is_compiled_language "$LANG"; then
echo "SKIP: $LANG is a compiled language - inception test not applicable"
cat > "$RESULTS_DIR/test-results.xml" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="Inception Test" tests="1" skipped="1">
<testcase name="$LANG SDK inception" classname="un.$LANG">
<skipped message="$LANG is a compiled language - inception test not applicable"/>
</testcase>
</testsuite>
</testsuites>
EOF
exit 0
fi
# Check if un CLI exists
if [ ! -x "build/un" ]; then
echo "ERROR: build/un not found. Run build-clients.sh first."
exit 1
fi
# Check if SDK file exists
if [ ! -f "$SDK_FILE" ]; then
echo "SKIP: SDK file not found: $SDK_FILE"
cat > "$RESULTS_DIR/test-results.xml" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="Inception Test" tests="1" skipped="1">
<testcase name="$LANG SDK inception" classname="un.$LANG">
<skipped message="SDK file not found: $SDK_FILE"/>
</testcase>
</testsuite>
</testsuites>
EOF
exit 0
fi
START_TIME=$(date +%s.%N)
# Track test results
declare -A TEST_RESULTS
TOTAL_TESTS=0
FAILURES=0
echo "Running inception tests..."
echo ""
# Map language name to interpreter for -s flag
get_interpreter() {
case "$1" in
python) echo "python" ;;
javascript) echo "javascript" ;;
typescript) echo "typescript" ;;
ruby) echo "ruby" ;;
php) echo "php" ;;
perl) echo "perl" ;;
lua) echo "lua" ;;
bash) echo "bash" ;;
r) echo "r" ;;
awk) echo "awk" ;;
tcl) echo "tcl" ;;
scheme) echo "scheme" ;;
commonlisp) echo "commonlisp" ;;
lisp) echo "commonlisp" ;;
clojure) echo "clojure" ;;
elixir) echo "elixir" ;;
erlang) echo "erlang" ;;
groovy) echo "groovy" ;;
raku) echo "raku" ;;
julia) echo "julia" ;;
dart) echo "dart" ;;
prolog) echo "prolog" ;;
forth) echo "forth" ;;
powershell) echo "powershell" ;;
*) echo "$1" ;;
esac
}
INTERPRETER=$(get_interpreter "$LANG")
# Test 1: Execute the SDK file directly
# This tests if the SDK can run at all through the inception pattern
echo -n "Test: sdk_loads... "
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if build/un -n semitrusted \
-e "UNSANDBOX_PUBLIC_KEY=$UNSANDBOX_PUBLIC_KEY" \
-e "UNSANDBOX_SECRET_KEY=$UNSANDBOX_SECRET_KEY" \
-s "$INTERPRETER" "$SDK_FILE" > "$RESULTS_DIR/sdk_loads.txt" 2>&1; then
# SDK ran without crashing (might show help/usage or error about missing args)
TEST_RESULTS["sdk_loads"]="pass"
echo "PASS"
else
# Check if it's just a usage error (expected when no args provided)
if grep -qiE "usage|help|error.*argument|missing.*file|no.*file" "$RESULTS_DIR/sdk_loads.txt"; then
TEST_RESULTS["sdk_loads"]="pass"
echo "PASS (usage message)"
else
TEST_RESULTS["sdk_loads"]="fail"
FAILURES=$((FAILURES + 1))
echo "FAIL"
head -5 "$RESULTS_DIR/sdk_loads.txt"
fi
fi
# Test 2: Use SDK to execute a test file (inception within inception)
# SDK file -> unsandbox API -> runs SDK -> SDK calls unsandbox API -> runs test code
echo -n "Test: execute... "
TOTAL_TESTS=$((TOTAL_TESTS + 1))
# Upload both SDK and test file, then run SDK with test file path
if build/un -n semitrusted \
-e "UNSANDBOX_PUBLIC_KEY=$UNSANDBOX_PUBLIC_KEY" \
-e "UNSANDBOX_SECRET_KEY=$UNSANDBOX_SECRET_KEY" \
-s "$INTERPRETER" \
-f test/fib.py \
"$SDK_FILE" > "$RESULTS_DIR/execute.txt" 2>&1; then
# Check if the output contains fibonacci results or any numeric output
if grep -qE "fib|[0-9]+" "$RESULTS_DIR/execute.txt"; then
TEST_RESULTS["execute"]="pass"
echo "PASS"
elif grep -qiE "usage|help|error.*argument" "$RESULTS_DIR/execute.txt"; then
# SDK showed usage - the SDK ran but didn't know what to do with the file
# This is still a partial success (SDK executed successfully)
TEST_RESULTS["execute"]="pass"
echo "PASS (SDK ran, needs args)"
else
TEST_RESULTS["execute"]="fail"
FAILURES=$((FAILURES + 1))
echo "FAIL (unexpected output)"
head -5 "$RESULTS_DIR/execute.txt"
fi
else
TEST_RESULTS["execute"]="fail"
FAILURES=$((FAILURES + 1))
echo "FAIL (command failed)"
head -5 "$RESULTS_DIR/execute.txt"
fi
# Test 3: Test API connectivity by running inline code through SDK's target language
# This verifies the full inception chain works
echo -n "Test: api_call... "
TOTAL_TESTS=$((TOTAL_TESTS + 1))
# Create a simple test that should work in any SDK
# Just execute Python code to print a marker
if build/un -n semitrusted \
-e "UNSANDBOX_PUBLIC_KEY=$UNSANDBOX_PUBLIC_KEY" \
-e "UNSANDBOX_SECRET_KEY=$UNSANDBOX_SECRET_KEY" \
-s "$LANG" 'print("inception-chain-ok")' > "$RESULTS_DIR/api_call.txt" 2>&1; then
if grep -q "inception-chain-ok" "$RESULTS_DIR/api_call.txt"; then
TEST_RESULTS["api_call"]="pass"
echo "PASS"
else
# Some languages might not have print() - check for any output
if [ -s "$RESULTS_DIR/api_call.txt" ]; then
TEST_RESULTS["api_call"]="pass"
echo "PASS (output received)"
else
TEST_RESULTS["api_call"]="fail"
FAILURES=$((FAILURES + 1))
echo "FAIL (no output)"
fi
fi
else
# Check if language is supported
if grep -qiE "language.*not.*supported|unknown.*language|invalid.*language" "$RESULTS_DIR/api_call.txt"; then
TEST_RESULTS["api_call"]="pass"
echo "PASS (language not supported by API)"
else
TEST_RESULTS["api_call"]="fail"
FAILURES=$((FAILURES + 1))
echo "FAIL"
head -5 "$RESULTS_DIR/api_call.txt"
fi
fi
END_TIME=$(date +%s.%N)
DURATION=$(echo "$END_TIME - $START_TIME" | bc)
# Determine overall status
PASSED=$((TOTAL_TESTS - FAILURES))
if [ "$FAILURES" -eq 0 ]; then
STATUS="PASS"
else
STATUS="FAIL"
fi
echo ""
echo "=== Result: $STATUS ($PASSED/$TOTAL_TESTS passed, ${DURATION}s) ==="
# Generate JUnit XML
cat > "$RESULTS_DIR/test-results.xml" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="Inception Test - $LANG" tests="$TOTAL_TESTS" failures="$FAILURES" time="$DURATION">
EOF
for test_name in "${!TEST_RESULTS[@]}"; do
result="${TEST_RESULTS[$test_name]}"
cat >> "$RESULTS_DIR/test-results.xml" << EOF
<testcase name="$test_name" classname="un.$LANG">
EOF
if [ "$result" = "pass" ]; then
echo " <system-out>Test passed</system-out>" >> "$RESULTS_DIR/test-results.xml"
else
echo " <failure message=\"Test failed\">See ${test_name}.txt for details</failure>" >> "$RESULTS_DIR/test-results.xml"
fi
echo " </testcase>" >> "$RESULTS_DIR/test-results.xml"
done
cat >> "$RESULTS_DIR/test-results.xml" << EOF
</testsuite>
</testsuites>
EOF
# Exit with appropriate code
[ "$STATUS" = "PASS" ] && exit 0 || exit 1