sim-kernel 0.1.0-rc.1

SIM workspace package for sim kernel.
Documentation
use std::sync::Arc;

use crate::{
    AbiVersion, CORE_EXPR_CLASS_ID, Cx, DefaultFactory, Error, ExportKind, ExportState, Expr,
    Factory, FunctionId, LibId, LibManifest, LibTarget, Registry, Result, RuntimeId, Symbol, Test,
    TestReport, Value,
    catalog::{CatalogStore, CatalogWritePolicy},
};

use super::{
    export_row, exports_table, install_registry_catalog_schema, lib_row, libs_table,
    number_ops_table, plain_value_row, promotion_rules_table, runtime_table, runtime_value_row,
    schema::{
        field, lib_key, plain_value_key, registry_catalog_specs, runtime_key, sequence_key,
        split_key, test_key,
    },
    schema_table, sequence_row, sequences_table, test_row, tests_by_lib_table, tests_table,
    value_promotion_rules_table,
};

fn manifest(id: &str) -> LibManifest {
    LibManifest {
        id: Symbol::new(id),
        version: crate::Version("1.2.3".to_owned()),
        abi: AbiVersion { major: 0, minor: 1 },
        target: LibTarget::HostRegistered,
        requires: Vec::new(),
        capabilities: Vec::new(),
        exports: Vec::new(),
    }
}

fn int(value: &str) -> Expr {
    Expr::Number(crate::NumberLiteral {
        domain: Symbol::qualified("numbers", "i64"),
        canonical: value.to_owned(),
    })
}

fn bool_value(value: bool) -> Value {
    DefaultFactory.bool(value).unwrap()
}

fn assert_duplicate_export(err: Error, kind: &'static str, symbol: &Symbol) {
    assert!(matches!(
        err,
        Error::DuplicateExport {
            kind: duplicate_kind,
            symbol: duplicate_symbol,
        } if duplicate_kind == kind && duplicate_symbol == symbol.clone()
    ));
}

struct DummyTest;

impl Test for DummyTest {
    fn symbol(&self) -> Symbol {
        Symbol::new("demo-test")
    }

    fn lib(&self) -> Symbol {
        Symbol::new("demo-lib")
    }

    fn describe(&self, cx: &mut Cx) -> Result<Value> {
        cx.factory().nil()
    }

    fn run(&self, _cx: &mut Cx) -> Result<TestReport> {
        Ok(TestReport::from_result(
            Symbol::new("demo-test"),
            true,
            None,
        ))
    }
}

#[test]
fn schema_installs_every_registry_catalog_table() {
    let mut store = CatalogStore::new();

    install_registry_catalog_schema(&mut store).unwrap();

    let expected = registry_catalog_specs()
        .into_iter()
        .map(|spec| spec.name)
        .collect::<Vec<_>>();
    assert_eq!(store.tables().len(), expected.len());
    for table in expected {
        assert!(store.table(&table).is_some(), "missing table {table}");
    }
}

#[test]
fn schema_uses_expected_write_policies() {
    let mut store = CatalogStore::new();
    install_registry_catalog_schema(&mut store).unwrap();

    let expected = [
        (schema_table(), CatalogWritePolicy::Sealed),
        (sequences_table(), CatalogWritePolicy::Mutable),
        (libs_table(), CatalogWritePolicy::Sealed),
        (exports_table(), CatalogWritePolicy::Sealed),
        (runtime_table(), CatalogWritePolicy::Sealed),
        (tests_table(), CatalogWritePolicy::Sealed),
        (tests_by_lib_table(), CatalogWritePolicy::Derived),
        (number_ops_table(), CatalogWritePolicy::AppendOnly),
        (promotion_rules_table(), CatalogWritePolicy::AppendOnly),
        (
            value_promotion_rules_table(),
            CatalogWritePolicy::AppendOnly,
        ),
    ];

    for (table, policy) in expected {
        assert_eq!(store.table(&table).unwrap().policy, policy);
    }
}

