diff --git a/tools/smokes/v2/lib/output_validator.sh b/tools/smokes/v2/lib/output_validator.sh new file mode 100644 index 00000000..8a9f6aca --- /dev/null +++ b/tools/smokes/v2/lib/output_validator.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Output Validator SSOT +# Single Source of Truth for smoke test output validation +# +# Box-First principle: Separation of concerns +# - Extract: Pattern-based extraction (numeric lines, etc.) +# - Assert: Comparison and failure reporting +# +# Fail-Fast principle: Explicit errors with clear messages + +set -e + +# Extract numeric lines from output +# Usage: extract_numeric_lines N < output.txt +# Returns: First N lines that match numeric pattern (including negative numbers) +extract_numeric_lines() { + local limit="$1" + if [ -z "$limit" ]; then + echo "[ERROR] extract_numeric_lines: limit parameter required" >&2 + return 1 + fi + + # Pattern: optional minus, followed by digits + # Use head to limit output + grep -E '^-?[0-9]+$' | head -n "$limit" | tr -d '\r' +} + +# Assert multiline string equality +# Usage: assert_equals_multiline EXPECTED ACTUAL +# Returns: 0 if equal, 1 if different (with error message) +assert_equals_multiline() { + local expected="$1" + local actual="$2" + + if [ -z "$expected" ]; then + echo "[ERROR] assert_equals_multiline: expected parameter required" >&2 + return 1 + fi + + if [ "$actual" = "$expected" ]; then + return 0 + else + echo "[FAIL] Output mismatch" >&2 + echo "[EXPECTED]:" >&2 + echo "$expected" >&2 + echo "[ACTUAL]:" >&2 + echo "$actual" >&2 + return 1 + fi +} + +# Validate numeric output (extract + assert in one step) +# Usage: validate_numeric_output EXPECTED_LINES EXPECTED_VALUE OUTPUT +# Returns: 0 if valid, 1 if invalid +validate_numeric_output() { + local expected_lines="$1" + local expected_value="$2" + local output="$3" + + if [ -z "$expected_lines" ] || [ -z "$expected_value" ]; then + echo "[ERROR] validate_numeric_output: expected_lines and expected_value required" >&2 + return 1 + fi + + local clean + clean=$(printf "%s\n" "$output" | extract_numeric_lines "$expected_lines") + + assert_equals_multiline "$expected_value" "$clean" +} + +# Box-First design notes: +# 1. extract_numeric_lines: Single Responsibility - extraction only +# 2. assert_equals_multiline: Single Responsibility - comparison only +# 3. validate_numeric_output: Composition box - combines extract + assert +# 4. All functions follow Fail-Fast principle - explicit parameter checks +# 5. Clear separation of concerns - easy to extend with new patterns diff --git a/tools/smokes/v2/profiles/integration/apps/phase103_if_only_early_return_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase103_if_only_early_return_vm.sh index 444349f4..03eb346e 100644 --- a/tools/smokes/v2/profiles/integration/apps/phase103_if_only_early_return_vm.sh +++ b/tools/smokes/v2/profiles/integration/apps/phase103_if_only_early_return_vm.sh @@ -2,6 +2,7 @@ # Phase 103 P1: if-only early return regression (VM) source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/output_validator.sh" export SMOKES_USE_PYVM=0 require_env || exit 2 @@ -26,12 +27,10 @@ if [ "$EXIT_CODE" -eq 124 ]; then FAIL_COUNT=$((FAIL_COUNT + 1)) elif [ "$EXIT_CODE" -eq 0 ]; then EXPECTED=$'7\n2' - CLEAN=$(printf "%s\n" "$OUTPUT" | grep -E '^-?[0-9]+$' | head -n 2 | paste -sd '\n' - | tr -d '\r') - if [ "$CLEAN" = "$EXPECTED" ]; then + if validate_numeric_output 2 "$EXPECTED" "$OUTPUT"; then echo "[PASS] Output verified: 7 then 2" PASS_COUNT=$((PASS_COUNT + 1)) else - echo "[FAIL] Unexpected output (expected lines: 7 then 2)" echo "[INFO] output (tail):" echo "$OUTPUT" | tail -n 50 || true FAIL_COUNT=$((FAIL_COUNT + 1)) diff --git a/tools/smokes/v2/profiles/integration/apps/phase103_if_only_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase103_if_only_vm.sh index 20b1a2f9..24c01dd6 100644 --- a/tools/smokes/v2/profiles/integration/apps/phase103_if_only_vm.sh +++ b/tools/smokes/v2/profiles/integration/apps/phase103_if_only_vm.sh @@ -4,6 +4,7 @@ # Verifies that if-only lowering (nested if + merge) is stable in VM backend. source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/output_validator.sh" export SMOKES_USE_PYVM=0 require_env || exit 2 @@ -28,12 +29,10 @@ if [ "$EXIT_CODE" -eq 124 ]; then FAIL_COUNT=$((FAIL_COUNT + 1)) elif [ "$EXIT_CODE" -eq 0 ]; then EXPECTED="2" - CLEAN=$(printf "%s\n" "$OUTPUT" | grep -E '^-?[0-9]+$' | head -n 1 | tr -d '\r') - if [ "$CLEAN" = "$EXPECTED" ]; then + if validate_numeric_output 1 "$EXPECTED" "$OUTPUT"; then echo "[PASS] Output verified: 2" PASS_COUNT=$((PASS_COUNT + 1)) else - echo "[FAIL] Unexpected output (expected: 2)" echo "[INFO] output (tail):" echo "$OUTPUT" | tail -n 50 || true FAIL_COUNT=$((FAIL_COUNT + 1)) diff --git a/tools/smokes/v2/profiles/integration/apps/phase113_if_only_partial_assign_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase113_if_only_partial_assign_vm.sh index c4a17d87..558630ed 100644 --- a/tools/smokes/v2/profiles/integration/apps/phase113_if_only_partial_assign_vm.sh +++ b/tools/smokes/v2/profiles/integration/apps/phase113_if_only_partial_assign_vm.sh @@ -4,6 +4,7 @@ # Verifies that if-only lowering with partial assignment preserves original value on else side. source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/output_validator.sh" export SMOKES_USE_PYVM=0 require_env || exit 2 @@ -28,12 +29,10 @@ if [ "$EXIT_CODE" -eq 124 ]; then FAIL_COUNT=$((FAIL_COUNT + 1)) elif [ "$EXIT_CODE" -eq 0 ]; then EXPECTED=$'1\n2' - CLEAN=$(printf "%s\n" "$OUTPUT" | grep -E '^-?[0-9]+$' | head -n 2 | tr -d '\r') - if [ "$CLEAN" = "$EXPECTED" ]; then + if validate_numeric_output 2 "$EXPECTED" "$OUTPUT"; then echo "[PASS] Output verified: 1\\n2" PASS_COUNT=$((PASS_COUNT + 1)) else - echo "[FAIL] Unexpected output (expected: 1\\n2)" echo "[INFO] output (tail):" echo "$OUTPUT" | tail -n 50 || true FAIL_COUNT=$((FAIL_COUNT + 1)) diff --git a/tools/smokes/v2/profiles/integration/apps/phase114_if_only_return_then_post_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase114_if_only_return_then_post_vm.sh index aad52152..acdde57f 100644 --- a/tools/smokes/v2/profiles/integration/apps/phase114_if_only_return_then_post_vm.sh +++ b/tools/smokes/v2/profiles/integration/apps/phase114_if_only_return_then_post_vm.sh @@ -4,6 +4,7 @@ # Verifies that if-only with early return and post-if statements works correctly. source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/output_validator.sh" export SMOKES_USE_PYVM=0 require_env || exit 2 @@ -28,12 +29,10 @@ if [ "$EXIT_CODE" -eq 124 ]; then FAIL_COUNT=$((FAIL_COUNT + 1)) elif [ "$EXIT_CODE" -eq 0 ]; then EXPECTED=$'7\n2' - CLEAN=$(printf "%s\n" "$OUTPUT" | grep -E '^-?[0-9]+$' | head -n 2 | tr -d '\r') - if [ "$CLEAN" = "$EXPECTED" ]; then + if validate_numeric_output 2 "$EXPECTED" "$OUTPUT"; then echo "[PASS] Output verified: 7\\n2" PASS_COUNT=$((PASS_COUNT + 1)) else - echo "[FAIL] Unexpected output (expected: 7\\n2)" echo "[INFO] output (tail):" echo "$OUTPUT" | tail -n 50 || true FAIL_COUNT=$((FAIL_COUNT + 1))