#!/bin/bash # test_runner.sh - 中核実行器(強制使用) # スモークテストv2の核心ライブラリ # set -eは使わない(個々のテストが失敗しても全体を続行するため) set -uo pipefail # ルート/バイナリ検出(CWDに依存しない実行を保証) if [ -z "${NYASH_ROOT:-}" ]; then export NYASH_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" fi # Prefer hakorune binary if exists; fallback to nyash for compatibility if [ -z "${NYASH_BIN:-}" ]; then if [ -x "$NYASH_ROOT/target/release/hakorune" ]; then export NYASH_BIN="$NYASH_ROOT/target/release/hakorune" else export NYASH_BIN="$NYASH_ROOT/target/release/nyash" fi fi # Debug convenience: HAKO_DEBUG=1 enables execution trace and log passthrough if [ "${HAKO_DEBUG:-0}" = "1" ]; then export HAKO_TRACE_EXECUTION=1 export HAKO_VERIFY_SHOW_LOGS=1 fi # グローバル変数 export SMOKES_V2_LIB_LOADED=1 export SMOKES_START_TIME=$(date +%s.%N) export SMOKES_TEST_COUNT=0 export SMOKES_PASS_COUNT=0 export SMOKES_FAIL_COUNT=0 export SMOKES_INCLUDE_SKIP_COUNT=0 declare -a SMOKES_INCLUDE_SKIP_LIST=() # 色定義(重複回避) if [ -z "${RED:-}" ]; then readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly BLUE='\033[0;34m' readonly NC='\033[0m' # No Color fi # ログ関数 log_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2 } log_success() { echo -e "${GREEN}[PASS]${NC} $*" >&2 } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2 } log_error() { echo -e "${RED}[FAIL]${NC} $*" >&2 } # 共通ノイズフィルタ(VM実行時の出力整形) filter_noise() { if [ "${HAKO_SHOW_CALL_LOGS:-0}" = "1" ]; then # Show raw logs (no filtering) to allow call traces / diagnostics cat return fi # プラグイン初期化やメタログ、動的ローダの案内等を除去 grep -v "^\[UnifiedBoxRegistry\]" \ | grep -v "^\[FileBox\]" \ | grep -v "^\[provider-registry\]" \ | grep -v "^\[plugin/missing\]" \ | grep -v "^\[plugin/hint\]" \ | grep -v "^Net plugin:" \ | grep -v "^\[.*\] Plugin" \ | grep -v "Using builtin StringBox" \ | grep -v "Using builtin ArrayBox" \ | grep -v "Using builtin MapBox" \ | grep -v "^\[using\]" \ | grep -v "^\[using/resolve\]" \ | grep -v "^\[using/text-merge\]" \ | grep -v "^\[builder\]" \ | grep -v "^\\[vm-trace\\]" \ | grep -v "^\[vm\] Stage-3" \ | grep -v "^\[DEBUG\]" \ | grep -v '^\{"ev":' \ | grep -v '^\[warn\]' \ | grep -v '^\[error\]' \ | grep -v '^\[warn\] dev fallback: user instance BoxCall' \ | sed -E 's/^❌ VM fallback error: *//' \ | grep -v '^\[warn\] dev verify: NewBox ' \ | grep -v '^\[warn\] dev verify: NewBox→birth invariant warnings:' \ | grep -v '^\[ny-compiler\]' \ | grep -v '^\[using/cache\]' \ | grep -v "plugins/nyash-array-plugin" \ | grep -v "plugins/nyash-map-plugin" \ | grep -v "Phase 15.5: Everything is Plugin" \ | grep -v "cargo build -p nyash-string-plugin" \ | grep -v "^\[plugin-loader\] backend=" \ | grep -v "^\[using\] ctx:" \ | grep -v "^🔌 plugin host initialized" \ | grep -v "^✅ plugin host fully configured" \ | grep -v "Failed to load nyash.toml - plugins disabled" \ | grep -v "^⚠️ Failed to load plugin config (hakorune.toml/nyash.toml) - plugins disabled" \ | grep -v "^🚀 Nyash VM Backend - Executing file:" \ | grep -v "^🚀 Hakorune VM Backend - Executing file:" } # 環境チェック(必須) require_env() { local required_tools=("cargo" "grep" "jq") local missing_tools=() for tool in "${required_tools[@]}"; do if ! command -v "$tool" &> /dev/null; then missing_tools+=("$tool") fi done if [ ${#missing_tools[@]} -ne 0 ]; then log_error "Required tools missing: ${missing_tools[*]}" log_error "Please install missing tools and try again" return 1 fi # Nyash実行ファイル確認 if [ ! -f "$NYASH_BIN" ]; then log_error "Nyash executable not found at $NYASH_BIN" log_error "Please run 'cargo build --release' first (in $NYASH_ROOT)" return 1 fi log_info "Environment check passed" return 0 } # プラグイン整合性チェック(必須) preflight_plugins() { # プラグインマネージャーが存在する場合は実行 if [ -f "$(dirname "${BASH_SOURCE[0]}")/plugin_manager.sh" ]; then source "$(dirname "${BASH_SOURCE[0]}")/plugin_manager.sh" check_plugin_integrity || return 1 else log_warn "Plugin manager not found, skipping plugin checks" fi return 0 } # テスト実行関数 run_test() { local test_name="$1" local test_func="$2" ((SMOKES_TEST_COUNT++)) local start_time=$(date +%s.%N) log_info "Running test: $test_name" if $test_func; then local end_time=$(date +%s.%N) local duration=$(echo "$end_time - $start_time" | bc -l) log_success "$test_name (${duration}s)" ((SMOKES_PASS_COUNT++)) return 0 else local end_time=$(date +%s.%N) local duration=$(echo "$end_time - $start_time" | bc -l) log_error "$test_name (${duration}s)" ((SMOKES_FAIL_COUNT++)) return 1 fi } # Nyash実行ヘルパー(Rust VM) run_nyash_vm() { local program="$1" shift local USE_PYVM="${SMOKES_USE_PYVM:-0}" local EXTRA_ARGS=() if [ "${SMOKES_USE_DEV:-0}" = "1" ]; then EXTRA_ARGS+=("--dev") fi # Optional env sanitization between rapid invocations (default OFF) # Enable with: SMOKES_CLEAN_ENV=1 local ENV_PREFIX=( ) if [ "${SMOKES_CLEAN_ENV:-0}" = "1" ]; then # Preserve NYASH_JSON_ONLY to allow quiet JSON pipelines (e.g., v1 emitters) ENV_PREFIX=(env -u NYASH_DEBUG_ENABLE -u NYASH_DEBUG_KINDS -u NYASH_DEBUG_SINK \ -u NYASH_RESOLVE_FIX_BRACES -u NYASH_USING_AST \ -u NYASH_VM_TRACE -u NYASH_VM_VERIFY_MIR -u NYASH_VM_TOLERATE_VOID \ -u NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN) fi # -c オプションの場合は一時ファイル経由で実行 if [ "$program" = "-c" ]; then local code="$1" shift local tmpfile="/tmp/nyash_test_$$.hako" echo "$code" > "$tmpfile" # (shim removed) provider tag shortcut — hv1 inline is stable now # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ]; then sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$tmpfile" || true fi # プラグイン初期化メッセージを除外 # Optional preinclude for include-based code local runfile="$tmpfile" if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then local prefile="/tmp/nyash_pre_$$.hako" "$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$tmpfile" "$prefile" >/dev/null || true runfile="$prefile" fi # Optional hint for include lines when preinclude is OFF if grep -q '^include\s\"' "$tmpfile" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then echo "[WARN] VM backend does not support include. Prefer using+alias, or set NYASH_PREINCLUDE=1 for dev." >&2 fi HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \ NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} \ NYASH_USING_AST=1 NYASH_PARSER_SEAM_TOLERANT=1 \ "${ENV_PREFIX[@]}" \ "$NYASH_BIN" --backend vm "$runfile" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise local exit_code=${PIPESTATUS[0]} # prefile may be unset when preinclude is OFF; use default expansion to avoid set -u errors rm -f "$tmpfile" "${prefile:-}" 2>/dev/null || true if [ "${SMOKES_FORCE_ZERO:-0}" = "1" ]; then return 0 fi return $exit_code else # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true fi # プラグイン初期化メッセージを除外 # Optional preinclude local runfile2="$program" if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then local prefile2="/tmp/nyash_pre_$$.hako" "$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$program" "$prefile2" >/dev/null || true runfile2="$prefile2" fi # Optional hint for include lines when preinclude is OFF if [ -f "$program" ] && grep -q '^include\s\"' "$program" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then # Policy: quick は SKIP 既定。それ以外は WARN(SMOKES_INCLUDE_POLICY で上書き可能)。 local policy="${SMOKES_INCLUDE_POLICY:-}" if [ -z "$policy" ]; then case "$program" in */profiles/quick/*) policy="error" ;; *) policy="warn" ;; esac fi if [ "$policy" = "skip" ]; then SMOKES_INCLUDE_SKIP_COUNT=$((SMOKES_INCLUDE_SKIP_COUNT + 1)) local rel_path="$program" if [[ "$program" == "$NYASH_ROOT/"* ]]; then rel_path="${program#$NYASH_ROOT/}" fi SMOKES_INCLUDE_SKIP_LIST+=("$rel_path") echo "[SKIP] include is deprecated in 20.36+ (quick). Prefer using+alias." >&2 return 0 elif [ "$policy" = "error" ]; then echo "[ERROR] include is deprecated in 20.36+. Prefer using+alias." >&2 return 2 else echo "[WARN] include is deprecated in 20.36+. Prefer using+alias. Preinclude is dev-only (NYASH_PREINCLUDE=1)." >&2 fi fi HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \ NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} \ NYASH_USING_AST=1 NYASH_PARSER_SEAM_TOLERANT=1 \ "${ENV_PREFIX[@]}" \ "$NYASH_BIN" --backend vm "$runfile2" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise local exit_code=${PIPESTATUS[0]} # prefile2 may be unset when preinclude is OFF rm -f "${prefile2:-}" 2>/dev/null || true if [ "${SMOKES_FORCE_ZERO:-0}" = "1" ]; then return 0 fi return $exit_code fi } # Verify MIR JSON rc using selected primary (Core or Hakorune VM) verify_mir_rc() { local json_path="$1" # 20.36: hakovm を primary 既定へ(Core は診断 fallback) local primary="${HAKO_VERIFY_PRIMARY:-hakovm}" if [ "$primary" = "hakovm" ]; then # For MIR JSON v1, try Hakovm v1 dispatcher first (default ON), fallback to Core on failure. # Allow forcing Core with HAKO_VERIFY_V1_FORCE_CORE=1 if grep -q '"schema_version"' "$json_path" 2>/dev/null; then if [ "${HAKO_VERIFY_V1_FORCE_CORE:-0}" = "1" ]; then if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi "$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1; return $? fi # hv1 直行(main.rs 早期経路)。成功時は rc を採用、失敗時は Core にフォールバック。 # ただしフロー検証(dispatcher flow / phi 実験)が有効な場合は Core を優先(hv1-inline は最小実装のため)。 if [ "${HAKO_VERIFY_V1_FORCE_HAKOVM:-0}" != "1" ]; then local hv1_rc; hv1_rc=$(verify_v1_inline_file "$json_path" || true) if [[ "$hv1_rc" =~ ^-?[0-9]+$ ]]; then if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hv1_inline (rust)" >&2; fi local n=$hv1_rc; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi; return $n fi fi # 強制 hv1(-c ラッパ): NyVmDispatcherV1Box.run を直接呼び出して rc を取得 if [ "${HAKO_VERIFY_V1_FORCE_HAKOVM:-0}" = "1" ]; then local hv1_rc_force; hv1_rc_force=$(verify_v1_inline_file "$json_path" || true) if [[ "$hv1_rc_force" =~ ^-?[0-9]+$ ]]; then if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hv1_inline (rust)" >&2; fi local n=$hv1_rc_force; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi; return $n fi return 1 fi # No include+preinclude fallback succeeded → Core にフォールバック if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi "$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1 return $? fi # Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded if [ ! -f "$json_path" ]; then echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2 return 2 fi # Escape JSON as a single string literal via jq -Rs (preserves newlines) local json_literal json_literal="$(jq -Rs . < "$json_path")" build_and_run_driver_alias() { local header="$1" local code=$(cat </dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}' } build_and_run_driver_include() { local inc_path="$1" local code=$(cat </dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}' } # Try alias header first; fallback to dev-file header; final fallback: include+preinclude local out out="$(build_and_run_driver_alias 'using selfhost.vm.entry as MiniVmEntryBox')" if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then out="$(build_and_run_driver_alias 'using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox')" fi if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then out="$(build_and_run_driver_include 'lang/src/vm/boxes/mini_vm_entry.hako')" fi if [[ "$out" =~ ^-?[0-9]+$ ]]; then local n=$out # normalize into [0,255] if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi return $n fi # Fallback: core primary when MiniVM resolution is unavailable if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then local json_literal3; json_literal3="$(jq -Rs . < "$json_path")" local code=$(cat < "$tmpwrap" NYASH_PREINCLUDE=1 run_nyash_vm "$tmpwrap" >/dev/null 2>&1; local r=$?; rm -f "$tmpwrap"; return $r fi NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $? else # Core primary: detect MIR(JSON) vs Program(JSON v0) if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then "$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1; return $? fi NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $? fi } # New function: verify_program_via_builder_to_core # Purpose: Program(JSON v0) → MirBuilder(Hako) → MIR(JSON v0) → Core execution # This is dev-only for testing builder output quality verify_program_via_builder_to_core() { local prog_json_path="$1" # Step 1: Use minimal runner to convert Program → MIR(env経由でJSONを渡す) local mir_json_path="/tmp/builder_output_$$.json" local builder_code_min=$(cat <<'HCODE' using "hako.mir.builder.internal.runner_min" as BuilderRunnerMinBox static box Main { method main(args) { local prog_json = env.get("HAKO_BUILDER_PROGRAM_JSON") if prog_json == null { print("Builder failed"); return 1 } local mir_out = BuilderRunnerMinBox.run(prog_json) if mir_out == null { print("Builder failed"); return 1 } print("[MIR_OUT_BEGIN]") print("" + mir_out) print("[MIR_OUT_END]") return 0 } } HCODE ) # Read program JSON to env (avoid embedding/escaping) local prog_json_raw prog_json_raw="$(cat "$prog_json_path")" # Run builder with internal lowers enabled using v1 dispatcher local mir_json="" local builder_stderr="/tmp/builder_stderr_$$.log" local builder_stdout="/tmp/builder_stdout_$$.log" # Try minimal runner first (fast path), unless HAKO_PREFER_MIRBUILDER=1 if [ "${HAKO_PREFER_MIRBUILDER:-0}" = "1" ]; then : # skip minimal runner else mir_json=$(HAKO_MIR_BUILDER_INTERNAL=1 \ HAKO_MIR_RUNNER_MIN_NO_METHODS=1 \ HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \ HAKO_ROUTE_HAKOVM=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ NYASH_USING_AST=1 \ NYASH_RESOLVE_FIX_BRACES=1 \ NYASH_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 \ HAKO_PARSER_STAGE3=1 \ NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ HAKO_BUILDER_PROGRAM_JSON="$prog_json_raw" \ run_nyash_vm -c "$builder_code_min" 2>"$builder_stderr" | tee "$builder_stdout" | awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag') fi if [ "${HAKO_MIR_BUILDER_DEBUG:-0}" = "1" ]; then echo "[builder debug] stdout (tail):" >&2 tail -n 60 "$builder_stdout" >&2 || true echo "[builder debug] stderr (tail):" >&2 tail -n 60 "$builder_stderr" >&2 || true fi # Fallback Option A: try full MirBuilderBox (emit) when minimal runner fails if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then local builder_code_full=$(cat <<'HCODE' using "hako.mir.builder" as MirBuilderBox static box Main { method main(args) { local prog_json = env.get("HAKO_BUILDER_PROGRAM_JSON") if prog_json == null { print("Builder failed"); return 1 } local mir_out = MirBuilderBox.emit_from_program_json_v0(prog_json, null) if mir_out == null { print("Builder failed"); return 1 } print("[MIR_OUT_BEGIN]") print("" + mir_out) print("[MIR_OUT_END]") return 0 } } HCODE ) mir_json=$(HAKO_MIR_BUILDER_INTERNAL=1 \ HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \ HAKO_ROUTE_HAKOVM=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 \ NYASH_DISABLE_NY_COMPILER=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ HAKO_BUILDER_PROGRAM_JSON="$prog_json_raw" \ run_nyash_vm -c "$builder_code_full" 2>>"$builder_stderr" | tee -a "$builder_stdout" | awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag') fi # PRIMARY no-fallback: if requested, do not fall back to Rust CLI builder if [ "${HAKO_PRIMARY_NO_FALLBACK:-0}" = "1" ]; then if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then return 1 fi fi # Fallback Option B: use Rust CLI builder when Hako builder fails if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then if [ "${HAKO_MIR_BUILDER_DEBUG:-0}" = "1" ] && [ -f "$builder_stderr" ]; then echo "[builder debug] Hako builder failed, falling back to Rust CLI" >&2 cat "$builder_stderr" >&2 cp "$builder_stderr" /tmp/builder_last_error.log fi rm -f "$builder_stderr" local tmp_mir="/tmp/ny_builder_conv_$$.json" if "$NYASH_BIN" --program-json-to-mir "$tmp_mir" --json-file "$prog_json_path" >/dev/null 2>&1; then if [ "${HAKO_VERIFY_BUILDER_ONLY:-0}" = "1" ]; then # Builder-only: check structure only if grep -q '"functions"' "$tmp_mir" && grep -q '"blocks"' "$tmp_mir"; then rm -f "$tmp_mir"; return 0 else rm -f "$tmp_mir"; return 1 fi else "$NYASH_BIN" --mir-json-file "$tmp_mir" >/dev/null 2>&1 local rc=$? rm -f "$tmp_mir" return $rc fi else return 1 fi fi rm -f "$builder_stderr" "$builder_stdout" # Validate builder output looks like MIR JSON; otherwise fallback to Rust CLI (unless PRIMARY no-fallback) if ! echo "$mir_json" | grep -q '"functions"' || ! echo "$mir_json" | grep -q '"blocks"'; then if [ "${HAKO_PRIMARY_NO_FALLBACK:-0}" = "1" ]; then return 1 fi # fallback: Rust CLI builder local tmp_mir="/tmp/ny_builder_conv_$$.json" if "$NYASH_BIN" --program-json-to-mir "$tmp_mir" --json-file "$prog_json_path" >/dev/null 2>&1; then "$NYASH_BIN" --mir-json-file "$tmp_mir" >/dev/null 2>&1 local rc=$? rm -f "$tmp_mir" return $rc else return 1 fi fi # Route: if builder output contains v1 hints, run hv1 dispatcher inline. if echo "$mir_json" | grep -q '"schema_version"' || echo "$mir_json" | grep -q '"op"\s*:\s*"mir_call"'; then local hv1_rc local mir_literal; mir_literal="$(printf '%s' "$mir_json" | jq -Rs .)" hv1_rc=$(run_hv1_inline_alias_wrapper "$mir_literal") if [[ "$hv1_rc" =~ ^-?[0-9]+$ ]]; then if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hakovm (hako)" >&2; fi local n=$hv1_rc; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi return $n fi fi # Route: if builder output is v0 but contains unified-only ops (newbox/boxcall), # execute via Hako Core dispatcher (NyVmDispatcher.run) which supports extended v0. if echo "$mir_json" | grep -q '"op"\s*:\s*"newbox"' || echo "$mir_json" | grep -q '"op"\s*:\s*"boxcall"'; then local mir_literal2; mir_literal2="$(printf '%s' "$mir_json" | jq -Rs .)" local code=$(cat <<'HCODE' include "lang/src/vm/core/dispatcher.hako" static box Main { method main(args) { local j = env.get("NYASH_VERIFY_JSON") local r = NyVmDispatcher.run(j) print("" + r) return r } } HCODE ) local out; out=$(NYASH_VERIFY_JSON="$mir_literal2" NYASH_PREINCLUDE=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}') if [[ "$out" =~ ^-?[0-9]+$ ]]; then local n=$out; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi return $n fi fi # Optional: structure-only check (builder only) for fast mode if [ "${HAKO_VERIFY_BUILDER_ONLY:-0}" = "1" ]; then if echo "$mir_json" | grep -q '"functions"' && echo "$mir_json" | grep -q '"blocks"'; then return 0 else return 1 fi fi # Write MIR JSON to temp file and execute via Core echo "$mir_json" > "$mir_json_path" if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi "$NYASH_BIN" --mir-json-file "$mir_json_path" >/dev/null 2>&1 local rc=$? rm -f "$mir_json_path" return $rc } # hv1 inline alias-only wrapper (env JSON → hv1 dispatcher) # Usage: run_hv1_inline_alias_wrapper "$json_literal" → prints rc line; returns rc run_hv1_inline_alias_wrapper() { local json_literal="$1" local code=$(cat <<'HCODE' using "selfhost.vm.hv1.dispatch" as NyVm static box Main { method main(args) { local j = env.get("NYASH_VERIFY_JSON") local r = NyVm.NyVmDispatcherV1Box.run(j) print("" + r) return r } } HCODE ) HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 \ NYASH_VERIFY_JSON="$json_literal" run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}' } # Nyash実行ヘルパー(LLVM) run_nyash_llvm() { local program="$1" shift # Allow developer to force LLVM run (env guarantees availability) if [ "${SMOKES_FORCE_LLVM:-0}" != "1" ]; then # Skip gracefully when LLVM backend is not available in this build # Primary check: version string advertises features if ! "$NYASH_BIN" --version 2>/dev/null | grep -q "features.*llvm"; then # Fallback check: binary contains LLVM harness symbols (ny-llvmc / NYASH_LLVM_USE_HARNESS) if ! strings "$NYASH_BIN" 2>/dev/null | grep -E -q 'ny-llvmc|NYASH_LLVM_USE_HARNESS'; then log_warn "LLVM backend not available in this build; skipping LLVM run" log_info "Hint: build ny-llvmc + enable harness: cargo build --release -p nyash-llvm-compiler && cargo build --release --features llvm" return 0 fi fi fi # -c オプションの場合は一時ファイル経由で実行 if [ "$program" = "-c" ]; then local code="$1" shift local tmpfile="/tmp/nyash_test_$$.hako" echo "$code" > "$tmpfile" # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ]; then sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$tmpfile" || true fi # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true fi # プラグイン初期化メッセージを除外 PYTHONPATH="${PYTHONPATH:-$NYASH_ROOT}" NYASH_NY_LLVM_COMPILER="$NYASH_ROOT/target/release/ny-llvmc" NYASH_LLVM_USE_HARNESS=1 NYASH_EMIT_EXE_NYRT="$NYASH_ROOT/target/release" NYASH_VM_USE_PY=0 NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 "$NYASH_BIN" --backend llvm "$tmpfile" "$@" 2>&1 | \ grep -v "^\[UnifiedBoxRegistry\]" | grep -v "^\[FileBox\]" | grep -v "^Net plugin:" | grep -v "^\[.*\] Plugin" | \ grep -v '^\[plugin-loader\] backend=' | \ grep -v '^🔌 plugin host initialized' | grep -v '^✅ plugin host fully configured' | \ grep -v '^⚡ Hakorune LLVM Backend' | \ grep -v '^✅ LLVM (harness) execution completed' | grep -v '^📊 MIR Module compiled successfully' | grep -v '^📊 Functions:' | grep -v 'JSON Parse Errors:' | grep -v 'Parsing errors' | grep -v 'No parsing errors' | grep -v 'Error at line ' | \ grep -v '^\[using\]' | grep -v '^\[using/resolve\]' | grep -v '^\[using/cache\]' | \ grep -v '^\[ny-llvmc\]' | grep -v '^\[harness\]' | grep -v '^Compiled to ' | grep -v '^/usr/bin/ld:' local exit_code=${PIPESTATUS[0]} rm -f "$tmpfile" return $exit_code else # 軽量ASIFix(テスト用): ブロック終端の余剰セミコロンを寛容に除去 if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true fi # プラグイン初期化メッセージを除外 PYTHONPATH="${PYTHONPATH:-$NYASH_ROOT}" NYASH_NY_LLVM_COMPILER="$NYASH_ROOT/target/release/ny-llvmc" NYASH_LLVM_USE_HARNESS=1 NYASH_EMIT_EXE_NYRT="$NYASH_ROOT/target/release" NYASH_VM_USE_PY=0 NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 "$NYASH_BIN" --backend llvm "$program" "$@" 2>&1 | \ grep -v "^\[UnifiedBoxRegistry\]" | grep -v "^\[FileBox\]" | grep -v "^Net plugin:" | grep -v "^\[.*\] Plugin" | \ grep -v '^\[plugin-loader\] backend=' | \ grep -v '^🔌 plugin host initialized' | grep -v '^✅ plugin host fully configured' | \ grep -v '^⚡ Hakorune LLVM Backend' | \ grep -v '^✅ LLVM (harness) execution completed' | grep -v '^📊 MIR Module compiled successfully' | grep -v '^📊 Functions:' | grep -v 'JSON Parse Errors:' | grep -v 'Parsing errors' | grep -v 'No parsing errors' | grep -v 'Error at line ' | \ grep -v '^\[using\]' | grep -v '^\[using/resolve\]' | grep -v '^\[using/cache\]' | \ grep -v '^\[ny-llvmc\]' | grep -v '^\[harness\]' | grep -v '^Compiled to ' | grep -v '^/usr/bin/ld:' return ${PIPESTATUS[0]} fi } # シンプルテスト補助(スクリプト互換) test_pass() { log_success "$1"; return 0; } test_fail() { log_error "$1 ${2:-}"; return 1; } test_skip() { log_warn "SKIP $1 ${2:-}"; return 0; } # 出力比較ヘルパー compare_outputs() { local expected="$1" local actual="$2" local test_name="$3" if [ "$expected" = "$actual" ]; then return 0 else log_error "$test_name output mismatch:" log_error " Expected: $expected" log_error " Actual: $actual" return 1 fi } # 結果サマリー出力 print_summary() { local end_time=$(date +%s.%N) local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l) echo "" echo "===============================================" echo "Smoke Test Summary" echo "===============================================" echo "Total tests: $SMOKES_TEST_COUNT" echo "Passed: $SMOKES_PASS_COUNT" echo "Failed: $SMOKES_FAIL_COUNT" echo "Duration: ${total_duration}s" echo "" if [ "${SMOKES_INCLUDE_SKIP_COUNT:-0}" -gt 0 ]; then echo "Include SKIPs: $SMOKES_INCLUDE_SKIP_COUNT" for entry in "${SMOKES_INCLUDE_SKIP_LIST[@]}"; do echo " - $entry" done echo "" fi if [ $SMOKES_FAIL_COUNT -eq 0 ]; then log_success "All tests passed! ✨" return 0 else log_error "$SMOKES_FAIL_COUNT test(s) failed" return 1 fi } # JSON出力関数 output_json() { local profile="${1:-unknown}" local end_time=$(date +%s.%N) local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l) cat << EOF { "profile": "$profile", "total": $SMOKES_TEST_COUNT, "passed": $SMOKES_PASS_COUNT, "failed": $SMOKES_FAIL_COUNT, "duration": $total_duration, "timestamp": "$(date -Iseconds)", "success": $([ $SMOKES_FAIL_COUNT -eq 0 ] && echo "true" || echo "false") } EOF } # JUnit XML出力関数 output_junit() { local profile="${1:-unknown}" local end_time=$(date +%s.%N) local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l) cat << EOF EOF } # v1 JSON 正規化(オブジェクトキー順序ソート、配列順は保持)→ SHA256 ハッシュ v1_normalized_hash() { local json_path="$1" if [ ! -f "$json_path" ]; then echo "[FAIL] v1_normalized_hash: json not found: $json_path" >&2 return 2 fi if ! command -v jq >/dev/null 2>&1; then echo "[FAIL] v1_normalized_hash: jq required" >&2 return 2 fi # Extract JSON object line defensively (strip any leading noise like 'RC: N' or logs) # Extract JSON object block from first '{' to EOF (handles pretty JSON) local raw_json raw_json=$(awk 'BEGIN{on=0} { if(on){print} else if($0 ~ /^[[:space:]]*\{/){ on=1; print } }' "$json_path") if [ -z "$raw_json" ]; then return 1 fi local canon canon=$(printf '%s' "$raw_json" | jq -S -c .) || return 1 printf "%s" "$canon" | sha256sum | awk '{print $1}' } # 2つの v1 JSON ファイルの正規化ハッシュを比較(等しければ0) compare_v1_hash() { local a="$1"; local b="$2" local ha hb ha=$(v1_normalized_hash "$a" || true) hb=$(v1_normalized_hash "$b" || true) if [ -z "$ha" ] || [ -z "$hb" ]; then return 2 fi [ "$ha" = "$hb" ] } # Run hv1 inline (early route) and return numeric rc (0-255). Returns non-zero exit on failure to execute. verify_v1_inline_file() { local json_path="$1" if [ ! -f "$json_path" ]; then echo "[FAIL] verify_v1_inline_file: json not found: $json_path" >&2 return 2 fi local out # Optional: show full logs for debugging (default OFF) if [ "${HAKO_VERIFY_SHOW_LOGS:-0}" = "1" ]; then # Show all output to stderr, then extract numeric rc (env-sanitized for determinism) env -i PATH="$PATH" \ HAKO_TRACE_EXECUTION="${HAKO_TRACE_EXECUTION:-0}" HAKO_VERIFY_SHOW_LOGS="${HAKO_VERIFY_SHOW_LOGS:-0}" \ HAKO_ROUTE_HAKOVM=1 HAKO_VERIFY_V1_FORCE_HAKOVM=1 \ NYASH_VERIFY_JSON="$(cat "$json_path")" \ "$NYASH_BIN" --backend vm /dev/null 2>&1 | tr -d '\r' | tee /tmp/hv1_debug.log >&2 out=$(awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}' /tmp/hv1_debug.log) else out=$(env -i PATH="$PATH" \ HAKO_TRACE_EXECUTION="${HAKO_TRACE_EXECUTION:-0}" \ HAKO_ROUTE_HAKOVM=1 HAKO_VERIFY_V1_FORCE_HAKOVM=1 \ NYASH_VERIFY_JSON="$(cat "$json_path")" \ "$NYASH_BIN" --backend vm /dev/null 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}') fi if [[ "$out" =~ ^-?[0-9]+$ ]]; then # echo numeric rc and return success; caller normalizes/returns as exit code echo "$out" return 0 fi return 1 }