Files
turso/simulator/generation/plan.rs

449 lines
15 KiB
Rust

use std::{fmt::Display, rc::Rc};
use limbo_core::{Connection, Result, StepResult};
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use crate::{
model::{
query::{Create, Insert, Predicate, Query, Select},
table::Value,
},
SimConnection, SimulatorEnv,
};
use crate::generation::{frequency, Arbitrary, ArbitraryFrom};
use super::{pick, pick_index};
pub(crate) type ResultSet = Result<Vec<Vec<Value>>>;
pub(crate) struct InteractionPlan {
pub(crate) plan: Vec<Interaction>,
pub(crate) stack: Vec<ResultSet>,
pub(crate) interaction_pointer: usize,
}
impl Display for InteractionPlan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for interaction in &self.plan {
match interaction {
Interaction::Query(query) => writeln!(f, "{};", query)?,
Interaction::Assertion(assertion) => {
writeln!(f, "-- ASSERT: {};", assertion.message)?
}
Interaction::Fault(fault) => writeln!(f, "-- FAULT: {};", fault)?,
}
}
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct InteractionStats {
pub(crate) read_count: usize,
pub(crate) write_count: usize,
pub(crate) delete_count: usize,
pub(crate) create_count: usize,
}
impl Display for InteractionStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Read: {}, Write: {}, Delete: {}, Create: {}",
self.read_count, self.write_count, self.delete_count, self.create_count
)
}
}
pub(crate) enum Interaction {
Query(Query),
Assertion(Assertion),
Fault(Fault),
}
impl Display for Interaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Query(query) => write!(f, "{}", query),
Self::Assertion(assertion) => write!(f, "ASSERT: {}", assertion.message),
Self::Fault(fault) => write!(f, "FAULT: {}", fault),
}
}
}
type AssertionFunc = dyn Fn(&Vec<ResultSet>) -> bool;
pub(crate) struct Assertion {
pub(crate) func: Box<AssertionFunc>,
pub(crate) message: String,
}
pub(crate) enum Fault {
Disconnect,
}
impl Display for Fault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Fault::Disconnect => write!(f, "DISCONNECT"),
}
}
}
pub(crate) struct Interactions(Vec<Interaction>);
impl Interactions {
pub(crate) fn shadow(&self, env: &mut SimulatorEnv) {
for interaction in &self.0 {
match interaction {
Interaction::Query(query) => match query {
Query::Create(create) => {
if !env.tables.iter().any(|t| t.name == create.table.name) {
env.tables.push(create.table.clone());
}
}
Query::Insert(insert) => {
let table = env
.tables
.iter_mut()
.find(|t| t.name == insert.table)
.unwrap();
table.rows.extend(insert.values.clone());
}
Query::Delete(_) => todo!(),
Query::Select(_) => {}
},
Interaction::Assertion(_) => {}
Interaction::Fault(_) => {}
}
}
}
}
impl InteractionPlan {
pub(crate) fn new() -> Self {
Self {
plan: Vec::new(),
stack: Vec::new(),
interaction_pointer: 0,
}
}
pub(crate) fn push(&mut self, interaction: Interaction) {
self.plan.push(interaction);
}
pub(crate) fn stats(&self) -> InteractionStats {
let mut read = 0;
let mut write = 0;
let mut delete = 0;
let mut create = 0;
for interaction in &self.plan {
match interaction {
Interaction::Query(query) => match query {
Query::Select(_) => read += 1,
Query::Insert(_) => write += 1,
Query::Delete(_) => delete += 1,
Query::Create(_) => create += 1,
},
Interaction::Assertion(_) => {}
Interaction::Fault(_) => {}
}
}
InteractionStats {
read_count: read,
write_count: write,
delete_count: delete,
create_count: create,
}
}
}
impl ArbitraryFrom<SimulatorEnv> for InteractionPlan {
fn arbitrary_from<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Self {
let mut plan = InteractionPlan::new();
let mut env = SimulatorEnv {
opts: env.opts.clone(),
tables: vec![],
connections: vec![],
io: env.io.clone(),
db: env.db.clone(),
rng: ChaCha8Rng::seed_from_u64(rng.next_u64()),
};
let num_interactions = env.opts.max_interactions;
// First create at least one table
let create_query = Create::arbitrary(rng);
env.tables.push(create_query.table.clone());
plan.push(Interaction::Query(Query::Create(create_query)));
while plan.plan.len() < num_interactions {
log::debug!(
"Generating interaction {}/{}",
plan.plan.len(),
num_interactions
);
let interactions = Interactions::arbitrary_from(rng, &(&env, plan.stats()));
interactions.shadow(&mut env);
plan.plan.extend(interactions.0.into_iter());
}
log::info!("Generated plan with {} interactions", plan.plan.len());
plan
}
}
impl Interaction {
pub(crate) fn execute_query(&self, conn: &mut Rc<Connection>) -> ResultSet {
match self {
Self::Query(query) => {
let query_str = query.to_string();
let rows = conn.query(&query_str);
if rows.is_err() {
let err = rows.err();
log::error!(
"Error running query '{}': {:?}",
&query_str[0..query_str.len().min(4096)],
err
);
return Err(err.unwrap());
}
let rows = rows.unwrap();
assert!(rows.is_some());
let mut rows = rows.unwrap();
let mut out = Vec::new();
while let Ok(row) = rows.next_row() {
match row {
StepResult::Row(row) => {
let mut r = Vec::new();
for el in &row.values {
let v = match el {
limbo_core::Value::Null => Value::Null,
limbo_core::Value::Integer(i) => Value::Integer(*i),
limbo_core::Value::Float(f) => Value::Float(*f),
limbo_core::Value::Text(t) => Value::Text(t.to_string()),
limbo_core::Value::Blob(b) => Value::Blob(b.to_vec()),
};
r.push(v);
}
out.push(r);
}
StepResult::IO => {}
StepResult::Interrupt => {}
StepResult::Done => {
break;
}
StepResult::Busy => {}
}
}
Ok(out)
}
Self::Assertion(_) => {
unreachable!("unexpected: this function should only be called on queries")
}
Interaction::Fault(_) => {
unreachable!("unexpected: this function should only be called on queries")
}
}
}
pub(crate) fn execute_assertion(&self, stack: &Vec<ResultSet>) -> Result<()> {
match self {
Self::Query(_) => {
unreachable!("unexpected: this function should only be called on assertions")
}
Self::Assertion(assertion) => {
if !assertion.func.as_ref()(stack) {
return Err(limbo_core::LimboError::InternalError(
assertion.message.clone(),
));
}
Ok(())
}
Self::Fault(_) => {
unreachable!("unexpected: this function should only be called on assertions")
}
}
}
pub(crate) fn execute_fault(&self, env: &mut SimulatorEnv, conn_index: usize) -> Result<()> {
match self {
Self::Query(_) => {
unreachable!("unexpected: this function should only be called on faults")
}
Self::Assertion(_) => {
unreachable!("unexpected: this function should only be called on faults")
}
Self::Fault(fault) => {
match fault {
Fault::Disconnect => {
match env.connections[conn_index] {
SimConnection::Connected(ref mut conn) => {
conn.close()?;
}
SimConnection::Disconnected => {
return Err(limbo_core::LimboError::InternalError(
"Tried to disconnect a disconnected connection".to_string(),
));
}
}
env.connections[conn_index] = SimConnection::Disconnected;
}
}
Ok(())
}
}
}
}
fn property_insert_select<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
// Get a random table
let table = pick(&env.tables, rng);
// Pick a random column
let column_index = pick_index(table.columns.len(), rng);
let column = &table.columns[column_index].clone();
// Generate a random value of the column type
let value = Value::arbitrary_from(rng, &column.column_type);
// Create a whole new row
let mut row = Vec::new();
for (i, column) in table.columns.iter().enumerate() {
if i == column_index {
row.push(value.clone());
} else {
let value = Value::arbitrary_from(rng, &column.column_type);
row.push(value);
}
}
// Insert the row
let insert_query = Interaction::Query(Query::Insert(Insert {
table: table.name.clone(),
values: vec![row.clone()],
}));
// Select the row
let select_query = Interaction::Query(Query::Select(Select {
table: table.name.clone(),
predicate: Predicate::Eq(column.name.clone(), value.clone()),
}));
// Check that the row is there
let assertion = Interaction::Assertion(Assertion {
message: format!(
"row [{:?}] not found in table {} after inserting ({} = {})",
row.iter().map(|v| v.to_string()).collect::<Vec<String>>(),
table.name,
column.name,
value,
),
func: Box::new(move |stack: &Vec<ResultSet>| {
let rows = stack.last().unwrap();
match rows {
Ok(rows) => rows.iter().any(|r| r == &row),
Err(_) => false,
}
}),
});
Interactions(vec![insert_query, select_query, assertion])
}
fn property_double_create_failure<R: rand::Rng>(rng: &mut R, _env: &SimulatorEnv) -> Interactions {
let create_query = Create::arbitrary(rng);
let table_name = create_query.table.name.clone();
let cq1 = Interaction::Query(Query::Create(create_query.clone()));
let cq2 = Interaction::Query(Query::Create(create_query.clone()));
let assertion = Interaction::Assertion(Assertion {
message:
"creating two tables with the name should result in a failure for the second query"
.to_string(),
func: Box::new(move |stack: &Vec<ResultSet>| {
let last = stack.last().unwrap();
match last {
Ok(_) => false,
Err(e) => e
.to_string()
.contains(&format!("Table {table_name} already exists")),
}
}),
});
Interactions(vec![cq1, cq2, assertion])
}
fn create_table<R: rand::Rng>(rng: &mut R, _env: &SimulatorEnv) -> Interactions {
let create_query = Interaction::Query(Query::Create(Create::arbitrary(rng)));
Interactions(vec![create_query])
}
fn random_read<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
let select_query = Interaction::Query(Query::Select(Select::arbitrary_from(rng, &env.tables)));
Interactions(vec![select_query])
}
fn random_write<R: rand::Rng>(rng: &mut R, env: &SimulatorEnv) -> Interactions {
let table = pick(&env.tables, rng);
let insert_query = Interaction::Query(Query::Insert(Insert::arbitrary_from(rng, table)));
Interactions(vec![insert_query])
}
fn random_fault<R: rand::Rng>(_rng: &mut R, _env: &SimulatorEnv) -> Interactions {
let fault = Interaction::Fault(Fault::Disconnect);
Interactions(vec![fault])
}
impl ArbitraryFrom<(&SimulatorEnv, InteractionStats)> for Interactions {
fn arbitrary_from<R: rand::Rng>(
rng: &mut R,
(env, stats): &(&SimulatorEnv, InteractionStats),
) -> Self {
let remaining_read = ((env.opts.max_interactions as f64 * env.opts.read_percent / 100.0)
- (stats.read_count as f64))
.max(0.0);
let remaining_write = ((env.opts.max_interactions as f64 * env.opts.write_percent / 100.0)
- (stats.write_count as f64))
.max(0.0);
let remaining_create = ((env.opts.max_interactions as f64 * env.opts.create_percent
/ 100.0)
- (stats.create_count as f64))
.max(0.0);
frequency(
vec![
(
f64::min(remaining_read, remaining_write),
Box::new(|rng: &mut R| property_insert_select(rng, env)),
),
(
remaining_read,
Box::new(|rng: &mut R| random_read(rng, env)),
),
(
remaining_write,
Box::new(|rng: &mut R| random_write(rng, env)),
),
(
remaining_create,
Box::new(|rng: &mut R| create_table(rng, env)),
),
(1.0, Box::new(|rng: &mut R| random_fault(rng, env))),
(
remaining_create / 2.0,
Box::new(|rng: &mut R| property_double_create_failure(rng, env)),
),
],
rng,
)
}
}