#[test]
fn row_keys_match_documented_forms() {
    let lib = manifest("demo-lib");
    let value = bool_value(true);

    assert_eq!(
        sequence_row(Symbol::new("lib"), 1).key.as_qualified_str(),
        "registry-seq/lib"
    );
    assert_eq!(
        lib_row(LibId(7), &lib, true).key.as_qualified_str(),
        "registry-lib/demo-lib"
    );
    assert_eq!(
        export_row(
            ExportKind::named(ExportKind::FUNCTION),
            Symbol::qualified("core", "add"),
            Symbol::new("demo-lib"),
            ExportState::Resolved {
                id: RuntimeId::Function(FunctionId(3)),
            },
        )
        .key
        .as_qualified_str(),
        "registry-export/function/core%2Fadd"
    );
    assert_eq!(
        runtime_value_row(
            Symbol::new("class"),
            7,
            Symbol::new("Widget"),
            value.clone()
        )
        .key
        .as_qualified_str(),
        "registry-runtime/class/7"
    );
    assert_eq!(
        plain_value_row(Symbol::qualified("core", "pi"), value.clone())
            .key
            .as_qualified_str(),
        "registry-runtime/value/core%2Fpi"
    );
    assert_eq!(
        test_row(
            Symbol::qualified("demo", "smoke"),
            Symbol::new("demo-lib"),
            Arc::new(DummyTest),
            Vec::new(),
        )
        .key
        .as_qualified_str(),
        "registry-test/demo%2Fsmoke"
    );
}

#[test]
fn row_key_helpers_round_trip_components() {
    let qualified = Symbol::qualified("core", "Thing");

    assert_eq!(
        split_key(&sequence_key(&Symbol::new("number-domain"))),
        vec!["registry-seq", "number-domain"]
    );
    assert_eq!(
        split_key(&lib_key(&qualified)),
        vec!["registry-lib", "core/Thing"]
    );
    assert_eq!(
        split_key(&runtime_key(&Symbol::new("shape"), 42)),
        vec!["registry-runtime", "shape", "42"]
    );
    assert_eq!(
        split_key(&plain_value_key(&qualified)),
        vec!["registry-runtime", "value", "core/Thing"]
    );
    assert_eq!(
        split_key(&test_key(&qualified)),
        vec!["registry-test", "core/Thing"]
    );
}

#[test]
fn row_data_fields_are_deterministic() {
    let manifest = manifest("demo-lib");
    let row = lib_row(LibId(7), &manifest, true);

    assert_eq!(row.data.get(&field("id")), Some(&int("7")));
    assert_eq!(
        row.data.get(&field("symbol")),
        Some(&Expr::Symbol(Symbol::new("demo-lib")))
    );
    assert_eq!(
        row.data.get(&field("version")),
        Some(&Expr::String("1.2.3".to_owned()))
    );
    assert_eq!(row.data.get(&field("abi-major")), Some(&int("0")));
    assert_eq!(row.data.get(&field("abi-minor")), Some(&int("1")));
    assert_eq!(
        row.data.get(&field("target")),
        Some(&Expr::Symbol(Symbol::new("host-registered")))
    );
    assert_eq!(row.data.get(&field("trusted")), Some(&Expr::Bool(true)));
}

#[test]
fn export_rows_encode_resolved_and_failure_states() {
    let resolved = export_row(
        ExportKind::named(ExportKind::CLASS),
        Symbol::new("Widget"),
        Symbol::new("demo-lib"),
        ExportState::Resolved {
            id: RuntimeId::Class(CORE_EXPR_CLASS_ID),
        },
    );
    assert_eq!(
        resolved.data.get(&field("state")),
        Some(&Expr::Symbol(Symbol::new("resolved")))
    );
    assert_eq!(
        resolved.data.get(&field("runtime-kind")),
        Some(&Expr::Symbol(Symbol::new("class")))
    );
    assert_eq!(resolved.data.get(&field("runtime-id")), Some(&int("9")));

    let value = export_row(
        ExportKind::named(ExportKind::VALUE),
        Symbol::new("answer"),
        Symbol::new("demo-lib"),
        ExportState::Resolved {
            id: RuntimeId::Value,
        },
    );
    assert_eq!(
        value.data.get(&field("runtime-kind")),
        Some(&Expr::Symbol(Symbol::new("value")))
    );
    assert!(!value.data.contains_key(&field("runtime-id")));

    let unsupported = export_row(
        ExportKind::named(ExportKind::FUNCTION),
        Symbol::new("future"),
        Symbol::new("demo-lib"),
        ExportState::Unsupported {
            reason: "not implemented".to_owned(),
        },
    );
    assert_eq!(
        unsupported.data.get(&field("state")),
        Some(&Expr::Symbol(Symbol::new("unsupported")))
    );
    assert_eq!(
        unsupported.data.get(&field("reason")),
        Some(&Expr::String("not implemented".to_owned()))
    );

    let invalid = export_row(
        ExportKind::named(ExportKind::FUNCTION),
        Symbol::new("bad"),
        Symbol::new("demo-lib"),
        ExportState::Invalid {
            error: "bad export".to_owned(),
        },
    );
    assert_eq!(
        invalid.data.get(&field("state")),
        Some(&Expr::Symbol(Symbol::new("invalid")))
    );
    assert_eq!(
        invalid.data.get(&field("error")),
        Some(&Expr::String("bad export".to_owned()))
    );
}

