vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Test generator — materializes tests from spec.

/// Cross product module.
pub mod cross_product;
/// Emit module.
pub mod emit;
/// Independence module.
pub mod independence;
/// Naming module.
pub mod naming;
/// Provenance module.
pub mod provenance;
/// Seed module.
pub mod seed;

use std::fmt;
use std::path::{Path, PathBuf};

use crate::generate::archetypes::Archetype;
use crate::generate::templates::TemplateError;
use crate::spec::types::OpSpec;

/// Template family used to render generator context before code emission.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum TemplateKind {
    /// Concrete spec-table row.
    OpCorrectness,
    /// Algebraic law assertion.
    Law,
    /// Backend equivalence test family.
    BackendEquiv,
    /// Archetype-driven parity test.
    Archetype,
    /// Validation rule test family.
    Validation,
    /// Mutation-kill test family.
    MutationKill,
}

/// User-supplied generation request.
pub struct GenerationPlan {
    /// Operations to materialize.
    pub ops: Vec<&'static OpSpec>,
    /// Archetypes to combine with each operation.
    pub archetypes: Vec<&'static dyn Archetype>,
    /// Templates enabled for routing.
    pub templates: Vec<TemplateKind>,
    /// Master seed for deterministic per-test seeds.
    pub seed: u64,
}

/// Summary returned after successful generation.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GenerationReport {
    /// Files emitted by this run.
    pub files: Vec<GeneratedFile>,
    /// Applicable op/archetype pairs that produced no input values.
    pub skipped_empty_inputs: usize,
}

/// One materialized generated test file.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GeneratedFile {
    /// Path written on disk.
    pub path: PathBuf,
    /// Rust test function name.
    pub test_name: String,
    /// Operation id.
    pub op: &'static str,
    /// Archetype id.
    pub archetype: &'static str,
    /// Oracle family selected by hierarchy.
    pub oracle: String,
    /// Template used for routing.
    pub template: TemplateKind,
    /// Deterministic per-test seed.
    pub seed: u64,
}

/// Generator failure.
#[derive(Debug)]
pub enum GenError {
    /// The generation plan is internally inconsistent.
    InvalidPlan {
        /// Actionable reason the plan cannot be generated.
        reason: String,
    },
    /// Template rendering failed.
    Template(
        /// Underlying template subsystem error.
        TemplateError,
    ),
    /// Generated Rust failed syntax validation.
    InvalidRust {
        /// Test name whose generated file failed parsing.
        test_name: String,
        /// Parser diagnostic.
        reason: String,
    },
    /// Writing a generated file failed.
    Io(
        /// Underlying filesystem error.
        std::io::Error,
    ),
    /// Two tuples produced the same deterministic test name.
    NameCollision(
        /// Colliding deterministic test function name.
        String,
    ),
    /// The generator's independence certificate rejected an emitted test
    /// because it would compute its expected value from the op under test
    /// (tautology). See [`independence::verify_test_independence`] for the
    /// specific rule that fired.
    TautologicalTest {
        /// The test function name that was rejected.
        test_name: String,
        /// The op under test whose call path appeared on the expected
        /// binding's right-hand side.
        op: String,
        /// Source line of the offending binding.
        line: usize,
        /// The forbidden call path the certificate matched.
        call_path: String,
        /// Actionable hint — forwarded verbatim from the certificate.
        hint: String,
    },
    /// Multiple errors occurred during generation.
    Multiple(
        /// Underlying errors.
        Vec<GenError>,
    ),
    /// Arity mismatch between archetype input and op signature.
    ArityMismatch {
        /// Expected arity from op signature.
        expected: usize,
        /// Actual arity from archetype input.
        actual: usize,
        /// Op id for context.
        op: &'static str,
    },
}

impl fmt::Display for GenError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidPlan { reason } => write!(f, "invalid generation plan. Fix: {reason}"),
            Self::Template(err) => write!(f, "template rendering failed. Fix: {err}"),
            Self::InvalidRust { test_name, reason } => write!(
                f,
                "generated Rust for `{test_name}` is invalid. Fix: repair the template or generator context. {reason}"
            ),
            Self::Io(err) => write!(f, "generated file write failed. Fix: {err}"),
            Self::NameCollision(name) => write!(
                f,
                "deterministic test name collision for `{name}`. Fix: add distinguishing tuple data to the seed hash."
            ),
            Self::TautologicalTest { test_name, op, line, call_path, hint } => write!(
                f,
                "generated test `{test_name}` for op `{op}` is tautological: \
                 line {line} binds an expected value to `{call_path}`, which \
                 resolves into the op under test. Fix: {hint}"
            ),
            Self::Multiple(errors) => {
                write!(f, "multiple generation errors ({}):", errors.len())?;
                for err in errors {
                    write!(f, " [{}]", err)?;
                }
                Ok(())
            }
            Self::ArityMismatch { expected, actual, op } => write!(
                f,
                "arity mismatch for op `{op}`: expected {expected} inputs, got {actual}. \
                 Fix: ensure archetype inputs match the op signature."
            ),
        }
    }
}

impl std::error::Error for GenError {}

impl From<std::io::Error> for GenError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<TemplateError> for GenError {
    fn from(value: TemplateError) -> Self {
        Self::Template(value)
    }
}

/// Materialize Rust test files into `out_dir`.
#[inline]
pub fn generate(plan: &GenerationPlan, out_dir: &Path) -> Result<GenerationReport, GenError> {
    if plan.ops.is_empty() {
        return Err(GenError::InvalidPlan {
            reason: "provide at least one OpSpec in GenerationPlan::ops.".to_string(),
        });
    }
    if plan.archetypes.is_empty() {
        return Err(GenError::InvalidPlan {
            reason: "provide at least one archetype in GenerationPlan::archetypes.".to_string(),
        });
    }
    if plan.templates.is_empty() {
        return Err(GenError::InvalidPlan {
            reason: "provide at least one TemplateKind in GenerationPlan::templates.".to_string(),
        });
    }

    cross_product::generate_cross_product(plan, out_dir)
}