mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-29 22:14:23 +01:00
Merge 'Simulator integrity_check' from Pedro Muniz
Adds a Sqlite integrity check after the simulator completes a run. Also changed error handling in the simulator to use `anyhow` to lazily add context to error methods instead of eagerly using `.or` and losing the original error. Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #1697
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -152,9 +152,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.97"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@@ -1955,6 +1955,7 @@ name = "limbo_sim"
|
||||
version = "0.0.22-pre.1"
|
||||
dependencies = [
|
||||
"anarchist-readable-name-generator-lib",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs 6.0.0",
|
||||
|
||||
@@ -56,6 +56,7 @@ strum = { version = "0.26", features = ["derive"] }
|
||||
strum_macros = "0.26"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0.98"
|
||||
|
||||
[profile.release]
|
||||
debug = "line-tables-only"
|
||||
|
||||
@@ -19,7 +19,7 @@ path = "main.rs"
|
||||
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
anyhow.workspace = true
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.5.31", features = ["derive"] }
|
||||
clap_complete = { version = "=4.5.47", features = ["unstable-dynamic"] }
|
||||
|
||||
@@ -34,4 +34,5 @@ dirs = "6.0.0"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
anyhow.workspace = true
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![allow(clippy::arc_with_non_send_sync, dead_code)]
|
||||
use anyhow::{anyhow, Context};
|
||||
use clap::Parser;
|
||||
use generation::plan::{Interaction, InteractionPlan, InteractionPlanState};
|
||||
use generation::ArbitraryFrom;
|
||||
@@ -49,14 +50,14 @@ impl Paths {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
fn main() -> anyhow::Result<()> {
|
||||
init_logger();
|
||||
let mut cli_opts = SimulatorCLI::parse();
|
||||
cli_opts.validate()?;
|
||||
|
||||
match cli_opts.subcommand {
|
||||
Some(SimulatorCommand::List) => {
|
||||
let mut bugbase = BugBase::load().map_err(|e| format!("{:?}", e))?;
|
||||
let mut bugbase = BugBase::load()?;
|
||||
bugbase.list_bugs()
|
||||
}
|
||||
Some(SimulatorCommand::Loop { n, short_circuit }) => {
|
||||
@@ -76,7 +77,7 @@ fn main() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
Some(SimulatorCommand::Test { filter }) => {
|
||||
let mut bugbase = BugBase::load().map_err(|e| format!("{:?}", e))?;
|
||||
let mut bugbase = BugBase::load()?;
|
||||
let bugs = bugbase.load_bugs()?;
|
||||
let mut bugs = bugs
|
||||
.into_iter()
|
||||
@@ -127,11 +128,11 @@ fn main() -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn testing_main(cli_opts: &SimulatorCLI) -> Result<(), String> {
|
||||
fn testing_main(cli_opts: &SimulatorCLI) -> anyhow::Result<()> {
|
||||
let mut bugbase = if cli_opts.disable_bugbase {
|
||||
None
|
||||
} else {
|
||||
Some(BugBase::load().map_err(|e| format!("{:?}", e))?)
|
||||
Some(BugBase::load()?)
|
||||
};
|
||||
|
||||
let last_execution = Arc::new(Mutex::new(Execution::new(0, 0, 0)));
|
||||
@@ -143,7 +144,7 @@ fn testing_main(cli_opts: &SimulatorCLI) -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let result = if cli_opts.differential {
|
||||
let mut result = if cli_opts.differential {
|
||||
differential_testing(
|
||||
seed,
|
||||
bugbase.as_mut(),
|
||||
@@ -164,6 +165,17 @@ fn testing_main(cli_opts: &SimulatorCLI) -> Result<(), String> {
|
||||
)
|
||||
};
|
||||
|
||||
if let Err(err) = integrity_check(&paths.db) {
|
||||
tracing::error!("Integrity Check failed:\n{}", err);
|
||||
if result.is_err() {
|
||||
result = result.with_context(|| anyhow!("Integrity Check failed:\n{}", err));
|
||||
} else {
|
||||
result = Err(err);
|
||||
}
|
||||
} else if let Some(bugbase) = bugbase.as_mut() {
|
||||
bugbase.mark_successful_run(seed, cli_opts).unwrap();
|
||||
};
|
||||
|
||||
// Print the seed, the locations of the database and the plan file at the end again for easily accessing them.
|
||||
println!("seed: {}", seed);
|
||||
println!("path: {}", paths.base.display());
|
||||
@@ -237,7 +249,7 @@ fn run_simulator(
|
||||
env: SimulatorEnv,
|
||||
plans: Vec<InteractionPlan>,
|
||||
last_execution: Arc<Mutex<Execution>>,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
tracing::error!("panic occurred");
|
||||
|
||||
@@ -278,9 +290,6 @@ fn run_simulator(
|
||||
SandboxedResult::Correct => {
|
||||
tracing::info!("simulation succeeded");
|
||||
println!("simulation succeeded");
|
||||
if let Some(bugbase) = bugbase {
|
||||
bugbase.mark_successful_run(seed, cli_opts).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
SandboxedResult::Panicked {
|
||||
@@ -357,7 +366,7 @@ fn run_simulator(
|
||||
.add_bug(seed, plans[0].clone(), Some(error.clone()), cli_opts)
|
||||
.unwrap();
|
||||
}
|
||||
Err(format!("failed with error: '{}'", error))
|
||||
Err(anyhow!("failed with error: '{}'", error))
|
||||
} else {
|
||||
tracing::info!(
|
||||
"shrinking succeeded, reduced the plan from {} to {}",
|
||||
@@ -375,7 +384,7 @@ fn run_simulator(
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Err(format!("failed with error: '{}'", e1))
|
||||
Err(anyhow!("failed with error: '{}'", e1))
|
||||
}
|
||||
}
|
||||
(_, SandboxedResult::Correct) => {
|
||||
@@ -388,7 +397,7 @@ fn run_simulator(
|
||||
.add_bug(seed, plans[0].clone(), Some(error.clone()), cli_opts)
|
||||
.unwrap();
|
||||
}
|
||||
Err(format!("failed with error: '{}'", error))
|
||||
Err(anyhow!("failed with error: '{}'", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,7 +413,7 @@ fn doublecheck(
|
||||
plans: &[InteractionPlan],
|
||||
last_execution: Arc<Mutex<Execution>>,
|
||||
result: SandboxedResult,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
let env = SimulatorEnv::new(seed, cli_opts, &paths.doublecheck_db);
|
||||
let env = Arc::new(Mutex::new(env));
|
||||
|
||||
@@ -456,9 +465,6 @@ fn doublecheck(
|
||||
Ok(_) => {
|
||||
tracing::info!("doublecheck succeeded");
|
||||
println!("doublecheck succeeded");
|
||||
if let Some(bugbase) = bugbase {
|
||||
bugbase.mark_successful_run(seed, cli_opts)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -468,7 +474,7 @@ fn doublecheck(
|
||||
.add_bug(seed, plans[0].clone(), Some(e.clone()), cli_opts)
|
||||
.unwrap();
|
||||
}
|
||||
Err(format!("doublecheck failed: '{}'", e))
|
||||
Err(anyhow!("doublecheck failed: '{}'", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,7 +486,7 @@ fn differential_testing(
|
||||
paths: &Paths,
|
||||
plans: Vec<InteractionPlan>,
|
||||
last_execution: Arc<Mutex<Execution>>,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
let env = Arc::new(Mutex::new(SimulatorEnv::new(seed, cli_opts, &paths.db)));
|
||||
let rusqlite_env = Arc::new(Mutex::new(SimulatorEnv::new(
|
||||
seed,
|
||||
@@ -506,9 +512,6 @@ fn differential_testing(
|
||||
SandboxedResult::Correct => {
|
||||
tracing::info!("simulation succeeded, output of Limbo conforms to SQLite");
|
||||
println!("simulation succeeded, output of Limbo conforms to SQLite");
|
||||
if let Some(bugbase) = bugbase {
|
||||
bugbase.mark_successful_run(seed, cli_opts).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
SandboxedResult::Panicked { error, .. } | SandboxedResult::FoundBug { error, .. } => {
|
||||
@@ -518,7 +521,7 @@ fn differential_testing(
|
||||
.add_bug(seed, plans[0].clone(), Some(error.clone()), cli_opts)
|
||||
.unwrap();
|
||||
}
|
||||
Err(format!("simulation failed: '{}'", error))
|
||||
Err(anyhow!("simulation failed: '{}'", error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -738,3 +741,25 @@ const BANNER: &str = r#"
|
||||
\____________________________/
|
||||
|
||||
"#;
|
||||
|
||||
fn integrity_check(db_path: &Path) -> anyhow::Result<()> {
|
||||
let conn = rusqlite::Connection::open(db_path)?;
|
||||
let mut stmt = conn.prepare("SELECT * FROM pragma_integrity_check;")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
let mut result: Vec<String> = Vec::new();
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
result.push(row.get(0)?);
|
||||
}
|
||||
if result.is_empty() {
|
||||
anyhow::bail!("integrity_check should return `ok` or a list of problems")
|
||||
}
|
||||
if !result[0].eq_ignore_ascii_case("ok") {
|
||||
// Build a list of problems
|
||||
result
|
||||
.iter_mut()
|
||||
.for_each(|row| *row = format!("- {}", row));
|
||||
anyhow::bail!(result.join("\n"))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -71,7 +72,7 @@ pub(crate) struct BugBase {
|
||||
|
||||
impl BugBase {
|
||||
/// Create a new bug base.
|
||||
fn new(path: PathBuf) -> Result<Self, String> {
|
||||
fn new(path: PathBuf) -> anyhow::Result<Self> {
|
||||
let mut bugs = HashMap::new();
|
||||
// list all the bugs in the path as directories
|
||||
if let Ok(entries) = std::fs::read_dir(&path) {
|
||||
@@ -82,10 +83,12 @@ impl BugBase {
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.parse::<u64>()
|
||||
.or(Err(format!(
|
||||
"failed to parse seed from directory name {}",
|
||||
entry.file_name().to_string_lossy()
|
||||
)))?;
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to parse seed from directory name {}",
|
||||
entry.file_name().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
bugs.insert(seed, Bug::Unloaded { seed });
|
||||
}
|
||||
}
|
||||
@@ -95,15 +98,14 @@ impl BugBase {
|
||||
}
|
||||
|
||||
/// Load the bug base from one of the potential paths.
|
||||
pub(crate) fn load() -> Result<Self, String> {
|
||||
pub(crate) fn load() -> anyhow::Result<Self> {
|
||||
let potential_paths = vec![
|
||||
// limbo project directory
|
||||
BugBase::get_limbo_project_dir()?,
|
||||
// home directory
|
||||
dirs::home_dir().ok_or("should be able to get home directory".to_string())?,
|
||||
dirs::home_dir().with_context(|| "should be able to get home directory")?,
|
||||
// current directory
|
||||
std::env::current_dir()
|
||||
.or(Err("should be able to get current directory".to_string()))?,
|
||||
std::env::current_dir().with_context(|| "should be able to get current directory")?,
|
||||
];
|
||||
|
||||
for path in &potential_paths {
|
||||
@@ -121,19 +123,18 @@ impl BugBase {
|
||||
}
|
||||
}
|
||||
|
||||
Err("failed to create bug base".to_string())
|
||||
Err(anyhow!("failed to create bug base"))
|
||||
}
|
||||
|
||||
/// Load the bug base from one of the potential paths.
|
||||
pub(crate) fn interactive_load() -> Result<Self, String> {
|
||||
pub(crate) fn interactive_load() -> anyhow::Result<Self> {
|
||||
let potential_paths = vec![
|
||||
// limbo project directory
|
||||
BugBase::get_limbo_project_dir()?,
|
||||
// home directory
|
||||
dirs::home_dir().ok_or("should be able to get home directory".to_string())?,
|
||||
dirs::home_dir().with_context(|| "should be able to get home directory")?,
|
||||
// current directory
|
||||
std::env::current_dir()
|
||||
.or(Err("should be able to get current directory".to_string()))?,
|
||||
std::env::current_dir().with_context(|| "should be able to get current directory")?,
|
||||
];
|
||||
|
||||
for path in potential_paths {
|
||||
@@ -157,21 +158,21 @@ impl BugBase {
|
||||
let choice = choice
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.or(Err(format!("invalid choice {choice}")))?;
|
||||
.with_context(|| format!("invalid choice {choice}"))?;
|
||||
let path = match choice {
|
||||
1 => BugBase::get_limbo_project_dir()?.join(".bugbase"),
|
||||
2 => {
|
||||
let home = std::env::var("HOME").or(Err("failed to get home directory"))?;
|
||||
let home = std::env::var("HOME").with_context(|| "failed to get home directory")?;
|
||||
PathBuf::from(home).join(".bugbase")
|
||||
}
|
||||
3 => PathBuf::from(".bugbase"),
|
||||
_ => return Err(format!("invalid choice {choice}")),
|
||||
_ => anyhow::bail!(format!("invalid choice {choice}")),
|
||||
};
|
||||
|
||||
if path.exists() {
|
||||
unreachable!("bug base already exists at {}", path.display());
|
||||
} else {
|
||||
std::fs::create_dir_all(&path).or(Err("failed to create bug base"))?;
|
||||
std::fs::create_dir_all(&path).with_context(|| "failed to create bug base")?;
|
||||
tracing::info!("bug base created at {}", path.display());
|
||||
BugBase::new(path)
|
||||
}
|
||||
@@ -184,7 +185,7 @@ impl BugBase {
|
||||
plan: InteractionPlan,
|
||||
error: Option<String>,
|
||||
cli_options: &SimulatorCLI,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
tracing::debug!("adding bug with seed {}", seed);
|
||||
let bug = self.get_bug(seed);
|
||||
|
||||
@@ -221,7 +222,7 @@ impl BugBase {
|
||||
}
|
||||
|
||||
/// Save a bug to the bug base.
|
||||
fn save_bug(&self, seed: u64) -> Result<(), String> {
|
||||
fn save_bug(&self, seed: u64) -> anyhow::Result<()> {
|
||||
let bug = self.get_bug(seed);
|
||||
|
||||
match bug {
|
||||
@@ -231,57 +232,59 @@ impl BugBase {
|
||||
Some(Bug::Loaded(bug)) => {
|
||||
let bug_path = self.path.join(seed.to_string());
|
||||
std::fs::create_dir_all(&bug_path)
|
||||
.or(Err("should be able to create bug directory".to_string()))?;
|
||||
.with_context(|| "should be able to create bug directory")?;
|
||||
|
||||
let seed_path = bug_path.join("seed.txt");
|
||||
std::fs::write(&seed_path, seed.to_string())
|
||||
.or(Err("should be able to write seed file".to_string()))?;
|
||||
.with_context(|| "should be able to write seed file")?;
|
||||
|
||||
let plan_path = bug_path.join("plan.json");
|
||||
std::fs::write(
|
||||
&plan_path,
|
||||
serde_json::to_string_pretty(&bug.plan)
|
||||
.or(Err("should be able to serialize plan".to_string()))?,
|
||||
.with_context(|| "should be able to serialize plan")?,
|
||||
)
|
||||
.or(Err("should be able to write plan file".to_string()))?;
|
||||
.with_context(|| "should be able to write plan file")?;
|
||||
|
||||
let readable_plan_path = bug_path.join("plan.sql");
|
||||
std::fs::write(&readable_plan_path, bug.plan.to_string())
|
||||
.or(Err("should be able to write readable plan file".to_string()))?;
|
||||
.with_context(|| "should be able to write readable plan file")?;
|
||||
|
||||
let runs_path = bug_path.join("runs.json");
|
||||
std::fs::write(
|
||||
&runs_path,
|
||||
serde_json::to_string_pretty(&bug.runs)
|
||||
.or(Err("should be able to serialize runs".to_string()))?,
|
||||
.with_context(|| "should be able to serialize runs")?,
|
||||
)
|
||||
.or(Err("should be able to write runs file".to_string()))?;
|
||||
.with_context(|| "should be able to write runs file")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_bug(&mut self, seed: u64) -> Result<LoadedBug, String> {
|
||||
pub(crate) fn load_bug(&mut self, seed: u64) -> anyhow::Result<LoadedBug> {
|
||||
let seed_match = self.bugs.get(&seed);
|
||||
|
||||
match seed_match {
|
||||
None => Err(format!("No bugs found for seed {}", seed)),
|
||||
None => anyhow::bail!("No bugs found for seed {}", seed),
|
||||
Some(Bug::Unloaded { .. }) => {
|
||||
let plan =
|
||||
std::fs::read_to_string(self.path.join(seed.to_string()).join("plan.json"))
|
||||
.or(Err(format!(
|
||||
"should be able to read plan file at {}",
|
||||
self.path.join(seed.to_string()).join("plan.json").display()
|
||||
)))?;
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"should be able to read plan file at {}",
|
||||
self.path.join(seed.to_string()).join("plan.json").display()
|
||||
)
|
||||
})?;
|
||||
let plan: InteractionPlan = serde_json::from_str(&plan)
|
||||
.or(Err("should be able to deserialize plan".to_string()))?;
|
||||
.with_context(|| "should be able to deserialize plan")?;
|
||||
|
||||
let runs =
|
||||
std::fs::read_to_string(self.path.join(seed.to_string()).join("runs.json"))
|
||||
.or(Err("should be able to read runs file".to_string()))?;
|
||||
.with_context(|| "should be able to read runs file")?;
|
||||
let runs: Vec<BugRun> = serde_json::from_str(&runs)
|
||||
.or(Err("should be able to deserialize runs".to_string()))?;
|
||||
.with_context(|| "should be able to deserialize runs")?;
|
||||
|
||||
let bug = LoadedBug {
|
||||
seed,
|
||||
@@ -307,13 +310,13 @@ impl BugBase {
|
||||
&mut self,
|
||||
seed: u64,
|
||||
cli_options: &SimulatorCLI,
|
||||
) -> Result<(), String> {
|
||||
) -> anyhow::Result<()> {
|
||||
let bug = self.get_bug(seed);
|
||||
match bug {
|
||||
None => {
|
||||
tracing::debug!("removing bug base entry for {}", seed);
|
||||
std::fs::remove_dir_all(self.path.join(seed.to_string()))
|
||||
.or(Err("should be able to remove bug directory".to_string()))?;
|
||||
.with_context(|| "should be able to remove bug directory")?;
|
||||
}
|
||||
Some(_) => {
|
||||
let mut bug = self.load_bug(seed)?;
|
||||
@@ -326,7 +329,7 @@ impl BugBase {
|
||||
self.bugs.insert(seed, Bug::Loaded(bug.clone()));
|
||||
// Save the bug to the bug base.
|
||||
self.save_bug(seed)
|
||||
.or(Err("should be able to save bug".to_string()))?;
|
||||
.with_context(|| "should be able to save bug")?;
|
||||
tracing::debug!("Updated bug with seed {}", seed);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +337,7 @@ impl BugBase {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn load_bugs(&mut self) -> Result<Vec<LoadedBug>, String> {
|
||||
pub(crate) fn load_bugs(&mut self) -> anyhow::Result<Vec<LoadedBug>> {
|
||||
let seeds = self.bugs.keys().map(|seed| *seed).collect::<Vec<_>>();
|
||||
|
||||
seeds
|
||||
@@ -343,7 +346,7 @@ impl BugBase {
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
pub(crate) fn list_bugs(&mut self) -> Result<(), String> {
|
||||
pub(crate) fn list_bugs(&mut self) -> anyhow::Result<()> {
|
||||
let bugs = self.load_bugs()?;
|
||||
for bug in bugs {
|
||||
println!("seed: {}", bug.seed);
|
||||
@@ -393,31 +396,31 @@ impl BugBase {
|
||||
}
|
||||
|
||||
impl BugBase {
|
||||
pub(crate) fn get_current_commit_hash() -> Result<String, String> {
|
||||
pub(crate) fn get_current_commit_hash() -> anyhow::Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.or(Err("should be able to get the commit hash".to_string()))?;
|
||||
.with_context(|| "should be able to get the commit hash")?;
|
||||
let commit_hash = String::from_utf8(output.stdout)
|
||||
.or(Err("commit hash should be valid utf8".to_string()))?
|
||||
.with_context(|| "commit hash should be valid utf8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
Ok(commit_hash)
|
||||
}
|
||||
|
||||
pub(crate) fn get_limbo_project_dir() -> Result<PathBuf, String> {
|
||||
pub(crate) fn get_limbo_project_dir() -> anyhow::Result<PathBuf> {
|
||||
Ok(PathBuf::from(
|
||||
String::from_utf8(
|
||||
Command::new("git")
|
||||
.args(["rev-parse", "--git-dir"])
|
||||
.output()
|
||||
.or(Err("should be able to get the git path".to_string()))?
|
||||
.with_context(|| "should be able to get the git path")?
|
||||
.stdout,
|
||||
)
|
||||
.or(Err("commit hash should be valid utf8".to_string()))?
|
||||
.with_context(|| "commit hash should be valid utf8")?
|
||||
.trim()
|
||||
.strip_suffix(".git")
|
||||
.ok_or("should be able to strip .git suffix".to_string())?,
|
||||
.with_context(|| "should be able to strip .git suffix")?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,12 @@ pub enum SimulatorCommand {
|
||||
}
|
||||
|
||||
impl SimulatorCLI {
|
||||
pub fn validate(&mut self) -> Result<(), String> {
|
||||
pub fn validate(&mut self) -> anyhow::Result<()> {
|
||||
if self.minimum_tests < 1 {
|
||||
return Err("minimum size must be at least 1".to_string());
|
||||
anyhow::bail!("minimum size must be at least 1");
|
||||
}
|
||||
if self.maximum_tests < 1 {
|
||||
return Err("maximum size must be at least 1".to_string());
|
||||
anyhow::bail!("maximum size must be at least 1");
|
||||
}
|
||||
|
||||
if self.minimum_tests > self.maximum_tests {
|
||||
@@ -112,7 +112,7 @@ impl SimulatorCLI {
|
||||
}
|
||||
|
||||
if self.seed.is_some() && self.load.is_some() {
|
||||
return Err("Cannot set seed and load plan at the same time".to_string());
|
||||
anyhow::bail!("Cannot set seed and load plan at the same time");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -15,7 +15,7 @@ name = "integration_tests"
|
||||
path = "integration/mod.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
anyhow.workspace = true
|
||||
env_logger = "0.10.1"
|
||||
limbo_core = { path = "../core" }
|
||||
rusqlite = { version = "0.34", features = ["bundled"] }
|
||||
|
||||
Reference in New Issue
Block a user