saddle-framework 0.3.11

The single business-facing facade for Saddle applications
use super::*;

crate::database_operations! {
    pub mod small {
        table records { id: u64, payload: bytes<8> }
        query_optional Read {
            table records;
            parameters Params { id => id }
            result Row { payload => payload }
        }
    }
}
crate::database_operations! {
    pub mod large {
        table records { id: u64, payload: bytes<1048576> }
        query_optional Read {
            table records;
            parameters Params { id => id }
            result Row { payload => payload }
        }
    }
}

struct Fixture(PathBuf);
impl Fixture {
    fn new() -> Self {
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        let path =
            std::env::temp_dir().join(format!("saddle-db-memory-{}-{nonce}", std::process::id()));
        std::fs::create_dir(&path).unwrap();
        std::fs::create_dir(path.join("mapping")).unwrap();
        std::fs::write(path.join("mapping/records.json"),
            r#"{"table":{"from":"records","to":"physical_records"},"columns":[{"from":"id","to":"physical_id"},{"from":"payload","to":"physical_payload"}]}"#).unwrap();
        Self(path)
    }

    fn config(&self, memory: usize) -> ProcessConfig<()> {
        let path = self.0.join("saddle.toml");
        std::fs::write(
            &path,
            format!(
                r#"
[framework]
listen = "127.0.0.1:38001"
[framework.management]
bind = "127.0.0.1:38002"
[framework.admission]
cpuCores = 16
memoryMb = {memory}
[framework.admission.dependencies]
databaseConcurrency = 16
profusecontractConcurrency = 16
[framework.profusecontract]
authority = "http://profusecontract.internal:50051"
[secrets]
"#
            ),
        )
        .unwrap();
        ProcessConfig::load(path).unwrap()
    }

    fn with_database(&self, memory: usize) -> ProcessConfig<()> {
        let mut config = self.config(memory);
        // No network connection: the real registry/freeze path is tested.
        config.database = Some(
            saddle_db::DatabaseConfig::new("mysql://unused@127.0.0.1/db")
                .name_mapping_directory(self.0.join("mapping")),
        );
        config
    }
}
impl Drop for Fixture {
    fn drop(&mut self) {
        std::fs::remove_dir_all(&self.0).unwrap();
    }
}

fn prepare(
    mut config: ProcessConfig<()>,
) -> Option<saddle_admission::ProfuseGwLightweightProcessOwner> {
    if let Some(db) = config.database.take() {
        let (db, budget) = db
            .freeze_for_admission(config.deployment_resource_budget)
            .ok()?;
        config.database = Some(db);
        config.deployment_resource_budget = budget;
    }
    let (app, listener) = saddle_core::BootstrapRendezvousIssuer::issue()
        .freeze_application(saddle_core::GeneratedApplicationFreezeSource::new(
            "memory-fixture",
            b"generated-memory-fixture",
            &["query"],
        ))
        .unwrap();
    let listener = listener
        .freeze_listener(saddle_core::ListenerStartupFreezeSource::new(
            "memory-fixture",
            config.listen,
            config.management_bind,
            config.request_timeout,
        ))
        .ok()?;
    let (whole, receipt) = saddle_core::pair_bootstrap_rendezvous(app, listener).ok()?;
    let budget = saddle_admission::bind_deployment_resource_budget_bootstrap(
        config.deployment_resource_budget,
        whole,
        receipt,
    )
    .ok()?;
    saddle_admission::prepare_profusegw_lightweight_profile(budget).ok()
}

#[test]
fn generated_database_memory_changes_real_capacity_without_database_io() {
    let fixture = Fixture::new();
    let no_db = prepare(fixture.config(32)).unwrap();
    let no_db_limit = no_db.capacity_snapshot().active_limit();
    no_db.finish().unwrap();
    let small = prepare(
        fixture
            .with_database(32)
            .__register_database_query::<small::Read, ()>(),
    )
    .unwrap();
    let small_limit = small.capacity_snapshot().active_limit();
    small.finish().unwrap();
    let large = prepare(
        fixture
            .with_database(32)
            .__register_database_query::<large::Read, ()>(),
    )
    .unwrap();
    let large_limit = large.capacity_snapshot().active_limit();
    large.finish().unwrap();
    assert!(no_db_limit >= small_limit);
    assert!(
        small_limit > large_limit,
        "generated bytes declaration must affect actual capacity"
    );
    let combined = prepare(
        fixture
            .with_database(32)
            .__register_database_query::<small::Read, ()>()
            .__register_database_query::<large::Read, ()>(),
    )
    .unwrap();
    assert_eq!(combined.capacity_snapshot().active_limit(), large_limit);
    combined.finish().unwrap();
    let no_db_small_budget = prepare(fixture.config(8)).unwrap();
    no_db_small_budget.finish().unwrap();
    assert!(
        prepare(
            fixture
                .with_database(8)
                .__register_database_query::<large::Read, ()>()
        )
        .is_none()
    );
    println!(
        "GENERATED_DB_MEMORY_PASS no_db={no_db_limit} small={small_limit} large={large_limit} max=PASS reject=PASS"
    );
}

#[test]
fn generated_database_memory_missing_frame_restores_config_and_budget_for_retry() {
    let fixture = Fixture::new();
    let mut config = fixture.with_database(32);
    let db = config
        .database
        .take()
        .unwrap()
        .register_query_operation::<small::Read>()
        // A different operation's measured frame cannot complete this one.
        .with_registered_execution_layout::<large::Read>(
            crate::database_capability::query_memory_layout::<large::Read, ()>(),
        );
    let (db, budget, error) = db
        .freeze_for_admission(config.deployment_resource_budget)
        .err()
        .unwrap();
    assert_eq!(
        error,
        saddle_db::DatabaseNameMappingError::MissingMemoryLayout
    );
    let db = db.with_registered_execution_layout::<small::Read>(
        crate::database_capability::query_memory_layout::<small::Read, ()>(),
    );
    assert!(db.freeze_for_admission(budget).is_ok());
}