mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 17:14:20 +01:00
281 lines
9.1 KiB
Rust
281 lines
9.1 KiB
Rust
pub mod grammar_generator;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::rc::Rc;
|
|
|
|
use rand::SeedableRng;
|
|
use rand_chacha::ChaCha8Rng;
|
|
use rusqlite::params;
|
|
|
|
use crate::{
|
|
common::TempDatabase,
|
|
fuzz::grammar_generator::{rand_int, rand_str, GrammarGenerator},
|
|
};
|
|
|
|
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)
|
|
}
|
|
|
|
fn sqlite_exec_row(conn: &rusqlite::Connection, query: &str) -> 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);
|
|
}
|
|
assert!(rows.next().unwrap().is_none());
|
|
|
|
columns
|
|
}
|
|
|
|
fn limbo_exec_row(
|
|
conn: &Rc<limbo_core::Connection>,
|
|
query: &str,
|
|
) -> 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;
|
|
}
|
|
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()
|
|
}
|
|
|
|
#[test]
|
|
pub fn arithmetic_expression_fuzz_ex1() {
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
for query in [
|
|
"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);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn arithmetic_expression_fuzz() {
|
|
let _ = env_logger::try_init();
|
|
let g = GrammarGenerator::new();
|
|
let (expr, expr_builder) = g.create_handle();
|
|
let (bin_op, bin_op_builder) = g.create_handle();
|
|
let (unary_op, unary_op_builder) = g.create_handle();
|
|
let (paren, paren_builder) = g.create_handle();
|
|
|
|
paren_builder
|
|
.concat("")
|
|
.push_str("(")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build();
|
|
|
|
unary_op_builder
|
|
.concat(" ")
|
|
.push(g.create().choice().options_str(["~", "+", "-"]).build())
|
|
.push(expr)
|
|
.build();
|
|
|
|
bin_op_builder
|
|
.concat(" ")
|
|
.push(expr)
|
|
.push(
|
|
g.create()
|
|
.choice()
|
|
.options_str(["+", "-", "*", "/", "%", "&", "|", "<<", ">>"])
|
|
.build(),
|
|
)
|
|
.push(expr)
|
|
.build();
|
|
|
|
expr_builder
|
|
.choice()
|
|
.option_w(unary_op, 1.0)
|
|
.option_w(bin_op, 1.0)
|
|
.option_w(paren, 1.0)
|
|
.option_symbol_w(rand_int(-10..10), 1.0)
|
|
.build();
|
|
|
|
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
|
|
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
let (mut rng, seed) = rng_from_time();
|
|
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);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn logical_expression_fuzz_ex1() {
|
|
let _ = env_logger::try_init();
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
for query in [
|
|
"SELECT FALSE",
|
|
"SELECT NOT FALSE",
|
|
"SELECT ((NULL) IS NOT TRUE <= ((NOT (FALSE))))",
|
|
"SELECT ifnull(0, NOT 0)",
|
|
"SELECT like('a%', 'a') = 1",
|
|
] {
|
|
let limbo = limbo_exec_row(&limbo_conn, query);
|
|
let sqlite = sqlite_exec_row(&sqlite_conn, query);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn logical_expression_fuzz_run() {
|
|
let _ = env_logger::try_init();
|
|
let g = GrammarGenerator::new();
|
|
let (expr, expr_builder) = g.create_handle();
|
|
let (bin_op, bin_op_builder) = g.create_handle();
|
|
let (unary_infix_op, unary_infix_op_builder) = g.create_handle();
|
|
let (scalar, scalar_builder) = g.create_handle();
|
|
let (paren, paren_builder) = g.create_handle();
|
|
|
|
paren_builder
|
|
.concat("")
|
|
.push_str("(")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build();
|
|
|
|
unary_infix_op_builder
|
|
.concat(" ")
|
|
.push(g.create().choice().options_str(["NOT"]).build())
|
|
.push(expr)
|
|
.build();
|
|
|
|
bin_op_builder
|
|
.concat(" ")
|
|
.push(expr)
|
|
.push(
|
|
g.create()
|
|
.choice()
|
|
.options_str(["AND", "OR", "IS", "IS NOT", "=", "<>", ">", "<", ">=", "<="])
|
|
.build(),
|
|
)
|
|
.push(expr)
|
|
.build();
|
|
|
|
scalar_builder
|
|
.choice()
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("like('")
|
|
.push_symbol(rand_str("", 2))
|
|
.push_str("', '")
|
|
.push_symbol(rand_str("", 2))
|
|
.push_str("')")
|
|
.build(),
|
|
)
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("ifnull(")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build(),
|
|
)
|
|
.option(
|
|
g.create()
|
|
.concat("")
|
|
.push_str("iif(")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(",")
|
|
.push(expr)
|
|
.push_str(")")
|
|
.build(),
|
|
)
|
|
.build();
|
|
|
|
expr_builder
|
|
.choice()
|
|
.option_w(unary_infix_op, 1.0)
|
|
.option_w(bin_op, 1.0)
|
|
.option_w(paren, 1.0)
|
|
.option_w(scalar, 1.0)
|
|
// unfortunately, sqlite behaves weirdly when IS operator is used with TRUE/FALSE constants
|
|
// e.g. 8 IS TRUE == 1 (although 8 = TRUE == 0)
|
|
// so, we do not use TRUE/FALSE constants as they will produce diff with sqlite results
|
|
.options_str(["1", "0", "NULL", "2.0", "1.5", "-0.5", "-2.0", "(1 / 0)"])
|
|
.build();
|
|
|
|
let sql = g.create().concat(" ").push_str("SELECT").push(expr).build();
|
|
|
|
let db = TempDatabase::new_empty();
|
|
let limbo_conn = db.connect_limbo();
|
|
let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap();
|
|
|
|
let (mut rng, seed) = rng_from_time();
|
|
log::info!("seed: {}", seed);
|
|
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);
|
|
assert_eq!(
|
|
limbo, sqlite,
|
|
"query: {}, limbo: {:?}, sqlite: {:?}",
|
|
query, limbo, sqlite
|
|
);
|
|
}
|
|
}
|
|
}
|