| from __future__ import annotations |
|
|
| import json |
| import shutil |
| from pathlib import Path |
|
|
| from adam.config import ConfigManager |
| from adam.external_tools import ExternalToolStore, scan_folder |
| from adam.planner import Planner |
| from adam.registry import ToolRegistry |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
|
|
|
|
| def make_project(tmp_path: Path) -> Path: |
| (tmp_path / "config").mkdir() |
| shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") |
| return tmp_path |
|
|
|
|
| def test_analyzer_detects_training_contract(tmp_path: Path) -> None: |
| script = tmp_path / "train.py" |
| script.write_text( |
| """ |
| import argparse |
| from tqdm import tqdm |
| |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--dataset-dir", required=True) |
| parser.add_argument("--epochs", type=int, default=10) |
| parser.add_argument("--output-dir", required=True) |
| parser.add_argument("--resume-from") |
| |
| if __name__ == "__main__": |
| args = parser.parse_args() |
| for epoch in tqdm(range(args.epochs)): |
| print("loss", epoch) |
| checkpoint = "checkpoint.pt" |
| """, |
| encoding="utf-8", |
| ) |
|
|
| analysis = scan_folder(str(tmp_path)) |
|
|
| assert analysis.selected_entry == "train.py" |
| assert analysis.score >= 8 |
| assert analysis.required_arguments == ["dataset_dir", "output_dir"] |
| assert "resume_from" in analysis.resume_behavior |
| assert "tqdm" in analysis.progress_behavior |
|
|
|
|
| def test_analyzer_lowers_rating_for_risky_calls(tmp_path: Path) -> None: |
| (tmp_path / "train.py").write_text( |
| """ |
| import os |
| import shutil |
| if __name__ == "__main__": |
| os.system("unknown command") |
| shutil.rmtree("output") |
| """, |
| encoding="utf-8", |
| ) |
|
|
| analysis = scan_folder(str(tmp_path)) |
|
|
| assert analysis.score <= 3 |
| assert any("delete" in warning or "os.system" in warning for warning in analysis.warnings) |
|
|
|
|
| def test_saved_external_tool_is_confirmation_gated_and_plannable(tmp_path: Path) -> None: |
| project = make_project(tmp_path) |
| tool_folder = tmp_path / "apvd" |
| tool_folder.mkdir() |
| (tool_folder / "train.py").write_text( |
| """ |
| import argparse |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--dataset", required=True) |
| parser.add_argument("--epochs", type=int, required=True) |
| parser.add_argument("--output") |
| if __name__ == "__main__": |
| args = parser.parse_args() |
| print("training progress") |
| """, |
| encoding="utf-8", |
| ) |
| analysis = scan_folder(str(tool_folder)) |
| ExternalToolStore(project).save_connector( |
| name="APVD Model Trainer", |
| description="Train the APVD model.", |
| analysis=analysis, |
| arguments=analysis.arguments, |
| required_arguments=analysis.required_arguments, |
| ) |
| registry = ToolRegistry(project) |
| spec = registry.get("external_apvd_model_trainer") |
|
|
| assert spec.requires_confirmation is True |
| assert spec.backend["type"] == "script" |
|
|
| config = ConfigManager(project) |
| config.settings["provider"] = "manual" |
| planner = Planner(project, registry, config) |
| plan = planner.plan( |
| "Run APVD Model Trainer with dataset=D:/DreamData, epochs=20, output=D:/Runs" |
| ) |
|
|
| assert plan.requires_confirmation is True |
| assert plan.steps[0].tool_id == "external_apvd_model_trainer" |
| assert plan.steps[0].arguments["epochs"] == 20 |
|
|
|
|
| def test_external_registry_cannot_override_builtin_tool(tmp_path: Path) -> None: |
| project = make_project(tmp_path) |
| (project / "config" / "external_tools.json").write_text( |
| json.dumps( |
| { |
| "tools": [ |
| { |
| "id": "ddpm_trainer", |
| "name": "Replacement", |
| "description": "Not allowed", |
| "category": "External", |
| "backend": { |
| "type": "script", |
| "path": str((project / "train.py").resolve()), |
| "root": str(project.resolve()), |
| }, |
| } |
| ] |
| } |
| ), |
| encoding="utf-8", |
| ) |
|
|
| try: |
| ToolRegistry(project) |
| except Exception as exc: |
| assert "external_" in str(exc) or "replace" in str(exc) |
| else: |
| raise AssertionError("External registry override was accepted") |
|
|