Skip to content

Commit

Permalink
centralize Rust integration and regression tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sonhmai committed Jan 20, 2025
1 parent 9369f06 commit c530885
Show file tree
Hide file tree
Showing 16 changed files with 735 additions and 718 deletions.
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ members = [
"macros",
"simulator",
"sqlite3",
"test", "extensions/percentile",
"test",
"extensions/percentile",
]
exclude = ["perf/latency/limbo"]

Expand Down
5 changes: 0 additions & 5 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,3 @@ miette = { version = "7.4.0", features = ["fancy"] }

[features]
io_uring = ["limbo_core/io_uring"]

# not testing the cli on windows as rexpect does not support it.
[target.'cfg(not(windows))'.dev-dependencies]
assert_cmd = "^2"
rexpect = "0.6.0"
8 changes: 5 additions & 3 deletions test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Internal tester of write path"
description = "Integration tests"

[lib]
name = "test"
Expand All @@ -22,6 +22,8 @@ rustyline = "12.0.0"
rusqlite = { version = "0.29", features = ["bundled"] }
tempfile = "3.0.7"
log = "0.4.22"
assert_cmd = "^2"

[dev-dependencies]
rstest = "0.18.2"
# rexpect does not support windows.
[target.'cfg(not(windows))'.dependencies]
rexpect = "0.6.0"
8 changes: 7 additions & 1 deletion test/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Currently the best way to run these tests are like this due to long running tests:

Integration and regression test suite.

```bash

# run all tests
cargo test

# run individual test
cargo test test_sequential_write -- --nocapture
```
69 changes: 69 additions & 0 deletions test/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use limbo_core::{CheckpointStatus, Connection, Database, IO};
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use tempfile::TempDir;

#[allow(dead_code)]
pub struct TempDatabase {
pub path: PathBuf,
pub io: Arc<dyn IO>,
}

#[allow(dead_code, clippy::arc_with_non_send_sync)]
impl TempDatabase {
pub fn new(table_sql: &str) -> Self {
let mut path = TempDir::new().unwrap().into_path();
path.push("test.db");
{
let connection = rusqlite::Connection::open(&path).unwrap();
connection
.pragma_update(None, "journal_mode", "wal")
.unwrap();
connection.execute(table_sql, ()).unwrap();
}
let io: Arc<dyn limbo_core::IO> = Arc::new(limbo_core::PlatformIO::new().unwrap());

Self { path, io }
}

pub fn connect_limbo(&self) -> Rc<limbo_core::Connection> {
log::debug!("conneting to limbo");
let db = Database::open_file(self.io.clone(), self.path.to_str().unwrap()).unwrap();

let conn = db.connect();
log::debug!("connected to limbo");
conn
}
}

pub(crate) fn do_flush(conn: &Rc<Connection>, tmp_db: &TempDatabase) -> anyhow::Result<()> {
loop {
match conn.cacheflush()? {
CheckpointStatus::Done => {
break;
}
CheckpointStatus::IO => {
tmp_db.io.run_once()?;
}
}
}
Ok(())
}

pub(crate) fn compare_string(a: &String, b: &String) {
assert_eq!(a.len(), b.len(), "Strings are not equal in size!");
let a = a.as_bytes();
let b = b.as_bytes();

let len = a.len();
for i in 0..len {
if a[i] != b[i] {
println!(
"Bytes differ \n\t at index: dec -> {} hex -> {:#02x} \n\t values dec -> {}!={} hex -> {:#02x}!={:#02x}",
i, i, a[i], b[i], a[i], b[i]
);
break;
}
}
}
1 change: 1 addition & 0 deletions test/src/functions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod test_rowid_functions;
83 changes: 83 additions & 0 deletions test/src/functions/test_rowid_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::common::{do_flush, TempDatabase};
use limbo_core::{StepResult, Value};

