DevOps_Debugger / tests /test_rewards.py
printf-sourav's picture
Initial commit
27cdb3e
Raw
History Blame Contribute Delete
10.8 kB
"""
Tests for the Reward Engine — 100% coverage of all reward signals.
"""
import pytest
from rewards.engine import RewardEngine
from executor.docker_executor import ExecutionResult
from scenarios.registry import Scenario
def _make_scenario(**kwargs):
"""Create a test scenario with sensible defaults."""
defaults = dict(
id="test_scenario",
level=1,
description="Test scenario",
initial_state={},
success_condition=lambda output: "success" in output.lower(),
hint_commands=["pip install flask"],
error_fingerprint=r"ModuleNotFoundError",
)
defaults.update(kwargs)
return Scenario(**defaults)
def _make_result(**kwargs):
"""Create a test ExecutionResult with sensible defaults."""
defaults = dict(stdout="", stderr="", exit_code=0, timed_out=False, blocked=False, block_reason="")
defaults.update(kwargs)
return ExecutionResult(**defaults)
class TestRewardSuccess:
"""Test the success reward signal."""
def test_success_gives_positive_reward(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="Successfully installed flask\nSuccess")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=1,
command_history=["pip install flask"],
prev_error_log="ModuleNotFoundError",
curr_error_log="Successfully installed flask",
)
assert breakdown.get("success", 0) == 10.0
assert total > 0
def test_no_success_gives_no_success_reward(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="some output", stderr="still broken")
total, breakdown = engine.compute_reward(
action="ls",
result=result,
scenario=scenario,
step_count=1,
command_history=["ls"],
prev_error_log="error",
curr_error_log="still broken",
)
assert "success" not in breakdown
class TestRewardCorrectCommand:
"""Test the correct_command reward signal."""
def test_hint_command_gets_bonus(self):
engine = RewardEngine()
scenario = _make_scenario(hint_commands=["pip install flask"])
result = _make_result(stdout="installed")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=1,
command_history=["pip install flask"],
prev_error_log="error",
curr_error_log="installed",
)
assert breakdown.get("correct_command", 0) == 1.5
def test_wrong_command_no_bonus(self):
engine = RewardEngine()
scenario = _make_scenario(hint_commands=["pip install flask"])
result = _make_result(stdout="output")
total, breakdown = engine.compute_reward(
action="apt-get install python",
result=result,
scenario=scenario,
step_count=1,
command_history=["apt-get install python"],
prev_error_log="error",
curr_error_log="output",
)
assert "correct_command" not in breakdown
class TestRewardProgress:
"""Test the progress and no_progress signals."""
def test_progress_when_error_changes(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="new output")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=1,
command_history=["pip install flask"],
prev_error_log="ModuleNotFoundError: No module named 'flask'",
curr_error_log="installed successfully",
)
assert breakdown.get("progress", 0) == 1.0
def test_no_progress_when_identical_logs(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="same error")
same_log = "ModuleNotFoundError: No module named 'flask'"
total, breakdown = engine.compute_reward(
action="echo hello",
result=result,
scenario=scenario,
step_count=2,
command_history=["echo hello"],
prev_error_log=same_log,
curr_error_log=same_log,
)
assert breakdown.get("no_progress", 0) == -1.0
class TestRewardEfficiency:
"""Test the efficiency_bonus signal."""
def test_efficiency_bonus_when_solved_fast(self):
engine = RewardEngine()
scenario = _make_scenario(hint_commands=["pip install flask"])
result = _make_result(stdout="Success")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=1,
command_history=["pip install flask"],
prev_error_log="error",
curr_error_log="Success",
)
assert breakdown.get("efficiency_bonus", 0) == 2.0
def test_no_efficiency_bonus_when_too_many_steps(self):
engine = RewardEngine()
scenario = _make_scenario(hint_commands=["pip install flask"])
result = _make_result(stdout="Success")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=5,
command_history=["a", "b", "c", "d", "pip install flask"],
prev_error_log="error",
curr_error_log="Success",
)
assert "efficiency_bonus" not in breakdown
class TestRewardPenalties:
"""Test penalty signals."""
def test_blocked_command_penalty(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(blocked=True, block_reason="Command 'foo' is not in the whitelist")
total, breakdown = engine.compute_reward(
action="foo",
result=result,
scenario=scenario,
step_count=1,
command_history=["foo"],
prev_error_log="error",
curr_error_log="blocked",
)
assert breakdown.get("invalid_command", 0) == -2.0
def test_dangerous_command_penalty(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(blocked=True, block_reason="Dangerous blocklist pattern matched")
total, breakdown = engine.compute_reward(
action="rm -rf /",
result=result,
scenario=scenario,
step_count=1,
command_history=["rm -rf /"],
prev_error_log="error",
curr_error_log="blocked",
)
assert breakdown.get("dangerous_command", 0) == -10.0
def test_timeout_penalty(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(timed_out=True)
total, breakdown = engine.compute_reward(
action="sleep 100",
result=result,
scenario=scenario,
step_count=1,
command_history=["sleep 100"],
prev_error_log="error",
curr_error_log="timeout",
)
assert breakdown.get("timeout", 0) == -5.0
def test_repeated_command_penalty(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="output")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=2,
command_history=["pip install flask", "pip install flask"],
prev_error_log="error",
curr_error_log="output",
)
assert breakdown.get("repeated_command", 0) == -1.5
def test_step_cost_always_applied(self):
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(stdout="output")
total, breakdown = engine.compute_reward(
action="ls",
result=result,
scenario=scenario,
step_count=1,
command_history=["ls"],
prev_error_log="error",
curr_error_log="output",
)
assert breakdown.get("step_cost", 0) == -0.2
class TestRewardCombinations:
"""Test reward signal combinations."""
def test_perfect_solve_gives_max_reward(self):
engine = RewardEngine()
scenario = _make_scenario(hint_commands=["pip install flask"])
result = _make_result(stdout="Successfully installed flask. Success")
total, breakdown = engine.compute_reward(
action="pip install flask",
result=result,
scenario=scenario,
step_count=1,
command_history=["pip install flask"],
prev_error_log="ModuleNotFoundError",
curr_error_log="installed",
)
# Should get: success(10) + correct_command(1.5) + progress(1) + efficiency(2) + step_cost(-0.2) = 14.3
assert total > 14.0
assert "success" in breakdown
assert "correct_command" in breakdown
assert "efficiency_bonus" in breakdown
def test_blocked_command_short_circuits(self):
"""Blocked commands should only get step_cost + the block penalty."""
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(blocked=True, block_reason="not in whitelist")
total, breakdown = engine.compute_reward(
action="foo",
result=result,
scenario=scenario,
step_count=1,
command_history=["foo"],
prev_error_log="error",
curr_error_log="blocked",
)
assert len(breakdown) == 2 # step_cost + invalid_command
assert "success" not in breakdown
assert "progress" not in breakdown
def test_timed_out_short_circuits(self):
"""Timed out commands should only get step_cost + timeout penalty."""
engine = RewardEngine()
scenario = _make_scenario()
result = _make_result(timed_out=True)
total, breakdown = engine.compute_reward(
action="sleep 999",
result=result,
scenario=scenario,
step_count=1,
command_history=["sleep 999"],
prev_error_log="error",
curr_error_log="timeout",
)
assert len(breakdown) == 2 # step_cost + timeout
assert total == -5.2