Skip to content

Commit 425219e

Browse files
authored
Merge pull request #1 from kaushikb11/add/tests
tests: add unit tests
2 parents 8333781 + 04e939d commit 425219e

15 files changed

Lines changed: 334 additions & 85 deletions

.gitignore

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,3 @@ test_vmf.db
169169
*.db
170170
vmf_metrics.db
171171
verifiers_monitor.db
172-
173-
# Development/test files
174-
test.py
175-
test_*.py
176-
demo_*.py
177-
check_*.html
178-
*.log
179-
*_GUIDE.md
180-
*_DESIGN.md
181-
*_ROADMAP.md
182-
*_IMPACT.md
183-
*_ASPECTS_TO_TRACK.md
184-
TRAINING_*.md

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.5.0
3+
rev: v5.0.0
44
hooks:
55
- id: trailing-whitespace
66
- id: end-of-file-fixer
@@ -10,7 +10,7 @@ repos:
1010
- id: check-merge-conflict
1111

1212
- repo: https://github.com/psf/black
13-
rev: 23.12.1
13+
rev: 24.10.0
1414
hooks:
1515
- id: black
1616
language_version: python3
@@ -22,7 +22,7 @@ repos:
2222
args: ["--profile", "black"]
2323

2424
- repo: https://github.com/pre-commit/mirrors-mypy
25-
rev: v1.5.1
25+
rev: v1.18.2
2626
hooks:
2727
- id: mypy
2828
additional_dependencies: [

pyproject.toml

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "verifiers-monitor"
7-
version = "0.1.0"
7+
version = "0.1.1"
88
description = "Observability framework for Verifiers RL training and evaluation"
99
authors = [
1010
{name = "Kaushik Bokka", email = "kaushikbokka@gmail.com"},
1111
]
1212
license = {text = "Apache-2.0"}
1313
readme = "README.md"
14-
requires-python = ">=3.8"
14+
requires-python = ">=3.9"
1515
keywords = ["machine-learning", "reinforcement-learning", "monitoring", "observability", "verifiers"]
1616
classifiers = [
1717
"Development Status :: 4 - Beta",
@@ -20,7 +20,6 @@ classifiers = [
2020
"License :: OSI Approved :: Apache Software License",
2121
"Operating System :: OS Independent",
2222
"Programming Language :: Python :: 3",
23-
"Programming Language :: Python :: 3.8",
2423
"Programming Language :: Python :: 3.9",
2524
"Programming Language :: Python :: 3.10",
2625
"Programming Language :: Python :: 3.11",
@@ -31,35 +30,29 @@ classifiers = [
3130
]
3231

3332
dependencies = [
34-
"fastapi>=0.68.0",
35-
"uvicorn>=0.15.0",
36-
"websockets>=10.0",
37-
"psutil>=5.8.0",
38-
"aiohttp>=3.8.0",
39-
"pyngrok>=7.0.0",
40-
"sqlmodel>=0.0.14",
41-
"rich>=10.0.0",
33+
"fastapi>=0.118.0",
34+
"uvicorn>=0.37.0",
35+
"websockets>=15.0.0",
36+
"psutil>=7.1.0",
37+
"aiohttp>=3.12.0",
38+
"pyngrok>=7.4.0",
39+
"sqlmodel>=0.0.25",
40+
"rich>=14.1.0",
4241
"verifiers",
4342
]
4443

4544
[project.optional-dependencies]
4645
dev = [
47-
"pytest>=6.0",
48-
"pytest-asyncio>=0.18.0",
49-
"black>=21.0.0",
50-
"isort>=5.0.0",
51-
"mypy>=0.910",
52-
"pre-commit>=2.15.0",
46+
"pytest>=8.4.0",
47+
"pytest-asyncio>=0.25.0",
48+
"black>=25.9.0",
49+
"isort>=6.1.0",
50+
"mypy>=1.18.0",
51+
"pre-commit>=4.0.0",
5352
]
5453

5554
analysis = [
56-
"pandas>=1.3.0",
57-
]
58-
59-
export = [
60-
"wandb>=0.12.0",
61-
"prometheus-client>=0.14.0",
62-
"pandas>=1.3.0",
55+
"pandas>=2.3.0",
6356
]
6457

6558
gpu = [

tests/test_collectors.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
Test metrics collection functionality
3+
"""
4+
5+
import time
6+
7+
import pytest
8+
9+
from verifiers_monitor.core.collectors import MetricsCollector
10+
11+
12+
def test_metrics_collector_creation(sqlite_storage):
13+
"""Test metrics collector creation"""
14+
collector = MetricsCollector(sqlite_storage)
15+
16+
assert collector.storage == sqlite_storage
17+
assert collector._evaluation_count == 0
18+
19+
20+
def test_training_step_collection(sqlite_storage):
21+
"""Test training step metrics collection"""
22+
collector = MetricsCollector(sqlite_storage)
23+
24+
# Collect training metrics
25+
metrics = {"step": 1, "loss": 2.5, "compute_time": 0.1, "timestamp": time.time()}
26+
27+
collector.collect_training_step(metrics)
28+
29+
# Verify storage
30+
stored_data = sqlite_storage.get_recent("training_step")
31+
assert len(stored_data) == 1
32+
assert stored_data[0]["step"] == 1
33+
assert stored_data[0]["loss"] == 2.5
34+
35+
36+
def test_evaluation_session(sqlite_storage):
37+
"""Test evaluation session management"""
38+
collector = MetricsCollector(sqlite_storage)
39+
40+
# Start evaluation session
41+
collector.start_evaluation_session("test_model", 10)
42+
43+
# Verify session started
44+
session_data = sqlite_storage.get_recent("evaluation_session")
45+
assert len(session_data) == 1
46+
assert session_data[0]["model"] == "test_model"
47+
assert session_data[0]["num_examples"] == 10
48+
49+
50+
def test_rollout_collection(sqlite_storage):
51+
"""Test rollout metrics collection"""
52+
collector = MetricsCollector(sqlite_storage)
53+
54+
# Start session first
55+
collector.start_evaluation_session("test_model", 3)
56+
57+
# Collect rollout data
58+
rollout_data = {
59+
"prompt": "test prompt",
60+
"completion": "test completion",
61+
"reward": 0.8,
62+
"rollout_time": 0.05,
63+
}
64+
65+
collector.collect_rollout(rollout_data)
66+
67+
# Verify storage
68+
stored_data = sqlite_storage.get_recent("rollout")
69+
assert len(stored_data) == 1
70+
assert stored_data[0]["reward"] == 0.8
71+
assert stored_data[0]["example_number"] == 1
72+
73+
74+
def test_advantages_collection(sqlite_storage):
75+
"""Test advantages metrics collection"""
76+
collector = MetricsCollector(sqlite_storage)
77+
78+
# Mock advantages with statistics
79+
advantages = {"mean": 0.1, "std": 0.2, "min": -0.1, "max": 0.3}
80+
81+
metrics = {
82+
"step": 1,
83+
"loss": 2.0,
84+
"advantages": advantages,
85+
"timestamp": time.time(),
86+
}
87+
88+
collector.collect_training_step(metrics)
89+
90+
# Verify advantages are stored
91+
stored_data = sqlite_storage.get_recent("training_step")
92+
assert stored_data[0]["advantages_mean"] == 0.1
93+
assert stored_data[0]["advantages_std"] == 0.2

tests/test_core.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Test core Verifiers Monitor functionality
3+
"""
4+
5+
import time
6+
7+
import pytest
8+
9+
from verifiers_monitor import monitor
10+
11+
12+
def test_import():
13+
"""Test that Verifiers Monitor can be imported successfully"""
14+
from verifiers_monitor import monitor
15+
16+
assert monitor is not None
17+
18+
19+
def test_trainer_monitoring(mock_trainer):
20+
"""Test basic trainer monitoring functionality"""
21+
monitored_trainer = monitor(mock_trainer, dashboard=False)
22+
23+
# Test that the wrapper preserves the original interface
24+
assert hasattr(monitored_trainer, "train")
25+
assert hasattr(monitored_trainer, "state")
26+
27+
# Test training execution
28+
result = monitored_trainer.train()
29+
assert result == "success"
30+
assert monitored_trainer.state.global_step == 3
31+
32+
33+
def test_environment_monitoring(mock_environment):
34+
"""Test basic environment monitoring functionality"""
35+
monitored_env = monitor(mock_environment, dashboard=False)
36+
37+
# Test that the wrapper preserves the original interface
38+
assert hasattr(monitored_env, "evaluate")
39+
assert hasattr(monitored_env, "rollout")
40+
41+
# Test evaluation execution
42+
results = monitored_env.evaluate("client", "model")
43+
assert len(results.reward) == 3
44+
assert all(isinstance(r, float) for r in results.reward)
45+
46+
47+
def test_monitor_invalid_object():
48+
"""Test that monitor raises error for invalid objects"""
49+
with pytest.raises(ValueError, match="Cannot monitor object"):
50+
monitor("invalid_object", dashboard=False)

tests/test_dashboard.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Test dashboard functionality
3+
"""
4+
5+
import pytest
6+
7+
from verifiers_monitor.core.sqlmodel_storage import SQLModelStorage
8+
from verifiers_monitor.dashboard.server import DashboardServer
9+
10+
11+
def test_dashboard_creation(sqlite_storage):
12+
"""Test dashboard server creation"""
13+
dashboard = DashboardServer(sqlite_storage, port=8888)
14+
15+
assert dashboard.storage == sqlite_storage
16+
assert dashboard.port == 8888
17+
assert dashboard.app is not None
18+
19+
20+
def test_dashboard_html_generation(sqlite_storage):
21+
"""Test dashboard HTML generation"""
22+
dashboard = DashboardServer(sqlite_storage, port=8888)
23+
24+
html = dashboard._get_dashboard_html_static()
25+
26+
# Check for key components
27+
assert "Verifiers Monitor Dashboard" in html or "Verifiers Pro" in html
28+
assert "<!DOCTYPE html>" in html
29+
assert "</html>" in html
30+
31+
32+
def test_dashboard_routes_setup(sqlite_storage):
33+
"""Test that dashboard routes are properly configured"""
34+
dashboard = DashboardServer(sqlite_storage, port=8888)
35+
36+
# Check that FastAPI app has routes configured
37+
routes = [route.path for route in dashboard.app.routes]
38+
39+
expected_routes = ["/", "/api/metrics", "/api/status"]
40+
for expected in expected_routes:
41+
assert any(expected in route for route in routes)

tests/test_storage.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Test storage backend functionality
3+
"""
4+
5+
import time
6+
7+
import pytest
8+
9+
from verifiers_monitor.core.sqlmodel_storage import SQLModelStorage
10+
11+
12+
def test_sqlite_storage_basic(sqlite_storage):
13+
"""Test basic SQLite storage operations with rollout data"""
14+
rollout_data = {
15+
"session_id": "test_session",
16+
"example_number": 1,
17+
"rollout_number": 1,
18+
"prompt": [{"role": "user", "content": "test"}],
19+
"completion": [{"role": "assistant", "content": "response"}],
20+
"reward": 0.8,
21+
"rollout_time": 0.5,
22+
"timestamp": time.time(),
23+
}
24+
25+
# Test store and retrieve
26+
sqlite_storage.store("rollout", rollout_data)
27+
data = sqlite_storage.get_recent("rollout")
28+
29+
assert len(data) == 1
30+
assert data[0]["reward"] == 0.8
31+
32+
33+
def test_sqlite_storage_session(sqlite_storage):
34+
"""Test SQLite storage with evaluation sessions"""
35+
session_data = {
36+
"session_id": "test_session_123",
37+
"model_name": "gpt-4o-mini",
38+
"env_id": "math-python",
39+
"num_examples": 10,
40+
"timestamp": time.time(),
41+
}
42+
43+
# Store session
44+
sqlite_storage.store("evaluation_session", session_data)
45+
46+
# Verify session was stored
47+
sessions = sqlite_storage.get_sessions()
48+
assert len(sessions) > 0
49+
assert any(s.session_id == "test_session_123" for s in sessions)
50+
51+
52+
def test_storage_get_stats(sqlite_storage):
53+
"""Test get_stats functionality"""
54+
# Store some rollout data first
55+
rollout_data = {
56+
"session_id": "test_session",
57+
"example_number": 1,
58+
"rollout_number": 1,
59+
"prompt": [{"role": "user", "content": "test"}],
60+
"completion": [{"role": "assistant", "content": "response"}],
61+
"reward": 0.9,
62+
"rollout_time": 0.5,
63+
"timestamp": time.time(),
64+
}
65+
sqlite_storage.store("rollout", rollout_data)
66+
67+
stats = sqlite_storage.get_stats()
68+
assert "total_rollouts" in stats
69+
assert stats["total_rollouts"] >= 1

verifiers_monitor/core/collectors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ def start_evaluation_session(
126126

127127
# Verifiers uses round-robin pattern, not sequential
128128
self._prompt_hash_map: Dict[str, int] = {} # prompt_hash -> example_number
129-
self._example_rollout_counts: Dict[
130-
int, int
131-
] = {} # example_number -> rollout_count
129+
self._example_rollout_counts: Dict[int, int] = (
130+
{}
131+
) # example_number -> rollout_count
132132
self._next_example_number = 1
133133

134134
session_info = {

0 commit comments

Comments
 (0)