Files
wehub-resource-sync 7a0da7932b
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

405 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Branch-coverage tests for benchmarks/optimization/optuna_optimizer.py.
Targets branches not fully exercised by the existing test files:
- _get_default_param_space structure and types
- optimize() raising KeyboardInterrupt
- progress_callback invocation during optimize()
- _save_results: joblib.dump called for study
- _create_visualizations with PLOTTING_AVAILABLE=False
- metric_weights normalisation when sum != 1.0
"""
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": "branches coverage query"}
defaults.update(kwargs)
return OptunaOptimizer(**defaults)
# ---------------------------------------------------------------------------
# _get_default_param_space
# ---------------------------------------------------------------------------
class TestGetDefaultParamSpace:
# test_get_default_param_space_iterations_is_int_type is defined first so it
# runs first and warms up the expensive module import within the pytest-timeout
# window before the other tests (including the bare test_get_default_param_space)
# are collected.
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_get_default_param_space_iterations_is_int_type(
self, mock_evaluator
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
space = optimizer._get_default_param_space()
assert space["iterations"]["type"] == "int"
assert space["iterations"]["low"] >= 1
assert space["iterations"]["high"] >= space["iterations"]["low"]
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_get_default_param_space(self, mock_evaluator):
"""_get_default_param_space returns a dict with the four expected keys."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
space = optimizer._get_default_param_space()
assert isinstance(space, dict)
assert "iterations" in space
assert "questions_per_iteration" in space
assert "search_strategy" in space
assert "max_results" in space
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_get_default_param_space_search_strategy_is_categorical(
self, mock_evaluator
):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
space = optimizer._get_default_param_space()
assert space["search_strategy"]["type"] == "categorical"
choices = space["search_strategy"]["choices"]
assert isinstance(choices, list)
assert len(choices) > 0
assert "source-based" in choices
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_get_default_param_space_max_results_step(self, mock_evaluator):
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
space = optimizer._get_default_param_space()
mr = space["max_results"]
assert mr["type"] == "int"
assert mr["low"] > 0
assert mr["high"] > mr["low"]
# ---------------------------------------------------------------------------
# optimize() KeyboardInterrupt
# ---------------------------------------------------------------------------
class TestOptimizeKeyboardInterrupt:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_optimize_keyboard_interrupt(self, mock_optuna, mock_evaluator):
"""When study.optimize raises KeyboardInterrupt, best_params and value are still returned."""
mock_evaluator.return_value = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 2, "max_results": 50}
mock_study.best_value = 0.55
mock_study.trials = [Mock(), Mock()]
mock_study.optimize.side_effect = KeyboardInterrupt()
mock_optuna.create_study.return_value = mock_study
mock_optuna.samplers.TPESampler.return_value = Mock()
optimizer = _make_optimizer(n_trials=10)
with (
patch.object(optimizer, "_save_results") as mock_save,
patch.object(optimizer, "_create_visualizations") as mock_viz,
):
best_params, best_value = optimizer.optimize()
assert best_params == {"iterations": 2, "max_results": 50}
assert best_value == 0.55
mock_save.assert_called_once()
mock_viz.assert_called_once()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_optimize_keyboard_interrupt_with_callback(
self, mock_optuna, mock_evaluator
):
"""KeyboardInterrupt fires an 'interrupted' status callback."""
mock_evaluator.return_value = Mock()
callback = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 1}
mock_study.best_value = 0.3
mock_study.trials = [Mock(), Mock(), Mock()]
mock_study.optimize.side_effect = KeyboardInterrupt()
mock_optuna.create_study.return_value = mock_study
mock_optuna.samplers.TPESampler.return_value = Mock()
optimizer = _make_optimizer(n_trials=5, progress_callback=callback)
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
optimizer.optimize()
interrupted_calls = [
c
for c in callback.call_args_list
if c[0][2].get("status") == "interrupted"
]
assert len(interrupted_calls) == 1
info = interrupted_calls[0][0][2]
assert info["stage"] == "interrupted"
assert info["trials_completed"] == 3
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_optimize_keyboard_interrupt_no_callback(
self, mock_optuna, mock_evaluator
):
"""KeyboardInterrupt without a callback does not raise."""
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
mock_optuna.samplers.TPESampler.return_value = Mock()
optimizer = _make_optimizer(n_trials=3)
assert optimizer.progress_callback is None
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
params, value = optimizer.optimize()
assert params == {"iterations": 1}
# ---------------------------------------------------------------------------
# optimize() progress_callback invoked
# ---------------------------------------------------------------------------
class TestOptimizationCallbackInvoked:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_optimization_callback_invoked(self, mock_optuna, mock_evaluator):
"""progress_callback is called with 'starting' status before study.optimize."""
mock_evaluator.return_value = Mock()
callback = Mock()
mock_study = Mock()
mock_study.best_params = {"iterations": 3}
mock_study.best_value = 0.7
mock_study.trials = [Mock()]
mock_optuna.create_study.return_value = mock_study
mock_optuna.samplers.TPESampler.return_value = Mock()
optimizer = _make_optimizer(n_trials=1, progress_callback=callback)
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
optimizer.optimize()
# At least one call should have status 'starting'
all_statuses = [c[0][2].get("status") for c in callback.call_args_list]
assert "starting" in all_statuses
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.optuna")
def test_optimization_callback_invoked_on_completion(
self, mock_optuna, mock_evaluator
):
"""progress_callback is called with 'completed' status after study.optimize."""
mock_evaluator.return_value = Mock()
callback = Mock()
mock_study = Mock()
mock_study.best_params = {"max_results": 40}
mock_study.best_value = 0.82
mock_study.trials = [Mock(), Mock()]
mock_optuna.create_study.return_value = mock_study
mock_optuna.samplers.TPESampler.return_value = Mock()
optimizer = _make_optimizer(n_trials=2, progress_callback=callback)
with (
patch.object(optimizer, "_save_results"),
patch.object(optimizer, "_create_visualizations"),
):
optimizer.optimize()
completed_calls = [
c
for c in callback.call_args_list
if c[0][2].get("status") == "completed"
]
assert len(completed_calls) == 1
info = completed_calls[0][0][2]
assert info["best_value"] == 0.82
# ---------------------------------------------------------------------------
# _save_results joblib.dump called
# ---------------------------------------------------------------------------
class TestSaveResultsJoblib:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.joblib")
@patch(
"local_deep_research.security.file_write_verifier.write_json_verified"
)
def test_save_results_joblib(
self, mock_write_json, mock_joblib, mock_evaluator, tmp_path
):
"""_save_results calls joblib.dump to persist the study object."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
mock_study = Mock()
mock_study.best_params = {"iterations": 2}
mock_study.best_value = 0.75
mock_study.trials = [Mock()]
optimizer.study = mock_study
optimizer.trials_history = []
optimizer._save_results()
mock_joblib.dump.assert_called_once()
# First arg to dump should be the study, second arg should be the file path
call_args = mock_joblib.dump.call_args
assert call_args[0][0] is mock_study
assert str(call_args[0][1]).endswith(".pkl")
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.joblib")
@patch(
"local_deep_research.security.file_write_verifier.write_json_verified"
)
def test_save_results_joblib_not_called_without_study(
self, mock_write_json, mock_joblib, mock_evaluator, tmp_path
):
"""joblib.dump is NOT called when study is None."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(output_dir=str(tmp_path))
optimizer.study = None
optimizer.trials_history = []
optimizer._save_results()
mock_joblib.dump.assert_not_called()
# ---------------------------------------------------------------------------
# _create_visualizations PLOTTING_AVAILABLE=False
# ---------------------------------------------------------------------------
class TestCreateVisualizationsNoMatplotlib:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", False)
def test_create_visualizations_no_matplotlib(self, mock_evaluator):
"""_create_visualizations returns early and never calls plot functions when PLOTTING_AVAILABLE=False."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_study = Mock()
mock_study.trials = [Mock(), Mock(), Mock()]
optimizer.study = mock_study
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_visualizations()
mock_plot.assert_not_called()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", False)
def test_create_visualizations_no_matplotlib_does_not_raise(
self, mock_evaluator
):
"""Calling _create_visualizations without matplotlib available does not raise."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
optimizer.study = Mock()
optimizer.study.trials = [Mock(), Mock()]
# Should complete without exception
optimizer._create_visualizations()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", True)
def test_create_visualizations_skips_when_no_study(self, mock_evaluator):
"""_create_visualizations returns early when study is None."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
optimizer.study = None
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_visualizations()
mock_plot.assert_not_called()
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
@patch(f"{MODULE}.PLOTTING_AVAILABLE", True)
def test_create_visualizations_skips_with_only_one_trial(
self, mock_evaluator
):
"""_create_visualizations returns early when fewer than 2 trials are present."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
mock_study = Mock()
mock_study.trials = [Mock()] # only 1 trial
optimizer.study = mock_study
with patch(f"{MODULE}.plot_optimization_history") as mock_plot:
optimizer._create_visualizations()
mock_plot.assert_not_called()
# ---------------------------------------------------------------------------
# metric_weights normalisation
# ---------------------------------------------------------------------------
class TestWeightNormalization:
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_weight_normalization(self, mock_evaluator):
"""Weights that don't sum to 1.0 are normalised so the total becomes 1.0."""
mock_evaluator.return_value = Mock()
# Deliberately unbalanced weights
optimizer = _make_optimizer(
metric_weights={"quality": 3.0, "speed": 1.0}
)
total = sum(optimizer.metric_weights.values())
assert abs(total - 1.0) < 1e-9
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_weight_normalization_proportions_preserved(self, mock_evaluator):
"""After normalisation, the relative proportions remain correct."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(
metric_weights={"quality": 3.0, "speed": 1.0}
)
# quality was 3x speed, so after normalisation quality should be 0.75
assert abs(optimizer.metric_weights["quality"] - 0.75) < 1e-9
assert abs(optimizer.metric_weights["speed"] - 0.25) < 1e-9
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_weight_normalization_already_normalised(self, mock_evaluator):
"""Weights already summing to 1.0 remain unchanged."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(
metric_weights={"quality": 0.6, "speed": 0.4}
)
assert abs(optimizer.metric_weights["quality"] - 0.6) < 1e-9
assert abs(optimizer.metric_weights["speed"] - 0.4) < 1e-9
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_weight_normalization_three_metrics(self, mock_evaluator):
"""Three-metric weights are also normalised correctly."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer(
metric_weights={"quality": 4.0, "speed": 3.0, "resource": 3.0}
)
total = sum(optimizer.metric_weights.values())
assert abs(total - 1.0) < 1e-9
@patch(f"{MODULE}.CompositeBenchmarkEvaluator")
def test_default_weights_sum_to_one(self, mock_evaluator):
"""Default metric_weights are already normalised."""
mock_evaluator.return_value = Mock()
optimizer = _make_optimizer()
total = sum(optimizer.metric_weights.values())
assert abs(total - 1.0) < 1e-9