#[test]
fn runtime_and_test_rows_keep_live_payloads_out_of_data() {
    let field_value = field("value");
    let field_test = field("test");
    let value = bool_value(true);
    let runtime = runtime_value_row(Symbol::new("function"), 3, Symbol::new("f"), value.clone());
    let plain = plain_value_row(Symbol::new("answer"), value.clone());
    let test = test_row(
        Symbol::new("smoke"),
        Symbol::new("demo-lib"),
        Arc::new(DummyTest),
        vec![Symbol::new("answer"), Symbol::qualified("core", "add")],
    );

    assert_eq!(runtime.live_value(&field_value), Some(&value));
    assert_eq!(plain.live_value(&field_value), Some(&value));
    assert!(!runtime.data.contains_key(&field_value));
    assert!(!plain.data.contains_key(&field_value));
    assert!(test.live_test(&field_test).is_some());
    assert!(!test.data.contains_key(&field_test));
    assert_eq!(
        test.data.get(&field("subjects")),
        Some(&Expr::List(vec![
            Expr::Symbol(Symbol::new("answer")),
            Expr::Symbol(Symbol::qualified("core", "add")),
        ]))
    );
}

#[test]
fn direct_duplicate_class_still_returns_duplicate_export() {
    let mut registry = Registry::default();
    let symbol = Symbol::new("direct-dup-class");
    let class_id = registry
        .register_class_value(symbol.clone(), bool_value(true))
        .unwrap();

    assert!(registry.catalog_runtime_projection_matches(
        &ExportKind::named(ExportKind::CLASS),
        &symbol,
        RuntimeId::Class(class_id),
    ));
    let export_rows = registry.catalog_export_row_count_for_tests();
    let runtime_rows = registry.catalog_runtime_row_count_for_tests();
    let err = registry
        .register_class_value(symbol.clone(), bool_value(false))
        .unwrap_err();

    assert_duplicate_export(err, ExportKind::CLASS, &symbol);
    assert_eq!(registry.classes().get(&symbol), Some(&class_id));
    assert_eq!(registry.catalog_export_row_count_for_tests(), export_rows);
    assert_eq!(registry.catalog_runtime_row_count_for_tests(), runtime_rows);
}

#[test]
fn direct_duplicate_test_still_returns_duplicate_export() {
    let mut registry = Registry::default();
    let symbol = Symbol::qualified("direct", "smoke");
    let lib = Symbol::new("direct-lib");
    registry
        .register_test(
            symbol.clone(),
            lib.clone(),
            Arc::new(DummyTest),
            vec![Symbol::new("subject")],
        )
        .unwrap();

    assert!(
        registry
            .catalog_test_registration_matches(&symbol, registry.registered_test(&symbol).unwrap())
    );
    let export_rows = registry.catalog_export_row_count_for_tests();
    let test_rows = registry.catalog_test_row_count_for_tests();
    let err = registry
        .register_test(symbol.clone(), lib.clone(), Arc::new(DummyTest), Vec::new())
        .unwrap_err();

    assert_duplicate_export(err, "test", &symbol);
    assert_eq!(registry.tests_for_lib(&lib), Some(&[symbol.clone()][..]));
    assert_eq!(registry.catalog_export_row_count_for_tests(), export_rows);
    assert_eq!(registry.catalog_test_row_count_for_tests(), test_rows);
}

#[test]
fn failed_duplicate_value_write_leaves_catalog_and_projection_caches_unchanged() {
    let mut registry = Registry::default();
    let symbol = Symbol::new("direct-dup-value");
    let first = bool_value(true);
    registry
        .register_value(symbol.clone(), first.clone())
        .unwrap();

    let export_rows = registry.catalog_export_row_count_for_tests();
    let runtime_rows = registry.catalog_runtime_row_count_for_tests();
    let err = registry
        .register_value(symbol.clone(), bool_value(false))
        .unwrap_err();

    assert_duplicate_export(err, ExportKind::VALUE, &symbol);
    assert_eq!(registry.value_by_symbol(&symbol), Some(&first));
    assert_eq!(registry.catalog_export_row_count_for_tests(), export_rows);
    assert_eq!(registry.catalog_runtime_row_count_for_tests(), runtime_rows);
    assert!(registry.catalog_runtime_projection_matches(
        &ExportKind::named(ExportKind::VALUE),
        &symbol,
        RuntimeId::Value,
    ));
}