Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

256 lines
8.0 KiB
Python

"""Tests for perfxpert.agents.schemas — Pydantic I/O types."""
import pytest
from pydantic import ValidationError
from perfxpert.agents import schemas
# -- Root ------------------------------------------------------------------
def test_root_input_minimal_construction():
m = schemas.RootInput(user_query="why is this slow?", database_path=None)
assert m.user_query == "why is this slow?"
assert m.database_path is None
def test_root_output_requires_narrative_and_recs():
m = schemas.RootOutput(
narrative="Kernel X dominates runtime.",
recommendations=[],
primary_bottleneck="compute",
)
assert m.primary_bottleneck == "compute"
# -- Analysis --------------------------------------------------------------
def test_analysis_input_accepts_db_and_top_n():
m = schemas.AnalysisInput(database_path="/tmp/a.db", top_kernels=10)
assert m.top_kernels == 10
def test_analysis_output_has_classification_and_metrics():
m = schemas.AnalysisOutput(
primary_bottleneck="memory_transfer",
confidence=0.82,
time_breakdown={"kernel_pct": 0.55, "memcpy_pct": 0.30, "api_pct": 0.10, "idle_pct": 0.05},
hot_kernels=[{"name": "[KERNEL_1]", "pct": 0.40}],
counter_data_available=True,
)
assert m.confidence == 0.82
# -- Recommendation --------------------------------------------------------
def test_recommendation_input_threads_findings():
findings = schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=0.9,
time_breakdown={"kernel_pct": 0.9, "memcpy_pct": 0.0, "api_pct": 0.05, "idle_pct": 0.05},
hot_kernels=[{"name": "[KERNEL_1]", "pct": 0.80}],
counter_data_available=False,
)
m = schemas.RecommendationInput(findings=findings, gfx_id="gfx942", kernel_filter=None)
assert m.findings.primary_bottleneck == "compute"
def test_recommendation_input_requires_gfx_id():
findings = schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=0.9,
time_breakdown={"kernel_pct": 0.9, "memcpy_pct": 0.0, "api_pct": 0.05, "idle_pct": 0.05},
hot_kernels=[{"name": "[KERNEL_1]", "pct": 0.80}],
counter_data_available=False,
)
with pytest.raises(ValidationError):
schemas.RecommendationInput(findings=findings)
def test_recommendation_input_carries_gfx_id():
findings = schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=0.9,
time_breakdown={"kernel_pct": 0.9, "memcpy_pct": 0.0, "api_pct": 0.05, "idle_pct": 0.05},
hot_kernels=[{"name": "[KERNEL_1]", "pct": 0.80}],
counter_data_available=False,
)
m = schemas.RecommendationInput(findings=findings, gfx_id="gfx950")
assert m.gfx_id == "gfx950"
def test_recommendation_output_carries_ranked_list():
m = schemas.RecommendationOutput(
recommendations=[{"title": "Reduce VGPR count", "priority": "high", "category": "compute"}],
specialist_used="compute",
)
assert m.specialist_used == "compute"
# -- Specialists -----------------------------------------------------------
@pytest.mark.parametrize(
"schema_name",
[
"ComputeSpecialistInput",
"ComputeSpecialistOutput",
"MemorySpecialistInput",
"MemorySpecialistOutput",
"LatencySpecialistInput",
"LatencySpecialistOutput",
],
)
def test_specialist_schemas_exist(schema_name):
assert hasattr(schemas, schema_name), f"schemas.{schema_name} missing"
def test_specialist_input_has_arch_and_metrics():
m = schemas.ComputeSpecialistInput(
gfx_id="gfx942",
hot_kernels=[{"name": "[KERNEL_1]", "pct": 0.60}],
counter_data={"valu_util_pct": 0.85},
)
assert m.gfx_id == "gfx942"
# -- Correctness -----------------------------------------------------------
def test_correctness_input_consumes_gate_verdict():
verdict = schemas.GateVerdictModel(
status="regressed",
failing_gate="regression",
detail="total_runtime +15% vs baseline",
delta_pct=15.0,
)
m = schemas.CorrectnessInput(
gate_verdict=verdict,
kernel_name="[KERNEL_1]",
last_technique="launch_bounds",
source_dir="/tmp/project",
)
assert m.gate_verdict.status == "regressed"
assert m.source_dir == "/tmp/project"
def test_correctness_output_action_enum():
m = schemas.CorrectnessOutput(
verdict="regressed",
action="revert",
narrative="Gate 4 failed: +15% regression",
alternative_technique="lds_tiling",
)
assert m.action == "revert"
def test_correctness_action_rejects_invalid():
with pytest.raises(ValidationError):
schemas.CorrectnessOutput(
verdict="pass",
action="nuke_system", # invalid
narrative="",
)
# -- Frozen / immutable ----------------------------------------------------
def test_root_input_is_frozen():
m = schemas.RootInput(user_query="x", database_path=None)
with pytest.raises(ValidationError):
m.user_query = "y"
def test_analysis_output_is_frozen():
m = schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=0.9,
time_breakdown={"kernel_pct": 1.0, "memcpy_pct": 0.0, "api_pct": 0.0, "idle_pct": 0.0},
hot_kernels=[],
counter_data_available=False,
)
with pytest.raises(ValidationError):
m.confidence = 0.5
# -- Finding #4: Cross-field validators, confidence clamps, provider Literal --
def test_gate_verdict_pass_requires_no_failing_gate():
"""status='pass' with failing_gate set must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.GateVerdictModel(status="pass", failing_gate="compile")
def test_gate_verdict_reject_requires_failing_gate():
"""status='reject' without failing_gate must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.GateVerdictModel(status="reject")
def test_gate_verdict_regressed_requires_failing_gate():
"""status='regressed' without failing_gate must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.GateVerdictModel(status="regressed")
def test_gate_verdict_reject_with_failing_gate_passes():
m = schemas.GateVerdictModel(status="reject", failing_gate="sol")
assert m.status == "reject"
assert m.failing_gate == "sol"
def test_correctness_output_pass_accept_valid():
m = schemas.CorrectnessOutput(verdict="pass", action="accept", narrative="ok")
assert m.action == "accept"
def test_correctness_output_pass_revert_invalid():
"""verdict='pass' with action='revert' must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.CorrectnessOutput(verdict="pass", action="revert", narrative="n")
def test_correctness_output_reject_revert_valid():
m = schemas.CorrectnessOutput(verdict="reject", action="revert", narrative="bad")
assert m.action == "revert"
def test_analysis_output_confidence_clamped_below_zero():
"""confidence < 0.0 must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=-0.1,
time_breakdown={},
hot_kernels=[],
counter_data_available=False,
)
def test_analysis_output_confidence_clamped_above_one():
"""confidence > 1.0 must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.AnalysisOutput(
primary_bottleneck="compute",
confidence=1.01,
time_breakdown={},
hot_kernels=[],
counter_data_available=False,
)
def test_root_input_provider_literal_valid():
for prov in ("anthropic", "openai", "ollama", "private", "opencode"):
m = schemas.RootInput(user_query="q", provider=prov)
assert m.provider == prov
def test_root_input_provider_literal_invalid():
"""provider='bad-provider' must raise ValidationError."""
with pytest.raises(ValidationError):
schemas.RootInput(user_query="q", provider="bad-provider")