mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-23 17:05:36 +01:00
adding better shrinking and cli opts
This commit is contained in:
@@ -324,7 +324,7 @@ fn run_simulator(
|
||||
|
||||
tracing::error!("simulation failed: '{}'", error);
|
||||
|
||||
if cli_opts.disable_shrinking {
|
||||
if cli_opts.disable_heuristic_shrinking && !cli_opts.brute_force_shrink {
|
||||
tracing::info!("shrinking is disabled, skipping shrinking");
|
||||
if let Some(bugbase) = bugbase.as_deref_mut() {
|
||||
bugbase
|
||||
@@ -337,8 +337,8 @@ fn run_simulator(
|
||||
|
||||
|
||||
tracing::info!("Starting to shrink");
|
||||
|
||||
let shrunk_plans = plans
|
||||
let (shrunk_plans, shrunk) = if !cli_opts.disable_heuristic_shrinking {
|
||||
let shrunk_plans = plans
|
||||
.iter()
|
||||
.map(|plan| {
|
||||
let shrunk = plan.shrink_interaction_plan(last_execution);
|
||||
@@ -346,26 +346,30 @@ fn run_simulator(
|
||||
shrunk
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// Write the shrunk plan to a file
|
||||
let mut f = std::fs::File::create(&paths.shrunk_plan).unwrap();
|
||||
tracing::trace!("writing shrunk plan to {}", paths.shrunk_plan.display());
|
||||
f.write_all(shrunk_plans[0].to_string().as_bytes()).unwrap();
|
||||
|
||||
let last_execution = Arc::new(Mutex::new(*last_execution));
|
||||
let env = SimulatorEnv::new(seed, cli_opts, &paths.shrunk_db);
|
||||
|
||||
// Write the shrunk plan to a file
|
||||
let mut f = std::fs::File::create(&paths.shrunk_plan).unwrap();
|
||||
tracing::trace!("writing shrunk plan to {}", paths.shrunk_plan.display());
|
||||
f.write_all(shrunk_plans[0].to_string().as_bytes()).unwrap();
|
||||
let env = Arc::new(Mutex::new(env));
|
||||
let shrunk = SandboxedResult::from(
|
||||
std::panic::catch_unwind(|| {
|
||||
run_simulation(
|
||||
env.clone(),
|
||||
&mut shrunk_plans.clone(),
|
||||
last_execution.clone(),
|
||||
)
|
||||
}),
|
||||
last_execution,
|
||||
);
|
||||
(shrunk_plans, shrunk)
|
||||
} else {
|
||||
(plans.to_vec(), result.clone())
|
||||
};
|
||||
|
||||
let last_execution = Arc::new(Mutex::new(*last_execution));
|
||||
let env = SimulatorEnv::new(seed, cli_opts, &paths.shrunk_db);
|
||||
|
||||
let env = Arc::new(Mutex::new(env));
|
||||
let shrunk = SandboxedResult::from(
|
||||
std::panic::catch_unwind(|| {
|
||||
run_simulation(
|
||||
env.clone(),
|
||||
&mut shrunk_plans.clone(),
|
||||
last_execution.clone(),
|
||||
)
|
||||
}),
|
||||
last_execution,
|
||||
);
|
||||
|
||||
match (&shrunk, &result) {
|
||||
(
|
||||
@@ -396,17 +400,28 @@ fn run_simulator(
|
||||
);
|
||||
Err(anyhow!("failed with error: '{}'", error))
|
||||
} else {
|
||||
let final_plans = if cli_opts.brute_force_shrink {
|
||||
let brute_shrunk_plans = shrunk_plans
|
||||
.iter()
|
||||
.map(|plan| plan.brute_shrink_interaction_plan(&shrunk))
|
||||
.collect::<Vec<_>>();
|
||||
tracing::info!("Brute force shrinking completed");
|
||||
brute_shrunk_plans
|
||||
} else {
|
||||
shrunk_plans
|
||||
};
|
||||
|
||||
tracing::info!(
|
||||
"shrinking succeeded, reduced the plan from {} to {}",
|
||||
plans[0].plan.len(),
|
||||
shrunk_plans[0].plan.len()
|
||||
final_plans[0].plan.len()
|
||||
);
|
||||
// Save the shrunk database
|
||||
if let Some(bugbase) = bugbase.as_deref_mut() {
|
||||
bugbase.make_shrunk(
|
||||
seed,
|
||||
cli_opts,
|
||||
shrunk_plans[0].clone(),
|
||||
final_plans[0].clone(),
|
||||
Some(e1.clone()),
|
||||
)?;
|
||||
}
|
||||
@@ -557,6 +572,7 @@ fn differential_testing(
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
enum SandboxedResult {
|
||||
Panicked {
|
||||
error: String,
|
||||
|
||||
@@ -44,12 +44,14 @@ pub struct SimulatorCLI {
|
||||
pub watch: bool,
|
||||
#[clap(long, help = "run differential testing between sqlite and Limbo")]
|
||||
pub differential: bool,
|
||||
#[clap(long, help = "run brute force shrink (warning: takes a long time)")]
|
||||
pub brute_force_shrink: bool,
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Option<SimulatorCommand>,
|
||||
#[clap(long, help = "disable BugBase", default_value_t = false)]
|
||||
pub disable_bugbase: bool,
|
||||
#[clap(long, help = "disable shrinking", default_value_t = false)]
|
||||
pub disable_shrinking: bool,
|
||||
#[clap(long, help = "disable heuristic shrinking", default_value_t = false)]
|
||||
pub disable_heuristic_shrinking: bool,
|
||||
#[clap(long, help = "disable UPDATE Statement", default_value_t = false)]
|
||||
pub disable_update: bool,
|
||||
#[clap(long, help = "disable DELETE Statement", default_value_t = false)]
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Execution {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ExecutionHistory {
|
||||
pub(crate) history: Vec<Execution>,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use crate::model::query::{Query};
|
||||
use crate::{
|
||||
generation::{
|
||||
plan::{InteractionPlan, Interactions},
|
||||
plan::{Interaction, InteractionPlan, Interactions},
|
||||
property::Property,
|
||||
},
|
||||
model::query::Query,
|
||||
runner::execution::Execution,
|
||||
Interaction,
|
||||
run_simulation,
|
||||
runner::{cli::SimulatorCLI, execution::Execution},
|
||||
SandboxedResult, SimulatorEnv,
|
||||
};
|
||||
use clap::Parser;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
impl InteractionPlan {
|
||||
/// Create a smaller interaction plan by deleting a property
|
||||
@@ -105,4 +109,153 @@ impl InteractionPlan {
|
||||
|
||||
plan
|
||||
}
|
||||
/// Create a smaller interaction plan by deleting a property
|
||||
pub(crate) fn brute_shrink_interaction_plan(
|
||||
&self,
|
||||
result: &SandboxedResult,
|
||||
) -> InteractionPlan {
|
||||
let failing_execution = match result {
|
||||
SandboxedResult::Panicked { error: _, last_execution: e } => e,
|
||||
SandboxedResult::FoundBug { error: _, history: _, last_execution: e } => e,
|
||||
SandboxedResult::Correct => {
|
||||
unreachable!("shrink is never called on correct result")
|
||||
}
|
||||
};
|
||||
|
||||
let mut plan = self.clone();
|
||||
let failing_property = &self.plan[failing_execution.interaction_index];
|
||||
|
||||
let interactions = failing_property.interactions();
|
||||
|
||||
{
|
||||
let mut idx = failing_execution.secondary_index;
|
||||
loop {
|
||||
match &interactions[idx] {
|
||||
// Fault does not depend on
|
||||
Interaction::Fault(..) => break,
|
||||
_ => {
|
||||
// In principle we should never fail this checked_sub.
|
||||
// But if there is a bug in how we count the secondary index
|
||||
// we may panic if we do not use a checked_sub.
|
||||
if let Some(new_idx) = idx.checked_sub(1) {
|
||||
idx = new_idx;
|
||||
} else {
|
||||
tracing::warn!("failed to find error query");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let before = self.plan.len();
|
||||
|
||||
plan.plan.truncate(failing_execution.interaction_index + 1);
|
||||
|
||||
// phase 1: shrink extensions
|
||||
for interaction in &mut plan.plan {
|
||||
if let Interactions::Property(property) = interaction {
|
||||
match property {
|
||||
Property::InsertValuesSelect { queries, .. }
|
||||
| Property::DoubleCreateFailure { queries, .. }
|
||||
| Property::DeleteSelect { queries, .. }
|
||||
| Property::DropSelect { queries, .. } => {
|
||||
let mut temp_plan = InteractionPlan {
|
||||
plan: queries
|
||||
.iter()
|
||||
.map(|q| Interactions::Query(q.clone()))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
temp_plan =
|
||||
InteractionPlan::iterative_shrink(temp_plan, failing_execution, result);
|
||||
//temp_plan = Self::shrink_queries(temp_plan, failing_execution, result, env);
|
||||
|
||||
*queries = temp_plan
|
||||
.plan
|
||||
.into_iter()
|
||||
.filter_map(|i| match i {
|
||||
Interactions::Query(q) => Some(q),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
Property::WhereTrueFalseNull { .. } | Property::SelectLimit { .. } | Property::SelectSelectOptimizer { .. } | Property::FaultyQuery { .. } | Property::FsyncNoWait { .. }=> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// phase 2: shrink the entire plan
|
||||
plan = Self::iterative_shrink(plan, failing_execution, result);
|
||||
|
||||
let after = plan.plan.len();
|
||||
|
||||
tracing::info!(
|
||||
"Shrinking interaction plan from {} to {} properties",
|
||||
before,
|
||||
after
|
||||
);
|
||||
|
||||
plan
|
||||
}
|
||||
|
||||
/// shrink a plan by removing one interaction at a time (and its deps) while preserving the error
|
||||
fn iterative_shrink(
|
||||
mut plan: InteractionPlan,
|
||||
failing_execution: &Execution,
|
||||
old_result: &SandboxedResult,
|
||||
) -> InteractionPlan {
|
||||
for i in (0..plan.plan.len()).rev() {
|
||||
if i == failing_execution.interaction_index {
|
||||
continue;
|
||||
}
|
||||
let mut test_plan = plan.clone();
|
||||
|
||||
test_plan.plan.remove(i);
|
||||
|
||||
if Self::test_shrunk_plan(&test_plan, failing_execution, old_result) {
|
||||
plan = test_plan;
|
||||
}
|
||||
}
|
||||
plan
|
||||
}
|
||||
|
||||
fn test_shrunk_plan(
|
||||
test_plan: &InteractionPlan,
|
||||
failing_execution: &Execution,
|
||||
old_result: &SandboxedResult,
|
||||
) -> bool {
|
||||
let cli_opts = SimulatorCLI {
|
||||
seed: Some(0),
|
||||
..SimulatorCLI::parse()
|
||||
};
|
||||
let env = Arc::new(Mutex::new(SimulatorEnv::new(
|
||||
0,
|
||||
&cli_opts,
|
||||
Path::new("test.db"),
|
||||
)));
|
||||
let last_execution = Arc::new(Mutex::new(*failing_execution));
|
||||
|
||||
let result = SandboxedResult::from(
|
||||
std::panic::catch_unwind(|| {
|
||||
run_simulation(
|
||||
env.clone(),
|
||||
&mut [test_plan.clone()],
|
||||
last_execution.clone(),
|
||||
)
|
||||
}),
|
||||
last_execution.clone(),
|
||||
);
|
||||
match (old_result, &result) {
|
||||
(
|
||||
SandboxedResult::Panicked { error: e1, .. },
|
||||
SandboxedResult::Panicked { error: e2, .. },
|
||||
)
|
||||
| (
|
||||
SandboxedResult::FoundBug { error: e1, .. },
|
||||
SandboxedResult::FoundBug { error: e2, .. },
|
||||
) => e1 == e2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user