Keep K3 suite selection and report-schema scoring in bash, merge K3/vision dataset_args into dpv4 yamls, and pin EvalScope at 735d920ee911 with local patches. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
6.1 KiB
Python
172 lines
6.1 KiB
Python
"""Unit tests for cache resume behavior."""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
import pytest
|
|
|
|
from evalscope.api.dataset import MemoryDataset, Sample
|
|
from evalscope.api.evaluator import TaskState
|
|
from evalscope.api.evaluator.cache import CacheManager, ModelResult
|
|
from evalscope.utils.io_utils import OutputsStructure
|
|
|
|
|
|
def _make_task_state(sample_id: int, group_id: Optional[int] = None) -> TaskState:
|
|
sample = Sample(id=sample_id, group_id=group_id, input=f'question-{sample_id}', target='answer')
|
|
return TaskState(model='mock-model', sample=sample, completed=True)
|
|
|
|
|
|
def _review_row(sample_id: int, value: float) -> Dict[str, Any]:
|
|
return {
|
|
'index': sample_id,
|
|
'target': 'answer',
|
|
'messages': [],
|
|
'sample_score': {
|
|
'sample_id': sample_id,
|
|
'score': {
|
|
'value': {
|
|
'acc': value
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def _make_manager(tmp_path: Path) -> CacheManager:
|
|
outputs = OutputsStructure(outputs_dir=str(tmp_path), is_make=True)
|
|
return CacheManager(outputs=outputs, model_name='mock-model', benchmark_name='mock-bench')
|
|
|
|
|
|
def _write_review_cache(manager: CacheManager, subset: str, rows: list[Dict[str, Any]]) -> str:
|
|
review_file = manager.get_review_cache_path(subset)
|
|
with open(review_file, 'w', encoding='utf-8') as f:
|
|
for row in rows:
|
|
f.write(json.dumps(row) + '\n')
|
|
return review_file
|
|
|
|
|
|
@pytest.fixture
|
|
def caplog_evalscope(
|
|
caplog: pytest.LogCaptureFixture,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> pytest.LogCaptureFixture:
|
|
"""Capture records from EvalScope's non-propagating logger."""
|
|
monkeypatch.setattr(logging.getLogger('evalscope'), 'propagate', True)
|
|
caplog.set_level(logging.WARNING, logger='evalscope')
|
|
return caplog
|
|
|
|
|
|
def test_filter_review_cache_intersects_and_dedupes(
|
|
tmp_path: Path,
|
|
caplog_evalscope: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
task_states = [_make_task_state(0), _make_task_state(1)]
|
|
_write_review_cache(
|
|
manager,
|
|
'default',
|
|
[
|
|
_review_row(0, 1.0),
|
|
_review_row(1, 0.0),
|
|
_review_row(1, 1.0),
|
|
_review_row(99, 0.5),
|
|
],
|
|
)
|
|
|
|
cached_scores, remaining_states = manager.filter_review_cache('default', task_states)
|
|
|
|
assert [score.sample_id for score in cached_scores] == [0, 1]
|
|
assert cached_scores[1].score.main_value == 1.0
|
|
assert remaining_states == []
|
|
assert 'Dropped 1 orphan and 1 duplicate rows' in caplog_evalscope.text
|
|
|
|
|
|
def test_filter_review_cache_does_not_merge_distinct_repeat_samples(tmp_path: Path) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
task_states = [_make_task_state(10, group_id=5), _make_task_state(11, group_id=5)]
|
|
_write_review_cache(manager, 'default', [_review_row(10, 1.0), _review_row(11, 0.0)])
|
|
|
|
cached_scores, remaining_states = manager.filter_review_cache('default', task_states)
|
|
|
|
assert [score.sample_id for score in cached_scores] == [10, 11]
|
|
assert remaining_states == []
|
|
|
|
|
|
def test_filter_review_cache_returns_all_states_when_no_cache(tmp_path: Path) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
task_states = [_make_task_state(0), _make_task_state(1)]
|
|
|
|
cached_scores, remaining_states = manager.filter_review_cache('default', task_states)
|
|
|
|
assert cached_scores == []
|
|
assert remaining_states == task_states
|
|
|
|
|
|
def test_filter_review_cache_keeps_uncovered_states(tmp_path: Path) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
task_states = [_make_task_state(0), _make_task_state(1), _make_task_state(2)]
|
|
_write_review_cache(manager, 'default', [_review_row(2, 0.0)])
|
|
|
|
cached_scores, remaining_states = manager.filter_review_cache('default', task_states)
|
|
|
|
assert [score.sample_id for score in cached_scores] == [2]
|
|
assert [state.sample_id for state in remaining_states] == [0, 1]
|
|
|
|
|
|
def test_filter_review_cache_skips_malformed_and_schema_invalid_rows(
|
|
tmp_path: Path,
|
|
caplog_evalscope: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
task_states = [_make_task_state(0), _make_task_state(1)]
|
|
review_file = _write_review_cache(
|
|
manager,
|
|
'default',
|
|
[
|
|
_review_row(0, 1.0),
|
|
{'legacy_field': 'not a valid ReviewResult'},
|
|
_review_row(1, 0.0),
|
|
],
|
|
)
|
|
with open(review_file, 'a', encoding='utf-8') as f:
|
|
f.write('{"sample_score":')
|
|
|
|
cached_scores, remaining_states = manager.filter_review_cache('default', task_states)
|
|
|
|
assert [score.sample_id for score in cached_scores] == [0, 1]
|
|
assert remaining_states == []
|
|
assert 'Skipping invalid JSONL row' in caplog_evalscope.text
|
|
assert 'Skipping invalid review cache row' in caplog_evalscope.text
|
|
|
|
|
|
def test_filter_prediction_cache_skips_malformed_and_schema_invalid_rows(
|
|
tmp_path: Path,
|
|
caplog_evalscope: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
manager = _make_manager(tmp_path)
|
|
dataset = MemoryDataset(
|
|
samples=[
|
|
Sample(id=0, input='question-0', target='answer'),
|
|
Sample(id=1, input='question-1', target='answer'),
|
|
],
|
|
name='dummy',
|
|
)
|
|
valid_row = ModelResult.from_task_state(_make_task_state(0)).model_dump(mode='json')
|
|
incomplete_row = ModelResult.from_task_state(_make_task_state(1)).model_dump(mode='json')
|
|
incomplete_row['model_output'] = None
|
|
prediction_file = manager.get_prediction_cache_path('default')
|
|
with open(prediction_file, 'w', encoding='utf-8') as f:
|
|
f.write(json.dumps(valid_row) + '\n')
|
|
f.write(json.dumps(incomplete_row) + '\n')
|
|
f.write(json.dumps({'legacy_field': 'not a valid ModelResult'}) + '\n')
|
|
f.write('{"index":')
|
|
|
|
cached_task_states, filtered_dataset = manager.filter_prediction_cache('default', dataset)
|
|
|
|
assert [state.sample_id for state in cached_task_states] == [0]
|
|
assert [sample.id for sample in filtered_dataset.samples] == [1]
|
|
assert 'Skipping invalid JSONL row' in caplog_evalscope.text
|
|
assert 'Skipping invalid prediction cache row' in caplog_evalscope.text
|