mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-27 13:04:20 +01:00
119 lines
3.7 KiB
Rust
119 lines
3.7 KiB
Rust
use crate::common::{do_flush, run_query, run_query_on_row, TempDatabase};
|
|
use rand::{rng, RngCore};
|
|
use std::panic;
|
|
use turso_core::Row;
|
|
|
|
#[test]
|
|
fn test_per_page_checksum() -> anyhow::Result<()> {
|
|
let _ = env_logger::try_init();
|
|
let db_name = format!("test-{}.db", rng().next_u32());
|
|
let tmp_db = TempDatabase::new(&db_name, false);
|
|
let db_path = tmp_db.path.clone();
|
|
|
|
{
|
|
let conn = tmp_db.connect_limbo();
|
|
run_query(
|
|
&tmp_db,
|
|
&conn,
|
|
"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT);",
|
|
)?;
|
|
run_query(
|
|
&tmp_db,
|
|
&conn,
|
|
"INSERT INTO test (value) VALUES ('Hello, World!')",
|
|
)?;
|
|
let mut row_count = 0;
|
|
run_query_on_row(&tmp_db, &conn, "SELECT * FROM test", |row: &Row| {
|
|
assert_eq!(row.get::<i64>(0).unwrap(), 1);
|
|
assert_eq!(row.get::<String>(1).unwrap(), "Hello, World!");
|
|
row_count += 1;
|
|
})?;
|
|
assert_eq!(row_count, 1);
|
|
do_flush(&conn, &tmp_db)?;
|
|
}
|
|
|
|
{
|
|
let metadata = std::fs::metadata(&db_path)?;
|
|
assert_eq!(metadata.len(), 4096, "db file should be exactly 4096 bytes");
|
|
}
|
|
|
|
// let's test that page actually contains checksum bytes
|
|
{
|
|
let file_contents = std::fs::read(&db_path)?;
|
|
assert_eq!(
|
|
file_contents.len(),
|
|
4096,
|
|
"file contents should be 4096 bytes"
|
|
);
|
|
|
|
// split the page: first 4088 bytes are actual page, last 8 bytes are checksum
|
|
let actual_page = &file_contents[..4096 - 8];
|
|
let checksum_bytes = &file_contents[4096 - 8..];
|
|
let stored_checksum = u64::from_le_bytes(checksum_bytes.try_into().unwrap());
|
|
|
|
let expected_checksum = twox_hash::XxHash3_64::oneshot(actual_page);
|
|
assert_eq!(
|
|
stored_checksum, expected_checksum,
|
|
"Stored checksum should match manually calculated checksum"
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_checksum_detects_corruption() {
|
|
let _ = env_logger::try_init();
|
|
let db_name = format!("test-corruption-{}.db", rng().next_u32());
|
|
let tmp_db = TempDatabase::new(&db_name, false);
|
|
let db_path = tmp_db.path.clone();
|
|
|
|
// Create and populate the database
|
|
{
|
|
let conn = tmp_db.connect_limbo();
|
|
run_query(
|
|
&tmp_db,
|
|
&conn,
|
|
"CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT);",
|
|
)
|
|
.unwrap();
|
|
run_query(
|
|
&tmp_db,
|
|
&conn,
|
|
"INSERT INTO test (value) VALUES ('Hello, World!')",
|
|
)
|
|
.unwrap();
|
|
|
|
do_flush(&conn, &tmp_db).unwrap();
|
|
run_query(&tmp_db, &conn, "PRAGMA wal_checkpoint(TRUNCATE);").unwrap();
|
|
}
|
|
|
|
{
|
|
let mut file_contents = std::fs::read(&db_path).unwrap();
|
|
assert_eq!(file_contents.len(), 8192, "File should be 4096 bytes");
|
|
|
|
// lets corrupt the db at byte 2025, the year of Turso DB
|
|
file_contents[2025] = !file_contents[2025];
|
|
std::fs::write(&db_path, file_contents).unwrap();
|
|
}
|
|
|
|
{
|
|
let existing_db = TempDatabase::new_with_existent(&db_path, false);
|
|
// this query should fail and result in panic because db is now corrupted
|
|
let should_panic = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
|
let conn = existing_db.connect_limbo();
|
|
run_query_on_row(
|
|
&existing_db,
|
|
&conn,
|
|
"SELECT * FROM test",
|
|
|_: &Row| unreachable!(),
|
|
)
|
|
.unwrap();
|
|
}));
|
|
assert!(
|
|
should_panic.is_err(),
|
|
"should panic when accessing corrupted DB"
|
|
);
|
|
}
|
|
}
|