diff --git a/simulator/generation/expr.rs b/simulator/generation/expr.rs index 59d07c920..d3c09f869 100644 --- a/simulator/generation/expr.rs +++ b/simulator/generation/expr.rs @@ -257,12 +257,8 @@ impl ArbitraryFrom<&Vec<&SimValue>> for ast::Expr { return Self::Literal(ast::Literal::Null); } // TODO: for now just convert the value to an ast::Literal - let values = values - .iter() - .map(|value| ast::Expr::Literal((*value).into())) - .collect::>(); - - pick(&values, rng).to_owned().clone() + let value = pick(&values, rng); + Expr::Literal((*value).into()) } } diff --git a/simulator/generation/predicate/binary.rs b/simulator/generation/predicate/binary.rs index 704d7f14d..dc7f267b7 100644 --- a/simulator/generation/predicate/binary.rs +++ b/simulator/generation/predicate/binary.rs @@ -16,7 +16,7 @@ use crate::{ }; impl Predicate { - /// Generate an [ast::Expr::Binary] [Predicate] from a column and [Value] + /// Generate an [ast::Expr::Binary] [Predicate] from a column and [SimValue] pub fn from_column_binary( rng: &mut R, column_name: &str, @@ -209,36 +209,30 @@ impl Predicate { } impl SimplePredicate { - /// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [Table] for some values in the table - pub fn true_binary(rng: &mut R, table: &Table) -> Self { + /// Generates a true [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table + pub fn true_binary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); let column = &table.columns[column_index]; - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); + let column_value = &row[column_index]; + // Avoid creation of NULLs + if row.is_empty() { + return SimplePredicate(Predicate(Expr::Literal(SimValue::TRUE.into()))); + } let expr = one_of( vec![ - Box::new(|rng| { - let expr = if column_values.is_empty() { - Expr::arbitrary_from(rng, &column_values) - } else { - Expr::Literal((*pick(&column_values, rng)).into()) - }; - + Box::new(|_rng| { Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(table.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::Equals, - Box::new(expr), + Box::new(Expr::Literal(column_value.into())), ) }), Box::new(|rng| { - let lt_value = LTValue::arbitrary_from(rng, &column_values).0; + let lt_value = LTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(Expr::Qualified( ast::Name(table.name.clone()), @@ -249,7 +243,7 @@ impl SimplePredicate { ) }), Box::new(|rng| { - let gt_value = GTValue::arbitrary_from(rng, &column_values).0; + let gt_value = GTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(Expr::Qualified( ast::Name(table.name.clone()), @@ -265,35 +259,30 @@ impl SimplePredicate { SimplePredicate(Predicate(expr)) } - /// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [Table] for some values in the table - pub fn false_binary(rng: &mut R, table: &Table) -> Self { + /// Generates a false [ast::Expr::Binary] [SimplePredicate] from a [Table] for a row in the table + pub fn false_binary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); let column = &table.columns[column_index]; - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); + let column_value = &row[column_index]; + // Avoid creation of NULLs + if row.is_empty() { + return SimplePredicate(Predicate(Expr::Literal(SimValue::FALSE.into()))); + } let expr = one_of( vec![ - Box::new(|rng| { - let expr = if column_values.is_empty() { - Expr::arbitrary_from(rng, &column_values) - } else { - Expr::Literal((*pick(&column_values, rng)).into()) - }; + Box::new(|_rng| { Expr::Binary( Box::new(Expr::Qualified( ast::Name(table.name.clone()), ast::Name(column.name.clone()), )), ast::Operator::NotEquals, - Box::new(expr), + Box::new(Expr::Literal(column_value.into())), ) }), Box::new(|rng| { - let gt_value = GTValue::arbitrary_from(rng, &column_values).0; + let gt_value = GTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(table.name.clone()), @@ -304,7 +293,7 @@ impl SimplePredicate { ) }), Box::new(|rng| { - let lt_value = LTValue::arbitrary_from(rng, &column_values).0; + let lt_value = LTValue::arbitrary_from(rng, column_value).0; Expr::Binary( Box::new(ast::Expr::Qualified( ast::Name(table.name.clone()), @@ -323,25 +312,37 @@ impl SimplePredicate { 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( rng: &mut R, table: &Table, predicate_value: bool, ) -> Self { + // Cannot pick a row if the table is empty + if table.rows.is_empty() { + return Self( + predicate_value + .then_some(Predicate::true_()) + .unwrap_or(Predicate::false_()), + ); + } + let row = pick(&table.rows, rng); let predicate = if rng.gen_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.gen_range(0..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, (table, true)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, (table, row, true)).0) .reduce(|accum, curr| { + // dbg!(&accum, &curr); Predicate(Expr::Binary( Box::new(accum.0), ast::Operator::And, Box::new(curr.0), )) }) - .unwrap_or(Predicate::true_()) // Empty And is True + .unwrap_or(Predicate::true_()) } else { // Create a vector of random booleans let mut booleans = (0..rng.gen_range(0..=3)) @@ -357,7 +358,7 @@ impl CompoundPredicate { booleans .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0) .reduce(|accum, curr| { Predicate(Expr::Binary( Box::new(accum.0), @@ -365,7 +366,7 @@ impl CompoundPredicate { Box::new(curr.0), )) }) - .unwrap_or(Predicate::true_()) // Empty And is True + .unwrap_or(Predicate::false_()) } } else { // An OR for true requires at least one of its children to be true @@ -383,7 +384,7 @@ impl CompoundPredicate { booleans .iter() - .map(|b| SimplePredicate::arbitrary_from(rng, (table, *b)).0) + .map(|b| SimplePredicate::arbitrary_from(rng, (table, row, *b)).0) .reduce(|accum, curr| { Predicate(Expr::Binary( Box::new(accum.0), @@ -391,10 +392,10 @@ impl CompoundPredicate { Box::new(curr.0), )) }) - .unwrap_or(Predicate::false_()) // Empty Or is False + .unwrap_or(Predicate::true_()) } else { (0..rng.gen_range(0..=3)) - .map(|_| SimplePredicate::arbitrary_from(rng, (table, false)).0) + .map(|_| SimplePredicate::arbitrary_from(rng, (table, row, false)).0) .reduce(|accum, curr| { Predicate(Expr::Binary( Box::new(accum.0), @@ -402,7 +403,7 @@ impl CompoundPredicate { Box::new(curr.0), )) }) - .unwrap_or(Predicate::false_()) // Empty Or is False + .unwrap_or(Predicate::false_()) } }; Self(predicate) @@ -504,7 +505,8 @@ mod tests { }) .collect(); table.rows.extend(values.clone()); - let predicate = SimplePredicate::true_binary(&mut rng, &table); + 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)) @@ -531,7 +533,8 @@ mod tests { }) .collect(); table.rows.extend(values.clone()); - let predicate = SimplePredicate::false_binary(&mut rng, &table); + 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)) diff --git a/simulator/generation/predicate/mod.rs b/simulator/generation/predicate/mod.rs index 917025032..9f2c08ee5 100644 --- a/simulator/generation/predicate/mod.rs +++ b/simulator/generation/predicate/mod.rs @@ -17,19 +17,22 @@ struct CompoundPredicate(Predicate); #[derive(Debug)] struct SimplePredicate(Predicate); -impl ArbitraryFrom<(&Table, bool)> for SimplePredicate { - fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { +impl ArbitraryFrom<(&Table, &[SimValue], bool)> for SimplePredicate { + fn arbitrary_from( + rng: &mut R, + (table, row, predicate_value): (&Table, &[SimValue], bool), + ) -> Self { let choice = rng.gen_range(0..2); // Pick an operator match predicate_value { true => match choice { - 0 => SimplePredicate::true_binary(rng, table), - 1 => SimplePredicate::true_unary(rng, table), + 0 => SimplePredicate::true_binary(rng, table, row), + 1 => SimplePredicate::true_unary(rng, table, row), _ => unreachable!(), }, false => match choice { - 0 => SimplePredicate::false_binary(rng, table), - 1 => SimplePredicate::false_unary(rng, table), + 0 => SimplePredicate::false_binary(rng, table, row), + 1 => SimplePredicate::false_unary(rng, table, row), _ => unreachable!(), }, } @@ -45,6 +48,12 @@ impl ArbitraryFrom<(&Table, bool)> for CompoundPredicate { impl ArbitraryFrom<&Table> for Predicate { fn arbitrary_from(rng: &mut R, table: &Table) -> Self { let predicate_value = rng.gen_bool(0.5); + Predicate::arbitrary_from(rng, (table, predicate_value)) + } +} + +impl ArbitraryFrom<(&Table, bool)> for Predicate { + fn arbitrary_from(rng: &mut R, (table, predicate_value): (&Table, bool)) -> Self { CompoundPredicate::arbitrary_from(rng, (table, predicate_value)).0 } } @@ -216,3 +225,164 @@ impl ArbitraryFrom<(&Table, &Vec)> for Predicate { 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.gen_range(1..10); + let values: Vec> = (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().map_or(false, |value| value.into_bool()), + "Predicate: {:#?}\nValue: {:#?}\nSeed: {}", + predicate, + value, + 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.gen_range(1..10); + let values: Vec> = (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().map_or(false, |value| value.into_bool()), + "Predicate: {:#?}\nValue: {:#?}\nSeed: {}", + predicate, + value, + 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.gen_range(1..10); + let values: Vec> = (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().map_or(false, |value| value.into_bool()), + "Predicate: {:#?}\nValue: {:#?}\nSeed: {}", + predicate, + value, + 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.gen_range(1..10); + let values: Vec> = (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: {:#?}\nSeed: {}", predicate, 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.gen_range(1..10); + let values: Vec> = (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: {:#?}\nSeed: {}", predicate, seed) + } + } +} diff --git a/simulator/generation/predicate/unary.rs b/simulator/generation/predicate/unary.rs index 885abb99a..8f090a206 100644 --- a/simulator/generation/predicate/unary.rs +++ b/simulator/generation/predicate/unary.rs @@ -1,4 +1,4 @@ -//! Contains code for generation for [ast::Expr::Unary] Predicate +//! 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] @@ -100,21 +100,21 @@ impl ArbitraryFromMaybe<(&Vec<&SimValue>, bool)> for BitNotValue { // TODO: have some more complex generation with columns names here as well impl SimplePredicate { /// Generates a true [ast::Expr::Unary] [SimplePredicate] from a [Table] for some values in the table - pub fn true_unary(rng: &mut R, table: &Table) -> Self { + pub fn true_unary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); - let num_retries = column_values.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_values).map(|value| { + TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(value.0.into_bool()); // Positive is a no-op in Sqlite Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into())) @@ -124,7 +124,7 @@ impl SimplePredicate { ( num_retries, Box::new(|rng| { - TrueValue::arbitrary_from_maybe(rng, &column_values).map(|value| { + TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(value.0.into_bool()); // True Value with negative is still True Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into())) @@ -134,20 +134,18 @@ impl SimplePredicate { ( num_retries, Box::new(|rng| { - BitNotValue::arbitrary_from_maybe(rng, (&column_values, true)).map( - |value| { - Expr::unary( - ast::UnaryOperator::BitwiseNot, - Expr::Literal(value.0.into()), - ) - }, - ) + 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_values).map(|value| { + FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(!value.0.into_bool()); Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into())) }) @@ -162,22 +160,22 @@ impl SimplePredicate { )) } - /// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [Table] for some values in the table - pub fn false_unary(rng: &mut R, table: &Table) -> Self { + /// Generates a false [ast::Expr::Unary] [SimplePredicate] from a [Table] for a row in the table + pub fn false_unary(rng: &mut R, table: &Table, row: &[SimValue]) -> Self { // Pick a random column let column_index = rng.gen_range(0..table.columns.len()); - let column_values = table - .rows - .iter() - .map(|r| &r[column_index]) - .collect::>(); - let num_retries = column_values.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_values).map(|value| { + FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(!value.0.into_bool()); // Positive is a no-op in Sqlite Expr::unary(ast::UnaryOperator::Positive, Expr::Literal(value.0.into())) @@ -187,7 +185,7 @@ impl SimplePredicate { ( num_retries, Box::new(|rng| { - FalseValue::arbitrary_from_maybe(rng, &column_values).map(|value| { + FalseValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(!value.0.into_bool()); // True Value with negative is still True Expr::unary(ast::UnaryOperator::Negative, Expr::Literal(value.0.into())) @@ -197,20 +195,18 @@ impl SimplePredicate { ( num_retries, Box::new(|rng| { - BitNotValue::arbitrary_from_maybe(rng, (&column_values, false)).map( - |value| { - Expr::unary( - ast::UnaryOperator::BitwiseNot, - Expr::Literal(value.0.into()), - ) - }, - ) + 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_values).map(|value| { + TrueValue::arbitrary_from_maybe(rng, column_value).map(|value| { assert!(value.0.into_bool()); Expr::unary(ast::UnaryOperator::Not, Expr::Literal(value.0.into())) }) @@ -221,7 +217,7 @@ impl SimplePredicate { ); // If cannot generate a value SimplePredicate(Predicate( - expr.unwrap_or(Expr::Literal(SimValue::TRUE.into())), + expr.unwrap_or(Expr::Literal(SimValue::FALSE.into())), )) } } @@ -232,7 +228,7 @@ mod tests { use rand_chacha::ChaCha8Rng; use crate::{ - generation::{predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _}, + generation::{pick, predicate::SimplePredicate, Arbitrary, ArbitraryFrom as _}, model::table::{SimValue, Table}, }; @@ -260,7 +256,8 @@ mod tests { }) .collect(); table.rows.extend(values.clone()); - let predicate = SimplePredicate::true_unary(&mut rng, &table); + 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)) @@ -287,7 +284,8 @@ mod tests { }) .collect(); table.rows.extend(values.clone()); - let predicate = SimplePredicate::false_unary(&mut rng, &table); + 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)) @@ -295,7 +293,4 @@ mod tests { assert!(result, "Predicate: {:#?}\nSeed: {}", predicate, seed) } } - - #[test] - fn test_x() {} } diff --git a/simulator/model/query/predicate.rs b/simulator/model/query/predicate.rs index 9e3c0f4eb..d2d495cec 100644 --- a/simulator/model/query/predicate.rs +++ b/simulator/model/query/predicate.rs @@ -48,6 +48,7 @@ pub fn expr_to_value(expr: &ast::Expr, row: &[SimValue], table: &Table) -> Optio ast::Expr::Binary(lhs, op, rhs) => { let lhs = expr_to_value(lhs, row, table)?; let rhs = expr_to_value(rhs, row, table)?; + // dbg!(&lhs, &rhs); Some(lhs.binary_compare(&rhs, *op)) } ast::Expr::Like {