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
256 lines
8.0 KiB
Python
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")
|