mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-02 14:54:23 +01:00
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
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<Vec<Vec<Value>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct InteractionPlan {
|
||||
pub(crate) plan: Vec<Interactions>,
|
||||
}
|
||||
@@ -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(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<R: rand::Rng>(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// 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<R: rand::Rng>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
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<Mutex<Execution>>,
|
||||
shrink: Option<&Execution>,
|
||||
) -> ExecutionResult {
|
||||
) -> (SimulatorEnv, Vec<InteractionPlan>) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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<Mutex<SimulatorEnv>>,
|
||||
plans: &mut [InteractionPlan],
|
||||
last_execution: Arc<Mutex<Execution>>,
|
||||
) -> ExecutionResult {
|
||||
let mut states = plans
|
||||
.iter()
|
||||
.map(|_| InteractionPlanState {
|
||||
@@ -414,27 +413,9 @@ fn run_simulation(
|
||||
secondary_pointer: 0,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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");
|
||||
|
||||
@@ -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<Predicate>), // p1 AND p2 AND p3... AND pn
|
||||
Or(Vec<Predicate>), // 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<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
||||
@@ -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<Vec<Value>>,
|
||||
pub(crate) name: String,
|
||||
pub(crate) columns: Vec<Column>,
|
||||
}
|
||||
#[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),
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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<Table>,
|
||||
|
||||
@@ -55,13 +55,14 @@ impl ExecutionResult {
|
||||
}
|
||||
|
||||
pub(crate) fn execute_plans(
|
||||
env: &mut SimulatorEnv,
|
||||
env: Arc<Mutex<SimulatorEnv>>,
|
||||
plans: &mut [InteractionPlan],
|
||||
states: &mut [InteractionPlanState],
|
||||
last_execution: Arc<Mutex<Execution>>,
|
||||
) -> 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));
|
||||
|
||||
Reference in New Issue
Block a user