mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 08:34:19 +01:00
read Profile file from path or use predefined profiles
This commit is contained in:
74
Cargo.lock
generated
74
Cargo.lock
generated
@@ -2000,6 +2000,17 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "julian_day_converter"
|
||||
version = "0.4.5"
|
||||
@@ -2182,6 +2193,7 @@ dependencies = [
|
||||
"garde",
|
||||
"hex",
|
||||
"itertools 0.14.0",
|
||||
"json5",
|
||||
"log",
|
||||
"notify",
|
||||
"rand 0.9.2",
|
||||
@@ -2193,6 +2205,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sql_generation",
|
||||
"strum",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"turso_core",
|
||||
@@ -2690,6 +2703,50 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -3530,6 +3587,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -4374,6 +4442,12 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
|
||||
1
simulator/.gitignore
vendored
Normal file
1
simulator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
configs/custom
|
||||
@@ -4,7 +4,7 @@
|
||||
name = "limbo_sim"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
edition = "2024"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "The Limbo deterministic simulator"
|
||||
@@ -40,3 +40,5 @@ sql_generation = { workspace = true }
|
||||
turso_parser = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
garde = { workspace = true, features = ["derive", "serde"] }
|
||||
json5 = { version = "0.4.1" }
|
||||
strum = { workspace = true }
|
||||
|
||||
@@ -26,7 +26,7 @@ impl GenerationContext for SimulatorEnv {
|
||||
}
|
||||
|
||||
fn opts(&self) -> &sql_generation::generation::Opts {
|
||||
&self.gen_opts
|
||||
&self.profile.query.gen_opts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ impl GenerationContext for &mut SimulatorEnv {
|
||||
}
|
||||
|
||||
fn opts(&self) -> &sql_generation::generation::Opts {
|
||||
&self.gen_opts
|
||||
&self.profile.query.gen_opts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,25 @@ use std::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sql_generation::{
|
||||
generation::{frequency, query::SelectFree, Arbitrary, ArbitraryFrom, GenerationContext},
|
||||
generation::{Arbitrary, ArbitraryFrom, GenerationContext, frequency, query::SelectFree},
|
||||
model::{
|
||||
query::{update::Update, Create, CreateIndex, Delete, Drop, Insert, Select},
|
||||
query::{Create, CreateIndex, Delete, Drop, Insert, Select, update::Update},
|
||||
table::SimValue,
|
||||
},
|
||||
};
|
||||
use turso_core::{Connection, Result, StepResult};
|
||||
|
||||
use crate::{
|
||||
SimulatorEnv,
|
||||
generation::Shadow,
|
||||
model::Query,
|
||||
runner::{
|
||||
env::{SimConnection, SimulationType, SimulatorTables},
|
||||
io::SimulatorIO,
|
||||
},
|
||||
SimulatorEnv,
|
||||
};
|
||||
|
||||
use super::property::{remaining, Property};
|
||||
use super::property::{Property, remaining};
|
||||
|
||||
pub(crate) type ResultSet = Result<Vec<Vec<SimValue>>>;
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_generation::{
|
||||
generation::{frequency, pick, pick_index, Arbitrary, ArbitraryFrom, GenerationContext},
|
||||
generation::{Arbitrary, ArbitraryFrom, GenerationContext, frequency, pick, pick_index},
|
||||
model::{
|
||||
query::{
|
||||
Create, Delete, Drop, Insert, Select,
|
||||
predicate::Predicate,
|
||||
select::{CompoundOperator, CompoundSelect, ResultColumn, SelectBody, SelectInner},
|
||||
transaction::{Begin, Commit, Rollback},
|
||||
update::Update,
|
||||
Create, Delete, Drop, Insert, Select,
|
||||
},
|
||||
table::SimValue,
|
||||
},
|
||||
};
|
||||
use turso_core::{types, LimboError};
|
||||
use turso_core::{LimboError, types};
|
||||
use turso_parser::ast::{self, Distinctness};
|
||||
|
||||
use crate::{
|
||||
@@ -305,7 +305,10 @@ impl Property {
|
||||
for row in rows {
|
||||
for (i, (col, val)) in update.set_values.iter().enumerate() {
|
||||
if &row[i] != val {
|
||||
return Ok(Err(format!("updated row {} has incorrect value for column {col}: expected {val}, got {}", i, row[i])));
|
||||
return Ok(Err(format!(
|
||||
"updated row {} has incorrect value for column {col}: expected {val}, got {}",
|
||||
i, row[i]
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +387,10 @@ impl Property {
|
||||
if found {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("row [{:?}] not found in table", row.iter().map(|v| v.to_string()).collect::<Vec<String>>())))
|
||||
Ok(Err(format!(
|
||||
"row [{:?}] not found in table",
|
||||
row.iter().map(|v| v.to_string()).collect::<Vec<String>>()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Err(err) => Err(LimboError::InternalError(err.to_string())),
|
||||
@@ -858,15 +864,22 @@ impl Property {
|
||||
match (select_result_set, select_tlp_result_set) {
|
||||
(Ok(select_rows), Ok(select_tlp_rows)) => {
|
||||
if select_rows.len() != select_tlp_rows.len() {
|
||||
return Ok(Err(format!("row count mismatch: select returned {} rows, select_tlp returned {} rows", select_rows.len(), select_tlp_rows.len())));
|
||||
return Ok(Err(format!(
|
||||
"row count mismatch: select returned {} rows, select_tlp returned {} rows",
|
||||
select_rows.len(),
|
||||
select_tlp_rows.len()
|
||||
)));
|
||||
}
|
||||
// Check if any row in select_rows is not in select_tlp_rows
|
||||
for row in select_rows.iter() {
|
||||
if !select_tlp_rows.iter().any(|r| r == row) {
|
||||
tracing::debug!(
|
||||
"select and select_tlp returned different rows, ({}) is in select but not in select_tlp",
|
||||
row.iter().map(|v| v.to_string()).collect::<Vec<String>>().join(", ")
|
||||
);
|
||||
"select and select_tlp returned different rows, ({}) is in select but not in select_tlp",
|
||||
row.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
return Ok(Err(format!(
|
||||
"row mismatch: row [{}] exists in select results but not in select_tlp results",
|
||||
print_row(row)
|
||||
@@ -877,9 +890,12 @@ impl Property {
|
||||
for row in select_tlp_rows.iter() {
|
||||
if !select_rows.iter().any(|r| r == row) {
|
||||
tracing::debug!(
|
||||
"select and select_tlp returned different rows, ({}) is in select_tlp but not in select",
|
||||
row.iter().map(|v| v.to_string()).collect::<Vec<String>>().join(", ")
|
||||
);
|
||||
"select and select_tlp returned different rows, ({}) is in select_tlp but not in select",
|
||||
row.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
return Ok(Err(format!(
|
||||
"row mismatch: row [{}] exists in select_tlp but not in select",
|
||||
@@ -939,7 +955,9 @@ impl Property {
|
||||
if union_count == count1 + count2 {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(format!("UNION ALL should preserve cardinality but it didn't: {count1} + {count2} != {union_count}")))
|
||||
Ok(Err(format!(
|
||||
"UNION ALL should preserve cardinality but it didn't: {count1} + {count2} != {union_count}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
(Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::model::Query;
|
||||
use rand::Rng;
|
||||
use sql_generation::{
|
||||
generation::{frequency, Arbitrary, ArbitraryFrom, GenerationContext},
|
||||
model::query::{update::Update, Create, Delete, Insert, Select},
|
||||
generation::{Arbitrary, ArbitraryFrom, GenerationContext, frequency},
|
||||
model::query::{Create, Delete, Insert, Select, update::Update},
|
||||
};
|
||||
|
||||
use super::property::Remaining;
|
||||
|
||||
@@ -8,19 +8,20 @@ use rand::prelude::*;
|
||||
use runner::bugbase::{Bug, BugBase, LoadedBug};
|
||||
use runner::cli::{SimulatorCLI, SimulatorCommand};
|
||||
use runner::env::SimulatorEnv;
|
||||
use runner::execution::{execute_plans, Execution, ExecutionHistory, ExecutionResult};
|
||||
use runner::execution::{Execution, ExecutionHistory, ExecutionResult, execute_plans};
|
||||
use runner::{differential, watch};
|
||||
use std::any::Any;
|
||||
use std::backtrace::Backtrace;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{IsTerminal, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::field::MakeExt;
|
||||
use tracing_subscriber::fmt::format;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use crate::profiles::Profile;
|
||||
use crate::runner::doublecheck;
|
||||
use crate::runner::env::{Paths, SimulationPhase, SimulationType};
|
||||
|
||||
@@ -35,6 +36,10 @@ fn main() -> anyhow::Result<()> {
|
||||
let mut cli_opts = SimulatorCLI::parse();
|
||||
cli_opts.validate()?;
|
||||
|
||||
let profile = Profile::parse_from_type(cli_opts.profile.clone())?;
|
||||
tracing::debug!(sim_profile = ?profile);
|
||||
dbg!(&profile);
|
||||
|
||||
match cli_opts.subcommand {
|
||||
Some(SimulatorCommand::List) => {
|
||||
let mut bugbase = BugBase::load()?;
|
||||
@@ -44,7 +49,7 @@ fn main() -> anyhow::Result<()> {
|
||||
banner();
|
||||
for i in 0..n {
|
||||
println!("iteration {i}");
|
||||
let result = testing_main(&cli_opts);
|
||||
let result = testing_main(&cli_opts, &profile);
|
||||
if result.is_err() && short_circuit {
|
||||
println!("short circuiting after {i} iterations");
|
||||
return result;
|
||||
@@ -91,7 +96,7 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
let results = bugs
|
||||
.into_iter()
|
||||
.map(|cli_opts| testing_main(&cli_opts))
|
||||
.map(|cli_opts| testing_main(&cli_opts, &profile))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (successes, failures): (Vec<_>, Vec<_>) =
|
||||
@@ -103,12 +108,12 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
None => {
|
||||
banner();
|
||||
testing_main(&cli_opts)
|
||||
testing_main(&cli_opts, &profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn testing_main(cli_opts: &SimulatorCLI) -> anyhow::Result<()> {
|
||||
fn testing_main(cli_opts: &SimulatorCLI, profile: &Profile) -> anyhow::Result<()> {
|
||||
let mut bugbase = if cli_opts.disable_bugbase {
|
||||
None
|
||||
} else {
|
||||
@@ -116,7 +121,7 @@ fn testing_main(cli_opts: &SimulatorCLI) -> anyhow::Result<()> {
|
||||
Some(BugBase::load()?)
|
||||
};
|
||||
|
||||
let (seed, mut env, plans) = setup_simulation(bugbase.as_mut(), cli_opts);
|
||||
let (seed, mut env, plans) = setup_simulation(bugbase.as_mut(), cli_opts, profile);
|
||||
|
||||
if cli_opts.watch {
|
||||
watch_mode(env).unwrap();
|
||||
@@ -471,6 +476,7 @@ impl SandboxedResult {
|
||||
fn setup_simulation(
|
||||
bugbase: Option<&mut BugBase>,
|
||||
cli_opts: &SimulatorCLI,
|
||||
profile: &Profile,
|
||||
) -> (u64, SimulatorEnv, Vec<InteractionPlan>) {
|
||||
if let Some(seed) = &cli_opts.load {
|
||||
let seed = seed.parse::<u64>().expect("seed should be a number");
|
||||
@@ -484,7 +490,13 @@ fn setup_simulation(
|
||||
if !paths.base.exists() {
|
||||
std::fs::create_dir_all(&paths.base).unwrap();
|
||||
}
|
||||
let env = SimulatorEnv::new(bug.seed(), cli_opts, paths, SimulationType::Default);
|
||||
let env = SimulatorEnv::new(
|
||||
bug.seed(),
|
||||
cli_opts,
|
||||
paths,
|
||||
SimulationType::Default,
|
||||
profile,
|
||||
);
|
||||
|
||||
let plan = match bug {
|
||||
Bug::Loaded(LoadedBug { plan, .. }) => plan.clone(),
|
||||
@@ -528,7 +540,7 @@ fn setup_simulation(
|
||||
Paths::new(&dir)
|
||||
};
|
||||
|
||||
let mut env = SimulatorEnv::new(seed, cli_opts, paths, SimulationType::Default);
|
||||
let mut env = SimulatorEnv::new(seed, cli_opts, paths, SimulationType::Default, profile);
|
||||
|
||||
tracing::info!("Generating database interaction plan...");
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_generation::model::{
|
||||
query::{
|
||||
Create, CreateIndex, Delete, Drop, EmptyContext, Insert, Select,
|
||||
select::{CompoundOperator, FromClause, ResultColumn, SelectInner},
|
||||
transaction::{Begin, Commit, Rollback},
|
||||
update::Update,
|
||||
Create, CreateIndex, Delete, Drop, EmptyContext, Insert, Select,
|
||||
},
|
||||
table::{JoinTable, JoinType, SimValue, Table, TableContext},
|
||||
};
|
||||
use turso_parser::ast::{fmt::ToTokens, Distinctness};
|
||||
use turso_parser::ast::{Distinctness, fmt::ToTokens};
|
||||
|
||||
use crate::{generation::Shadow, runner::env::SimulatorTables};
|
||||
|
||||
@@ -282,10 +282,11 @@ impl Shadow for SelectInner {
|
||||
|
||||
Ok(join_table)
|
||||
} else {
|
||||
assert!(self
|
||||
.columns
|
||||
.iter()
|
||||
.all(|col| matches!(col, ResultColumn::Expr(_))));
|
||||
assert!(
|
||||
self.columns
|
||||
.iter()
|
||||
.all(|col| matches!(col, ResultColumn::Expr(_)))
|
||||
);
|
||||
|
||||
// If `WHERE` is false, just return an empty table
|
||||
if !self.where_clause.test(&[], &Table::anonymous(vec![])) {
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
use std::fmt::Display;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use garde::Validate;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sql_generation::generation::Opts;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::profiles::{io::IOProfile, query::QueryProfile};
|
||||
|
||||
@@ -10,6 +18,7 @@ mod io;
|
||||
mod query;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct Profile {
|
||||
#[garde(skip)]
|
||||
/// Experimental MVCC feature
|
||||
@@ -30,6 +39,83 @@ impl Default for Profile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn write_heavy() -> Self {
|
||||
Profile {
|
||||
query: QueryProfile {
|
||||
gen_opts: Opts {
|
||||
// TODO: in the future tweak blob size for bigger inserts
|
||||
// TODO: increase number of rows increased as well
|
||||
..Default::default()
|
||||
},
|
||||
delete: false,
|
||||
update: false,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_from_type(profile_type: ProfileType) -> anyhow::Result<Self> {
|
||||
let profile = match profile_type {
|
||||
ProfileType::Default => Profile::default(),
|
||||
ProfileType::WriteHeavy => Self::write_heavy(),
|
||||
ProfileType::Custom(path) => {
|
||||
Self::parse(path).with_context(|| "failed to parse JSON profile")?
|
||||
}
|
||||
};
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
// TODO: in the future handle extension and composability of profiles here
|
||||
pub fn parse(path: impl AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let contents = fs::read_to_string(path)?;
|
||||
// use json5 so we can support comments and trailing commas
|
||||
let profile = json5::from_str(&contents)?;
|
||||
Ok(profile)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
EnumString,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
strum::Display,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
|
||||
pub enum ProfileType {
|
||||
#[default]
|
||||
Default,
|
||||
WriteHeavy,
|
||||
#[strum(disabled)]
|
||||
Custom(PathBuf),
|
||||
}
|
||||
|
||||
impl ProfileType {
|
||||
pub fn parse(s: &str) -> anyhow::Result<Self> {
|
||||
if let Ok(prof) = ProfileType::from_str(s) {
|
||||
Ok(prof)
|
||||
} else if let path = PathBuf::from(s)
|
||||
&& path.exists()
|
||||
{
|
||||
Ok(ProfileType::Custom(path))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"failed identifying predifined profile or custom profile path"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum value of field is dependent on another field in the struct
|
||||
fn min_dependent<T: PartialOrd + Display>(min: &T) -> impl FnOnce(&T, &()) -> garde::Result + '_ {
|
||||
move |value, _| {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{Context, anyhow};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
use clap::{command, Parser};
|
||||
use clap::{
|
||||
Arg, Command, Error, Parser,
|
||||
builder::{PossibleValue, TypedValueParser, ValueParserFactory},
|
||||
command,
|
||||
error::{ContextKind, ContextValue, ErrorKind},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::profiles::ProfileType;
|
||||
|
||||
#[derive(Parser, Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
#[command(name = "limbo-simulator")]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
@@ -135,6 +142,9 @@ pub struct SimulatorCLI {
|
||||
default_value_t = false
|
||||
)]
|
||||
pub keep_files: bool,
|
||||
#[clap(long, default_value_t = ProfileType::Default)]
|
||||
/// Profile selector for Simulation run
|
||||
pub profile: ProfileType,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
@@ -206,3 +216,70 @@ impl SimulatorCLI {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProfileTypeParser;
|
||||
|
||||
impl TypedValueParser for ProfileTypeParser {
|
||||
type Value = ProfileType;
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
cmd: &Command,
|
||||
arg: Option<&Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, Error> {
|
||||
let s = value
|
||||
.to_str()
|
||||
.ok_or_else(|| Error::new(ErrorKind::InvalidUtf8).with_cmd(cmd))?;
|
||||
|
||||
ProfileType::parse(s).map_err(|_| {
|
||||
let mut err = Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
err.insert(
|
||||
ContextKind::InvalidArg,
|
||||
ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
err.insert(
|
||||
ContextKind::InvalidValue,
|
||||
ContextValue::String(s.to_string()),
|
||||
);
|
||||
err.insert(
|
||||
ContextKind::ValidValue,
|
||||
ContextValue::Strings(
|
||||
self.possible_values()
|
||||
.unwrap()
|
||||
.map(|s| s.get_name().to_string())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
use strum::VariantNames;
|
||||
Some(Box::new(
|
||||
Self::Value::VARIANTS
|
||||
.into_iter()
|
||||
.map(|variant| {
|
||||
// Custom variant should be listed as a Custom path
|
||||
if variant.eq_ignore_ascii_case("custom") {
|
||||
"CUSTOM_PATH"
|
||||
} else {
|
||||
variant
|
||||
}
|
||||
})
|
||||
.map(|s| PossibleValue::new(s)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueParserFactory for ProfileType {
|
||||
type Parser = ProfileTypeParser;
|
||||
|
||||
fn value_parser() -> Self::Parser {
|
||||
ProfileTypeParser
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,18 @@ use sql_generation::{generation::pick_index, model::table::SimValue};
|
||||
use turso_core::Value;
|
||||
|
||||
use crate::{
|
||||
InteractionPlan,
|
||||
generation::{
|
||||
plan::{Interaction, InteractionPlanState, ResultSet},
|
||||
Shadow as _,
|
||||
plan::{Interaction, InteractionPlanState, ResultSet},
|
||||
},
|
||||
model::Query,
|
||||
runner::execution::ExecutionContinuation,
|
||||
InteractionPlan,
|
||||
};
|
||||
|
||||
use super::{
|
||||
env::{SimConnection, SimulatorEnv},
|
||||
execution::{execute_interaction, Execution, ExecutionHistory, ExecutionResult},
|
||||
execution::{Execution, ExecutionHistory, ExecutionResult, execute_interaction},
|
||||
};
|
||||
|
||||
pub(crate) fn run_simulation(
|
||||
@@ -249,7 +249,9 @@ fn execute_plan(
|
||||
match (limbo_values, rusqlite_values) {
|
||||
(Ok(limbo_values), Ok(rusqlite_values)) => {
|
||||
if limbo_values != rusqlite_values {
|
||||
tracing::error!("returned values from limbo and rusqlite results do not match");
|
||||
tracing::error!(
|
||||
"returned values from limbo and rusqlite results do not match"
|
||||
);
|
||||
let diff = limbo_values
|
||||
.iter()
|
||||
.zip(rusqlite_values.iter())
|
||||
@@ -303,7 +305,9 @@ fn execute_plan(
|
||||
tracing::warn!("rusqlite error {}", rusqlite_err);
|
||||
}
|
||||
(Ok(limbo_result), Err(rusqlite_err)) => {
|
||||
tracing::error!("limbo and rusqlite results do not match, limbo returned values but rusqlite failed");
|
||||
tracing::error!(
|
||||
"limbo and rusqlite results do not match, limbo returned values but rusqlite failed"
|
||||
);
|
||||
tracing::error!("limbo values {:?}", limbo_result);
|
||||
tracing::error!("rusqlite error {}", rusqlite_err);
|
||||
return Err(turso_core::LimboError::InternalError(
|
||||
@@ -311,7 +315,9 @@ fn execute_plan(
|
||||
));
|
||||
}
|
||||
(Err(limbo_err), Ok(_)) => {
|
||||
tracing::error!("limbo and rusqlite results do not match, limbo failed but rusqlite returned values");
|
||||
tracing::error!(
|
||||
"limbo and rusqlite results do not match, limbo failed but rusqlite returned values"
|
||||
);
|
||||
tracing::error!("limbo error {}", limbo_err);
|
||||
return Err(turso_core::LimboError::InternalError(
|
||||
"limbo and rusqlite results do not match".into(),
|
||||
|
||||
@@ -6,13 +6,13 @@ use std::{
|
||||
use sql_generation::generation::pick_index;
|
||||
|
||||
use crate::{
|
||||
generation::plan::InteractionPlanState, runner::execution::ExecutionContinuation,
|
||||
InteractionPlan,
|
||||
InteractionPlan, generation::plan::InteractionPlanState,
|
||||
runner::execution::ExecutionContinuation,
|
||||
};
|
||||
|
||||
use super::{
|
||||
env::{SimConnection, SimulatorEnv},
|
||||
execution::{execute_interaction, Execution, ExecutionHistory, ExecutionResult},
|
||||
execution::{Execution, ExecutionHistory, ExecutionResult, execute_interaction},
|
||||
};
|
||||
|
||||
pub(crate) fn run_simulation(
|
||||
@@ -207,7 +207,9 @@ fn execute_plan(
|
||||
match (limbo_values, doublecheck_values) {
|
||||
(Ok(limbo_values), Ok(doublecheck_values)) => {
|
||||
if limbo_values != doublecheck_values {
|
||||
tracing::error!("returned values from limbo and doublecheck results do not match");
|
||||
tracing::error!(
|
||||
"returned values from limbo and doublecheck results do not match"
|
||||
);
|
||||
tracing::debug!("limbo values {:?}", limbo_values);
|
||||
tracing::debug!(
|
||||
"doublecheck values {:?}",
|
||||
@@ -231,7 +233,9 @@ fn execute_plan(
|
||||
}
|
||||
}
|
||||
(Ok(limbo_result), Err(doublecheck_err)) => {
|
||||
tracing::error!("limbo and doublecheck results do not match, limbo returned values but doublecheck failed");
|
||||
tracing::error!(
|
||||
"limbo and doublecheck results do not match, limbo returned values but doublecheck failed"
|
||||
);
|
||||
tracing::error!("limbo values {:?}", limbo_result);
|
||||
tracing::error!("doublecheck error {}", doublecheck_err);
|
||||
return Err(turso_core::LimboError::InternalError(
|
||||
@@ -239,7 +243,9 @@ fn execute_plan(
|
||||
));
|
||||
}
|
||||
(Err(limbo_err), Ok(_)) => {
|
||||
tracing::error!("limbo and doublecheck results do not match, limbo failed but doublecheck returned values");
|
||||
tracing::error!(
|
||||
"limbo and doublecheck results do not match, limbo failed but doublecheck returned values"
|
||||
);
|
||||
tracing::error!("limbo error {}", limbo_err);
|
||||
return Err(turso_core::LimboError::InternalError(
|
||||
"limbo and doublecheck results do not match".into(),
|
||||
|
||||
@@ -7,10 +7,10 @@ use std::sync::Arc;
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use sql_generation::generation::Opts;
|
||||
use sql_generation::model::table::Table;
|
||||
use turso_core::Database;
|
||||
|
||||
use crate::profiles::Profile;
|
||||
use crate::runner::io::SimulatorIO;
|
||||
|
||||
use super::cli::SimulatorCLI;
|
||||
@@ -60,7 +60,7 @@ impl Deref for SimulatorTables {
|
||||
|
||||
pub(crate) struct SimulatorEnv {
|
||||
pub(crate) opts: SimulatorOpts,
|
||||
pub gen_opts: Opts,
|
||||
pub profile: Profile,
|
||||
pub(crate) connections: Vec<SimConnection>,
|
||||
pub(crate) io: Arc<SimulatorIO>,
|
||||
pub(crate) db: Option<Arc<Database>>,
|
||||
@@ -87,7 +87,7 @@ impl SimulatorEnv {
|
||||
paths: self.paths.clone(),
|
||||
type_: self.type_,
|
||||
phase: self.phase,
|
||||
gen_opts: self.gen_opts.clone(),
|
||||
profile: self.profile.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ impl SimulatorEnv {
|
||||
cli_opts: &SimulatorCLI,
|
||||
paths: Paths,
|
||||
simulation_type: SimulationType,
|
||||
profile: &Profile,
|
||||
) -> Self {
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||
|
||||
@@ -294,11 +295,6 @@ impl SimulatorEnv {
|
||||
.map(|_| SimConnection::Disconnected)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let gen_opts = Opts {
|
||||
indexes: opts.experimental_indexes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
SimulatorEnv {
|
||||
opts,
|
||||
tables: SimulatorTables::new(),
|
||||
@@ -309,7 +305,7 @@ impl SimulatorEnv {
|
||||
db: Some(db),
|
||||
type_: simulation_type,
|
||||
phase: SimulationPhase::Test,
|
||||
gen_opts,
|
||||
profile: profile.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ use tracing::instrument;
|
||||
use turso_core::{Connection, LimboError, Result, StepResult};
|
||||
|
||||
use crate::generation::{
|
||||
plan::{Interaction, InteractionPlan, InteractionPlanState, ResultSet},
|
||||
Shadow as _,
|
||||
plan::{Interaction, InteractionPlan, InteractionPlanState, ResultSet},
|
||||
};
|
||||
|
||||
use super::env::{SimConnection, SimulatorEnv};
|
||||
|
||||
@@ -6,10 +6,10 @@ use std::{
|
||||
|
||||
use rand::Rng as _;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use tracing::{instrument, Level};
|
||||
use tracing::{Level, instrument};
|
||||
use turso_core::{File, Result};
|
||||
|
||||
use crate::runner::{clock::SimulatorClock, FAULT_ERROR_MSG};
|
||||
use crate::runner::{FAULT_ERROR_MSG, clock::SimulatorClock};
|
||||
pub(crate) struct SimulatorFile {
|
||||
pub path: String,
|
||||
pub(crate) inner: Arc<dyn File>,
|
||||
@@ -201,7 +201,9 @@ impl File for SimulatorFile {
|
||||
self.nr_sync_calls.set(self.nr_sync_calls.get() + 1);
|
||||
if self.fault.get() {
|
||||
// TODO: Enable this when https://github.com/tursodatabase/turso/issues/2091 is fixed.
|
||||
tracing::debug!("ignoring sync fault because it causes false positives with current simulator design");
|
||||
tracing::debug!(
|
||||
"ignoring sync fault because it causes false positives with current simulator design"
|
||||
);
|
||||
self.fault.set(false);
|
||||
}
|
||||
let c = if let Some(latency) = self.generate_latency_duration() {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use turso_core::{Clock, Instant, OpenFlags, PlatformIO, Result, IO};
|
||||
use turso_core::{Clock, IO, Instant, OpenFlags, PlatformIO, Result};
|
||||
|
||||
use crate::runner::{clock::SimulatorClock, file::SimulatorFile};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
env::{SimConnection, SimulatorEnv},
|
||||
execution::{execute_interaction, Execution, ExecutionHistory, ExecutionResult},
|
||||
execution::{Execution, ExecutionHistory, ExecutionResult, execute_interaction},
|
||||
};
|
||||
|
||||
pub(crate) fn run_simulation(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
SandboxedResult, SimulatorEnv,
|
||||
generation::{
|
||||
plan::{Interaction, InteractionPlan, Interactions},
|
||||
property::Property,
|
||||
@@ -6,7 +7,6 @@ use crate::{
|
||||
model::Query,
|
||||
run_simulation,
|
||||
runner::execution::Execution,
|
||||
SandboxedResult, SimulatorEnv,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user