diff --git a/Cargo.lock b/Cargo.lock index 3187ad56920..40ddf5ea619 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8413,7 +8413,7 @@ dependencies = [ ] [[package]] -name = "spacetimedb-dst" +name = "spacetimedb-dst-lib" version = "2.7.0" dependencies = [ "anyhow", diff --git a/crates/dst/Cargo.toml b/crates/dst/Cargo.toml index 722d293ba19..7d47c9a1b47 100644 --- a/crates/dst/Cargo.toml +++ b/crates/dst/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "spacetimedb-dst" +name = "spacetimedb-dst-lib" version.workspace = true edition.workspace = true rust-version.workspace = true +[features] +default = ["fallocate"] +fallocate = ["spacetimedb-commitlog/fallocate"] + [dependencies] anyhow.workspace = true clap.workspace = true diff --git a/crates/dst/README.md b/crates/dst/README.md deleted file mode 100644 index 1a4dfcb2396..00000000000 --- a/crates/dst/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# SpacetimeDB DST - -Deterministic Simulation Testing framework for SpacetimeDB. - -## Test - -```sh -cargo test -p spacetimedb-dst -``` - -## Run - -```sh -cargo run -p spacetimedb-dst -- run --seed 42 --max-interactions 1000 -``` - -Options: - -- `--seed ` — RNG seed (defaults to wall-clock nanos) -- `--max-interactions ` — interaction budget diff --git a/crates/dst/src/engine.rs b/crates/dst/src/engine.rs index 8dcfaab46a7..451b7417e41 100644 --- a/crates/dst/src/engine.rs +++ b/crates/dst/src/engine.rs @@ -274,10 +274,11 @@ impl EngineTarget { impl TargetDriver for EngineTarget { type Observation = Observation; - fn execute(&mut self, interaction: &Interaction) -> Result { + async fn execute<'a>(&'a mut self, interaction: &'a Interaction) -> Result { EngineTarget::execute(self, interaction) } } + pub struct EngineTest; impl TestSuite for EngineTest { @@ -289,7 +290,7 @@ impl TestSuite for EngineTest { type Properties = EngineProperties; - fn build(&self, rng: Rng) -> Result<(Self::Interactions, Self::Target, Self::Properties), anyhow::Error> { + async fn build(&self, rng: Rng) -> Result<(Self::Interactions, Self::Target, Self::Properties), anyhow::Error> { let schema = default_schema(rng.clone()); let runtime_seed = rng.next_u64(); let target = EngineTarget::init(schema.clone(), runtime_seed)?; diff --git a/crates/dst/src/lib.rs b/crates/dst/src/lib.rs new file mode 100644 index 00000000000..8d12c575e4c --- /dev/null +++ b/crates/dst/src/lib.rs @@ -0,0 +1,6 @@ +pub mod engine; +pub mod schema; +pub mod sim; +pub mod traits; + +pub use traits::{Properties, TargetDriver, TestSuite, TestSuiteParts}; diff --git a/crates/dst/src/main.rs b/crates/dst/src/main.rs deleted file mode 100644 index e208f921a83..00000000000 --- a/crates/dst/src/main.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -use clap::{Args, Parser, Subcommand}; -use spacetimedb_runtime::sim::Rng; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - -mod engine; -mod schema; -mod sim; -mod traits; - -use crate::{engine::EngineTest, traits::TestSuite}; - -#[derive(Parser, Debug)] -#[command(name = "spacetimedb-dst")] -#[command(about = "Run deterministic simulation targets")] -struct Cli { - #[command(subcommand)] - command: Command, -} - -#[derive(Subcommand, Debug)] -enum Command { - Run(RunArgs), -} - -#[derive(Args, Debug)] -struct RunArgs { - #[arg(long, help = "Seed for generated choices. Defaults to wall-clock time.")] - seed: Option, - #[arg(long, help = "Deterministic interaction budget.")] - max_interactions: Option, -} - -fn main() -> anyhow::Result<()> { - init_tracing(); - match Cli::parse().command { - Command::Run(args) => run_command(args), - } -} - -fn init_tracing() { - let timer = tracing_subscriber::fmt::time(); - let format = tracing_subscriber::fmt::format::Format::default() - .with_timer(timer) - .with_line_number(true) - .with_file(true) - .with_target(false) - .compact(); - let fmt_layer = tracing_subscriber::fmt::Layer::default() - .event_format(format) - .with_writer(std::io::stderr); - let env_filter_layer = tracing_subscriber::EnvFilter::from_default_env(); - - let _ = tracing_subscriber::Registry::default() - .with(fmt_layer) - .with(env_filter_layer) - .try_init(); -} - -fn run_command(args: RunArgs) -> anyhow::Result<()> { - let seed = resolve_seed(args.seed); - let config = RunConfig { - max_interactions: args.max_interactions, - seed, - }; - - tracing::info!(?config, "initial run config"); - - // Generate schema from seed. - let rng = Rng::new(config.seed); - - let test = EngineTest {}; - test.run(rng, config.max_interactions)?; - Ok(()) -} - -fn resolve_seed(seed: Option) -> u64 { - seed.unwrap_or_else(|| { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("time went backwards") - .as_nanos() as u64 - }) -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RunConfig { - pub max_interactions: Option, - pub seed: u64, -} diff --git a/crates/dst/src/sim/commitlog.rs b/crates/dst/src/sim/commitlog.rs index dbf6d004a4c..f2644d8767a 100644 --- a/crates/dst/src/sim/commitlog.rs +++ b/crates/dst/src/sim/commitlog.rs @@ -22,6 +22,12 @@ pub struct InMemoryCommitlog { options: Options, } +impl Default for InMemoryCommitlog { + fn default() -> Self { + Self::new() + } +} + impl InMemoryCommitlog { pub fn new() -> Self { Self { @@ -394,6 +400,10 @@ impl FileLike for Segment { Ok(()) } + + fn fallocate(&mut self, _size: u64) -> io::Result<()> { + Ok(()) + } } pub struct ReadOnlySegment { diff --git a/crates/dst/src/traits.rs b/crates/dst/src/traits.rs index d84aafa97db..2185e0ec918 100644 --- a/crates/dst/src/traits.rs +++ b/crates/dst/src/traits.rs @@ -5,7 +5,10 @@ use spacetimedb_runtime::sim::Rng; pub trait TargetDriver { type Observation; - fn execute(&mut self, interaction: &I) -> Result; + fn execute<'a>( + &'a mut self, + interaction: &'a I, + ) -> impl std::future::Future> + 'a; } /// Ensures if Output of `TargetDrive` is expected for the input @@ -20,32 +23,35 @@ pub type TestSuiteParts = ( ); pub trait TestSuite { - type Interaction; + type Interaction: std::fmt::Debug; type Interactions: Iterator + std::fmt::Debug; type Target: TargetDriver; type Properties: Properties>::Observation>; - fn build(&self, rng: Rng) -> Result, Error> + fn build(&self, rng: Rng) -> impl std::future::Future, Error>> + '_ where Self: Sized; - fn run(&self, rng: Rng, max_interactions: Option) -> Result<(), Error> + fn run(&self, rng: Rng, max_interactions: usize) -> impl std::future::Future> + '_ where Self: Sized, { - let (mut interactions, mut target, mut properties) = self.build(rng)?; + async move { + let (mut interactions, mut target, mut properties) = self.build(rng).await?; - let result = (|| { - for interaction in interactions.by_ref().take(max_interactions.unwrap_or(usize::MAX)) { - let observation = target.execute(&interaction)?; - properties.observe(&interaction, &observation)?; - } + let result = async { + for interaction in interactions.by_ref().take(max_interactions) { + let observation = target.execute(&interaction).await?; + properties.observe(&interaction, &observation)?; + } - Ok(()) - })(); + Ok(()) + } + .await; - tracing::info!(interaction_counts = ?interactions, "final interaction counts"); + tracing::info!(interaction_counts = ?interactions, "final interaction counts"); - result + result + } } }