Files
dotnet__skills/msbench/shared/eval_helpers/assertion_runner.sh
2026-03-13 11:48:13 +01:00

259 lines
7.4 KiB
Bash

#!/bin/bash
# assertion_runner.sh — Generic assertion evaluation framework
# Source this from test.sh to get assertion helper functions.
#
# Usage in test.sh:
# source /app/assertion_runner.sh
# init_assertions
# assert_output_contains "expected text"
# assert_file_exists "*.csv"
# finalize_assertions
# write_eval_result "$INSTANCE_ID" "$ALL_PASSED"
set -euo pipefail
# --- Globals ---
TOTAL_ASSERTIONS=0
PASSED_ASSERTIONS=0
# Collect agent output from known locations
AGENT_OUTPUT=""
if [ -f /testbed/agent_output.txt ]; then
AGENT_OUTPUT=$(cat /testbed/agent_output.txt 2>/dev/null || true)
fi
if [ -f /output/agent_output.txt ]; then
AGENT_OUTPUT="${AGENT_OUTPUT}$(cat /output/agent_output.txt 2>/dev/null || true)"
fi
if [ -f /output/trajectory.txt ]; then
AGENT_OUTPUT="${AGENT_OUTPUT}$(cat /output/trajectory.txt 2>/dev/null || true)"
fi
# --- Initialization ---
init_assertions() {
TOTAL_ASSERTIONS=0
PASSED_ASSERTIONS=0
}
# --- Assertion Functions ---
# Check if agent output or any modified file contains a string (case-insensitive)
assert_output_contains() {
local expected="$1"
local label="${2:-output_contains: $expected}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if echo "$AGENT_OUTPUT" | grep -qi "$expected" 2>/dev/null; then
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
fi
echo "FAIL: $label"
return 0 # Don't exit on failure — we count them
}
# Check that agent output does NOT contain a string (case-insensitive)
assert_output_not_contains() {
local unexpected="$1"
local label="${2:-output_not_contains: $unexpected}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if echo "$AGENT_OUTPUT" | grep -qi "$unexpected" 2>/dev/null; then
echo "FAIL: $label (found unwanted content)"
return 0
fi
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
}
# Check if agent output matches a regex pattern (case-insensitive, Perl-compatible)
assert_output_matches() {
local pattern="$1"
local label="${2:-output_matches: $pattern}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if echo "$AGENT_OUTPUT" | grep -qPi "$pattern" 2>/dev/null; then
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
fi
echo "FAIL: $label"
return 0
}
# Check that agent output does NOT match a regex pattern
assert_output_not_matches() {
local pattern="$1"
local label="${2:-output_not_matches: $pattern}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if echo "$AGENT_OUTPUT" | grep -qPi "$pattern" 2>/dev/null; then
echo "FAIL: $label (matched unwanted pattern)"
return 0
fi
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
}
# Check if a file matching a glob pattern exists under /testbed/
assert_file_exists() {
local pattern="$1"
local label="${2:-file_exists: $pattern}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if compgen -G "/testbed/$pattern" > /dev/null 2>&1; then
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
fi
# Try find for recursive glob patterns
if find /testbed/ -path "/testbed/$pattern" -print -quit 2>/dev/null | grep -q .; then
echo "PASS: $label (found via find)"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
fi
# Handle ** glob patterns
if [[ "$pattern" == *"**"* ]]; then
local base_pattern="${pattern##**/}"
if find /testbed/ -name "$base_pattern" -print -quit 2>/dev/null | grep -q .; then
echo "PASS: $label (found via recursive search)"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
fi
fi
echo "FAIL: $label"
return 0
}
# Check that a file matching a glob pattern does NOT exist
assert_file_not_exists() {
local pattern="$1"
local label="${2:-file_not_exists: $pattern}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
if compgen -G "/testbed/$pattern" > /dev/null 2>&1; then
echo "FAIL: $label (file exists)"
return 0
fi
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
}
# Check if a file contains specific content (case-insensitive)
assert_file_contains() {
local filepath="$1"
local content="$2"
local label="${3:-file_contains: $filepath -> $content}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
# Handle glob patterns in filepath
local found=false
for f in /testbed/$filepath; do
if [ -f "$f" ] && grep -qi "$content" "$f" 2>/dev/null; then
found=true
break
fi
done
# Try recursive find for ** patterns
if [ "$found" = false ] && [[ "$filepath" == *"**"* ]]; then
local base_pattern="${filepath##**/}"
while IFS= read -r f; do
if grep -qi "$content" "$f" 2>/dev/null; then
found=true
break
fi
done < <(find /testbed/ -name "$base_pattern" 2>/dev/null)
fi
if [ "$found" = true ]; then
echo "PASS: $label"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
else
echo "FAIL: $label"
fi
return 0
}
# exit_success is always true in MSBench (agent ran to completion)
assert_exit_success() {
local label="${1:-exit_success}"
TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1))
echo "PASS: $label (always true in MSBench)"
PASSED_ASSERTIONS=$((PASSED_ASSERTIONS + 1))
return 0
}
# --- Finalization ---
# Check if all assertions passed
finalize_assertions() {
if [ "$TOTAL_ASSERTIONS" -gt 0 ] && [ "$PASSED_ASSERTIONS" -eq "$TOTAL_ASSERTIONS" ]; then
ALL_PASSED=true
else
ALL_PASSED=false
fi
echo ""
echo "=== Assertion Summary ==="
echo "Passed: $PASSED_ASSERTIONS / $TOTAL_ASSERTIONS"
echo "Resolved: $ALL_PASSED"
}
# --- Reward Computation ---
# Computes a fractional reward based on assertion pass rate (0.0 to 1.0).
# This mirrors the SkillValidator scoring model where each assertion has
# equal weight and the pass rate is the meaningful signal.
# The fractional reward is written directly to reward.txt so that MSBench
# can report variable-scale success (not just binary 0/1).
# Must be called after finalize_assertions.
compute_reward() {
if [ "$TOTAL_ASSERTIONS" -gt 0 ]; then
REWARD=$(python3 -c "print(round($PASSED_ASSERTIONS / $TOTAL_ASSERTIONS, 4))")
else
REWARD="0.0"
fi
echo ""
echo "=== Reward Calculation ==="
echo "Formula: REWARD = PASSED_ASSERTIONS / TOTAL_ASSERTIONS"
echo "Values:"
echo " PASSED_ASSERTIONS = $PASSED_ASSERTIONS"
echo " TOTAL_ASSERTIONS = $TOTAL_ASSERTIONS"
echo " ALL_PASSED = $ALL_PASSED"
echo "Result: REWARD = $REWARD (fractional, variable-scale)"
echo "========================="
echo ""
mkdir -p /logs/verifier
echo "$REWARD" > /logs/verifier/reward.txt
echo "Reward: $REWARD"
}
# --- Build Check ---
# Try to build the .NET project and return success/failure
check_dotnet_build() {
local build_dir="${1:-/testbed}"
local BUILD_SUCCESS=0
if ls "$build_dir"/*.sln "$build_dir"/*.csproj 2>/dev/null | head -1 > /dev/null 2>&1; then
if dotnet build "$build_dir" 2>&1 | tee /output/build.log; then
BUILD_SUCCESS=1
fi
else
BUILD_SUCCESS=1 # No buildable project — skip
fi
echo "$BUILD_SUCCESS"
}