compare rows order insensitively for differential testing

This commit is contained in:
pedrocarlo
2025-09-22 15:00:50 -03:00
parent 2cd7c68c35
commit 6569d27bdc
3 changed files with 58 additions and 18 deletions

View File

@@ -1528,6 +1528,7 @@ impl ArbitraryFrom<(&SimulatorEnv, &InteractionStats)> for Property {
env.profile.experimental_mvcc,
);
#[allow(clippy::type_complexity)]
let choices: Vec<(_, Box<dyn Fn(&mut R) -> Property>)> = vec![
(
if !env.opts.disable_insert_values_select {

View File

@@ -1,5 +1,9 @@
use std::sync::{Arc, Mutex};
use std::{
collections::{BTreeMap, btree_map::Entry},
sync::{Arc, Mutex},
};
use itertools::Itertools;
use similar_asserts::SimpleDiff;
use sql_generation::model::table::SimValue;
@@ -140,13 +144,13 @@ fn compare_results(
) -> turso_core::Result<()> {
match (turso_res, rusqlite_res) {
(Ok(..), Ok(..)) => {
let limbo_values = turso_conn_state.stack.last();
let turso_values = turso_conn_state.stack.last();
let rusqlite_values = rusqlite_conn_state.stack.last();
match (limbo_values, rusqlite_values) {
(Some(limbo_values), Some(rusqlite_values)) => {
match (limbo_values, rusqlite_values) {
(Ok(limbo_values), Ok(rusqlite_values)) => {
if limbo_values != rusqlite_values {
match (turso_values, rusqlite_values) {
(Some(turso_values), Some(rusqlite_values)) => {
match (turso_values, rusqlite_values) {
(Ok(turso_values), Ok(rusqlite_values)) => {
if !compare_order_insensitive(turso_values, rusqlite_values) {
tracing::error!(
"returned values from limbo and rusqlite results do not match"
);
@@ -165,17 +169,19 @@ fn compare_results(
}
}
let limbo_string_values: Vec<Vec<_>> = limbo_values
let turso_string_values: Vec<Vec<_>> = turso_values
.iter()
.map(|rows| rows.iter().map(|row| val_to_string(row)).collect())
.map(|rows| rows.iter().map(val_to_string).collect())
.sorted()
.collect();
let rusqlite_string_values: Vec<Vec<_>> = rusqlite_values
.iter()
.map(|rows| rows.iter().map(|row| val_to_string(row)).collect())
.map(|rows| rows.iter().map(val_to_string).collect())
.sorted()
.collect();
let turso_string = format!("{limbo_string_values:#?}");
let turso_string = format!("{turso_string_values:#?}");
let rusqlite_string = format!("{rusqlite_string_values:#?}");
let diff = SimpleDiff::from_str(
&turso_string,
@@ -191,26 +197,26 @@ fn compare_results(
));
}
}
(Err(limbo_err), Err(rusqlite_err)) => {
(Err(turso_err), Err(rusqlite_err)) => {
tracing::warn!("limbo and rusqlite both fail, requires manual check");
tracing::warn!("limbo error {}", limbo_err);
tracing::warn!("limbo error {}", turso_err);
tracing::warn!("rusqlite error {}", rusqlite_err);
}
(Ok(limbo_result), Err(rusqlite_err)) => {
(Ok(turso_err), Err(rusqlite_err)) => {
tracing::error!(
"limbo and rusqlite results do not match, limbo returned values but rusqlite failed"
);
tracing::error!("limbo values {:?}", limbo_result);
tracing::error!("limbo values {:?}", turso_err);
tracing::error!("rusqlite error {}", rusqlite_err);
return Err(turso_core::LimboError::InternalError(
"limbo and rusqlite results do not match".into(),
));
}
(Err(limbo_err), Ok(_)) => {
(Err(turso_err), Ok(_)) => {
tracing::error!(
"limbo and rusqlite results do not match, limbo failed but rusqlite returned values"
);
tracing::error!("limbo error {}", limbo_err);
tracing::error!("limbo error {}", turso_err);
return Err(turso_core::LimboError::InternalError(
"limbo and rusqlite results do not match".into(),
));
@@ -249,3 +255,34 @@ fn compare_results(
}
Ok(())
}
fn count_rows(values: &[Vec<SimValue>]) -> BTreeMap<&Vec<SimValue>, i32> {
let mut counter = BTreeMap::new();
for row in values.iter() {
match counter.entry(row) {
Entry::Vacant(entry) => {
entry.insert(1);
}
Entry::Occupied(mut entry) => {
let counter = entry.get_mut();
*counter += 1;
}
}
}
counter
}
fn compare_order_insensitive(
turso_values: &[Vec<SimValue>],
rusqlite_values: &[Vec<SimValue>],
) -> bool {
if turso_values.len() != rusqlite_values.len() {
return false;
}
let turso_counter = count_rows(turso_values);
let rusqlite_counter = count_rows(rusqlite_values);
turso_counter == rusqlite_counter
}

View File

@@ -269,7 +269,7 @@ impl SimulatorEnv {
) -> Self {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let opts = SimulatorOpts {
let mut opts = SimulatorOpts {
seed,
ticks: rng.random_range(cli_opts.minimum_tests..=cli_opts.maximum_tests),
max_tables: rng.random_range(0..128),
@@ -323,6 +323,8 @@ impl SimulatorEnv {
if cli_opts.differential {
// Disable faults when running against sqlite as we cannot control faults on it
profile.io.enable = false;
// Disable limits due to differences in return order from turso and rusqlite
opts.disable_select_limit = true;
}
profile.validate().unwrap();