assura-codegen 0.3.0

Rust code generation from type-checked Assura contracts
Documentation
use super::*;

#[test]
fn empty_file_generates_project() {
    let project = codegen_ok("");
    assert!(!project.cargo_toml.is_empty());
    assert_eq!(project.files.len(), 1);
    assert_eq!(project.files[0].0, "src/lib.rs");
    assert!(
        project.files[0]
            .1
            .contains("Generated by the Assura compiler")
    );
}

#[test]
fn struct_codegen() {
    let project = codegen_ok(
        r#"
type Point {
    x: Int
    y: Int
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(lib.contains("struct Point"), "should contain struct Point");
    assert!(lib.contains("i64"), "should map Int to i64");
}

#[test]
fn enum_codegen() {
    let project = codegen_ok(
        r#"
enum Color {
    Red,
    Green,
    Blue
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(lib.contains("enum Color"), "should contain enum Color");
    assert!(lib.contains("Red"), "should contain Red variant");
    assert!(lib.contains("Green"), "should contain Green variant");
    assert!(lib.contains("Blue"), "should contain Blue variant");
}

#[test]
fn enum_generates_display_impl() {
    let project = codegen_ok(
        r#"
enum Status {
    Active,
    Inactive,
    Pending
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(
        lib.contains("impl std::fmt::Display for Status"),
        "should generate Display impl: {lib}"
    );
    assert!(
        lib.contains("Status::Active => write!(f, \"Active\")"),
        "should have Active display arm: {lib}"
    );
}

#[test]
fn contract_generates_module() {
    let project = codegen_ok(
        r#"
contract SafeDivision {
    requires { true }
    ensures  { true }
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(
        lib.contains("contract_safedivision"),
        "should contain contract module"
    );
}

#[test]
fn fn_def_codegen() {
    // Note: the current parser stores fn clauses in return_ty as raw
    // tokens rather than structured Clause objects. So fn codegen
    // generates the return type string including clause keywords.
    // The codegen still generates a valid function stub.
    let project = codegen_ok(
        r#"
fn helper(n: Int) -> Int {
    requires { n >= 0 }
    ensures  { result >= 0 }
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(lib.contains("fn helper"), "should contain fn helper");
}

#[test]
fn contract_generates_debug_assert() {
    let project = codegen_ok(
        r#"
contract Positive {
    requires { true }
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(
        lib.contains("debug_assert!"),
        "contract requires should generate debug_assert"
    );
}

#[test]
fn project_name_used_in_cargo_toml() {
    let project = codegen_ok(
        r#"
project my_cool_project {
    profile: [core]
}

contract Foo {
    requires { true }
}
"#,
    );
    assert!(
        project.cargo_toml.contains("my_cool_project"),
        "Cargo.toml should use the project name"
    );
}

#[test]
fn service_generates_module() {
    let project = codegen_ok(
        r#"
service MyService {
    states: Init -> Running -> Stopped

    operation Start {
        requires: true
    }
}
"#,
    );
    let lib = &project.files[0].1;
    assert!(
        lib.contains("mod myservice"),
        "should contain service module"
    );
    // S008: typestate encoding generates marker structs instead of State enum
    assert!(
        lib.contains("pub struct Init"),
        "should contain state marker struct Init: {lib}"
    );
    assert!(
        lib.contains("PhantomData"),
        "should contain PhantomData for typestate: {lib}"
    );
}

#[test]
fn service_state_transition_codegen() {
    let project = codegen_ok(
        r#"
service Connection {
    states: Disconnected -> Connected -> Closed

    operation Connect {
        requires { self.state == Disconnected }
        ensures { self.state == Connected }
    }
}
"#,
    );
    let lib = &project.files[0].1;
    // S008: typestate encoding puts Connect in impl Connection<Disconnected>
    assert!(
        lib.contains("impl Connection<Disconnected>"),
        "should contain state-specific impl block: {lib}"
    );
    // Return type encodes the transition to Connected
    assert!(
        lib.contains("Connection<Connected>"),
        "should contain transition return type: {lib}"
    );
    // Method consumes self (not &mut self)
    assert!(
        lib.contains("fn Connect(self)"),
        "state-transitioning method should consume self: {lib}"
    );
}

#[test]
fn cargo_toml_well_formed() {
    let project = codegen_ok("");
    assert!(project.cargo_toml.contains("[package]"));
    assert!(project.cargo_toml.contains("edition = \"2024\""));
    assert!(project.cargo_toml.contains("[dependencies]"));
}