From 435ca7fe7a4026a010f9f429b89ecb253bdce477 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Wed, 23 Jul 2025 21:05:53 +0400 Subject: [PATCH] add fuzz tests for raw WAL API --- tests/integration/common.rs | 12 +++- tests/integration/functions/test_wal_api.rs | 64 ++++++++++++++++++++- tests/integration/fuzz/mod.rs | 11 +--- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/tests/integration/common.rs b/tests/integration/common.rs index 3c0816b14..05207490f 100644 --- a/tests/integration/common.rs +++ b/tests/integration/common.rs @@ -1,4 +1,5 @@ -use rand::{rng, RngCore}; +use rand::{rng, RngCore, SeedableRng}; +use rand_chacha::ChaCha8Rng; use rusqlite::params; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -238,6 +239,15 @@ pub(crate) fn limbo_exec_rows_error( } } +pub(crate) fn rng_from_time() -> (ChaCha8Rng, u64) { + let seed = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + let rng = ChaCha8Rng::seed_from_u64(seed); + (rng, seed) +} + #[cfg(test)] mod tests { use std::vec; diff --git a/tests/integration/functions/test_wal_api.rs b/tests/integration/functions/test_wal_api.rs index edef8569d..48bbfdf02 100644 --- a/tests/integration/functions/test_wal_api.rs +++ b/tests/integration/functions/test_wal_api.rs @@ -1,6 +1,8 @@ +use rand::{RngCore, SeedableRng}; +use rand_chacha::ChaCha8Rng; use rusqlite::types::Value; -use crate::common::{limbo_exec_rows, TempDatabase}; +use crate::common::{limbo_exec_rows, rng_from_time, TempDatabase}; #[test] fn test_wal_frame_count() { @@ -142,3 +144,63 @@ fn test_wal_frame_far_away_write() { db1.io.wait_for_completion(c).unwrap(); assert!(conn2.wal_insert_frame(5, &frame).is_err()); } + +#[test] +fn test_wal_frame_api_no_schema_changes_fuzz() { + let (mut rng, _) = rng_from_time(); + for _ in 0..4 { + let db1 = TempDatabase::new_empty(false); + let conn1 = db1.connect_limbo(); + let db2 = TempDatabase::new_empty(false); + let conn2 = db2.connect_limbo(); + conn1 + .execute("CREATE TABLE t(x INTEGER PRIMARY KEY, y)") + .unwrap(); + conn2 + .execute("CREATE TABLE t(x INTEGER PRIMARY KEY, y)") + .unwrap(); + + let seed = rng.next_u64(); + let mut rng = ChaCha8Rng::seed_from_u64(seed); + println!("SEED: {}", seed); + + let (mut size, mut synced_frame) = (0, conn2.wal_frame_count().unwrap()); + let mut commit_frames = vec![conn1.wal_frame_count().unwrap()]; + for _ in 0..256 { + if rng.next_u32() % 10 != 0 { + let key = rng.next_u32(); + let length = rng.next_u32() % (4 * 4096); + let query = format!("INSERT INTO t VALUES ({}, randomblob({}))", key, length); + // println!("{}", query); + conn1.execute(&query).unwrap(); + commit_frames.push(conn1.wal_frame_count().unwrap()); + } else { + let last_frame = conn1.wal_frame_count().unwrap(); + let next_frame = + synced_frame + (rng.next_u32() as u64 % (last_frame - synced_frame + 1)); + let mut frame = [0u8; 24 + 4096]; + // println!("sync WAL frames: [{}..{}]", synced_frame + 1, next_frame); + conn2.wal_insert_begin().unwrap(); + for frame_no in (synced_frame + 1)..=next_frame { + let c = conn1.wal_get_frame(frame_no as u32, &mut frame).unwrap(); + db1.io.wait_for_completion(c).unwrap(); + conn2.wal_insert_frame(frame_no as u32, &frame[..]).unwrap(); + } + conn2.wal_insert_end().unwrap(); + for (i, committed) in commit_frames.iter().enumerate() { + if *committed <= next_frame { + size = size.max(i); + synced_frame = *committed; + } + } + if rng.next_u32() % 10 == 0 { + synced_frame = rng.next_u32() as u64 % synced_frame; + } + assert_eq!( + limbo_exec_rows(&db2, &conn2, "SELECT COUNT(*) FROM t"), + vec![vec![Value::Integer(size as i64)]] + ); + } + } + } +} diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 2798c8006..e56d7db64 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -10,21 +10,12 @@ mod tests { use rusqlite::params; use crate::{ - common::{limbo_exec_rows, sqlite_exec_rows, TempDatabase}, + common::{limbo_exec_rows, rng_from_time, sqlite_exec_rows, TempDatabase}, fuzz::grammar_generator::{const_str, rand_int, rand_str, GrammarGenerator}, }; use super::grammar_generator::SymbolHandle; - fn rng_from_time() -> (ChaCha8Rng, u64) { - let seed = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs(); - let rng = ChaCha8Rng::seed_from_u64(seed); - (rng, seed) - } - /// [See this issue for more info](https://github.com/tursodatabase/turso/issues/1763) #[test] pub fn fuzz_failure_issue_1763() {