#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BIN="${NYASH_BIN:-$ROOT/target/release/hakorune}" if [ ! -x "$BIN" ]; then echo "[TEST] hakorune not built: $BIN" >&2 echo "Run: cargo build --release" >&2 exit 2 fi TARGET_DIR="$ROOT/tools/hako_check/tests" fail=0 skipped_count=0 run_case() { local dir="$1" # Skip HC017 (Non-ASCII Quotes) until UTF-8 byte-level support lands case "$(basename "$dir")" in HC017_*) echo "[TEST] skip - HC017 (non_ascii_quotes) - UTF-8 byte-level support required" skipped_count=$((skipped_count + 1)) return ;; esac local expected="$dir/expected.json" local input_ok="$dir/ok.hako" local input_ng="$dir/ng.hako" if [ ! -f "$expected" ]; then echo "[TEST] skip (no expected): $dir"; return; fi if [ ! -f "$input_ok" ] && [ ! -f "$input_ng" ]; then echo "[TEST] skip (no inputs): $dir"; return; fi local tmp_out="/tmp/hako_test_$$.json" # Build a tiny wrapper program to call HakoAnalyzerBox.run with constructed argv local path_ok text_ok local path_ng text_ng if [ -f "$input_ok" ]; then path_ok="$input_ok" text_ok="$(sed 's/\r$//' "$input_ok")" else : fi if [ -f "$input_ng" ]; then path_ng="$input_ng" text_ng="$(sed 's/\r$//' "$input_ng")" else : fi # Build argv array for analyzer CLI (preserve newlines in text) ARGS=( --debug --format json-lsp ) # Restrict rules to the target under test for stability local base base="$(basename "$dir")" local rules_key="" case "$base" in HC011_*) rules_key="dead_methods" ;; HC012_*) rules_key="dead_static_box" ;; HC013_*) rules_key="duplicate_method" ;; HC014_*) rules_key="missing_entrypoint" ;; HC015_*) rules_key="arity_mismatch" ;; HC016_*) rules_key="unused_alias" ;; HC017_*) rules_key="non_ascii_quotes" ;; HC018_*) rules_key="top_level_local" ;; HC021_*) rules_key="analyzer_io_safety" ;; HC022_*) rules_key="stage3_gate" ;; HC031_*) rules_key="brace_heuristics" ;; *) rules_key="" ;; esac if [ -n "$rules_key" ]; then ARGS+=( --rules "$rules_key" ); fi if [ -f "$input_ok" ]; then ARGS+=( --source-file "$path_ok" "$text_ok" ); fi if [ -f "$input_ng" ]; then ARGS+=( --source-file "$path_ng" "$text_ng" ); fi # Directly invoke analyzer CLI with args via '--', avoid wrapper/FS # Ensure plugin path is set export LD_LIBRARY_PATH="${ROOT}/target/release:${LD_LIBRARY_PATH:-}" NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_SEAM_TOLERANT=1 HAKO_PARSER_SEAM_TOLERANT=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 NYASH_USING_AST=1 \ NYASH_DISABLE_PLUGINS=1 \ NYASH_BOX_FACTORY_POLICY=builtin_first \ NYASH_JSON_ONLY=1 \ "$BIN" --backend vm "$ROOT/tools/hako_check/cli.hako" -- "${ARGS[@]}" >"$tmp_out" 2>&1 || true # Extract diagnostics JSON (one-line or pretty block) tmp_json="/tmp/hako_test_json_$$.json" json_line=$(grep -m1 '^\{"diagnostics"' "$tmp_out" || true) if [ -n "$json_line" ] && echo "$json_line" | grep -q '\]}' ; then echo "$json_line" > "$tmp_json" else json_block=$(awk '/^\{"diagnostics"/{f=1} f{print} /\]\}/{exit}' "$tmp_out" ) if [ -z "$json_block" ]; then echo "[TEST/ERROR] no diagnostics JSON found; possible VM error. log head:" >&2 sed -n '1,120p' "$tmp_out" >&2 || true json_block='{"diagnostics":[]}' fi printf "%s\n" "$json_block" > "$tmp_json" fi # Normalize absolute paths to basenames for stable comparison tmp_norm="/tmp/hako_test_norm_$$.json" cp "$tmp_json" "$tmp_norm" if [ -f "$input_ok" ]; then base_ok="$(basename "$input_ok")"; abs_ok="$input_ok" sed -i "s#\"file\":\"$abs_ok\"#\"file\":\"$base_ok\"#g" "$tmp_norm" sed -i "s#${abs_ok//\//\/}#${base_ok//\//\/}#g" "$tmp_norm" fi if [ -f "$input_ng" ]; then base_ng="$(basename "$input_ng")"; abs_ng="$input_ng" sed -i "s#\"file\":\"$abs_ng\"#\"file\":\"$base_ng\"#g" "$tmp_norm" sed -i "s#${abs_ng//\//\/}#${base_ng//\//\/}#g" "$tmp_norm" fi # Align trailing blank line behavior to expected (tolerate one extra blank line) if [ -f "$expected" ]; then if [ -z "$(tail -n1 "$tmp_norm")" ]; then :; else if [ -z "$(tail -n1 "$expected")" ]; then printf "\n" >> "$tmp_norm"; fi fi fi # Replace absolute path occurrences in message with PLACEHOLDER if [ -f "$input_ng" ]; then sed -i "s#${abs_ng//\//\/}#PLACEHOLDER#g" "$tmp_norm" fi if ! diff -u "$expected" "$tmp_norm" >/dev/null; then echo "[TEST/FAIL] $dir" >&2 diff -u "$expected" "$tmp_norm" || true fail=$((fail+1)) else echo "[TEST/OK] $dir" fi rm -f "$tmp_out" "$tmp_norm" "$tmp_json" } # Handle arguments: if provided, test only specified dirs; otherwise test all if [ $# -gt 0 ]; then # Test only specified directories for arg in "$@"; do # Convert relative path to absolute if [[ "$arg" == /* ]]; then d="$arg" else d="$ROOT/$arg" fi [ -d "$d" ] || { echo "[TEST] not a directory: $d" >&2; continue; } run_case "$d" done else # Test all directories for d in "$TARGET_DIR"/*; do [ -d "$d" ] || continue run_case "$d" done fi if [ $fail -ne 0 ]; then echo "[TEST/SUMMARY] failures=$fail, skipped=$skipped_count" >&2 exit 1 fi if [ $skipped_count -gt 0 ]; then echo "[TEST/SUMMARY] all passed, $skipped_count skipped" else echo "[TEST/SUMMARY] all green" fi exit 0