vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Acceptance tests for the template renderer.
//!
//! Renders every template with a mock context and verifies that:
//! - interpolation succeeds without error
//! - `{include:preamble}` expands correctly
//! - every template output contains its REQUIRED and FORBIDDEN literal strings

use vyre_conform::templates::{render, TemplateContext, TemplateError};

fn preamble_assertions(output: &str) {
    assert!(
        output.contains("ONE test"),
        "preamble inclusion failed: missing 'ONE test'"
    );
    assert!(
        output.contains("mutation-tested"),
        "preamble inclusion failed: missing 'mutation-tested'"
    );
}

#[test]
fn preamble_renders_standalone() {
    let src = include_str!("../src/generate/templates/preamble.tmpl");
    let ctx = TemplateContext::new();
    let out = render(src, &ctx).expect("preamble should render without fields");
    assert!(out.contains("ONE test"));
    assert!(out.contains("unlimited time"));
}

#[test]
fn op_correctness_renders() {
    let src = include_str!("../src/generate/templates/op_correctness.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("op_name", "add")
        .with_scalar("file", "add")
        .with_scalar("expected", "5")
        .with_scalar("inputs", "[2, 3]")
        .with_scalar("spec_path", "src/spec/tables/add.rs")
        .with_scalar("line", "42")
        .with_scalar("category", "A")
        .with_scalar("op", "add")
        .with_scalar("input_desc", "2_3");

    let out = render(src, &ctx).expect("op_correctness should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("add from vyre/src/ops/primitive/add.rs"));
    assert!(out.contains("test_add_2_3_spec_table"));
}

#[test]
fn law_renders() {
    let src = include_str!("../src/generate/templates/law.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("op_name", "add")
        .with_scalar("law_name", "Commutativity")
        .with_scalar("law_description", "a + b == b + a")
        .with_scalar("input_class", "u32")
        .with_scalar("law_formula", "a + b == b + a")
        .with_scalar("specific_inputs", "[0, 1]")
        .with_scalar("op", "add")
        .with_scalar("law", "Commutativity");

    let out = render(src, &ctx).expect("law should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("Commutativity (a + b == b + a)"));
}

#[test]
fn validation_renders() {
    let src = include_str!("../src/generate/templates/validation.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("V_NNN", "V001")
        .with_scalar("description", "empty program body")
        .with_scalar("NNN", "001");

    let out = render(src, &ctx).expect("validation should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("Validation rule V001"));
}

#[test]
fn backend_equiv_renders() {
    let src = include_str!("../src/generate/templates/backend_equiv.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("op_name", "add")
        .with_scalar("op", "add")
        .with_scalar("input_class", "u32");

    let out = render(src, &ctx).expect("backend_equiv should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("I3 (backend equivalence)"));
}

#[test]
fn archetype_renders() {
    let src = include_str!("../src/generate/templates/archetype.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("op_name", "add")
        .with_scalar("archetype_id", "A1")
        .with_scalar("archetype_name", "IdentityPair")
        .with_scalar("property", "identity element holds")
        .with_scalar("oracle_chosen_from_hierarchy", "ReferenceInterpreter")
        .with_scalar(
            "archetype_description",
            "pair of values where one is the identity",
        )
        .with_scalar("op", "add");

    let out = render(src, &ctx).expect("archetype should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("A1 (IdentityPair)"));
}

#[test]
fn mutation_kill_renders() {
    let src = include_str!("../src/generate/templates/mutation_kill.tmpl");
    let ctx = TemplateContext::new()
        .with_scalar("op_name", "add")
        .with_scalar("mutation_description", "ArithOpSwap Add->Sub")
        .with_scalar("oracle_chosen_from_hierarchy", "ReferenceInterpreter")
        .with_scalar(
            "mutation_details",
            "Swap BinOp::Add to BinOp::Sub in primitive add.rs",
        )
        .with_scalar("op", "add");

    let out = render(src, &ctx).expect("mutation_kill should render");
    preamble_assertions(&out);
    assert!(out.contains("REQUIRED:"), "missing REQUIRED section");
    assert!(out.contains("FORBIDDEN:"), "missing FORBIDDEN section");
    assert!(out.contains("ArithOpSwap Add->Sub"));
}

#[test]
fn list_directive_expands() {
    let src = "Items: {list:items}- {name}\n{/list}";
    let item1 = TemplateContext::new().with_scalar("name", "foo");
    let item2 = TemplateContext::new().with_scalar("name", "bar");
    let ctx = TemplateContext::new().with_list("items", vec![item1, item2]);
    let out = render(src, &ctx).expect("list should render");
    assert_eq!(out, "Items: - foo\n- bar\n");
}

#[test]
fn missing_list_directive_errors() {
    let src = "{list:ops}OP:{name}{/list}";
    let ctx = TemplateContext::new();
    let err = render(src, &ctx).expect_err("missing list must not render as empty output");
    assert!(
        matches!(err, TemplateError::UnknownField(ref s) if s == "ops"),
        "Fix: missing list field must report UnknownField(\"ops\"), got {err:?}"
    );
}

#[test]
fn if_directive_conditional() {
    let src = "{if:show}visible{/if}{if:hide}hidden{/if}";
    let ctx = TemplateContext::new()
        .with_conditional("show", true)
        .with_conditional("hide", false);
    let out = render(src, &ctx).expect("if should render");
    assert_eq!(out, "visible");
}

#[test]
fn nested_list_and_if() {
    let src = "{list:outer}{if:ok}[{name}]{/if}\n{/list}";
    let item1 = TemplateContext::new()
        .with_scalar("name", "a")
        .with_conditional("ok", true);
    let item2 = TemplateContext::new()
        .with_scalar("name", "b")
        .with_conditional("ok", false);
    let ctx = TemplateContext::new().with_list("outer", vec![item1, item2]);
    let out = render(src, &ctx).expect("nested directives should render");
    assert_eq!(out, "[a]\n\n");
}

#[test]
fn unknown_field_errors() {
    let src = "{missing}";
    let ctx = TemplateContext::new();
    let err = render(src, &ctx).expect_err("should error on unknown field");
    assert!(matches!(err, TemplateError::UnknownField(ref s) if s == "missing"));
}

#[test]
fn malformed_unclosed_brace_errors() {
    let src = "{unclosed";
    let ctx = TemplateContext::new();
    let err = render(src, &ctx).expect_err("should error on unclosed brace");
    assert!(matches!(err, TemplateError::MalformedDirective));
}

#[test]
fn unclosed_list_block_errors() {
    let src = "{list:items}no close";
    let ctx = TemplateContext::new();
    let err = render(src, &ctx).expect_err("should error on unclosed list");
    assert!(matches!(err, TemplateError::MalformedDirective));
}

#[test]
fn include_not_found_errors() {
    let src = "{include:no_such_template}";
    let ctx = TemplateContext::new();
    let err = render(src, &ctx).expect_err("should error on missing include");
    assert!(matches!(err, TemplateError::IncludeNotFound(ref s) if s == "no_such_template"));
}