vyre-driver 0.7.2

Driver layer: registry, runtime, pipeline, routing, diagnostics. Substrate-agnostic backend machinery. Part of the vyre GPU compiler.
Documentation
//! Driver error, capability, validation, and fixture contracts.

use std::collections::HashSet;
use std::path::PathBuf;

use vyre_driver::backend::{validate_program, BackendError, VyreBackend};
use vyre_foundation::ir::{Node, OpId, Program};
use vyre_foundation::program_caps::{check_backend_capabilities, RequiredCapabilities};

// ---------------------------------------------------------------------------
// Public validation errors remain actionable with Fix guidance
// ---------------------------------------------------------------------------

/// Every public error type that vyre-driver surfaces must contain a Fix: hint.
#[test]
fn backend_errors_contain_fix_guidance() {
    let errors: Vec<BackendError> = vec![
        BackendError::DeviceOutOfMemory {
            requested: 1,
            available: 0,
        },
        BackendError::UnsupportedFeature {
            name: "x".into(),
            backend: "y".into(),
        },
        BackendError::PoisonedLock {
            lock_error: "x".into(),
        },
        BackendError::KernelCompileFailed {
            backend: "y".into(),
            compiler_message: "z".into(),
        },
        BackendError::DispatchFailed {
            code: None,
            message: "m".into(),
        },
        BackendError::InvalidProgram {
            fix: "Fix: do something".into(),
        },
    ];

    for err in &errors {
        let msg = err.to_string();
        assert!(
            msg.contains("Fix:"),
            "BackendError must carry actionable Fix: guidance; got: {msg}"
        );
    }
}

/// BackendError::new preserves owner-authored messages without parsing prose.
#[test]
fn backend_error_new_preserves_complete_message() {
    let err = BackendError::new("something broke");
    assert_eq!(err.to_string(), "something broke");
}

// ---------------------------------------------------------------------------
// Capability contracts are explicit
// ---------------------------------------------------------------------------

/// A backend that advertises zero supported ops must reject a program
/// that contains any statement node. The contract must be explicit:
/// silence is not consent.
#[test]
fn empty_capability_set_rejects_any_program_with_nodes() {
    struct NoOpBackend;

    impl vyre_driver::backend::private::Sealed for NoOpBackend {}

    impl VyreBackend for NoOpBackend {
        fn id(&self) -> &'static str {
            "noop"
        }
        fn supported_ops(&self) -> &HashSet<OpId> {
            static EMPTY: std::sync::OnceLock<HashSet<OpId>> = std::sync::OnceLock::new();
            EMPTY.get_or_init(HashSet::new)
        }
        fn dispatch(
            &self,
            _program: &Program,
            _inputs: &[Vec<u8>],
            _config: &vyre_driver::DispatchConfig,
        ) -> Result<Vec<Vec<u8>>, BackendError> {
            Ok(vec![])
        }
    }

    let program = Program::wrapped(vec![], [1, 1, 1], vec![Node::Return]);
    let backend = NoOpBackend;
    let err = validate_program(&program, &backend)
        .expect_err("a backend with empty supported_ops must reject a program containing nodes");
    let msg = err.to_string();
    assert!(
        msg.contains("Fix:") || msg.contains("unsupported") || msg.contains("supported"),
        "empty-capability rejection must be actionable: {msg}"
    );
}

/// Capability negotiation must list every missing bit, not stop at the first.
#[test]
fn capability_negotiation_lists_all_missing_bits() {
    let mut required = RequiredCapabilities::none();
    required.subgroup_ops = true;
    required.f16 = true;
    required.bf16 = true;
    required.indirect_dispatch = true;
    required.trap = true;
    let err = check_backend_capabilities(
        "test",
        false,
        false,
        false,
        false,
        false,
        false,
        [1, 1, 1],
        &required,
    )
    .unwrap_err();
    let missing = err.missing;
    let expected: HashSet<&str> = [
        "subgroup_ops",
        "f16",
        "bf16",
        "indirect_dispatch",
        "trap_propagation",
    ]
    .iter()
    .copied()
    .collect();
    // `missing` is now `Vec<String>` so the entries can carry
    // axis-specific workgroup_size diagnostics; the test only
    // checks the simple capability names so borrow as &str.
    let actual: HashSet<&str> = missing.iter().map(String::as_str).collect();
    assert_eq!(
        actual, expected,
        "capability negotiation must report every missing capability explicitly"
    );
}

