Files
NexQuant/test/local/test_nexquant_parallel.py

201 lines
8.0 KiB
Python

"""Deep tests for nexquant_parallel.py — property-based, state transitions, edge cases.
Tests RunState, ParallelRunner configuration, environment building,
command building, and API key loading logic.
"""
from __future__ import annotations
from hypothesis import given, settings, HealthCheck
from hypothesis import strategies as st
import os
import signal
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
PROJECT_ROOT = Path(__file__).parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from hypothesis import given, settings, HealthCheck
from hypothesis import strategies as st
@pytest.fixture
def runstate():
from scripts.nexquant_parallel import RunState
return RunState(run_id=1, api_key_idx=0, model="local")
class TestRunState:
def test_init_defaults(self, runstate):
assert runstate.run_id == 1
assert runstate.api_key_idx == 0
assert runstate.model == "local"
assert runstate.status == "pending"
assert runstate.process is None
assert runstate.exit_code is None
def test_elapsed_not_started(self, runstate):
assert runstate.elapsed == "--:--:--"
def test_elapsed_running(self, runstate):
runstate.start_time = datetime(2024, 1, 1, 12, 0, 0)
with patch("scripts.nexquant_parallel.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2024, 1, 1, 13, 30, 45)
assert runstate.elapsed == "01:30:45"
def test_elapsed_completed(self, runstate):
runstate.start_time = datetime(2024, 1, 1, 12, 0, 0)
runstate.end_time = datetime(2024, 1, 1, 14, 5, 30)
assert runstate.elapsed == "02:05:30"
def test_elapsed_over_24h(self, runstate):
runstate.start_time = datetime(2024, 1, 1, 0, 0, 0)
runstate.end_time = datetime(2024, 1, 3, 6, 30, 15)
assert runstate.elapsed == "54:30:15"
@pytest.mark.parametrize("status,icon", [
("pending", "⏳"), ("running", "🔄"), ("success", "✅"),
("failed", "❌"), ("stopped", "⏹️"),
])
def test_status_icons(self, runstate, status, icon):
runstate.status = status
assert runstate.status_icon == icon
def test_unknown_status_icon(self, runstate):
runstate.status = "weird_status"
assert runstate.status_icon == "❓"
@given(
hours=st.integers(min_value=0, max_value=1000),
mins=st.integers(min_value=0, max_value=59),
secs=st.integers(min_value=0, max_value=59),
)
@settings(max_examples=200, deadline=5000,
suppress_health_check=[HealthCheck.function_scoped_fixture])
def test_elapsed_format_property(self, runstate, hours, mins, secs):
"""Elapsed time format must always be HH:MM:SS with zero-padding."""
runstate.start_time = datetime(2024, 1, 1, 0, 0, 0)
total_secs = hours * 3600 + mins * 60 + secs
if total_secs < 365 * 86400:
from datetime import timedelta
runstate.end_time = runstate.start_time + timedelta(seconds=total_secs)
e = runstate.elapsed
parts = e.split(":")
assert len(parts) == 3
assert len(parts[1]) == 2 and len(parts[2]) == 2 # mins and secs always 2 digits
assert all(p.isdigit() for p in parts)
assert int(parts[0]) == hours
class TestParallelRunnerConfig:
@patch.dict(os.environ, {}, clear=True)
def test_load_api_keys_openrouter(self):
from scripts.nexquant_parallel import ParallelRunner
with patch.dict(os.environ, {
"OPENROUTER_API_KEY": "sk-key1",
"OPENROUTER_API_KEY_2": "sk-key2",
}):
runner = ParallelRunner(num_runs=2, num_api_keys=2, model="openrouter")
keys = runner.api_keys
assert len(keys) == 2
assert keys[0].startswith("sk-")
@patch.dict(os.environ, {}, clear=True)
def test_load_api_keys_local(self):
from scripts.nexquant_parallel import ParallelRunner
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
assert runner.api_keys == ["local"]
@patch.dict(os.environ, {}, clear=True)
def test_load_api_keys_round_robin(self):
from scripts.nexquant_parallel import ParallelRunner
runner = ParallelRunner(num_runs=5, num_api_keys=2, model="local")
assert len(runner.runs) == 5
idxs = [r.api_key_idx for r in runner.runs]
assert idxs == [0, 1, 0, 1, 0]
@patch.dict(os.environ, {}, clear=True)
def test_build_env_local_model(self):
from scripts.nexquant_parallel import ParallelRunner, RunState
with patch.dict(os.environ, {
"OPENAI_API_KEY": "local",
"OPENAI_API_BASE": "http://localhost:8081/v1",
"CHAT_MODEL": "openai/qwen3.5-35b",
}):
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
rs = RunState(run_id=1, api_key_idx=0, model="local")
env = runner._build_env(rs)
assert env["OPENAI_API_KEY"] == "local"
assert "localhost:8081" in env["OPENAI_API_BASE"]
assert env["CHAT_MODEL"] == "openai/qwen3.5-35b"
@patch.dict(os.environ, {}, clear=True)
def test_build_env_openrouter(self):
from scripts.nexquant_parallel import ParallelRunner, RunState
with patch.dict(os.environ, {
"OPENROUTER_API_KEY": "sk-test",
"OPENROUTER_API_KEY_2": "sk-test2",
}):
runner = ParallelRunner(num_runs=1, num_api_keys=2, model="openrouter")
rs = RunState(run_id=1, api_key_idx=0, model="openrouter")
env = runner._build_env(rs)
assert "openrouter" in env["OPENAI_API_BASE"]
@patch.dict(os.environ, {}, clear=True)
def test_build_env_sets_workspace(self):
from scripts.nexquant_parallel import ParallelRunner, RunState
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
rs = RunState(run_id=42, api_key_idx=0, model="local")
env = runner._build_env(rs)
assert "run42" in env.get("RD_AGENT_WORKSPACE", "")
assert env["PARALLEL_RUN_ID"] == "42"
@patch.dict(os.environ, {}, clear=True)
def test_build_command(self):
from scripts.nexquant_parallel import ParallelRunner, RunState
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
rs = RunState(run_id=7, api_key_idx=0, model="local")
cmd = runner._build_command(rs)
assert "nexquant.py" in cmd[1] or "nexquant" in cmd[1]
assert "quant" in cmd
assert "--model" in cmd
assert "local" in cmd
assert "7" in [str(a) for a in cmd]
@patch.dict(os.environ, {}, clear=True)
def test_parallel_runner_init_counts(self):
from scripts.nexquant_parallel import ParallelRunner
for n in [1, 3, 10]:
runner = ParallelRunner(num_runs=n, num_api_keys=2, model="local")
assert len(runner.runs) == n
assert runner.num_runs == n
class TestParallelRunnerEdgeCases:
@patch.dict(os.environ, {}, clear=True)
def test_max_runs_limit(self):
from scripts.nexquant_parallel import ParallelRunner
runner = ParallelRunner(num_runs=100, num_api_keys=1, model="local")
assert len(runner.runs) == 100
@patch.dict(os.environ, {}, clear=True)
def test_api_keys_empty_uses_local(self):
from scripts.nexquant_parallel import ParallelRunner
runner = ParallelRunner(num_runs=1, num_api_keys=2, model="openrouter")
assert len(runner.api_keys) >= 1
@patch.dict(os.environ, {}, clear=True)
def test_build_env_preserves_existing_env(self, monkeypatch):
monkeypatch.setenv("MY_CUSTOM_VAR", "custom_value")
from scripts.nexquant_parallel import ParallelRunner, RunState
runner = ParallelRunner(num_runs=1, num_api_keys=1, model="local")
rs = RunState(run_id=1, api_key_idx=0, model="local")
env = runner._build_env(rs)
assert "MY_CUSTOM_VAR" in env