#[test]
fn test_last_insert_rowid_basic() -> anyhow::Result<()> {
let _ = env_logger::try_init();
let tmp_db = TempDatabase::new("CREATE TABLE test_rowid (id INTEGER PRIMARY KEY, val TEXT);");
let conn = tmp_db.connect_limbo();

// Simple insert
let mut insert_query = conn.query("INSERT INTO test_rowid (id, val) VALUES (NULL, 'test1')")?;
if let Some(ref mut rows) = insert_query {
loop {
match rows.next_row()? {
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Done => break,
_ => unreachable!(),
}
}
}

// Check last_insert_rowid separately
let mut select_query = conn.query("SELECT last_insert_rowid()")?;
if let Some(ref mut rows) = select_query {
loop {
match rows.next_row()? {
StepResult::Row(row) => {
if let Value::Integer(id) = row.values[0] {
assert_eq!(id, 1, "First insert should have rowid 1");
}
}
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Interrupt => break,
StepResult::Done => break,
StepResult::Busy => panic!("Database is busy"),
}
}
}

// Test explicit rowid
match conn.query("INSERT INTO test_rowid (id, val) VALUES (5, 'test2')") {
Ok(Some(ref mut rows)) => loop {
match rows.next_row()? {
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Done => break,
_ => unreachable!(),
}
},
Ok(None) => {}
Err(err) => eprintln!("{}", err),
};

// Check last_insert_rowid after explicit id
let mut last_id = 0;
match conn.query("SELECT last_insert_rowid()") {
Ok(Some(ref mut rows)) => loop {
match rows.next_row()? {
StepResult::Row(row) => {
if let Value::Integer(id) = row.values[0] {
last_id = id;
}
}
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Interrupt => break,
StepResult::Done => break,
StepResult::Busy => panic!("Database is busy"),
}
},
Ok(None) => {}
Err(err) => eprintln!("{}", err),
};
assert_eq!(last_id, 5, "Explicit insert should have rowid 5");
do_flush(&conn, &tmp_db)?;
Ok(())
}
2 changes: 2 additions & 0 deletions test/src/journal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod test_journal_through_cli;
mod test_wal;
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ mod tests {

fn run_cli() -> process::Command {
let bin_path = cargo_bin("limbo");
let cmd = process::Command::new(bin_path);
cmd
process::Command::new(bin_path)
}
}
143 changes: 143 additions & 0 deletions test/src/journal/test_wal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::common::{do_flush, TempDatabase};
use limbo_core::{Connection, StepResult, Value};
use log::debug;
use std::rc::Rc;

#[test]
#[ignore]
fn test_wal_checkpoint() -> anyhow::Result<()> {
let _ = env_logger::try_init();
let tmp_db = TempDatabase::new("CREATE TABLE test (x INTEGER PRIMARY KEY);");
// threshold is 1000 by default
let iterations = 1001_usize;
let conn = tmp_db.connect_limbo();

for i in 0..iterations {
let insert_query = format!("INSERT INTO test VALUES ({})", i);
do_flush(&conn, &tmp_db)?;
conn.checkpoint()?;
match conn.query(insert_query) {
Ok(Some(ref mut rows)) => loop {
match rows.next_row()? {
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Done => break,
_ => unreachable!(),
}
},
Ok(None) => {}
Err(err) => {
eprintln!("{}", err);
}
};
}

do_flush(&conn, &tmp_db)?;
conn.clear_page_cache()?;
let list_query = "SELECT * FROM test LIMIT 1";
let mut current_index = 0;
match conn.query(list_query) {
Ok(Some(ref mut rows)) => loop {
match rows.next_row()? {
StepResult::Row(row) => {
let first_value = &row.values[0];
let id = match first_value {
Value::Integer(i) => *i as i32,
Value::Float(f) => *f as i32,
_ => unreachable!(),
};
assert_eq!(current_index, id as usize);
current_index += 1;
}
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Interrupt => break,
StepResult::Done => break,
StepResult::Busy => unreachable!(),
}
},
Ok(None) => {}
Err(err) => {
eprintln!("{}", err);
}
}
do_flush(&conn, &tmp_db)?;
Ok(())
}

#[test]
fn test_wal_restart() -> anyhow::Result<()> {
let _ = env_logger::try_init();
let tmp_db = TempDatabase::new("CREATE TABLE test (x INTEGER PRIMARY KEY);");
// threshold is 1000 by default

fn insert(i: usize, conn: &Rc<Connection>, tmp_db: &TempDatabase) -> anyhow::Result<()> {
debug!("inserting {}", i);
let insert_query = format!("INSERT INTO test VALUES ({})", i);
match conn.query(insert_query) {
Ok(Some(ref mut rows)) => loop {
match rows.next_row()? {
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Done => break,
_ => unreachable!(),
}
},
Ok(None) => {}
Err(err) => {
eprintln!("{}", err);
}
};
debug!("inserted {}", i);
tmp_db.io.run_once()?;
Ok(())
}

fn count(conn: &Rc<Connection>, tmp_db: &TempDatabase) -> anyhow::Result<usize> {
debug!("counting");
let list_query = "SELECT count(x) FROM test";
loop {
if let Some(ref mut rows) = conn.query(list_query)? {
loop {
match rows.next_row()? {
StepResult::Row(row) => {
let first_value = &row.values[0];
let count = match first_value {
Value::Integer(i) => *i as i32,
_ => unreachable!(),
};
debug!("counted {}", count);
return Ok(count as usize);
}
StepResult::IO => {
tmp_db.io.run_once()?;
}
StepResult::Interrupt => break,
StepResult::Done => break,
StepResult::Busy => panic!("Database is busy"),
}
}
}
}
}

{
let conn = tmp_db.connect_limbo();
insert(1, &conn, &tmp_db)?;
assert_eq!(count(&conn, &tmp_db)?, 1);
conn.close()?;
}
{
let conn = tmp_db.connect_limbo();
assert_eq!(
count(&conn, &tmp_db)?,
1,
"failed to read from wal from another connection"
);
conn.close()?;
}
Ok(())
}
Loading

0 comments on commit c530885

Please sign in to comment.