add simple fuzz test with table data

This commit is contained in:
Nikita Sivukhin 2025-02-15 16:27:11 +04:00
parent f220f9a948
commit e7c501a3be

View file

@ -4,7 +4,7 @@ pub mod grammar_generator;
mod tests {
use std::rc::Rc;
use rand::SeedableRng;
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;
use rusqlite::params;
@ -22,50 +22,62 @@ mod tests {
(rng, seed)
}
fn sqlite_exec_row(conn: &rusqlite::Connection, query: &str) -> Vec<rusqlite::types::Value> {
fn sqlite_exec_rows(
conn: &rusqlite::Connection,
query: &str,
) -> Vec<Vec<rusqlite::types::Value>> {
let mut stmt = conn.prepare(&query).unwrap();
let mut rows = stmt.query(params![]).unwrap();
let mut columns = Vec::new();
let row = rows.next().unwrap().unwrap();
for i in 0.. {
let column: rusqlite::types::Value = match row.get(i) {
Ok(column) => column,
Err(rusqlite::Error::InvalidColumnIndex(_)) => break,
Err(err) => panic!("unexpected rusqlite error: {}", err),
};
columns.push(column);
let mut results = Vec::new();
while let Some(row) = rows.next().unwrap() {
let mut result = Vec::new();
for i in 0.. {
let column: rusqlite::types::Value = match row.get(i) {
Ok(column) => column,
Err(rusqlite::Error::InvalidColumnIndex(_)) => break,
Err(err) => panic!("unexpected rusqlite error: {}", err),
};
result.push(column);
}
results.push(result)
}
assert!(rows.next().unwrap().is_none());
columns
results
}
fn limbo_exec_row(
fn limbo_exec_rows(
conn: &Rc<limbo_core::Connection>,
query: &str,
) -> Vec<rusqlite::types::Value> {
) -> Vec<Vec<rusqlite::types::Value>> {
let mut stmt = conn.prepare(query).unwrap();
let result = stmt.step().unwrap();
let row = loop {
match result {
limbo_core::StepResult::Row => {
let row = stmt.row().unwrap();
break row;
let mut rows = Vec::new();
'outer: loop {
let row = loop {
let result = stmt.step().unwrap();
match result {
limbo_core::StepResult::Row => {
let row = stmt.row().unwrap();
break row;
}
limbo_core::StepResult::IO => continue,
limbo_core::StepResult::Done => break 'outer,
r => panic!("unexpected result {:?}: expecting single row", r),
}
limbo_core::StepResult::IO => continue,
r => panic!("unexpected result {:?}: expecting single row", r),
}
};
row.get_values()
.iter()
.map(|x| match x.to_value() {
limbo_core::Value::Null => rusqlite::types::Value::Null,
limbo_core::Value::Integer(x) => rusqlite::types::Value::Integer(x),
limbo_core::Value::Float(x) => rusqlite::types::Value::Real(x),
limbo_core::Value::Text(x) => rusqlite::types::Value::Text(x.to_string()),
limbo_core::Value::Blob(x) => rusqlite::types::Value::Blob(x.to_vec()),
})
.collect()
};
let row = row
.get_values()
.iter()
.map(|x| match x.to_value() {
limbo_core::Value::Null => rusqlite::types::Value::Null,
limbo_core::Value::Integer(x) => rusqlite::types::Value::Integer(x),
limbo_core::Value::Float(x) => rusqlite::types::Value::Real(x),
limbo_core::Value::Text(x) => rusqlite::types::Value::Text(x.to_string()),
limbo_core::Value::Blob(x) => rusqlite::types::Value::Blob(x.to_vec()),
})
.collect();
rows.push(row);
}
rows
}
#[test]
@ -78,8 +90,8 @@ mod tests {
"SELECT ~1 >> 1536",
"SELECT ~ + 3 << - ~ (~ (8)) - + -1 - 3 >> 3 + -6 * (-7 * 9 >> - 2)",
] {
let limbo = limbo_exec_row(&limbo_conn, query);
let sqlite = sqlite_exec_row(&sqlite_conn, query);
let limbo = limbo_exec_rows(&limbo_conn, query);
let sqlite = sqlite_exec_rows(&sqlite_conn, query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",
@ -140,8 +152,8 @@ mod tests {
log::info!("seed: {}", seed);
for _ in 0..1024 {
let query = g.generate(&mut rng, sql, 50);
let limbo = limbo_exec_row(&limbo_conn, &query);
let sqlite = sqlite_exec_row(&sqlite_conn, &query);
let limbo = limbo_exec_rows(&limbo_conn, &query);
let sqlite = sqlite_exec_rows(&sqlite_conn, &query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",
@ -167,8 +179,8 @@ mod tests {
"SELECT (COALESCE(0, COALESCE(0, 0)));",
"SELECT CAST((1 > 0) AS INTEGER);",
] {
let limbo = limbo_exec_row(&limbo_conn, query);
let sqlite = sqlite_exec_row(&sqlite_conn, query);
let limbo = limbo_exec_rows(&limbo_conn, query);
let sqlite = sqlite_exec_rows(&sqlite_conn, query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",
@ -274,8 +286,8 @@ mod tests {
for _ in 0..1024 {
let query = g.generate(&mut rng, sql, 50);
log::info!("query: {}", query);
let limbo = limbo_exec_row(&limbo_conn, &query);
let sqlite = sqlite_exec_row(&sqlite_conn, &query);
let limbo = limbo_exec_rows(&limbo_conn, &query);
let sqlite = sqlite_exec_rows(&sqlite_conn, &query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",
@ -451,8 +463,150 @@ mod tests {
for _ in 0..1024 {
let query = g.generate(&mut rng, sql, 50);
log::info!("query: {}", query);
let limbo = limbo_exec_row(&limbo_conn, &query);
let sqlite = sqlite_exec_row(&sqlite_conn, &query);
let limbo = limbo_exec_rows(&limbo_conn, &query);
let sqlite = sqlite_exec_rows(&sqlite_conn, &query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",
query, limbo, sqlite
);
}
}
#[test]
pub fn table_logical_expression_fuzz_ex1() {
let _ = env_logger::try_init();
for queries in [[
"CREATE TABLE t(x)",
"INSERT INTO t VALUES (10)",
"SELECT * FROM t WHERE x = 1 AND 1 OR 0",
]] {
let db = TempDatabase::new_empty();
let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
for query in queries.iter() {
let limbo = limbo_exec_rows(&limbo_conn, query);
let sqlite = sqlite_exec_rows(&sqlite_conn, query);
assert_eq!(
limbo, sqlite,
"queries: {:?}, query: {}, limbo: {:?}, sqlite: {:?}",
queries, query, limbo, sqlite
);
}
}
}
#[test]
pub fn table_logical_expression_fuzz_run() {
let _ = env_logger::try_init();
let g = GrammarGenerator::new();
let (expr, expr_builder) = g.create_handle();
let (value, value_builder) = g.create_handle();
let (cmp_op, cmp_op_builder) = g.create_handle();
let (bin_op, bin_op_builder) = g.create_handle();
let (in_op, in_op_builder) = g.create_handle();
let (paren, paren_builder) = g.create_handle();
value_builder
.choice()
.option(
g.create()
.concat(" ")
.push_str("(")
.push(value)
.push_str(")")
.build(),
)
.option_symbol(rand_int(0..i32::MAX))
.options_str(["x", "y", "z"])
.option(
g.create()
.concat(" ")
.push_str("(")
.push(value)
.push(g.create().choice().options_str(["+", "-"]).build())
.push(value)
.push_str(")")
.build(),
)
.build();
paren_builder
.concat("")
.push_str("(")
.push(expr)
.push_str(")")
.build();
cmp_op_builder
.concat(" ")
.push(value)
.push(
g.create()
.choice()
.options_str(["=", "<>", ">", "<", ">=", "<=", "IS", "IS NOT"])
.build(),
)
.push(value)
.build();
bin_op_builder
.concat(" ")
.push(expr)
.push(g.create().choice().options_str(["AND", "OR"]).build())
.push(expr)
.build();
in_op_builder
.concat(" ")
.push(value)
.push(g.create().choice().options_str(["IN", "NOT IN"]).build())
.push_str("(")
.push(g.create().concat("").push(value).repeat(1..5, ", ").build())
.push_str(")")
.build();
expr_builder
.choice()
.options_str(["1", "0"])
.option_w(paren, 10.0)
.option_w(cmp_op, 10.0)
.option_w(bin_op, 10.0)
.option_w(in_op, 10.0)
.build();
let db = TempDatabase::new_empty();
let limbo_conn = db.connect_limbo();
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
assert_eq!(
limbo_exec_rows(&limbo_conn, "CREATE TABLE t(x, y, z)"),
sqlite_exec_rows(&sqlite_conn, "CREATE TABLE t(x, y, z)")
);
let (mut rng, seed) = rng_from_time();
log::info!("seed: {}", seed);
for _ in 0..100 {
let (x, y, z) = (rng.next_u32(), rng.next_u32(), rng.next_u32());
let query = format!("INSERT INTO t VALUES ({}, {}, {})", x, y, z);
assert_eq!(
limbo_exec_rows(&limbo_conn, &query),
sqlite_exec_rows(&sqlite_conn, &query)
);
}
let sql = g
.create()
.concat(" ")
.push_str("SELECT * FROM t WHERE ")
.push(expr)
.build();
for _ in 0..128 {
let query = g.generate(&mut rng, sql, 50);
log::info!("query: {}", query);
let limbo = limbo_exec_rows(&limbo_conn, &query);
let sqlite = sqlite_exec_rows(&sqlite_conn, &query);
assert_eq!(
limbo, sqlite,
"query: {}, limbo: {:?}, sqlite: {:?}",