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<str>) -> Result<Output> {
let s = cmd.as_ref();
check_cmd_output(Command::new("sh").arg("-c").arg(s).output()?)
}
fn create_initialized_storage_dir<T: AsRef<Path>>(dir: T) -> Result<Output> {
run_js_writable(dir, "")
}
fn cp_dirs<T: AsRef<Path>>(a: T, b: T) -> Result<Output> {
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<T: AsRef<Path>>(a: T, b: T) -> Result<Output> {
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<i32> = $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<()> {
let (jsdir, rdir) = create_storage_dirs_with_same_keys()?;
let hb = Hyperbee::from_storage_dir(&rdir).await?;
let key = b"hello";
let value = b"world";
hb.put(key, Some(value)).await?;
let _ = run_js_writable(
&jsdir,
"
await hb.put('hello', 'world');
",
)?;
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<i32> = 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<i32> = 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<i32> = 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(())
}