Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

421 lines
16 KiB
Python

"""
Coverage tests for benchmarks/optimization/optuna_optimizer.py.
"""
import numpy as np
import pytest
from unittest.mock import Mock, patch
MODULE = "local_deep_research.benchmarks.optimization.optuna_optimizer"
def _make_optimizer(**kwargs):
from local_deep_research.benchmarks.optimization.optuna_optimizer import (
OptunaOptimizer,
)
defaults = {"base_query": "coverage test query"}
defaults.update(kwargs)
return OptunaOptimizer(**defaults)
class TestObjectiveFloatParamSuggestion:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_float_param_with_log_scale(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_trial = Mock()
mock_trial.number = 0
mock_trial.suggest_float.return_value = 0.01
param_space = {
"lr": {"type": "float", "low": 0.0001, "high": 1.0, "log": True}
}
with patch.object(optimizer, "_run_experiment") as mock_run:
mock_run.return_value = {"score": 0.77}
score = optimizer._objective(mock_trial, param_space=param_space)
mock_trial.suggest_float.assert_called_once_with(
"lr", 0.0001, 1.0, step=None, log=True
)
assert score == 0.77
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_float_param_with_step_no_log(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_trial = Mock()
mock_trial.number = 1
mock_trial.suggest_float.return_value = 0.5
param_space = {
"dropout": {"type": "float", "low": 0.0, "high": 1.0, "step": 0.1}
}
with patch.object(optimizer, "_run_experiment") as mock_run:
mock_run.return_value = {"score": 0.65}
score = optimizer._objective(mock_trial, param_space=param_space)
mock_trial.suggest_float.assert_called_once_with(
"dropout", 0.0, 1.0, step=0.1, log=False
)
assert score == 0.65
class TestObjectiveProgressCallbacks:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_callback_trial_started_then_completed(self, mock_evaluator):
mock_evaluator.return_value = Mock()
callback = Mock()
optimizer = _make_optimizer(progress_callback=callback, n_trials=5)
mock_trial = Mock()
mock_trial.number = 2
mock_trial.suggest_int.return_value = 3
mock_trial.suggest_categorical.return_value = "rapid"
with patch.object(optimizer, "_run_experiment") as mock_run:
mock_run.return_value = {"score": 0.88}
param_space = optimizer._get_default_param_space()
optimizer._objective(mock_trial, param_space=param_space)
stages = [c[0][2]["stage"] for c in callback.call_args_list]
assert "trial_started" in stages
assert "trial_completed" in stages
completed_call = [
c
for c in callback.call_args_list
if c[0][2]["stage"] == "trial_completed"
][0]
assert completed_call[0][2]["score"] == 0.88
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_callback_trial_error_on_exception(self, mock_evaluator):
mock_evaluator.return_value = Mock()
callback = Mock()
optimizer = _make_optimizer(progress_callback=callback, n_trials=5)
mock_trial = Mock()
mock_trial.number = 3
mock_trial.suggest_int.return_value = 1
mock_trial.suggest_categorical.return_value = "standard"
with patch.object(optimizer, "_run_experiment") as mock_run:
mock_run.side_effect = RuntimeError("timeout")
param_space = optimizer._get_default_param_space()
score = optimizer._objective(mock_trial, param_space=param_space)
assert score == float("-inf")
error_calls = [
c
for c in callback.call_args_list
if c[0][2].get("stage") == "trial_error"
]
assert len(error_calls) == 1
assert "timeout" in error_calls[0][0][2]["error"]
class TestOptimizeKeyboardInterrupt:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_keyboard_interrupt_saves_and_calls_callback(
self, mock_optuna, mock_evaluator
):
mock_evaluator.return_value = Mock()
callback = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 2}
mock_study.best_value = 0.45
mock_study.trials = [Mock(), Mock(), Mock()]
mock_study.optimize.side_effect = KeyboardInterrupt()
mock_optuna.create_study.return_value = mock_study
optimizer = _make_optimizer(n_trials=20, progress_callback=callback)
with (
patch.object(optimizer, "_save_results") as mock_save,
patch.object(optimizer, "_create_visualizations") as mock_viz,
):
best_params, best_value = optimizer.optimize()
mock_save.assert_called_once()
mock_viz.assert_called_once()
assert best_params == {"iterations": 2}
assert best_value == 0.45
interrupted = [
c
for c in callback.call_args_list
if c[0][2].get("status") == "interrupted"
]
assert len(interrupted) == 1
assert interrupted[0][0][2]["trials_completed"] == 3
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_keyboard_interrupt_without_callback(
self, mock_optuna, mock_evaluator
):
mock_evaluator.return_value = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 1}
mock_study.best_value = 0.1
mock_study.trials = []
mock_study.optimize.side_effect = KeyboardInterrupt()
mock_optuna.create_study.return_value = mock_study
optimizer = _make_optimizer(n_trials=5)
assert optimizer.progress_callback is None
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
best_params, best_value = optimizer.optimize()
assert best_params == {"iterations": 1}
class TestOptimizeCompletionCallback:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_completion_callback_includes_best_params_and_value(
self, mock_optuna, mock_evaluator
):
mock_evaluator.return_value = Mock()
callback = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 4, "max_results": 80}
mock_study.best_value = 0.93
mock_study.trials = [Mock(), Mock()]
mock_optuna.create_study.return_value = mock_study
optimizer = _make_optimizer(n_trials=2, progress_callback=callback)
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
optimizer.optimize()
completed = [
c
for c in callback.call_args_list
if c[0][2].get("status") == "completed"
and c[0][2].get("stage") == "finished"
]
assert len(completed) == 1
info = completed[0][0][2]
assert info["best_params"] == {"iterations": 4, "max_results": 80}
assert info["best_value"] == 0.93
assert info["trials_completed"] == 2
class TestOptimizationCallbackStoresTrial:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_saves_at_trial_20(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_study = Mock()
mock_trial = Mock()
mock_trial.number = 20
with (
patch.object(optimizer, "_save_results") as mock_save,
patch.object(optimizer, "_create_quick_visualizations") as mock_viz,
):
optimizer._optimization_callback(mock_study, mock_trial)
mock_save.assert_called_once()
mock_viz.assert_called_once()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_no_save_at_trial_5(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_study = Mock()
mock_trial = Mock()
mock_trial.number = 5
with patch.object(optimizer, "_save_results") as mock_save:
optimizer._optimization_callback(mock_study, mock_trial)
mock_save.assert_not_called()
class TestCreateQuickVisualizations:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", True)
@patch(f"{MODULE}.plot_optimization_history")
def test_quick_viz_with_sufficient_trials(
self, mock_plot_history, mock_evaluator, tmp_path
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
mock_study = Mock()
mock_study.trials = [Mock(), Mock(), Mock()]
optimizer.study = mock_study
mock_fig = Mock()
mock_plot_history.return_value = mock_fig
optimizer._create_quick_visualizations()
mock_plot_history.assert_called_once_with(mock_study)
mock_fig.write_image.assert_called_once()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", True)
def test_quick_viz_returns_early_fewer_than_2_trials(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_study = Mock()
mock_study.trials = [Mock()]
optimizer.study = mock_study
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_quick_visualizations()
mock_plot.assert_not_called()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", False)
def test_quick_viz_returns_early_without_matplotlib(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
optimizer.study = Mock()
optimizer.study.trials = [Mock(), Mock()]
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_quick_visualizations()
mock_plot.assert_not_called()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", True)
def test_quick_viz_returns_early_when_no_study(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
optimizer.study = None
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_quick_visualizations()
mock_plot.assert_not_called()
class TestSaveResultsNumpyConversion:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.joblib")
@patch(
"local_deep_research.security.file_write_verifier.write_json_verified"
)
def test_numpy_int64_top_level_converted(
self, mock_write_json, mock_joblib, mock_evaluator, tmp_path
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
optimizer.study = None
optimizer.trials_history = [
{
"trial_number": np.int64(0),
"score": np.float64(0.92),
"params": {"iterations": np.int64(3)},
}
]
optimizer._save_results()
assert mock_write_json.call_count == 1
written_data = mock_write_json.call_args_list[0][0][1]
assert isinstance(written_data[0]["trial_number"], float)
assert isinstance(written_data[0]["score"], float)
assert isinstance(written_data[0]["params"]["iterations"], float)
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.joblib")
@patch(
"local_deep_research.security.file_write_verifier.write_json_verified"
)
def test_numpy_float64_in_result_dict_converted(
self, mock_write_json, mock_joblib, mock_evaluator, tmp_path
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
optimizer.study = None
optimizer.trials_history = [
{
"trial_number": 0,
"result": {
"quality_score": np.float64(0.85),
"speed_score": np.float64(0.72),
},
"params": {},
"score": 0.8,
}
]
optimizer._save_results()
written_data = mock_write_json.call_args_list[0][0][1]
result_dict = written_data[0]["result"]
assert isinstance(result_dict["quality_score"], float)
assert isinstance(result_dict["speed_score"], float)
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.joblib")
@patch(
"local_deep_research.security.file_write_verifier.write_json_verified"
)
def test_save_results_with_study_saves_best_params_and_pkl(
self, mock_write_json, mock_joblib, mock_evaluator, tmp_path
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
mock_study = Mock()
mock_study.best_params = {"iterations": 3}
mock_study.best_value = 0.91
mock_study.trials = [Mock()]
optimizer.study = mock_study
optimizer.trials_history = []
optimizer._save_results()
assert mock_write_json.call_count == 2
mock_joblib.dump.assert_called_once()
class TestRunExperimentErrorPaths:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.SpeedProfiler")
def test_evaluator_error_returns_failure_stops_profiler(
self, mock_profiler_cls, mock_evaluator
):
mock_eval_instance = Mock()
mock_eval_instance.evaluate.side_effect = ValueError("bad config")
mock_evaluator.return_value = mock_eval_instance
mock_profiler = Mock()
mock_profiler_cls.return_value = mock_profiler
optimizer = _make_optimizer()
result = optimizer._run_experiment(
{"iterations": 2, "questions_per_iteration": 1}
)
assert result["success"] is False
assert result["score"] == 0.0
assert "bad config" in result["error"]
mock_profiler.start.assert_called_once()
mock_profiler.stop.assert_called_once()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.SpeedProfiler")
def test_profiler_get_summary_error_caught(
self, mock_profiler_cls, mock_evaluator
):
mock_eval_instance = Mock()
mock_eval_instance.evaluate.return_value = {
"quality_score": 0.7,
"benchmark_results": {},
}
mock_evaluator.return_value = mock_eval_instance
mock_profiler = Mock()
mock_profiler.get_summary.side_effect = RuntimeError("profiler broken")
mock_profiler_cls.return_value = mock_profiler
optimizer = _make_optimizer()
result = optimizer._run_experiment({"iterations": 1})
assert result["success"] is False
assert result["score"] == 0.0
assert "profiler broken" in result["error"]
assert mock_profiler.stop.call_count >= 1
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.SpeedProfiler")
def test_successful_experiment_returns_all_fields(
self, mock_profiler_cls, mock_evaluator
):
mock_eval_instance = Mock()
mock_eval_instance.evaluate.return_value = {
"quality_score": 0.85,
"benchmark_results": {"simpleqa": {"accuracy": 0.85}},
}
mock_evaluator.return_value = mock_eval_instance
mock_profiler = Mock()
mock_profiler.get_summary.return_value = {"total_duration": 120.0}
mock_profiler_cls.return_value = mock_profiler
optimizer = _make_optimizer(
metric_weights={"quality": 0.6, "speed": 0.4}
)
result = optimizer._run_experiment(
{
"iterations": 2,
"questions_per_iteration": 3,
"search_strategy": "iterdrag",
"max_results": 50,
}
)
assert result["success"] is True
assert result["quality_score"] == 0.85
assert result["speed_score"] == pytest.approx(2 / 3, abs=0.01)
assert result["total_duration"] == 120.0
assert "score" in result
assert "benchmark_results" in result