import json import asyncio import sys import types from dataclasses import dataclass from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from gfa_core.mas.stage_catalog import ( # noqa: E402 clamp_stage_cpu, get_stage_queue, get_stage_resource_profile, ) from gfa_core.mas.tools.stage_tools import StageTool, StageToolExecutor # noqa: E402 def test_stage_queue_and_cpu_profiles_are_resource_aware(monkeypatch, tmp_path): monkeypatch.setenv("BBAV5_QUEUE_PREFIX", "copy_a") monkeypatch.setenv("BBAV5_RESOURCE_STATE_DIR", str(tmp_path / "resource_governor")) assert get_stage_queue("phylogeny") == "copy_a.heavy" assert get_stage_queue("identify") == "copy_a.medium" assert get_stage_queue("ppi") == "copy_a.light" assert get_stage_queue("rnaseq_align") == "copy_a.rnaseq" assert clamp_stage_cpu("phylogeny", 32) == 6 assert clamp_stage_cpu("identify", 32) == 4 assert clamp_stage_cpu("promoter", 8) == 4 assert clamp_stage_cpu("rnaseq_align", 32) == 20 profile = get_stage_resource_profile("interspecies_synteny") assert profile["resource_class"] == "heavy" assert profile["max_cpu"] == 6 def test_stage_tool_executor_clamps_cpu_before_real_run_stage(monkeypatch, tmp_path): monkeypatch.setenv("BBAV5_RESOURCE_STATE_DIR", str(tmp_path / "resource_governor")) captured = {} @dataclass class FakeJobInfo: job_id: str input_dir: str workspace_dir: str logs_dir: str created_at: str def fake_run_stage(**kwargs): captured.update(kwargs) final_dir = Path(kwargs["job"].workspace_dir) / "final" final_dir.mkdir(parents=True, exist_ok=True) manifest = final_dir / f"{kwargs['prefix']}.{kwargs['stage']}.manifest.json" manifest.write_text(json.dumps({"status": "success", "stage": kwargs["stage"]}), encoding="utf-8") return "✅ fake stage completed", manifest fake_module = types.ModuleType("apps.web.pipeline_backend") fake_module.JobInfo = FakeJobInfo fake_module.run_stage = fake_run_stage monkeypatch.setitem(sys.modules, "apps.web.pipeline_backend", fake_module) executor = StageToolExecutor(project_root=tmp_path) stage_tool = StageTool( id="phylogeny", name="Phylogeny", script="apps/cli/run_phylogeny.py", category="analysis", description="fake", ) result = asyncio.run( executor.execute( stage_tool=stage_tool, job_id="job_resource_test", prefix="Ft.SPL", extra_params={"cpu": 32}, ) ) assert result["status"] == "success" assert result["requested_cpu"] == 32 assert result["effective_cpu"] == 6 assert captured["cpu"] == 6