Files

719 lines
29 KiB
Python
Raw Permalink Normal View History

"""Deep tests for workflow components: rd_loop.py, proposal, trace, hypothesis systems."""
from __future__ import annotations
import asyncio
import pickle
import sys
from multiprocessing import Queue
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
PROJECT_ROOT = Path(__file__).parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
def _make_mock_prop_setting(**overrides: Any) -> Any:
ps = MagicMock()
ps.scen = "rdagent.scenarios.qlib.scenario.QlibQuantScenario"
for k, v in overrides.items():
setattr(ps, k, v)
ps.model_dump.return_value = {}
return ps
# =============================================================================
# Import safety
# =============================================================================
WORKFLOW_MODULES = [
"rdagent.components.workflow.rd_loop",
"rdagent.components.workflow.conf",
"rdagent.core.proposal",
"rdagent.core.developer",
"rdagent.core.experiment",
"rdagent.core.scenario",
"rdagent.core.evolving_framework",
"rdagent.core.evolving_agent",
"rdagent.core.utils",
"rdagent.utils.workflow",
"rdagent.utils.qlib",
]
class TestWorkflowImports:
@pytest.mark.parametrize("module_name", WORKFLOW_MODULES)
def test_module_importable(self, module_name: str) -> None:
import importlib
mod = importlib.import_module(module_name)
assert mod is not None
# =============================================================================
# LoopBase and LoopMeta
# =============================================================================
class TestLoopBase:
def test_loop_base_is_importable(self) -> None:
from rdagent.utils.workflow import LoopBase
assert LoopBase is not None
def test_loop_meta_is_importable(self) -> None:
from rdagent.utils.workflow import LoopMeta
assert LoopMeta is not None
def test_loop_base_can_be_instantiated(self) -> None:
from rdagent.utils.workflow import LoopBase
loop = LoopBase()
assert loop is not None
# =============================================================================
# RDLoop — construction
# =============================================================================
class TestRDLoopConstruction:
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_imports_scenario(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
assert loop.trace is not None
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_creates_trace(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting()
loop = RDLoop(props)
assert hasattr(loop, "trace")
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_sets_experiment_plan(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting()
loop = RDLoop(props)
assert "features" in loop.plan
assert "feature_codes" in loop.plan
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_with_hypothesis_gen_setting(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting(hypothesis_gen="some.path.ClassName")
loop = RDLoop(props)
assert loop.hypothesis_gen is not None
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_without_hypothesis_gen_setting(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting()
props.hypothesis_gen = None
loop = RDLoop(props)
assert loop.hypothesis_gen is None
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_with_coder_setting(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting(coder="some.path.Coder")
loop = RDLoop(props)
assert loop.coder is not None
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_init_with_runner_setting(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_import.return_value = MagicMock()
props = _make_mock_prop_setting(runner="some.path.Runner")
loop = RDLoop(props)
assert loop.runner is not None
# =============================================================================
# RDLoop — step methods
# =============================================================================
class TestRDLoopPropose:
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_propose_returns_hypothesis(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import Hypothesis
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(hypothesis_gen="some.path")
loop = RDLoop(props)
mock_hypo = Hypothesis(hypothesis="test", reason="test",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
loop.hypothesis_gen = MagicMock()
loop.hypothesis_gen.gen.return_value = mock_hypo
result = loop._propose()
assert result == mock_hypo
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_propose_raises_loop_resume_on_llm_error(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.exception import LLMUnavailableError
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(hypothesis_gen="some.path")
loop = RDLoop(props)
loop.hypothesis_gen = MagicMock()
loop.hypothesis_gen.gen.side_effect = LLMUnavailableError("timeout")
with pytest.raises(loop.LoopResumeError):
loop._propose()
class TestRDLoopExpGen:
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_exp_gen_returns_experiment(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import Hypothesis
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(hypothesis2experiment="some.path")
loop = RDLoop(props)
mock_exp = MagicMock()
loop.hypothesis2experiment = MagicMock()
loop.hypothesis2experiment.convert.return_value = mock_exp
hypo = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
result = loop._exp_gen(hypo)
assert result == mock_exp
class TestRDLoopSteps:
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_direct_exp_gen_yields_dict(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_scen.patcher = None
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(hypothesis_gen="p.HG", hypothesis2experiment="p.H2E")
loop = RDLoop(props)
mock_hypo = MagicMock()
mock_hypo.action = "factor"
loop.hypothesis_gen = MagicMock()
loop.hypothesis_gen.gen.return_value = mock_hypo
loop.hypothesis2experiment = MagicMock()
mock_exp = MagicMock()
mock_exp.sub_tasks = []
mock_exp.based_experiments = None
loop.hypothesis2experiment.convert.return_value = mock_exp
result = asyncio.run(loop.direct_exp_gen({}))
assert "propose" in result
assert "exp_gen" in result
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_coding_calls_coder_develop(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(coder="p.Coder")
loop = RDLoop(props)
loop.coder = MagicMock()
loop.coder.develop.return_value = MagicMock()
prev_out = {"direct_exp_gen": {"exp_gen": MagicMock()}}
loop.coding(prev_out)
assert loop.coder.develop.called
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_running_calls_runner_develop(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(runner="p.Runner")
loop = RDLoop(props)
loop.runner = MagicMock()
loop.runner.develop.return_value = MagicMock()
prev_out = {"coding": MagicMock()}
loop.running(prev_out)
assert loop.runner.develop.called
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_feedback_on_exception_returns_reject_feedback(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import HypothesisFeedback
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(summarizer="p.Summarizer")
loop = RDLoop(props)
prev_out = {loop.EXCEPTION_KEY: "test error"}
result = loop.feedback(prev_out)
assert isinstance(result, HypothesisFeedback)
assert result.decision is False
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_feedback_normal_path_calls_summarizer(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import HypothesisFeedback
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting(summarizer="p.Summarizer")
loop = RDLoop(props)
loop.summarizer = MagicMock()
loop.summarizer.generate_feedback.return_value = HypothesisFeedback(
reason="ok", decision=True, code_change_summary="done", acceptable=True)
prev_out = {"running": MagicMock()}
result = loop.feedback(prev_out)
assert isinstance(result, HypothesisFeedback)
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_record_syncs_trace_dag(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
loop.trace = MagicMock()
mock_exp = MagicMock()
mock_exp.hypothesis = "hypo"
mock_fb = MagicMock()
prev_out = {"feedback": mock_fb, "running": mock_exp, loop.LOOP_IDX_KEY: 0}
loop.record(prev_out)
loop.trace.sync_dag_parent_and_hist.assert_called_once()
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_record_with_none_exp_does_not_crash(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
loop.trace = MagicMock()
prev_out = {"feedback": MagicMock(), "running": MagicMock(hypothesis=None), loop.LOOP_IDX_KEY: 0}
loop.record(prev_out)
loop.trace.sync_dag_parent_and_hist.assert_not_called()
# =============================================================================
# RDLoop — interaction methods
# =============================================================================
class TestRDLoopInteractions:
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_set_interactor_stores_queues(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
q1, q2 = Queue(), Queue()
loop._set_interactor(q1, q2)
assert loop.user_request_q is q1
assert loop.user_response_q is q2
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_interact_hypo_no_queues_returns_original(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import Hypothesis
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
hypo = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
result = loop._interact_hypo(hypo)
assert result is hypo
@patch("rdagent.components.workflow.rd_loop.logger.log_object")
@patch("rdagent.components.workflow.rd_loop.import_class")
def test_interact_feedback_no_queues_returns_original(self, mock_import: MagicMock, mock_log: MagicMock) -> None:
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.proposal import HypothesisFeedback
mock_scen = MagicMock()
mock_import.return_value = mock_scen
props = _make_mock_prop_setting()
loop = RDLoop(props)
fb = HypothesisFeedback(reason="r", decision=True, code_change_summary="ok", acceptable=True)
result = loop._interact_feedback(fb)
assert result is fb
# =============================================================================
# BasePropSetting
# =============================================================================
class TestBasePropSetting:
def test_base_prop_setting_is_pydantic_model(self) -> None:
from rdagent.components.workflow.conf import BasePropSetting
from pydantic import BaseModel
assert issubclass(BasePropSetting, BaseModel)
# =============================================================================
# Hypothesis and HypothesisFeedback
# =============================================================================
class TestHypothesis:
def test_construction_with_minimal_fields(self) -> None:
from rdagent.core.proposal import Hypothesis
h = Hypothesis(hypothesis="test", reason="because",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
assert h.hypothesis == "test"
assert h.reason == "because"
def test_has_conciseness_fields(self) -> None:
from rdagent.core.proposal import Hypothesis
h = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
assert h.concise_reason == "cr"
assert h.concise_observation == "co"
def test_pickle_safety(self) -> None:
from rdagent.core.proposal import Hypothesis
h = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
data = pickle.dumps(h)
h2 = pickle.loads(data)
assert h2.hypothesis == "h"
assert h2.concise_knowledge == "ck"
def test_dict_conversion(self) -> None:
from rdagent.core.proposal import Hypothesis
h = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
d = h.__dict__
h2 = type(h)(**d)
assert h2.hypothesis == h.hypothesis
class TestHypothesisFeedback:
def test_construction_with_all_fields(self) -> None:
from rdagent.core.proposal import HypothesisFeedback
fb = HypothesisFeedback(reason="good", decision=True, code_change_summary="fixed", acceptable=True)
assert fb.reason == "good"
assert fb.decision is True
def test_default_values(self) -> None:
from rdagent.core.proposal import HypothesisFeedback
fb = HypothesisFeedback(reason="reason", decision=False)
assert fb.decision is False
def test_pickle_safety(self) -> None:
from rdagent.core.proposal import HypothesisFeedback
fb = HypothesisFeedback(reason="r", decision=True, code_change_summary="c", acceptable=True)
data = pickle.dumps(fb)
fb2 = pickle.loads(data)
assert fb2.decision is True
# =============================================================================
# Trace
# =============================================================================
class TestTrace:
def test_trace_construction(self) -> None:
from rdagent.core.proposal import Trace
trace = Trace(scen=None)
assert trace is not None
def test_trace_has_hist_attribute(self) -> None:
from rdagent.core.proposal import Trace
trace = Trace(scen=None)
assert hasattr(trace, "hist")
assert isinstance(trace.hist, list)
def test_trace_sync_dag_parent_and_hist(self) -> None:
from rdagent.core.proposal import Trace
trace = Trace(scen=None)
exp = MagicMock()
exp.based_experiments = []
exp.hypothesis = "hypo"
fb = MagicMock()
trace.sync_dag_parent_and_hist((exp, fb), 0)
assert len(trace.hist) > 0
def test_trace_pickle_safety(self) -> None:
from rdagent.core.proposal import Trace
trace = Trace(scen=None)
trace.hist = [("entry",)]
data = pickle.dumps(trace)
trace2 = pickle.loads(data)
assert len(trace2.hist) == 1
# =============================================================================
# HypothesisGen, Hypothesis2Experiment, Experiment2Feedback
# =============================================================================
class TestProposalClasses:
def test_hypothesis_gen_is_importable(self) -> None:
from rdagent.core.proposal import HypothesisGen
assert HypothesisGen is not None
def test_hypothesis2experiment_is_importable(self) -> None:
from rdagent.core.proposal import Hypothesis2Experiment
assert Hypothesis2Experiment is not None
def test_experiment2feedback_is_importable(self) -> None:
from rdagent.core.proposal import Experiment2Feedback
assert Experiment2Feedback is not None
# =============================================================================
# Developer
# =============================================================================
class TestDeveloper:
def test_developer_is_importable(self) -> None:
from rdagent.core.developer import Developer
assert Developer is not None
def test_developer_stores_scenario(self) -> None:
from rdagent.core.developer import Developer
from rdagent.core.experiment import ASpecificExp
class ConcreteDev(Developer[ASpecificExp]):
def develop(self, exp: ASpecificExp) -> ASpecificExp:
return exp
scen = MagicMock()
dev = ConcreteDev(scen)
assert dev.scen is scen
# =============================================================================
# Scenario base class
# =============================================================================
class TestScenarioBase:
def test_scenario_is_abstract(self) -> None:
from rdagent.core.scenario import Scenario
assert hasattr(Scenario, "__abstractmethods__")
def test_scenario_has_required_properties(self) -> None:
from rdagent.core.scenario import Scenario
assert hasattr(Scenario, "background")
assert hasattr(Scenario, "rich_style_description")
assert hasattr(Scenario, "source_data")
# =============================================================================
# Qlib utilities
# =============================================================================
class TestQlibUtils:
def test_validate_qlib_features_importable(self) -> None:
from rdagent.utils.qlib import validate_qlib_features
assert callable(validate_qlib_features)
def test_validate_valid_features(self) -> None:
from rdagent.utils.qlib import validate_qlib_features
result = validate_qlib_features(["$close", "$high / $low"])
assert isinstance(result, bool)
def test_validate_empty_list(self) -> None:
from rdagent.utils.qlib import validate_qlib_features
result = validate_qlib_features([])
assert isinstance(result, bool)
def test_alpha20_importable(self) -> None:
from rdagent.utils.qlib import ALPHA20
assert isinstance(ALPHA20, dict)
assert len(ALPHA20) > 0
@pytest.mark.parametrize("features", [
["$close"], ["$open", "$high", "$low", "$close"], ["$close / $open", "$high - $low"], [],
])
def test_validate_qlib_features_variants(self, features: list) -> None:
from rdagent.utils.qlib import validate_qlib_features
result = validate_qlib_features(features)
assert isinstance(result, bool)
# =============================================================================
# Experiment classes
# =============================================================================
class TestExperimentClasses:
def test_task_is_importable(self) -> None:
from rdagent.core.experiment import Task
assert Task is not None
def test_workspace_is_importable(self) -> None:
from rdagent.core.experiment import Workspace
assert Workspace is not None
def test_fb_workspace_is_importable(self) -> None:
from rdagent.core.experiment import FBWorkspace
assert FBWorkspace is not None
def test_fb_workspace_inject_files(self) -> None:
from rdagent.core.experiment import FBWorkspace
ws = FBWorkspace()
ws.inject_files(**{"factor.py": "def calc(): pass"})
code = ws.all_codes
assert "def calc" in code
def test_fb_workspace_pickle_safety(self) -> None:
from rdagent.core.experiment import FBWorkspace
ws = FBWorkspace()
ws.inject_files(**{"factor.py": "x=1"})
data = pickle.dumps(ws)
ws2 = pickle.loads(data)
assert isinstance(ws2, FBWorkspace)
# =============================================================================
# Evolving framework imports
# =============================================================================
class TestEvolvingFrameworkImports:
@pytest.mark.parametrize("cls_name,module_path", [
("EvolvableSubjects", "rdagent.core.evolving_framework"),
("EvolvingKnowledgeBase", "rdagent.core.evolving_framework"),
("EvoStep", "rdagent.core.evolving_framework"),
("Knowledge", "rdagent.core.evolving_framework"),
("QueriedKnowledge", "rdagent.core.evolving_framework"),
("RAGStrategy", "rdagent.core.evolving_framework"),
("RAGEvaluator", "rdagent.core.evolving_agent"),
])
def test_class_importable(self, cls_name: str, module_path: str) -> None:
import importlib
mod = importlib.import_module(module_path)
assert hasattr(mod, cls_name)
# =============================================================================
# EvoStep — dataclass behavior
# =============================================================================
class TestEvoStep:
def test_default_construction(self) -> None:
from rdagent.core.evolving_framework import EvoStep
es = EvoStep(evolvable_subjects="mock_evo")
assert es.evolvable_subjects == "mock_evo"
assert es.queried_knowledge is None
assert es.feedback is None
def test_full_construction(self) -> None:
from rdagent.core.evolving_framework import EvoStep, QueriedKnowledge
qk = QueriedKnowledge()
es = EvoStep(evolvable_subjects="evo", queried_knowledge=qk, feedback="fb")
assert es.queried_knowledge is qk
assert es.feedback == "fb"
def test_equality_by_reference(self) -> None:
from rdagent.core.evolving_framework import EvoStep
es1 = EvoStep(evolvable_subjects="a")
es2 = EvoStep(evolvable_subjects="a")
assert es1 == es2
def test_pickle_safety(self) -> None:
from rdagent.core.evolving_framework import EvoStep
es = EvoStep(evolvable_subjects="subj", feedback="good")
data = pickle.dumps(es)
es2 = pickle.loads(data)
assert es2.evolvable_subjects == "subj"
assert es2.feedback == "good"
# =============================================================================
# import_class utility
# =============================================================================
class TestImportClass:
def test_import_class_is_callable(self) -> None:
from rdagent.core.utils import import_class
assert callable(import_class)
def test_import_class_resolves_known_class(self) -> None:
from rdagent.core.utils import import_class
cls = import_class("rdagent.core.proposal.Hypothesis")
from rdagent.core.proposal import Hypothesis
assert cls is Hypothesis
def test_import_class_raises_on_bad_path(self) -> None:
from rdagent.core.utils import import_class
with pytest.raises((ValueError, ImportError, ModuleNotFoundError)):
import_class("nonexistent.module.ClassName")
# =============================================================================
# LLMUnavailableError
# =============================================================================
class TestLLMUnavailableError:
def test_is_importable(self) -> None:
from rdagent.core.exception import LLMUnavailableError
assert issubclass(LLMUnavailableError, Exception)
def test_can_be_raised_and_caught(self) -> None:
from rdagent.core.exception import LLMUnavailableError
with pytest.raises(LLMUnavailableError):
raise LLMUnavailableError("test error")
def test_pickle_safety(self) -> None:
from rdagent.core.exception import LLMUnavailableError
e = LLMUnavailableError("pickle me")
data = pickle.dumps(e)
e2 = pickle.loads(data)
assert str(e2) == "pickle me"
# =============================================================================
# Pickle safety for combined workflow objects
# =============================================================================
class TestPickleSafetyComposite:
def test_combined_workflow_objects_pickle(self) -> None:
from rdagent.core.proposal import Hypothesis, HypothesisFeedback, Trace
h = Hypothesis(hypothesis="h", reason="r",
concise_reason="cr", concise_observation="co",
concise_justification="cj", concise_knowledge="ck")
fb = HypothesisFeedback(reason="r", decision=True, code_change_summary="ok", acceptable=True)
trace = Trace(scen=None)
trace.hist = []
bundle = {"hypothesis": h, "feedback": fb, "trace": trace}
data = pickle.dumps(bundle)
bundle2 = pickle.loads(data)
assert bundle2["hypothesis"].hypothesis == "h"