mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-02 06:44:23 +01:00
Merge 'Simulator: Better Shrinking' from Pedro Muniz
This PR attempts to get the specific query that failed in the simulator and get the correct tables that were used in the Query. Also, implements a fix where after clearing miscellaneous queries, we did not check again if the the interaction still referenced any of the tables that were involved in the failure. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1712
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Display, path::Path, rc::Rc, vec};
|
||||
use std::{collections::HashSet, fmt::Display, path::Path, rc::Rc, vec};
|
||||
|
||||
use limbo_core::{Connection, Result, StepResult, IO};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -97,7 +97,7 @@ pub(crate) struct InteractionPlanState {
|
||||
pub(crate) secondary_pointer: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) enum Interactions {
|
||||
Property(Property),
|
||||
Query(Query),
|
||||
@@ -123,13 +123,13 @@ impl Interactions {
|
||||
}
|
||||
|
||||
impl Interactions {
|
||||
pub(crate) fn dependencies(&self) -> Vec<String> {
|
||||
pub(crate) fn dependencies(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Interactions::Property(property) => {
|
||||
property
|
||||
.interactions()
|
||||
.iter()
|
||||
.fold(vec![], |mut acc, i| match i {
|
||||
.fold(HashSet::new(), |mut acc, i| match i {
|
||||
Interaction::Query(q) => {
|
||||
acc.extend(q.dependencies());
|
||||
acc
|
||||
@@ -138,7 +138,7 @@ impl Interactions {
|
||||
})
|
||||
}
|
||||
Interactions::Query(query) => query.dependencies(),
|
||||
Interactions::Fault(_) => vec![],
|
||||
Interactions::Fault(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use super::{
|
||||
|
||||
/// Properties are representations of executable specifications
|
||||
/// about the database behavior.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::fmt::Display;
|
||||
use std::{collections::HashSet, fmt::Display};
|
||||
|
||||
pub(crate) use create::Create;
|
||||
pub(crate) use create_index::CreateIndex;
|
||||
@@ -32,16 +32,18 @@ pub(crate) enum Query {
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub(crate) fn dependencies(&self) -> Vec<String> {
|
||||
pub(crate) fn dependencies(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Query::Create(_) => vec![],
|
||||
Query::Create(_) => HashSet::new(),
|
||||
Query::Select(Select { table, .. })
|
||||
| Query::Insert(Insert::Select { table, .. })
|
||||
| Query::Insert(Insert::Values { table, .. })
|
||||
| Query::Delete(Delete { table, .. })
|
||||
| Query::Update(Update { table, .. })
|
||||
| Query::Drop(Drop { table, .. }) => vec![table.clone()],
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => vec![table_name.clone()],
|
||||
| Query::Drop(Drop { table, .. }) => HashSet::from_iter([table.clone()]),
|
||||
Query::CreateIndex(CreateIndex { table_name, .. }) => {
|
||||
HashSet::from_iter([table_name.clone()])
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn uses(&self) -> Vec<String> {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
},
|
||||
model::query::Query,
|
||||
runner::execution::Execution,
|
||||
Interaction,
|
||||
};
|
||||
|
||||
impl InteractionPlan {
|
||||
@@ -13,36 +14,83 @@ impl InteractionPlan {
|
||||
// todo: this is a very naive implementation, next steps are;
|
||||
// - Shrink to multiple values by removing random interactions
|
||||
// - Shrink properties by removing their extensions, or shrinking their values
|
||||
|
||||
let mut plan = self.clone();
|
||||
let failing_property = &self.plan[failing_execution.interaction_index];
|
||||
let depending_tables = failing_property.dependencies();
|
||||
let mut depending_tables = failing_property.dependencies();
|
||||
|
||||
let interactions = failing_property.interactions();
|
||||
|
||||
{
|
||||
let mut idx = failing_execution.secondary_index;
|
||||
loop {
|
||||
match &interactions[idx] {
|
||||
Interaction::Query(query) => {
|
||||
depending_tables = query.dependencies();
|
||||
break;
|
||||
}
|
||||
// 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();
|
||||
|
||||
// Remove all properties after the failing one
|
||||
plan.plan.truncate(failing_execution.interaction_index + 1);
|
||||
|
||||
let mut idx = 0;
|
||||
// Remove all properties that do not use the failing tables
|
||||
plan.plan
|
||||
.retain(|p| p.uses().iter().any(|t| depending_tables.contains(t)));
|
||||
|
||||
// Remove the extensional parts of the properties
|
||||
for interaction in plan.plan.iter_mut() {
|
||||
if let Interactions::Property(p) = interaction {
|
||||
match p {
|
||||
Property::InsertValuesSelect { queries, .. }
|
||||
| Property::DoubleCreateFailure { queries, .. }
|
||||
| Property::DeleteSelect { queries, .. }
|
||||
| Property::DropSelect { queries, .. } => {
|
||||
queries.clear();
|
||||
plan.plan.retain_mut(|interactions| {
|
||||
let retain = if idx == failing_execution.interaction_index {
|
||||
true
|
||||
} else {
|
||||
let mut has_table = interactions
|
||||
.uses()
|
||||
.iter()
|
||||
.any(|t| depending_tables.contains(t));
|
||||
if has_table {
|
||||
// Remove the extensional parts of the properties
|
||||
if let Interactions::Property(p) = interactions {
|
||||
match p {
|
||||
Property::InsertValuesSelect { queries, .. }
|
||||
| Property::DoubleCreateFailure { queries, .. }
|
||||
| Property::DeleteSelect { queries, .. }
|
||||
| Property::DropSelect { queries, .. } => {
|
||||
queries.clear();
|
||||
}
|
||||
Property::SelectLimit { .. }
|
||||
| Property::SelectSelectOptimizer { .. } => {}
|
||||
}
|
||||
}
|
||||
Property::SelectLimit { .. } | Property::SelectSelectOptimizer { .. } => {}
|
||||
// Check again after query clear if the interactions still uses the failing table
|
||||
has_table = interactions
|
||||
.uses()
|
||||
.iter()
|
||||
.any(|t| depending_tables.contains(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plan.plan
|
||||
.retain(|p| !matches!(p, Interactions::Query(Query::Select(_))));
|
||||
has_table
|
||||
&& !matches!(
|
||||
interactions,
|
||||
Interactions::Query(Query::Select(_))
|
||||
| Interactions::Property(Property::SelectLimit { .. })
|
||||
| Interactions::Property(Property::SelectSelectOptimizer { .. })
|
||||
)
|
||||
};
|
||||
idx += 1;
|
||||
retain
|
||||
});
|
||||
|
||||
let after = plan.plan.len();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user