#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/.." raw_calls=$(grep -En -- '(->|\.)call\(' src/wasm/worker.cpp) if [[ $(wc -l <<<"$raw_calls") -ne 1 || "$raw_calls" != *'return(func.call(context, args));'* ]]; then echo "raw Wasmtime call bypasses call_guest(): $raw_calls" >&2 exit 1 fi if [[ "${1:-}" == "--static-only" ]]; then echo "wasm invocation timeout static call gate passed" exit 0 fi settings_file="${UCE_SETTINGS_FILE:-/etc/uce/settings.cfg}" site_directory="${UCE_TEST_SITE_DIRECTORY:-site}" socket_path="${UCE_CLI_SOCKET:-/run/uce/cli.sock}" bin_directory="${BIN_DIRECTORY:-/tmp/uce/work}" if [[ -r "$settings_file" ]]; then [[ -n "${UCE_TEST_SITE_DIRECTORY:-}" ]] || site_directory=$(awk -F= '/^[[:space:]]*SITE_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file") [[ -n "${UCE_CLI_SOCKET:-}" ]] || socket_path=$(awk -F= '/^[[:space:]]*CLI_SOCKET_PATH[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file") [[ -n "${BIN_DIRECTORY:-}" ]] || bin_directory=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file") invocation_ms=$(awk -F= '/^[[:space:]]*WASM_INVOCATION_TIMEOUT_MS[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$settings_file") cli_worker_count=$(awk -F= '/^[[:space:]]*CLI_WORKER_COUNT[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); value=$2} END{print value}' "$settings_file") fi site_directory="${site_directory:-site}" socket_path="${socket_path:-/run/uce/cli.sock}" bin_directory="${bin_directory:-/tmp/uce/work}" invocation_ms="${invocation_ms:-30000}" cli_worker_count="${cli_worker_count:-0}" curl_transport=(--unix-socket "$socket_path") test_base_url="http://localhost" if (( cli_worker_count > 0 )); then test_http_host="${UCE_TEST_HTTP_HOST:-uce.openfu.com}" curl_transport=(--resolve "$test_http_host:80:127.0.0.1") test_base_url="http://$test_http_host" fi test_name="invocation-timeout-test-$$" source_dir="$site_directory/$test_name" pid_file="/tmp/uce-$test_name-worker" cache_dir="" body_file="/tmp/uce-$test_name-body" cleanup() { rm -rf "$source_dir" [[ -z "$cache_dir" ]] || rm -rf "$cache_dir" rm -f "$pid_file" "$body_file" } trap cleanup EXIT mkdir -p "$source_dir" cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")" printf '%s\n' \ 'void timeout_test_run(Request& context) {' \ ' if(context.get["warm"] == "1") { print("warm"); return; }' \ " file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \ ' while(true) time_precise();' \ '}' \ 'CLI(Request& context) { timeout_test_run(context); }' \ 'RENDER(Request& context) { timeout_test_run(context); }' >"$source_dir/hostcall-loop.uce" printf '%s\n' \ 'void timeout_test_run(Request& context) {' \ ' if(context.get["warm"] == "1") { print("warm"); return; }' \ ' if(context.get["quick"] == "1") { print(shell_exec("printf quick")); return; }' \ ' if(context.get["status"] == "1") { DValue spec; spec["cmd"] = "exit 7"; spec["timeout_ms"] = (f64)500; print(shell_exec(spec)["exit_code"].to_u64()); return; }' \ ' if(context.get["zero"] == "1") { DValue spec; spec["cmd"] = "printf zero"; spec["timeout_ms"] = (f64)0; print(shell_exec(spec)["stdout"].to_string()); return; }' \ ' if(context.get["job"] == "1") { DValue spec; spec["cmd"] = "sleep 2"; spec["timeout_ms"] = (f64)5000; u64 job = shell_spawn(spec); f64 started = time_precise(); job_await(job, 300); u64 elapsed = (u64)((time_precise() - started) * 1000); job_cancel(job); print(elapsed); return; }' \ " file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \ ' print(shell_exec("printf shell-start; sleep 60 & printf shell-end"));' \ '}' \ 'CLI(Request& context) { timeout_test_run(context); }' \ 'RENDER(Request& context) { timeout_test_run(context); }' >"$source_dir/legacy-shell.uce" printf '%s\n' \ 'void timeout_test_run(Request& context) {' \ ' if(context.get["warm"] == "1") { print("warm"); return; }' \ " file_put_contents(\"$pid_file\", std::to_string(request_perf()[\"worker_pid\"].to_u64()));" \ ' while(true) sleep(60);' \ ' print("sleep-end");' \ '}' \ 'CLI(Request& context) { timeout_test_run(context); }' \ 'RENDER(Request& context) { timeout_test_run(context); }' >"$source_dir/sleep.uce" printf '%s\n' \ 'void timeout_test_run(Request& context) { print(request_perf()["worker_pid"].to_u64(), "|health"); }' \ 'CLI(Request& context) { timeout_test_run(context); }' \ 'RENDER(Request& context) { timeout_test_run(context); }' >"$source_dir/health.uce" max_seconds=$(( (invocation_ms + 15000) / 1000 )) (( max_seconds >= 10 )) || max_seconds=10 request() { local unit="$1" curl -sS --max-time "$max_seconds" -o "$body_file" -w '%{http_code}' "${curl_transport[@]}" "$test_base_url/$test_name/$unit" } same_worker_health() { local expected_pid="$1" kill -0 "$expected_pid" 2>/dev/null || return 1 local health health=$(curl -sS --max-time 5 --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/health.uce") [[ "$health" =~ ^[0-9]+\|health$ ]] } curl -sS --max-time "$max_seconds" --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/hostcall-loop.uce?warm=1" >/dev/null curl -sS --max-time "$max_seconds" --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/legacy-shell.uce?warm=1" >/dev/null curl -sS --max-time "$max_seconds" --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/sleep.uce?warm=1" >/dev/null curl -sS --max-time "$max_seconds" --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/health.uce" >/dev/null quick=$(curl -sS --max-time 5 --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/legacy-shell.uce?quick=1") [[ "$quick" == quick ]] || { echo "quick legacy shell returned: $quick" >&2; exit 1; } exit_status=$(curl -sS --max-time 5 --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/legacy-shell.uce?status=1") [[ "$exit_status" == 7 ]] || { echo "structured shell lost exit status: $exit_status" >&2; exit 1; } zero_timeout=$(curl -sS --max-time 5 --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/legacy-shell.uce?zero=1") [[ "$zero_timeout" == zero ]] || { echo "structured shell zero-timeout default changed: $zero_timeout" >&2; exit 1; } job_elapsed=$(curl -sS --max-time 5 --fail-with-body "${curl_transport[@]}" "$test_base_url/$test_name/legacy-shell.uce?job=1") [[ "$job_elapsed" =~ ^[0-9]+$ && "$job_elapsed" -ge 200 && "$job_elapsed" -lt 500 ]] || { echo "job_await two-call duration was ${job_elapsed}ms" >&2; exit 1; } for unit in hostcall-loop.uce legacy-shell.uce sleep.uce; do rm -f "$pid_file" started_ns=$(date +%s%N) status=$(request "$unit") elapsed_ms=$(( ($(date +%s%N) - started_ns) / 1000000 )) [[ "$status" == "500" ]] || { echo "$unit returned HTTP $status" >&2; exit 1; } if [[ "$unit" == legacy-shell.uce ]]; then if (( invocation_ms <= 5000 )); then grep -q 'UCE_INVOCATION_TIMEOUT:' "$body_file" || { echo "$unit lacked invocation-timeout classification" >&2; exit 1; } expected_ms=$invocation_ms else grep -q 'UCE_HOSTCALL_TIMEOUT:' "$body_file" || { echo "$unit lacked hostcall-timeout classification" >&2; exit 1; } expected_ms=5000 fi else grep -q 'UCE_INVOCATION_TIMEOUT:' "$body_file" || { echo "$unit lacked invocation-timeout classification: $(cat "$body_file")" >&2; exit 1; } expected_ms=$invocation_ms fi (( elapsed_ms >= expected_ms - 1000 && elapsed_ms <= expected_ms + 3000 )) || { echo "$unit completed in ${elapsed_ms}ms; expected about ${expected_ms}ms" >&2; exit 1; } grep -q "$unit" "$body_file" || { echo "$unit lacked a source-mapped trace" >&2; exit 1; } [[ -s "$pid_file" ]] || { echo "$unit did not record its worker" >&2; exit 1; } worker_pid=$(<"$pid_file") same_worker_health "$worker_pid" || { echo "worker $worker_pid did not survive $unit" >&2; exit 1; } done echo "wasm invocation timeout passed (absolute ${invocation_ms}ms; sleep and process hostcalls bounded)"