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);
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>()
.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());
}