8000 test(compat): cross-platform .graph file compatibility tests (closes #150) by adityamukho · Pull Request #154 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions examples/generate_compat_fixture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Generates `tests/fixtures/compat.graph` — a minimal v7 `.graph` file containing
//! two known facts used by the cross-platform compatibility tests.
//!
//! Run once when the file format changes or the fixture needs regenerating:
//! cargo run --example generate_compat_fixture
//!
//! The fixture is committed to the repository. Do not regenerate it unless the
//! v7 file format itself has changed — regenerating changes the binary and
//! every cross-platform test that embeds it via `include_bytes!`.

// wasm-pack compiles examples for the browser target; provide a no-op entry
// point so the example compiles cleanly. The actual generator only makes sense
// on native (it needs the file system and Minigraf::open).
#[cfg(target_arch = "wasm32")]
fn main() {}

#[cfg(not(target_arch = "wasm32"))]
fn main() -> anyhow::Result<()> {
use std::path::PathBuf;

let manifest_dir = env!("CARGO_MANIFEST_DIR");
let fixture_path = PathBuf::from(manifest_dir).join("tests/fixtures/compat.graph");
let tmp_path = fixture_path.with_extension("graph.tmp");

// Remove any leftover from a previous run.
let _ = std::fs::remove_file(&tmp_path);
let _ = std::fs::remove_file(tmp_path.with_extension("wal"));

// Populate with known facts.
let db = minigraf::Minigraf::open(&tmp_path)?;
db.execute(r#"(transact [[:alice :name "Alice"]])"#)?;
db.execute("(transact [[:alice :age 30]])")?;
// Checkpoint flushes WAL → main file so the bytes are self-contained.
db.checkpoint()?;
drop(db);

// Remove WAL sidecar before copying.
let wal_path = tmp_path.with_extension("graph.tmp.wal");
let _ = std::fs::remove_file(&wal_path);

std::fs::rename(&tmp_path, &fixture_path)?;
println!("Written: {}", fixture_path.display());
Ok(())
}
41 changes: 41 additions & 0 deletions src/browser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,47 @@ mod tests {
assert_eq!(blob.byte_length() as usize % crate::storage::PAGE_SIZE, 0);
}

/// Load the committed binary fixture (produced by `cargo run --example
/// generate_compat_fixture` from the native build) into a `BrowserDb` via
/// `import_graph` and verify the known facts are queryable.
///
/// This is the **native → browser** direction of the cross-platform
/// compatibility test. The companion native side lives in
/// `tests/cross_platform_compat_test.rs`.
#[wasm_bindgen_test]
async fn native_fixture_readable_by_browser_db() {
let fixture: &[u8] = include_bytes!("../../tests/fixtures/compat.graph");
let db = BrowserDb::open_in_memory().expect("open in-memory");
let arr = js_sys::Uint8Array::from(fixture);
db.import_graph(arr).await.expect("import native fixture");

let r = db
.execute(r#"(query [:find ?name :where [?e :name ?name]])"#.to_string())
.await
.expect("query name");
let v: serde_json::Value = serde_json::from_str(&r).unwrap();
let results = v["results"].as_array().unwrap();
assert_eq!(
results.len(),
1,
"expected 1 name result from native fixture"
);
assert_eq!(results[0][0], serde_json::Value::String("Alice".into()));

let r2 = db
.execute("(query [:find ?age :where [?e :age ?age]])".to_string())
.await
.expect("query age");
let v2: serde_json::Value = serde_json::from_str(&r2).unwrap();
let results2 = v2["results"].as_array().unwrap();
assert_eq!(
results2.len(),
1,
"expected 1 age result from native fixture"
);
assert_eq!(results2[0][0], serde_json::Value::Number(30.into()));
}

#[wasm_bindgen_test]
async fn idb_persistence_round_trip() {
let db_name = "minigraf-test-persistence";
Expand Down
119 changes: 119 additions & 0 deletions tests/cross_platform_compat_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Cross-platform `.graph` file compatibility tests (native side).
// These tests use Minigraf::open() and the file system, which are not available
// on wasm32 targets. wasm-pack compiles integration tests too, so gate the
// entire file.
#![cfg(not(target_arch = "wasm32"))]

//!
//!
//! Verifies that raw page bytes produced by the native storage layer are
//! self-consistent (mimicking what `BrowserDb::export_graph` / `import_graph`
//! do) and that the committed binary fixture is readable by `Minigraf::open`.
//!
//! The companion browser side lives in `src/browser/mod.rs` under
//! `#[wasm_bindgen_test]` and loads the same fixture via `include_bytes!`.

use minigraf::{Minigraf, QueryResult};
use std::path::PathBuf;

fn tmp(tag: &str) -> PathBuf {
std::env::temp_dir().join(format!("minigraf_compat_{tag}.graph"))
}

fn cleanup(path: &PathBuf) {
let _ = std::fs::remove_file(path);
let _ = std::fs::remove_file(path.with_extension("graph.wal"));
}

/// Produce a `.graph` file, read its raw bytes, write them to a second file,
/// open the second file — verifying that byte-for-byte copies are readable.
///
/// This mirrors what `BrowserDb::export_graph()` + `BrowserDb::import_graph()`
/// do: the export concatenates raw 4 KB pages; the import splits them back and
/// loads via a fresh `PersistentFactStorage`.
#[test]
fn native_raw_page_bytes_round_trip() {
let src = tmp("src");
let dst = tmp("dst");
cleanup(&src);
cleanup(&dst);

// Produce a populated, checkpointed .graph file.
{
let db = Minigraf::open(&src).expect("open src");
db.execute(r#"(transact [[:alice :name "Alice"]])"#)
.expect("transact name");
db.execute("(transact [[:alice :age 30]])")
.expect("transact age");
db.checkpoint().expect("checkpoint");
}

// Copy raw bytes to a new path (simulates export → import across the boundary).
let bytes = std::fs::read(&src).expect("read src");
std::fs::write(&dst, &bytes).expect("write dst");

// Open the byte-copy and verify both facts survive.
let db2 = Minigraf::open(&dst).expect("open dst");

let r = db2
.execute("(query [:find ?name :where [?e :name ?name]])")
.expect("query name");
match r {
QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 1, "expected 1 name result");
}
_ => panic!("expected QueryResults for name query"),
}

let r2 = db2
.execute("(query [:find ?age :where [?e :age ?age]])")
.expect("query age");
match r2 {
QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 1, "expected 1 age result");
}
_ => panic!("expected QueryResults for age query"),
}

cleanup(&src);
cleanup(&dst);
}

/// Load the committed fixture (produced by `cargo run --example generate_compat_fixture`)
/// via `Minigraf::open` and assert that the known facts are present.
///
/// This fixture is also loaded by the browser WASM tests via `include_bytes!` +
/// `BrowserDb::import_graph`, completing the cross-boundary coverage.
#[test]
fn fixture_readable_by_native() {
let fixture: &[u8] = include_bytes!("fixtures/compat.graph");

// Write to a temp path so Minigraf::open can use it.
let path = tmp("fixture");
cleanup(&path);
std::fs::write(&path, fixture).expect("write fixture");

let db = Minigraf::open(&path).expect("open fixture");

let r = db
.execute("(query [:find ?name :where [?e :name ?name]])")
.expect("query name");
match r {
QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 1, "expected 1 name result from fixture");
}
_ => panic!("expected QueryResults for name query"),
}

let r2 = db
.execute("(query [:find ?age :where [?e :age ?age]])")
.expect("query age");
match r2 {
QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 1, "expected 1 age result from fixture");
}
_ => panic!("expected QueryResults for age query"),
}

cleanup(&path);
}
Binary file added tests/fixtures/compat.graph
Binary file not shown.
Loading
0