"""Regression tests for the eval layer. Anchored to OFFICIAL benchmark grading behavior: - DROP: allennlp/simple-evals get_drop_metrics test cases - math: PRM800K grader semantics (sympy equivalence) - MCQ/gsm8k/BBH: extraction dispatch conventions Run: .venv/bin/python -m pytest tests/ -q (or python tests/test_eval.py) """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from evalharness.data.sample import Sample # noqa: E402 from evalharness.eval import evaluate, get_eval, list_evals # noqa: E402 from evalharness.eval.extractor import make_extractor # noqa: E402 from evalharness.eval.scorer import _drop_metrics # noqa: E402 def _mk(input_text='q', target='', **kw): return Sample(input=input_text, target=target, **kw) def test_drop_official_cases(): cases = [ (['test'], ['test'], (1.0, 100.0)), (['test'], ['testing'], (0.0, 0.0)), # token bags disjoint -> f1 0 (official) (['test test'], ['test'], (0.0, 100.0)), (['a'], ['b'], (0.0, 0.0)), (['1'], ['1.0'], (1.0, 100.0)), (['1'], ['2'], (0.0, 0.0)), (['ted', 'dan'], ['dan', 'ted'], (1.0, 100.0)), (['x'], ['y', 'z'], (0.0, 0.0)), ] for pred, gold, want in cases: got = _drop_metrics(pred, gold) assert got == want, f'{pred} vs {gold}: {got} != official {want}' def test_math_grader(): from evalharness.eval.math_grader import grade_answer assert grade_answer('0.5', '\\dfrac{1}{2}') assert not grade_answer('0.6', '\\dfrac{1}{2}') assert grade_answer('70000', '70,\\!000') assert not grade_answer('x=2', '2') assert grade_answer('1/2', '0.5') is True or grade_answer('1/2', '0.5') is False # deterministic def test_extractors(): box = make_extractor('math_boxed') assert box('\\boxed{42} done', _mk())[0] == '42' assert box('no box here', _mk())[1] is False cascade = make_extractor(['math_boxed', 'answer_phrase', 'last_number']) assert cascade('The answer is 7.', _mk())[0] == '7' assert cascade('total 3 apples and 5 pears', _mk())[0] == '5' letter = make_extractor('mcq_letter') assert letter('So the answer is (B).', _mk())[0] == 'B' assert letter('答案是C', _mk())[0] == 'C' spans = make_extractor('answer_spans') got, ok, _ = spans('Answer: Chaz Schilens\nAnswer: JaMarcus Russell', _mk()) assert ok and got == 'Chaz Schilens\nJaMarcus Russell' def test_gsm8k_end_to_end(): samples = [_mk(str(i), str(10 + i)) for i in range(4)] preds = ['\\boxed{10}', '#### 11', 'The answer is 12.', 'no idea'] rep = evaluate(samples, preds, get_eval('gsm8k')) assert rep.metrics['acc'] == 0.75 assert rep.num_failed_extractions == 1 def test_bbh_dispatch_by_target_format(): mc = _mk('q?', '(B)', metadata={'subset': 'date_understanding'}) ff = _mk('q?', 'True') ex = get_eval('bbh').resolve_extract() assert ex('So the answer is (B).', mc)[0] == 'B' assert ex('the answer is True', ff)[0] == 'True' def test_mcq_end_to_end(): samples = [_mk('q', 'B', choices=['a', 'b'])] * 2 rep = evaluate(samples, ['(B)', '答案是B'], get_eval('mmlu')) assert rep.metrics['acc'] == 1.0 def test_all_recipes_resolve(): from evalharness.eval.aggregator import get_aggregator from evalharness.eval.scorer import get_scorer for name in list_evals(): recipe = get_eval(name) recipe.resolve_extract() scorers = recipe.resolve_scorers() assert scorers recipe.resolve_aggregators() for spec in recipe.aggregators.values(): get_aggregator(spec[0] if isinstance(spec, tuple) else (spec or 'mean')) def test_judge_default_label(): from evalharness.eval.scorer import ScoreContext, get_scorer scorer = get_scorer('llm_judge') ctx = ScoreContext(judge=lambda msgs: 'zzz', params={ # no A/B/C anywhere 'prompt_template': '{prediction}', 'labels': {'A': {'is_correct': 1.0}, 'B': {'is_correct': 0.0}, 'C': {'is_correct': 0.0}}, 'default_label': 'C', 'primary': 'is_correct'}) scores, details = scorer('x', 'y', _mk('q'), ctx) assert scores['is_correct'] == 0.0 and details['is_correct']['judge_label'] == 'C' def test_aggregators(): from evalharness.eval.aggregator import grouped_avg, mean, unbiased_pass_at_k from evalharness.eval.record import SampleResult rs = [SampleResult(raw_prediction='', scores={'acc': 1.0}, group_key='x'), SampleResult(raw_prediction='', scores={'acc': 0.0}, group_key='x'), SampleResult(raw_prediction='', scores={'acc': 1.0}, group_key='y')] assert mean(rs, 'acc') == 2 / 3 assert grouped_avg(rs, 'acc') == {'x': 0.5, 'y': 1.0} assert unbiased_pass_at_k(10, 10, 5) == 1.0 assert abs(unbiased_pass_at_k(10, 5, 5) - (1 - 1 / 252)) < 1e-9 # C(5,5)/C(10,5)=1/252 def test_viz_renders(tmp_path=None): from evalharness.viz import render samples = [_mk('q', '1')] * 3 rep = evaluate(samples, ['\\boxed{1}'] * 3, get_eval('gsm8k'), model='m1') text = render(rep, style='text') assert 'acc' in text and '100.0%' in text md = render(rep, style='md') assert '| metric |' in md errs = render(rep, style='errors') assert 'Errors' in errs or errs == '' # no errors -> empty-ish if __name__ == '__main__': fails = 0 for name, fn in sorted({k: v for k, v in globals().items() if k.startswith('test_') and callable(v)}.items()): try: fn() print(f'PASS {name}') except AssertionError as e: fails += 1 print(f'FAIL {name}: {e}') except Exception as e: fails += 1 print(f'ERROR {name}: {type(e).__name__}: {e}') sys.exit(1 if fails else 0)