mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-28 04:14:34 +01:00
copy generation code from simulator
This commit is contained in:
586
sql_generation/generation/predicate/binary.rs
Normal file
586
sql_generation/generation/predicate/binary.rs
Normal file
@@ -0,0 +1,586 @@
|
||||
//! Contains code for generation for [ast::Expr::Binary] Predicate
|
||||
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{
|
||||
backtrack, one_of, pick,
|
||||
predicate::{CompoundPredicate, SimplePredicate},
|
||||
table::{GTValue, LTValue, LikeValue},
|
||||
ArbitraryFrom, ArbitraryFromMaybe as _,
|
||||
},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table, TableContext},
|
||||
},
|
||||
};
|
||||
|
||||
impl Predicate {
|
||||
/// Generate an [ast::Expr::Binary] [Predicate] from a column and [SimValue]
|
||||
pub fn from_column_binary<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
column_name: &str,
|
||||
value: &SimValue,
|
||||
) -> Predicate {
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Name::Ident(column_name.to_string()))),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Name::Ident(column_name.to_string()))),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Id(ast::Name::Ident(column_name.to_string()))),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
Predicate(expr)
|
||||
}
|
||||
|
||||
/// Produces a true [ast::Expr::Binary] [Predicate] that is true for the provided row in the given table
|
||||
pub fn true_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.random_range(0..t.columns.len());
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let value = &row[column_index];
|
||||
|
||||
let mut table_name = t.name.clone();
|
||||
if t.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a table prefix for a joined table")
|
||||
.to_string();
|
||||
column.name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a column suffix for a joined table")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
(
|
||||
1,
|
||||
Box::new(|_| {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let v = SimValue::arbitrary_from(rng, &column.column_type);
|
||||
if &v == value {
|
||||
None
|
||||
} else {
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
))
|
||||
}
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Some(Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
))
|
||||
}),
|
||||
),
|
||||
(
|
||||
1,
|
||||
Box::new(|rng| {
|
||||
// TODO: generation for Like and Glob expressions should be extracted to different module
|
||||
LikeValue::arbitrary_from_maybe(rng, value).map(|like| {
|
||||
Expr::Like {
|
||||
lhs: Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
not: false, // TODO: also generate this value eventually
|
||||
op: ast::LikeOperator::Like,
|
||||
rhs: Box::new(Expr::Literal(like.0.into())),
|
||||
escape: None, // TODO: implement
|
||||
}
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
// Backtrack will always return Some here
|
||||
Predicate(expr.unwrap())
|
||||
}
|
||||
|
||||
/// Produces an [ast::Expr::Binary] [Predicate] that is false for the provided row in the given table
|
||||
pub fn false_binary<R: rand::Rng>(rng: &mut R, t: &Table, row: &[SimValue]) -> Predicate {
|
||||
// Pick a column
|
||||
let column_index = rng.random_range(0..t.columns.len());
|
||||
let mut column = t.columns[column_index].clone();
|
||||
let mut table_name = t.name.clone();
|
||||
let value = &row[column_index];
|
||||
|
||||
if t.name.is_empty() {
|
||||
// If the table name is empty, we cannot create a qualified expression
|
||||
// so we use the column name directly
|
||||
let mut splitted = column.name.split('.');
|
||||
table_name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a table prefix for a joined table")
|
||||
.to_string();
|
||||
column.name = splitted
|
||||
.next()
|
||||
.expect("Column name should have a column suffix for a joined table")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let v = loop {
|
||||
let v = SimValue::arbitrary_from(rng, &column.column_type);
|
||||
if &v != value {
|
||||
break v;
|
||||
}
|
||||
};
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(v.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(&table_name),
|
||||
ast::Name::new(&column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
Predicate(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePredicate {
|
||||
/// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn true_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
// Pick a random column
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let table_name = column.table_name;
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Equals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
SimplePredicate(Predicate(expr))
|
||||
}
|
||||
|
||||
/// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn false_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column = columns[column_index];
|
||||
let column_value = &row[column_index];
|
||||
let table_name = column.table_name;
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
||||
}
|
||||
|
||||
let expr = one_of(
|
||||
vec![
|
||||
Box::new(|_rng| {
|
||||
Expr::Binary(
|
||||
Box::new(Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::NotEquals,
|
||||
Box::new(Expr::Literal(column_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let gt_value = GTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Greater,
|
||||
Box::new(Expr::Literal(gt_value.into())),
|
||||
)
|
||||
}),
|
||||
Box::new(|rng| {
|
||||
let lt_value = LTValue::arbitrary_from(rng, column_value).0;
|
||||
Expr::Binary(
|
||||
Box::new(ast::Expr::Qualified(
|
||||
ast::Name::new(table_name),
|
||||
ast::Name::new(&column.column.name),
|
||||
)),
|
||||
ast::Operator::Less,
|
||||
Box::new(Expr::Literal(lt_value.into())),
|
||||
)
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
SimplePredicate(Predicate(expr))
|
||||
}
|
||||
}
|
||||
|
||||
impl CompoundPredicate {
|
||||
/// Decide if you want to create an AND or an OR
|
||||
///
|
||||
/// Creates a Compound Predicate that is TRUE or FALSE for at least a single row
|
||||
pub fn from_table_binary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
predicate_value: bool,
|
||||
) -> Self {
|
||||
// Cannot pick a row if the table is empty
|
||||
let rows = table.rows();
|
||||
if rows.is_empty() {
|
||||
return Self(if predicate_value {
|
||||
Predicate::true_()
|
||||
} else {
|
||||
Predicate::false_()
|
||||
});
|
||||
}
|
||||
let row = pick(rows, rng);
|
||||
|
||||
let predicate = if rng.random_bool(0.7) {
|
||||
// An AND for true requires each of its children to be true
|
||||
// An AND for false requires at least one of its children to be false
|
||||
if predicate_value {
|
||||
(0..rng.random_range(1..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, true)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.random_range(1..=3))
|
||||
.map(|_| rng.random_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let len = booleans.len();
|
||||
|
||||
// Make sure at least one of them is false
|
||||
if booleans.iter().all(|b| *b) {
|
||||
booleans[rng.random_range(0..len)] = false;
|
||||
}
|
||||
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
}
|
||||
} else {
|
||||
// An OR for true requires at least one of its children to be true
|
||||
// An OR for false requires each of its children to be false
|
||||
if predicate_value {
|
||||
// Create a vector of random booleans
|
||||
let mut booleans = (0..rng.random_range(1..=3))
|
||||
.map(|_| rng.random_bool(0.5))
|
||||
.collect::<Vec<_>>();
|
||||
let len = booleans.len();
|
||||
// Make sure at least one of them is true
|
||||
if booleans.iter().all(|b| !*b) {
|
||||
booleans[rng.random_range(0..len)] = true;
|
||||
}
|
||||
|
||||
booleans
|
||||
.iter()
|
||||
.map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
} else {
|
||||
(0..rng.random_range(1..=3))
|
||||
.map(|_| SimplePredicate::arbitrary_from(rng, (table, row, false)).0)
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
}
|
||||
};
|
||||
Self(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng as _, SeedableRng as _};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use crate::{
|
||||
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
|
||||
model::{
|
||||
query::predicate::{expr_to_value, Predicate},
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_seed() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_true_binary_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::true_binary(&mut rng, &table, row);
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().is_some_and(|value| value.as_bool()),
|
||||
"Predicate: {predicate:#?}\nValue: {value:#?}\nSeed: {seed}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_binary_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::false_binary(&mut rng, &table, row);
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
!value.as_ref().is_some_and(|value| value.as_bool()),
|
||||
"Predicate: {predicate:#?}\nValue: {value:#?}\nSeed: {seed}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_true_binary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::true_binary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_binary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::false_binary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
}
|
||||
378
sql_generation/generation/predicate/mod.rs
Normal file
378
sql_generation/generation/predicate/mod.rs
Normal file
@@ -0,0 +1,378 @@
|
||||
use rand::{seq::SliceRandom as _, Rng};
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, Table, TableContext},
|
||||
};
|
||||
|
||||
use super::{one_of, ArbitraryFrom};
|
||||
|
||||
mod binary;
|
||||
mod unary;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CompoundPredicate(Predicate);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SimplePredicate(Predicate);
|
||||
|
||||
impl<A: AsRef<[SimValue]>, T: TableContext> ArbitraryFrom<(&T, A, bool)> for SimplePredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, row, predicate_value): (&T, A, bool)) -> Self {
|
||||
let row = row.as_ref();
|
||||
// Pick an operator
|
||||
let choice = rng.random_range(0..2);
|
||||
// Pick an operator
|
||||
match predicate_value {
|
||||
true => match choice {
|
||||
0 => SimplePredicate::true_binary(rng, table, row),
|
||||
1 => SimplePredicate::true_unary(rng, table, row),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
false => match choice {
|
||||
0 => SimplePredicate::false_binary(rng, table, row),
|
||||
1 => SimplePredicate::false_unary(rng, table, row),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for CompoundPredicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
|
||||
CompoundPredicate::from_table_binary(rng, table, predicate_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableContext> ArbitraryFrom<&T> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, table: &T) -> Self {
|
||||
let predicate_value = rng.random_bool(0.5);
|
||||
Predicate::arbitrary_from(rng, (table, predicate_value)).parens()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableContext> ArbitraryFrom<(&T, bool)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (table, predicate_value): (&T, bool)) -> Self {
|
||||
CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&str, &SimValue)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (column_name, value): (&str, &SimValue)) -> Self {
|
||||
Predicate::from_column_binary(rng, column_name, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFrom<(&Table, &Vec<SimValue>)> for Predicate {
|
||||
fn arbitrary_from<R: Rng>(rng: &mut R, (t, row): (&Table, &Vec<SimValue>)) -> Self {
|
||||
// We want to produce a predicate that is true for the row
|
||||
// We can do this by creating several predicates that
|
||||
// are true, some that are false, combiend them in ways that correspond to the creation of a true predicate
|
||||
|
||||
// Produce some true and false predicates
|
||||
let mut true_predicates = (1..=rng.random_range(1..=4))
|
||||
.map(|_| Predicate::true_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let false_predicates = (0..=rng.random_range(0..=3))
|
||||
.map(|_| Predicate::false_binary(rng, t, row))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Start building a top level predicate from a true predicate
|
||||
let mut result = true_predicates.pop().unwrap();
|
||||
|
||||
let mut predicates = true_predicates
|
||||
.iter()
|
||||
.map(|p| (true, p.clone()))
|
||||
.chain(false_predicates.iter().map(|p| (false, p.clone())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
predicates.shuffle(rng);
|
||||
|
||||
while !predicates.is_empty() {
|
||||
// Create a new predicate from at least 1 and at most 3 predicates
|
||||
let context =
|
||||
predicates[0..rng.random_range(0..=usize::min(3, predicates.len()))].to_vec();
|
||||
// Shift `predicates` to remove the predicates in the context
|
||||
predicates = predicates[context.len()..].to_vec();
|
||||
|
||||
// `result` is true, so we have the following three options to make a true predicate:
|
||||
// T or F
|
||||
// T or T
|
||||
// T and T
|
||||
|
||||
result = one_of(
|
||||
vec![
|
||||
// T or (X1 or X2 or ... or Xn)
|
||||
Box::new(|_| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::Or,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}),
|
||||
// T or (T1 and T2 and ... and Tn)
|
||||
Box::new(|_| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::Or,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}),
|
||||
// T and T
|
||||
Box::new(|_| {
|
||||
// Check if all the predicates in the context are true
|
||||
if context.iter().all(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::And,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::true_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}
|
||||
// Check if there is at least one true predicate
|
||||
else if context.iter().any(|(b, _)| *b) {
|
||||
// T and (X1 or X2 or ... or Xn)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap_or(Predicate::false_())
|
||||
.0,
|
||||
),
|
||||
))
|
||||
// Predicate::And(vec![
|
||||
// result.clone(),
|
||||
// Predicate::Or(context.iter().map(|(_, p)| p.clone()).collect()),
|
||||
// ])
|
||||
} else {
|
||||
// T and (X1 or X2 or ... or Xn or TRUE)
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(result.0.clone()),
|
||||
ast::Operator::And,
|
||||
Box::new(
|
||||
context
|
||||
.iter()
|
||||
.map(|(_, p)| p.clone())
|
||||
.chain(std::iter::once(Predicate::true_()))
|
||||
.reduce(|accum, curr| {
|
||||
Predicate(Expr::Binary(
|
||||
Box::new(accum.0),
|
||||
ast::Operator::Or,
|
||||
Box::new(curr.0),
|
||||
))
|
||||
})
|
||||
.unwrap() // Chain guarantees at least one value
|
||||
.0,
|
||||
),
|
||||
))
|
||||
}
|
||||
}),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng as _, SeedableRng as _};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use crate::{
|
||||
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
|
||||
model::{
|
||||
query::predicate::{expr_to_value, Predicate},
|
||||
table::{SimValue, Table},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_seed() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_table_true_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, true)).0;
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().is_some_and(|value| value.as_bool()),
|
||||
"Predicate: {predicate:#?}\nValue: {value:#?}\nSeed: {seed}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_table_false_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = SimplePredicate::arbitrary_from(&mut rng, (&table, row, false)).0;
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
!value.as_ref().is_some_and(|value| value.as_bool()),
|
||||
"Predicate: {predicate:#?}\nValue: {value:#?}\nSeed: {seed}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_row_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let row = pick(&values, &mut rng);
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, row));
|
||||
let value = expr_to_value(&predicate.0, row, &table);
|
||||
assert!(
|
||||
value.as_ref().is_some_and(|value| value.as_bool()),
|
||||
"Predicate: {predicate:#?}\nValue: {value:#?}\nSeed: {seed}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_true_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, true));
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_arbitrary_false_table_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let predicate = Predicate::arbitrary_from(&mut rng, (&table, false));
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
}
|
||||
306
sql_generation/generation/predicate/unary.rs
Normal file
306
sql_generation/generation/predicate/unary.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
//! Contains code regarding generation for [ast::Expr::Unary] Predicate
|
||||
//! TODO: for now just generating [ast::Literal], but want to also generate Columns and any
|
||||
//! arbitrary [ast::Expr]
|
||||
|
||||
use turso_parser::ast::{self, Expr};
|
||||
|
||||
use crate::{
|
||||
generation::{backtrack, pick, predicate::SimplePredicate, ArbitraryFromMaybe},
|
||||
model::{
|
||||
query::predicate::Predicate,
|
||||
table::{SimValue, TableContext},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct TrueValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<&SimValue> for TrueValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// If the Value is a true value return it else you cannot return a true Value
|
||||
value.as_bool().then_some(Self(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFromMaybe<&Vec<&SimValue>> for TrueValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if values.is_empty() {
|
||||
return Some(Self(SimValue::TRUE));
|
||||
}
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from_maybe(rng, *value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FalseValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<&SimValue> for FalseValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(_rng: &mut R, value: &SimValue) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// If the Value is a false value return it else you cannot return a false Value
|
||||
(!value.as_bool()).then_some(Self(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFromMaybe<&Vec<&SimValue>> for FalseValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(rng: &mut R, values: &Vec<&SimValue>) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if values.is_empty() {
|
||||
return Some(Self(SimValue::FALSE));
|
||||
}
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from_maybe(rng, *value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BitNotValue(pub SimValue);
|
||||
|
||||
impl ArbitraryFromMaybe<(&SimValue, bool)> for BitNotValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(
|
||||
_rng: &mut R,
|
||||
(value, predicate): (&SimValue, bool),
|
||||
) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let bit_not_val = value.unary_exec(ast::UnaryOperator::BitwiseNot);
|
||||
// If you bit not the Value and it meets the predicate return Some, else None
|
||||
(bit_not_val.as_bool() == predicate).then_some(BitNotValue(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue {
|
||||
fn arbitrary_from_maybe<R: rand::Rng>(
|
||||
rng: &mut R,
|
||||
(values, predicate): (&Vec<&SimValue>, bool),
|
||||
) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if values.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = pick(values, rng);
|
||||
Self::arbitrary_from_maybe(rng, (*value, predicate))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: have some more complex generation with columns names here as well
|
||||
impl SimplePredicate {
|
||||
/// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for some values in the table
|
||||
pub fn true_unary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into())));
|
||||
}
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
(
|
||||
num_retries,
|
||||
Box::new(|rng| {
|
||||
TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
assert!(value.0.as_bool());
|
||||
// Positive is a no-op in Sqlite
|
||||
Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into()))
|
||||
})
|
||||
}),
|
||||
),
|
||||
// (
|
||||
// num_retries,
|
||||
// Box::new(|rng| {
|
||||
// TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
// assert!(value.0.as_bool());
|
||||
// // True Value with negative is still True
|
||||
// Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into()))
|
||||
// })
|
||||
// }),
|
||||
// ),
|
||||
// (
|
||||
// num_retries,
|
||||
// Box::new(|rng| {
|
||||
// BitNotValue::arbitrary_from_maybe(rng, (column_value, true)).map(|value| {
|
||||
// Expr::unary(
|
||||
// ast::UnaryOperator::BitwiseNot,
|
||||
// Expr::Literal(value.0.into()),
|
||||
// )
|
||||
// })
|
||||
// }),
|
||||
// ),
|
||||
(
|
||||
num_retries,
|
||||
Box::new(|rng| {
|
||||
FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
assert!(!value.0.as_bool());
|
||||
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
// If cannot generate a value
|
||||
SimplePredicate(Predicate(
|
||||
expr.unwrap_or(Expr::Literal(SimValue::TRUE.into())),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [TableContext] for a row in the table
|
||||
pub fn false_unary<R: rand::Rng, T: TableContext>(
|
||||
rng: &mut R,
|
||||
table: &T,
|
||||
row: &[SimValue],
|
||||
) -> Self {
|
||||
let columns = table.columns().collect::<Vec<_>>();
|
||||
// Pick a random column
|
||||
let column_index = rng.random_range(0..columns.len());
|
||||
let column_value = &row[column_index];
|
||||
let num_retries = row.len();
|
||||
// Avoid creation of NULLs
|
||||
if row.is_empty() {
|
||||
return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into())));
|
||||
}
|
||||
let expr = backtrack(
|
||||
vec![
|
||||
// (
|
||||
// num_retries,
|
||||
// Box::new(|rng| {
|
||||
// FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
// assert!(!value.0.as_bool());
|
||||
// // Positive is a no-op in Sqlite
|
||||
// Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into()))
|
||||
// })
|
||||
// }),
|
||||
// ),
|
||||
// (
|
||||
// num_retries,
|
||||
// Box::new(|rng| {
|
||||
// FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
// assert!(!value.0.as_bool());
|
||||
// // True Value with negative is still True
|
||||
// Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into()))
|
||||
// })
|
||||
// }),
|
||||
// ),
|
||||
// (
|
||||
// num_retries,
|
||||
// Box::new(|rng| {
|
||||
// BitNotValue::arbitrary_from_maybe(rng, (column_value, false)).map(|value| {
|
||||
// Expr::unary(
|
||||
// ast::UnaryOperator::BitwiseNot,
|
||||
// Expr::Literal(value.0.into()),
|
||||
// )
|
||||
// })
|
||||
// }),
|
||||
// ),
|
||||
(
|
||||
num_retries,
|
||||
Box::new(|rng| {
|
||||
TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| {
|
||||
assert!(value.0.as_bool());
|
||||
Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into()))
|
||||
})
|
||||
}),
|
||||
),
|
||||
],
|
||||
rng,
|
||||
);
|
||||
// If cannot generate a value
|
||||
SimplePredicate(Predicate(
|
||||
expr.unwrap_or(Expr::Literal(SimValue::FALSE.into())),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng as _, SeedableRng as _};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
use crate::{
|
||||
generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _},
|
||||
model::table::{SimValue, Table},
|
||||
};
|
||||
|
||||
fn get_seed() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_true_unary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::true_unary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.reduce(|accum, curr| accum || curr)
|
||||
.unwrap_or(false);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzz_false_unary_simple_predicate() {
|
||||
let seed = get_seed();
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
for _ in 0..10000 {
|
||||
let mut table = Table::arbitrary(&mut rng);
|
||||
let num_rows = rng.random_range(1..10);
|
||||
let values: Vec<Vec<SimValue>> = (0..num_rows)
|
||||
.map(|_| {
|
||||
table
|
||||
.columns
|
||||
.iter()
|
||||
.map(|c| SimValue::arbitrary_from(&mut rng, &c.column_type))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
table.rows.extend(values.clone());
|
||||
let row = pick(&table.rows, &mut rng);
|
||||
let predicate = SimplePredicate::false_unary(&mut rng, &table, row);
|
||||
let result = values
|
||||
.iter()
|
||||
.map(|row| predicate.0.test(row, &table))
|
||||
.any(|res| !res);
|
||||
assert!(result, "Predicate: {predicate:#?}\nSeed: {seed}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user