From 28cde537a883f977296dfdaedaee7e8ee944a68b Mon Sep 17 00:00:00 2001 From: alpaylan Date: Fri, 17 Jan 2025 01:28:37 +0300 Subject: [PATCH] this commit; - makes interaction plans serializable - fixes the shadowing bug where non-created tables were assumed to be created in the shadow tables map - makes small changes to make clippy happy - reorganizes simulation running flow to remove unnecessary plan regenerations while shrinking and double checking --- Cargo.lock | 14 ++-- simulator/Cargo.toml | 2 + simulator/generation/plan.rs | 48 +++++++------ simulator/generation/property.rs | 7 +- simulator/main.rs | 119 +++++++++++++------------------ simulator/model/query.rs | 57 +++++++++++++-- simulator/model/table.rs | 12 ++-- simulator/runner/cli.rs | 1 + simulator/runner/env.rs | 1 + simulator/runner/execution.rs | 5 +- 10 files changed, 154 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cbce1207..03266c68f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1284,6 +1284,8 @@ dependencies = [ "log", "rand", "rand_chacha", + "serde", + "serde_json", "tempfile", ] @@ -2128,18 +2130,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -2148,9 +2150,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "indexmap", "itoa", diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index 31a54f1e6..9967c96ee 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -23,3 +23,5 @@ tempfile = "3.0.7" env_logger = "0.10.1" anarchist-readable-name-generator-lib = "0.1.2" clap = { version = "4.5", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } diff --git a/simulator/generation/plan.rs b/simulator/generation/plan.rs index 9acef25ad..135cdfee3 100644 --- a/simulator/generation/plan.rs +++ b/simulator/generation/plan.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, rc::Rc, vec}; use limbo_core::{Connection, Result, StepResult}; +use serde::{Deserialize, Serialize}; use crate::{ model::{ @@ -19,7 +20,7 @@ use super::{ pub(crate) type ResultSet = Result>>; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub(crate) struct InteractionPlan { pub(crate) plan: Vec, } @@ -30,7 +31,7 @@ pub(crate) struct InteractionPlanState { pub(crate) secondary_pointer: usize, } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub(crate) enum Interactions { Property(Property), Query(Query), @@ -178,7 +179,7 @@ pub(crate) struct Assertion { pub(crate) message: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) enum Fault { Disconnect, } @@ -195,6 +196,29 @@ impl Interactions { pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { match self { Interactions::Property(property) => { + match property { + Property::InsertSelect { + insert, + row_index: _, + queries, + select, + } => { + insert.shadow(env); + for query in queries { + query.shadow(env); + } + select.shadow(env); + } + Property::DoubleCreateFailure { create, queries } => { + if env.tables.iter().any(|t| t.name == create.table.name) { + return; + } + create.shadow(env); + for query in queries { + query.shadow(env); + } + } + } for interaction in property.interactions() { match interaction { Interaction::Query(query) => match query { @@ -220,23 +244,7 @@ impl Interactions { } } } - Interactions::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(_) => {} - }, + Interactions::Query(query) => query.shadow(env), Interactions::Fault(_) => {} } } diff --git a/simulator/generation/property.rs b/simulator/generation/property.rs index cae2a4145..2a7f57cb4 100644 --- a/simulator/generation/property.rs +++ b/simulator/generation/property.rs @@ -1,4 +1,5 @@ use limbo_core::LimboError; +use serde::{Deserialize, Serialize}; use crate::{ model::{ @@ -16,7 +17,7 @@ use super::{ /// Properties are representations of executable specifications /// about the database behavior. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub(crate) enum Property { /// Insert-Select is a property in which the inserted row /// must be in the resulting rows of a select query that has a @@ -205,7 +206,7 @@ fn property_insert_select( .collect::>(); // Pick a random row to select - let row_index = pick_index(rows.len(), rng).clone(); + let row_index = pick_index(rows.len(), rng); let row = rows[row_index].clone(); // Insert the rows @@ -228,7 +229,7 @@ fn property_insert_select( predicate, }) => { // The inserted row will not be deleted. - if t == &table.name && predicate.test(&row, &table) { + if t == &table.name && predicate.test(&row, table) { continue; } } diff --git a/simulator/main.rs b/simulator/main.rs index 680249d6a..c53552386 100644 --- a/simulator/main.rs +++ b/simulator/main.rs @@ -97,32 +97,26 @@ fn main() -> Result<(), String> { log::error!("captured backtrace:\n{}", bt); })); + let (env, plans) = setup_simulation(seed, &cli_opts, &paths.db, &paths.plan); + let env = Arc::new(Mutex::new(env)); let result = SandboxedResult::from( std::panic::catch_unwind(|| { - run_simulation( - seed, - &cli_opts, - &paths.db, - &paths.plan, - last_execution.clone(), - None, - ) + run_simulation(env.clone(), &mut plans.clone(), last_execution.clone()) }), last_execution.clone(), ); if cli_opts.doublecheck { + { + let mut env_ = env.lock().unwrap(); + env_.db = Database::open_file(env_.io.clone(), paths.doublecheck_db.to_str().unwrap()) + .unwrap(); + } + // Run the simulation again let result2 = SandboxedResult::from( std::panic::catch_unwind(|| { - run_simulation( - seed, - &cli_opts, - &paths.doublecheck_db, - &paths.plan, - last_execution.clone(), - None, - ) + run_simulation(env.clone(), &mut plans.clone(), last_execution.clone()) }), last_execution.clone(), ); @@ -202,18 +196,24 @@ fn main() -> Result<(), String> { if cli_opts.shrink { log::info!("Starting to shrink"); - let shrink = Some(last_execution); + + let shrunk_plans = plans + .iter() + .map(|plan| { + let shrunk = plan.shrink_interaction_plan(last_execution); + log::info!("{}", shrunk.stats()); + shrunk + }) + .collect::>(); + let last_execution = Arc::new(Mutex::new(*last_execution)); let shrunk = SandboxedResult::from( std::panic::catch_unwind(|| { run_simulation( - seed, - &cli_opts, - &paths.shrunk_db, - &paths.shrunk_plan, + env.clone(), + &mut shrunk_plans.clone(), last_execution.clone(), - shrink, ) }), last_execution, @@ -270,28 +270,6 @@ fn main() -> Result<(), String> { Ok(()) } -fn move_db_and_plan_files(output_dir: &Path) { - let old_db_path = output_dir.join("simulator.db"); - let old_plan_path = output_dir.join("simulator.plan"); - - let new_db_path = output_dir.join("simulator_double.db"); - let new_plan_path = output_dir.join("simulator_double.plan"); - - std::fs::rename(&old_db_path, &new_db_path).unwrap(); - std::fs::rename(&old_plan_path, &new_plan_path).unwrap(); -} - -fn revert_db_and_plan_files(output_dir: &Path) { - let old_db_path = output_dir.join("simulator.db"); - let old_plan_path = output_dir.join("simulator.plan"); - - let new_db_path = output_dir.join("simulator_double.db"); - let new_plan_path = output_dir.join("simulator_double.plan"); - - std::fs::rename(&new_db_path, &old_db_path).unwrap(); - std::fs::rename(&new_plan_path, &old_plan_path).unwrap(); -} - #[derive(Debug)] enum SandboxedResult { Panicked { @@ -345,14 +323,12 @@ impl SandboxedResult { } } -fn run_simulation( +fn setup_simulation( seed: u64, cli_opts: &SimulatorCLI, db_path: &Path, plan_path: &Path, - last_execution: Arc>, - shrink: Option<&Execution>, -) -> ExecutionResult { +) -> (SimulatorEnv, Vec) { let mut rng = ChaCha8Rng::seed_from_u64(seed); let (create_percent, read_percent, write_percent, delete_percent) = { @@ -403,9 +379,32 @@ fn run_simulation( }; log::info!("Generating database interaction plan..."); - let mut plans = (1..=env.opts.max_connections) + + let plans = (1..=env.opts.max_connections) .map(|_| InteractionPlan::arbitrary_from(&mut env.rng.clone(), &mut env)) .collect::>(); + + // todo: for now, we only use 1 connection, so it's safe to use the first plan. + let plan = plans[0].clone(); + + let mut f = std::fs::File::create(plan_path).unwrap(); + // todo: create a detailed plan file with all the plans. for now, we only use 1 connection, so it's safe to use the first plan. + f.write_all(plan.to_string().as_bytes()).unwrap(); + let mut f = std::fs::File::create(plan_path.with_extension(".json")).unwrap(); + f.write_all(serde_json::to_string(&plan).unwrap().as_bytes()) + .unwrap(); + + log::info!("{}", plan.stats()); + + log::info!("Executing database interaction plan..."); + (env, plans) +} + +fn run_simulation( + env: Arc>, + plans: &mut [InteractionPlan], + last_execution: Arc>, +) -> ExecutionResult { let mut states = plans .iter() .map(|_| InteractionPlanState { @@ -414,27 +413,9 @@ fn run_simulation( secondary_pointer: 0, }) .collect::>(); + let result = execute_plans(env.clone(), plans, &mut states, last_execution); - let plan = if let Some(failing_execution) = shrink { - // todo: for now, we only use 1 connection, so it's safe to use the first plan. - println!("Interactions Before: {}", plans[0].plan.len()); - let shrunk = plans[0].shrink_interaction_plan(failing_execution); - println!("Interactions After: {}", shrunk.plan.len()); - shrunk - } else { - plans[0].clone() - }; - - let mut f = std::fs::File::create(plan_path).unwrap(); - // todo: create a detailed plan file with all the plans. for now, we only use 1 connection, so it's safe to use the first plan. - f.write_all(plan.to_string().as_bytes()).unwrap(); - - log::info!("{}", plan.stats()); - - log::info!("Executing database interaction plan..."); - - let result = execute_plans(&mut env, &mut plans, &mut states, last_execution); - + let env = env.lock().unwrap(); env.io.print_stats(); log::info!("Simulation completed"); diff --git a/simulator/model/query.rs b/simulator/model/query.rs index 9138b1988..a0b7b33a2 100644 --- a/simulator/model/query.rs +++ b/simulator/model/query.rs @@ -1,8 +1,13 @@ use std::fmt::Display; -use crate::model::table::{Table, Value}; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq)] +use crate::{ + model::table::{Table, Value}, + runner::env::SimulatorEnv, +}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) enum Predicate { And(Vec), // p1 AND p2 AND p3... AND pn Or(Vec), // p1 OR p2 OR p3... OR pn @@ -83,7 +88,7 @@ impl Display for Predicate { } // This type represents the potential queries on the database. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) enum Query { Create(Create), Select(Select), @@ -108,30 +113,68 @@ impl Query { | Query::Delete(Delete { table, .. }) => vec![table.clone()], } } + + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + match self { + Query::Create(create) => create.shadow(env), + Query::Insert(insert) => insert.shadow(env), + Query::Delete(delete) => delete.shadow(env), + Query::Select(select) => select.shadow(env), + } + } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct Create { pub(crate) table: Table, } -#[derive(Clone, Debug, PartialEq)] +impl Create { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + if !env.tables.iter().any(|t| t.name == self.table.name) { + env.tables.push(self.table.clone()); + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Select { pub(crate) table: String, pub(crate) predicate: Predicate, } -#[derive(Clone, Debug, PartialEq)] +impl Select { + pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) {} +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Insert { pub(crate) table: String, pub(crate) values: Vec>, } -#[derive(Clone, Debug, PartialEq)] +impl Insert { + pub(crate) fn shadow(&self, env: &mut SimulatorEnv) { + let table = env + .tables + .iter_mut() + .find(|t| t.name == self.table) + .unwrap(); + table.rows.extend(self.values.clone()); + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct Delete { pub(crate) table: String, pub(crate) predicate: Predicate, } +impl Delete { + pub(crate) fn shadow(&self, _env: &mut SimulatorEnv) { + todo!() + } +} + impl Display for Query { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/simulator/model/table.rs b/simulator/model/table.rs index ab3b003af..10718b7f6 100644 --- a/simulator/model/table.rs +++ b/simulator/model/table.rs @@ -1,5 +1,7 @@ use std::{fmt::Display, ops::Deref}; +use serde::{Deserialize, Serialize}; + pub(crate) struct Name(pub(crate) String); impl Deref for Name { @@ -10,14 +12,14 @@ impl Deref for Name { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct Table { pub(crate) rows: Vec>, pub(crate) name: String, pub(crate) columns: Vec, } -#[allow(dead_code)] -#[derive(Debug, Clone)] + +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct Column { pub(crate) name: String, pub(crate) column_type: ColumnType, @@ -25,7 +27,7 @@ pub(crate) struct Column { pub(crate) unique: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) enum ColumnType { Integer, Float, @@ -44,7 +46,7 @@ impl Display for ColumnType { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) enum Value { Null, Integer(i64), diff --git a/simulator/runner/cli.rs b/simulator/runner/cli.rs index b4a6d94f1..a36fbaaaf 100644 --- a/simulator/runner/cli.rs +++ b/simulator/runner/cli.rs @@ -51,6 +51,7 @@ impl SimulatorCLI { if self.maximum_size < 1 { return Err("maximum size must be at least 1".to_string()); } + // todo: fix an issue here where if minimum size is not defined, it prevents setting low maximum sizes. if self.minimum_size > self.maximum_size { return Err("Minimum size cannot be greater than maximum size".to_string()); } diff --git a/simulator/runner/env.rs b/simulator/runner/env.rs index 7edad025f..90e7b446c 100644 --- a/simulator/runner/env.rs +++ b/simulator/runner/env.rs @@ -8,6 +8,7 @@ use crate::model::table::Table; use crate::runner::io::SimulatorIO; +#[derive(Clone)] pub(crate) struct SimulatorEnv { pub(crate) opts: SimulatorOpts, pub(crate) tables: Vec, diff --git a/simulator/runner/execution.rs b/simulator/runner/execution.rs index 3ac44e894..71765a60a 100644 --- a/simulator/runner/execution.rs +++ b/simulator/runner/execution.rs @@ -55,13 +55,14 @@ impl ExecutionResult { } pub(crate) fn execute_plans( - env: &mut SimulatorEnv, + env: Arc>, plans: &mut [InteractionPlan], states: &mut [InteractionPlanState], last_execution: Arc>, ) -> ExecutionResult { let mut history = ExecutionHistory::new(); let now = std::time::Instant::now(); + let mut env = env.lock().unwrap(); for _tick in 0..env.opts.ticks { // Pick the connection to interact with let connection_index = pick_index(env.connections.len(), &mut env.rng); @@ -77,7 +78,7 @@ pub(crate) fn execute_plans( last_execution.interaction_index = state.interaction_pointer; last_execution.secondary_index = state.secondary_pointer; // Execute the interaction for the selected connection - match execute_plan(env, connection_index, plans, states) { + match execute_plan(&mut env, connection_index, plans, states) { Ok(_) => {} Err(err) => { return ExecutionResult::new(history, Some(err));