mod common; use std::{ path::Path, process::{Command, Output}, }; use hyperbee::Hyperbee; use tempfile::{tempdir, TempDir}; use common::{check_cmd_output, i32_key_vec, js::run_js_writable, write_range_to_hb, Rand, Result}; fn run_command(cmd: impl AsRef) -> Result { let s = cmd.as_ref(); check_cmd_output(Command::new("sh").arg("-c").arg(s).output()?) } fn create_initialized_storage_dir>(dir: T) -> Result { run_js_writable(dir, "") } fn cp_dirs>(a: T, b: T) -> Result { let a = a.as_ref().to_string_lossy(); let b = b.as_ref().to_string_lossy(); let cmd = format!("cp -r {a}/. {b}/."); run_command(cmd) } fn diff_dirs>(a: T, b: T) -> Result { let astr = a.as_ref().to_string_lossy(); let bstr = b.as_ref().to_string_lossy(); let cmd = format!("diff {astr} {bstr}"); run_command(cmd) } fn create_storage_dirs_with_same_keys() -> Result<(TempDir, TempDir)> { let rdir = tempdir()?; let jsdir = tempdir()?; let _ = create_initialized_storage_dir(&jsdir)?; let _ = cp_dirs(&jsdir, &rdir)?; Ok((jsdir, rdir)) } macro_rules! put_rs_and_js_range { ($range:expr, $extra_js:expr) => {{ let (jsdir, rdir) = create_storage_dirs_with_same_keys()?; let keys: Vec = $range.clone().collect(); let hb = Hyperbee::from_storage_dir(&rdir).await?; write_range_to_hb!(&hb, $range); let js_code = format!( " const keys = {}; for (const ikey of keys) {{ const key = String(ikey); await hb.put(key, key); }} {}", serde_json::to_string(&keys)?, $extra_js, ); println!("{js_code}"); let _ = run_js_writable(&jsdir, &js_code)?; (hb, jsdir, rdir) }}; ($range:expr) => {{ put_rs_and_js_range!($range, "") }}; } #[tokio::test] async fn compare_disk_hello_world() -> Result<()> { // set up the initial directories let (jsdir, rdir) = create_storage_dirs_with_same_keys()?; // add hello world to rust let hb = Hyperbee::from_storage_dir(&rdir).await?; let key = b"hello"; let value = b"world"; hb.put(key, Some(value)).await?; // add hello world to js let _ = run_js_writable( &jsdir, " await hb.put('hello', 'world'); ", )?; // compare directies diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn compare_trees_of_some_ranges() -> Result<()> { for n_keys in (8..12).chain(47..53) { let (_hb, jsdir, rdir) = put_rs_and_js_range!(0..n_keys); diff_dirs(&jsdir, &rdir)?; } Ok(()) } #[tokio::test] async fn rotate_from_right_the_same() -> Result<()> { let delete_me = 37; let (hb, jsdir, rdir) = put_rs_and_js_range!(0..48, format!("await hb.del('{}')", delete_me)); hb.del(&i32_key_vec(delete_me)).await?; diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn rotate_from_left_the_same() -> Result<()> { let delete_me = 6; let (hb, jsdir, rdir) = put_rs_and_js_range!(0..48, format!("await hb.del('{}')", delete_me)); hb.del(&i32_key_vec(delete_me)).await?; diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn merge_from_left_the_same() -> Result<()> { let delete_me = 13; let (hb, jsdir, rdir) = put_rs_and_js_range!(0..48, format!("await hb.del('{}')", delete_me)); hb.del(&i32_key_vec(delete_me)).await?; diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn merge_from_right_the_same() -> Result<()> { let delete_me = 0; let (hb, jsdir, rdir) = put_rs_and_js_range!(0..48, format!("await hb.del('{}')", delete_me)); hb.del(&i32_key_vec(delete_me)).await?; diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn double_merge_replace_root() -> Result<()> { let rand = Rand::default(); let keys: Vec = rand.shuffle((0..100).collect()); let del_keys = rand.shuffle(keys.clone()).to_vec()[..43].to_vec(); let extra_js = format!( " const del_keys = {}; for (const ikey of del_keys) {{ const key = String(ikey); await hb.del(key); }} ", serde_json::to_string(&del_keys)?, ); let (hb, jsdir, rdir) = put_rs_and_js_range!(keys.clone().into_iter(), extra_js); for key in del_keys.iter() { let key = i32_key_vec(*key); hb.del(&key).await?; } diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn order_via_depth_first_search() -> Result<()> { let rand = Rand::default(); let n_keys = 148; let stop = 123; let mut keys: Vec = rand.shuffle((0..n_keys).collect())[..(stop as usize)].to_vec(); let last_i = keys.pop().unwrap(); let last = i32_key_vec(last_i); let extra_js = format!( " const key = '{}'; await hb.put(key, key); ", last_i ); let (hb, jsdir, rsdir) = put_rs_and_js_range!(keys.clone().into_iter(), extra_js); hb.put(&last, Some(&last)).await?; #[cfg(feature = "debug")] { let jshb = Hyperbee::from_storage_dir(&jsdir).await?; assert_eq!(hb.print().await?, jshb.print().await?); } diff_dirs(&jsdir, &rsdir)?; Ok(()) } #[tokio::test] async fn pulls_from_child_with_more_keys() -> Result<()> { let delete_me = 19; let range = (10..20).chain(1..2); let (hb, jsdir, rdir) = put_rs_and_js_range!(range, format!("await hb.del('{}')", delete_me)); hb.del(&i32_key_vec(delete_me)).await?; diff_dirs(&jsdir, &rdir)?; Ok(()) } #[tokio::test] async fn big_random_test() -> Result<()> { let rand = Rand::default(); let n_keys = 1000; let keys: Vec = rand.shuffle((0..n_keys).collect()); let del_keys = rand.shuffle(keys.clone()); let extra_js = format!( " const del_keys = {}; for (const ikey of del_keys) {{ const key = String(ikey); await hb.del(key); }} ", serde_json::to_string(&del_keys)?, ); let (hb, jsdir, rsdir) = put_rs_and_js_range!(keys.clone().into_iter(), extra_js); for key in del_keys.iter() { let key = i32_key_vec(*key); hb.del(&key).await?; } #[cfg(feature = "debug")] { let jshb = Hyperbee::from_storage_dir(&jsdir).await?; assert_eq!(hb.print().await?, jshb.print().await?); } diff_dirs(&jsdir, &rsdir)?; Ok(()) }