// ---------------------------------------------------------------------------
// Graph/program validation rejects malformed input instead of defaulting silently
// ---------------------------------------------------------------------------

/// validate_program must return an Err for a program containing an unsupported
/// operation, never silently defaulting to Ok.
#[test]
fn validation_rejects_unsupported_operation() {
    struct UnsupportedOpsBackend;

    impl vyre_driver::backend::private::Sealed for UnsupportedOpsBackend {}

    impl VyreBackend for UnsupportedOpsBackend {
        fn id(&self) -> &'static str {
            "unsupported-ops-contract"
        }
        fn supported_ops(&self) -> &HashSet<OpId> {
            static EMPTY: std::sync::OnceLock<HashSet<OpId>> = std::sync::OnceLock::new();
            EMPTY.get_or_init(HashSet::new)
        }
        fn dispatch(
            &self,
            _program: &Program,
            _inputs: &[Vec<u8>],
            _config: &vyre_driver::DispatchConfig,
        ) -> Result<Vec<Vec<u8>>, BackendError> {
            Ok(vec![])
        }
    }

    let program = Program::wrapped(vec![], [1, 1, 1], vec![Node::Return]);
    let err = validate_program(&program, &UnsupportedOpsBackend).unwrap_err();
    let msg = err.to_string();
    assert!(
        msg.contains("Fix:"),
        "validation rejection must carry actionable Fix: guidance; got: {msg}"
    );
}

// ---------------------------------------------------------------------------
// Test fixtures are external rather than inline
// ---------------------------------------------------------------------------

/// Integration tests load fixtures from external files.
/// This verifies the fixture directory exists and is readable.
#[test]
fn external_fixture_is_loadable() {
    let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let fixture = manifest.join("tests/fixtures/unsupported_op.txt");
    assert!(
        fixture.exists(),
        "external fixture must exist at {fixture:?}"
    );
    let content = std::fs::read_to_string(&fixture).unwrap();
    assert!(!content.trim().is_empty(), "fixture must not be empty");
}

/// A test that actually uses the external fixture to drive behavior.
#[test]
fn external_fixture_drives_validation_rejection() {
    let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let fixture = manifest.join("tests/fixtures/unsupported_op.txt");
    let op_id = std::fs::read_to_string(&fixture)
        .unwrap()
        .trim()
        .to_string();

    struct DenyAllBackend;

    impl vyre_driver::backend::private::Sealed for DenyAllBackend {}

    impl VyreBackend for DenyAllBackend {
        fn id(&self) -> &'static str {
            "deny_all"
        }
        fn supported_ops(&self) -> &HashSet<OpId> {
            static EMPTY: std::sync::OnceLock<HashSet<OpId>> = std::sync::OnceLock::new();
            EMPTY.get_or_init(HashSet::new)
        }
        fn dispatch(
            &self,
            _program: &Program,
            _inputs: &[Vec<u8>],
            _config: &vyre_driver::DispatchConfig,
        ) -> Result<Vec<Vec<u8>>, BackendError> {
            Ok(vec![])
        }
    }

    let program = Program::wrapped(vec![], [1, 1, 1], vec![Node::Return]);
    let err = validate_program(&program, &DenyAllBackend)
        .expect_err("validation must reject unsupported ops (fixture op={op_id})");
    let msg = err.to_string();
    assert!(
        msg.contains("Fix:"),
        "fixture-driven validation rejection must carry Fix: guidance; got: {msg}"
    );
}