Add more tests for variable and fixed size keys

next
Sayan Nandan 7 months ago
parent baaa36d336
commit 525dc46f86
No known key found for this signature in database
GPG Key ID: 0EBD769024B24F0A

@ -27,7 +27,7 @@
use { use {
crate::{ crate::{
engine::{ engine::{
core::{dml, model::Model, space::Space, EntityIDRef}, core::{dml, index::RowData, model::Model, space::Space, EntityID, EntityIDRef},
data::lit::Lit, data::lit::Lit,
error::QueryResult, error::QueryResult,
fractal::{test_utils::TestGlobal, GlobalInstanceLike}, fractal::{test_utils::TestGlobal, GlobalInstanceLike},
@ -44,15 +44,42 @@ use {
crossbeam_epoch::pin, crossbeam_epoch::pin,
}; };
fn create_model_and_space(global: &TestGlobal<VirtualFS>, create_model: &str) -> QueryResult<()> { const TEST_DATASET_SIZE: usize = 1000;
const TEST_UPDATE_DATASET_SIZE: usize = 8200; // this peculiar size to force the buffer to flush
fn create_test_kv_strings(change_count: usize) -> Vec<(String, String)> {
(1..=change_count)
.map(|i| {
(
format!("user-{i:0>change_count$}"),
format!("password-{i:0>change_count$}"),
)
})
.collect()
}
fn create_test_kv_int(change_count: usize) -> Vec<(u64, String)> {
(0..change_count)
.map(|i| (i as u64, format!("password-{i:0>change_count$}")))
.collect()
}
fn create_model_and_space(
global: &TestGlobal<VirtualFS>,
create_model: &str,
) -> QueryResult<EntityID> {
let tokens = lex_insecure(create_model.as_bytes()).unwrap(); let tokens = lex_insecure(create_model.as_bytes()).unwrap();
let create_model: CreateModel = ast::parse_ast_node_full(&tokens[2..]).unwrap(); let create_model: CreateModel = ast::parse_ast_node_full(&tokens[2..]).unwrap();
let mdl_name = EntityID::new(
create_model.model_name.space(),
create_model.model_name.entity(),
);
// first create space // first create space
let create_space_str = format!("create space {}", create_model.model_name.space()); let create_space_str = format!("create space {}", create_model.model_name.space());
let create_space_tokens = lex_insecure(create_space_str.as_bytes()).unwrap(); let create_space_tokens = lex_insecure(create_space_str.as_bytes()).unwrap();
let create_space: CreateSpace = ast::parse_ast_node_full(&create_space_tokens[2..]).unwrap(); let create_space: CreateSpace = ast::parse_ast_node_full(&create_space_tokens[2..]).unwrap();
Space::transactional_exec_create(global, create_space)?; Space::transactional_exec_create(global, create_space)?;
Model::transactional_exec_create(global, create_model).map(|_| ()) Model::transactional_exec_create(global, create_model).map(|_| mdl_name)
} }
fn run_insert(global: &TestGlobal<VirtualFS>, insert: &str) -> QueryResult<()> { fn run_insert(global: &TestGlobal<VirtualFS>, insert: &str) -> QueryResult<()> {
@ -67,171 +94,229 @@ fn run_update(global: &TestGlobal<VirtualFS>, update: &str) -> QueryResult<()> {
dml::update(global, insert) dml::update(global, insert)
} }
#[test] fn auto_hook<T>(msg: &str, f: impl Fn() -> T) -> T {
fn empty_model_data() { let hook = std::panic::take_hook();
test_utils::with_variable("empty_model_data", |log_name| { let decl_owned = msg.to_owned();
// create and close std::panic::set_hook(Box::new(move |pinfo| {
{ eprintln!("panic due to `{decl_owned}`: {pinfo}")
let global = TestGlobal::new_with_vfs_driver(log_name); }));
let _ = create_model_and_space( let r = f();
&global, std::panic::set_hook(hook);
"create model milky_way.solar_system(planet_name: string, population: uint64)", r
)
.unwrap();
}
// open
{
let global = TestGlobal::new_with_vfs_driver(log_name);
drop(global);
}
})
} }
fn create_test_kv(change_count: usize) -> Vec<(String, String)> { fn create_and_close(log_name: &str, decl: &str) {
(1..=change_count) auto_hook(decl, || {
.map(|i| { test_utils::with_variable(log_name, |log_name| {
( // create and close
format!("user-{i:0>change_count$}"), {
format!("password-{i:0>change_count$}"), let global = TestGlobal::new_with_vfs_driver(log_name);
) let _ = create_model_and_space(&global, decl).unwrap();
}
// open
{
let global = TestGlobal::new_with_vfs_driver(log_name);
drop(global);
}
}) })
.collect() })
} }
#[test] fn run_sample_inserts<K, V>(
fn model_data_inserts() { log_name: &str,
test_utils::with_variable(("model_data_inserts", 1000), |(log_name, change_count)| { decl: &str,
let key_values = create_test_kv(change_count); key_values: Vec<(K, V)>,
// create, insert and close make_insert_query: impl Fn(&K, &V) -> String,
{ as_pk: for<'a> fn(&'a K) -> Lit<'a>,
let mut global = TestGlobal::new_with_vfs_driver(log_name); check_row: impl Fn(&K, &V, &RowData),
global.set_max_data_pressure(change_count); ) {
let _ = create_model_and_space( auto_hook(decl, || {
&global, test_utils::with_variable(log_name, |log_name| {
"create model apps.social(user_name: string, password: string)", // create, insert and close
) let mdl_name;
.unwrap(); {
for (username, password) in key_values.iter() { let mut global = TestGlobal::new_with_vfs_driver(log_name);
run_insert( global.set_max_data_pressure(key_values.len());
&global, mdl_name = create_model_and_space(&global, decl).unwrap();
&format!("insert into apps.social('{username}', '{password}')"), for (username, password) in key_values.iter() {
) run_insert(&global, &make_insert_query(username, password)).unwrap();
.unwrap(); }
} }
} // reopen and verify 100 times
// reopen and verify 100 times test_utils::multi_run(100, || {
test_utils::multi_run(100, || { let global = TestGlobal::new_with_vfs_driver(log_name);
let global = TestGlobal::new_with_vfs_driver(log_name); global.load_model_drivers().unwrap();
global.load_model_drivers().unwrap(); global
global .state()
.state() .with_model(
.with_model(EntityIDRef::new("apps", "social"), |model| { EntityIDRef::new(mdl_name.space(), mdl_name.entity()),
let g = pin(); |model| {
for (username, password) in key_values.iter() { let g = pin();
assert_eq!( for (username, password) in key_values.iter() {
model let row = model
.primary_index() .primary_index()
.select(Lit::new_str(username.as_str()), &g) .select(as_pk(username), &g)
.unwrap() .unwrap()
.d_data() .d_data()
.read() .read();
.fields() check_row(username, password, &row)
.get("password") }
.unwrap() Ok(())
.str(), },
password.as_str() )
) .unwrap()
} })
Ok(())
})
.unwrap()
}) })
}) })
} }
#[test] fn run_sample_updates<K, V>(
fn model_data_updates() { log_name: &str,
test_utils::with_variable(("model_data_updates", 8200), |(log_name, n)| { decl: &str,
let key_values = create_test_kv(n); key_values: Vec<(K, V)>,
/* make_insert_query: impl Fn(&K, &V) -> String,
- we first open the log and then insert n values make_update_query: impl Fn(&K, &V) -> String,
- we then reopen the log 100 times, changing n / 100 values every time (we set the string to an empty one) as_pk: for<'a> fn(&'a K) -> Lit<'a>,
- we finally reopen the log and check if all the keys have empty string as the password check_row: impl Fn(&K, &V, &RowData),
*/ ) {
{ auto_hook(decl, || {
// insert n values test_utils::with_variable((log_name, TEST_UPDATE_DATASET_SIZE), |(log_name, n)| {
let mut global = TestGlobal::new_with_vfs_driver(log_name); /*
global.set_max_data_pressure(n); - we first open the log and then insert n values
let _ = create_model_and_space( - we then reopen the log 100 times, changing n / 100 values every time (we set the string to an empty one)
&global, - we finally reopen the log and check if all the keys have empty string as the password
"create model apps.social(user_name: string, password: string)", */
) let mdl_name;
.unwrap(); {
for (username, password) in key_values.iter() { // insert n values
run_insert(
&global,
&format!("insert into apps.social('{username}', '{password}')"),
)
.unwrap();
}
}
{
// reopen and update multiple times
// this effectively opens the log 100 times
let changes_per_cycle = n / 10;
let reopen_count = n / changes_per_cycle;
// now update values
let mut actual_position = 0;
for _ in 0..reopen_count {
let mut global = TestGlobal::new_with_vfs_driver(log_name); let mut global = TestGlobal::new_with_vfs_driver(log_name);
global.set_max_data_pressure(changes_per_cycle); global.set_max_data_pressure(n);
global.load_model_drivers().unwrap(); mdl_name = create_model_and_space(&global, decl).unwrap();
let mut j = 0; for (username, password) in key_values.iter() {
for _ in 0..changes_per_cycle { run_insert(&global, &make_insert_query(username, password)).unwrap();
let (username, _) = &key_values[actual_position];
run_update(
&global,
&format!(
"update apps.social set password = '' where user_name = '{username}'"
),
)
.unwrap();
actual_position += 1;
j += 1;
} }
assert_eq!(j, changes_per_cycle);
drop(global);
} }
assert_eq!(actual_position, n);
}
{
let global = TestGlobal::new_with_vfs_driver(log_name);
global.load_model_drivers().unwrap();
for (txn_id, (username, _)) in key_values
.iter()
.enumerate()
.map(|(i, x)| ((i + n) as u64, x))
{ {
global // reopen and update multiple times
.state() // this effectively opens the log 100 times
.with_model(EntityIDRef::new("apps", "social"), |model| { let changes_per_cycle = n / 10;
let g = pin(); let reopen_count = n / changes_per_cycle;
let row = model // now update values
.primary_index() let mut actual_position = 0;
.select(Lit::new_str(username.as_str()), &g) for _ in 0..reopen_count {
.unwrap() let mut global = TestGlobal::new_with_vfs_driver(log_name);
.d_data() global.set_max_data_pressure(changes_per_cycle);
.read(); global.load_model_drivers().unwrap();
let pass = row.fields().get("password").unwrap().str(); let mut j = 0;
assert!( for _ in 0..changes_per_cycle {
pass.is_empty(), let (username, pass) = &key_values[actual_position];
"failed for {username} because pass is {pass}", run_update(&global, &make_update_query(username, pass)).unwrap();
); actual_position += 1;
assert_eq!(row.get_txn_revised().value_u64(), txn_id); j += 1;
Ok(()) }
}) assert_eq!(j, changes_per_cycle);
.unwrap(); drop(global);
}
assert_eq!(actual_position, n);
} }
} {
let global = TestGlobal::new_with_vfs_driver(log_name);
global.load_model_drivers().unwrap();
for (txn_id, (username, password)) in key_values
.iter()
.enumerate()
.map(|(i, x)| ((i + n) as u64, x))
{
global
.state()
.with_model(
EntityIDRef::new(mdl_name.space(), mdl_name.entity()),
|model| {
let g = pin();
let row = model
.primary_index()
.select(as_pk(username), &g)
.unwrap()
.d_data()
.read();
check_row(username, password, &row);
assert_eq!(row.get_txn_revised().value_u64(), txn_id);
Ok(())
},
)
.unwrap();
}
}
})
}) })
} }
/*
test runs
*/
#[test]
fn empty_model_data() {
create_and_close(
"empty_model_data_variable_index_key",
"create model milky_way.solar_system(planet_name: string, population: uint64)",
);
create_and_close(
"empty_model_data_fixed_index_key",
"create model milky_way.solar_system(planet_id: uint64, population: uint64)",
);
}
#[test]
fn model_data_inserts() {
run_sample_inserts(
"model_data_inserts_variable_pk",
"create model apps.social(user_name: string, password: string)",
create_test_kv_strings(TEST_DATASET_SIZE),
|k, v| format!("insert into apps.social('{k}', '{v}')"),
|k| Lit::new_str(k),
|_, v, row| assert_eq!(row.fields().get("password").unwrap().str(), v),
);
run_sample_inserts(
"model_data_inserts_fixed_pk",
"create model apps.social(user_id: uint64, password: string)",
create_test_kv_int(TEST_DATASET_SIZE),
|k, v| format!("insert into apps.social({k}, '{v}')"),
|k| Lit::new_uint(*k),
|_, v, row| assert_eq!(row.fields().get("password").unwrap().str(), v),
)
}
#[test]
fn model_data_updates() {
run_sample_updates(
"model_data_updates_variable_key",
"create model apps.social(user_name: string, password: string)",
create_test_kv_strings(TEST_UPDATE_DATASET_SIZE),
|k, v| format!("insert into apps.social('{k}', '{v}')"),
|k, _| format!("update apps.social set password = '' where user_name = '{k}'"),
|k| Lit::new_str(k),
|username, _, row| {
let pass = row.fields().get("password").unwrap().str();
assert!(
pass.is_empty(),
"failed for {username} because pass is {pass}",
);
},
);
run_sample_updates(
"model_data_updates_fixed_key",
"create model apps.social(user_name: uint64, password: string)",
create_test_kv_int(TEST_UPDATE_DATASET_SIZE),
|k, v| format!("insert into apps.social({k}, '{v}')"),
|k, _| format!("update apps.social set password = '' where user_name = {k}"),
|k| Lit::new_uint(*k),
|username, _, row| {
let pass = row.fields().get("password").unwrap().str();
assert!(
pass.is_empty(),
"failed for {username} because pass is {pass}",
);
},
);
}

Loading…
Cancel
Save