diff --git a/Cargo.lock b/Cargo.lock index 89a9b33b..e9d0cc3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,7 @@ dependencies = [ "ordinal", "owo-colors", "pallas", + "rand", "regex", "serde_json", "thiserror", diff --git a/crates/aiken-lsp/src/server/lsp_project.rs b/crates/aiken-lsp/src/server/lsp_project.rs index 1134689f..e5b1c04e 100644 --- a/crates/aiken-lsp/src/server/lsp_project.rs +++ b/crates/aiken-lsp/src/server/lsp_project.rs @@ -1,7 +1,6 @@ -use std::{collections::HashMap, path::PathBuf}; - use aiken_lang::{ast::Tracing, line_numbers::LineNumbers}; use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project}; +use std::{collections::HashMap, path::PathBuf}; #[derive(Debug)] pub struct SourceInfo { @@ -30,9 +29,9 @@ impl LspProject { pub fn compile(&mut self) -> Result<(), Vec> { let checkpoint = self.project.checkpoint(); - let result = self - .project - .check(true, None, false, false, Tracing::silent()); + let result = + self.project + .check(true, None, false, false, u32::default(), Tracing::silent()); self.project.restore(checkpoint); diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index f413ccc2..99e36d00 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -226,6 +226,7 @@ where match_tests: Option>, verbose: bool, exact_match: bool, + seed: u32, tracing: Tracing, ) -> Result<(), Vec> { let options = Options { @@ -237,6 +238,7 @@ where match_tests, verbose, exact_match, + seed, } }, }; @@ -322,6 +324,7 @@ where match_tests, verbose, exact_match, + seed, } => { let tests = self.collect_tests(verbose, match_tests, exact_match, options.tracing)?; @@ -330,7 +333,7 @@ where self.event_listener.handle_event(Event::RunningTests); } - let tests = self.run_tests(tests); + let tests = self.run_tests(tests, seed); let errors: Vec = tests .iter() @@ -786,7 +789,7 @@ where Ok(tests) } - fn run_tests(&self, tests: Vec) -> Vec> { + fn run_tests(&self, tests: Vec, seed: u32) -> Vec> { use rayon::prelude::*; let data_types = utils::indexmap::as_ref_values(&self.data_types); @@ -797,7 +800,7 @@ where Test::UnitTest(unit_test) => unit_test.run(), // TODO: Get the seed from the command-line, defaulting to a random one when not // provided. - Test::PropertyTest(property_test) => property_test.run(42), + Test::PropertyTest(property_test) => property_test.run(seed), }) .collect::>>() .into_iter() diff --git a/crates/aiken-project/src/options.rs b/crates/aiken-project/src/options.rs index 2da0d13d..deaf49c9 100644 --- a/crates/aiken-project/src/options.rs +++ b/crates/aiken-project/src/options.rs @@ -10,6 +10,7 @@ pub enum CodeGenMode { match_tests: Option>, verbose: bool, exact_match: bool, + seed: u32, }, Build(bool), NoOp, diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index 1f75922c..9c422121 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -267,8 +267,6 @@ impl PropertyTest { } = next_prng { if result.failed(self.can_error) { - println!("{:#?}", result.result()); - let mut counterexample = Counterexample { value, choices: next_prng.choices(), diff --git a/crates/aiken-project/src/watch.rs b/crates/aiken-project/src/watch.rs index 99934ff8..4e973cba 100644 --- a/crates/aiken-project/src/watch.rs +++ b/crates/aiken-project/src/watch.rs @@ -1,4 +1,4 @@ -use crate::{telemetry::Terminal, Project}; +use crate::{telemetry::Terminal, Error, Project}; use miette::{Diagnostic, IntoDiagnostic}; use notify::{Event, RecursiveMode, Watcher}; use owo_colors::{OwoColorize, Stream::Stderr}; @@ -75,7 +75,12 @@ pub fn default_filter(evt: &Event) -> bool { } } -pub fn with_project(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()> +pub fn with_project( + directory: Option<&Path>, + seed: u32, + deny: bool, + mut action: A, +) -> miette::Result<()> where A: FnMut(&mut Project) -> Result<(), Vec>, { @@ -116,17 +121,24 @@ where } ); + if errs.iter().any(|e| matches!(e, Error::TestFailure { .. })) { + eprintln!( + " ━━━━━━\n ╰─▶ use {} {} to replay", + "--seed".if_supports_color(Stderr, |s| s.bold()), + format!("{seed}").if_supports_color(Stderr, |s| s.bold()) + ); + } return Err(ExitFailure::into_report()); - } else { - eprintln!( - "{}", - Summary { - error_count: 0, - warning_count - } - ); } + eprintln!( + "{}", + Summary { + error_count: 0, + warning_count + } + ); + if warning_count > 0 && deny { Err(ExitFailure::into_report()) } else { @@ -148,6 +160,7 @@ where pub fn watch_project( directory: Option<&Path>, filter: F, + seed: u32, debounce: u32, mut action: A, ) -> miette::Result<()> @@ -219,7 +232,7 @@ where .if_supports_color(Stderr, |s| s.bold()) .if_supports_color(Stderr, |s| s.purple()), ); - with_project(directory, false, &mut action).unwrap_or(()) + with_project(directory, seed, false, &mut action).unwrap_or(()) } } } diff --git a/crates/aiken/Cargo.toml b/crates/aiken/Cargo.toml index c47e2360..3c9cf3bd 100644 --- a/crates/aiken/Cargo.toml +++ b/crates/aiken/Cargo.toml @@ -38,3 +38,4 @@ clap_complete = "4.3.2" inquire = "0.6.2" num-bigint = "0.4.3" ordinal = "0.3.2" +rand = "0.8.5" diff --git a/crates/aiken/src/cmd/blueprint/address.rs b/crates/aiken/src/cmd/blueprint/address.rs index 285b30d3..3115c0f4 100644 --- a/crates/aiken/src/cmd/blueprint/address.rs +++ b/crates/aiken/src/cmd/blueprint/address.rs @@ -39,7 +39,7 @@ pub fn exec( mainnet, }: Args, ) -> miette::Result<()> { - with_project(directory.as_deref(), false, |p| { + with_project(directory.as_deref(), u32::default(), false, |p| { if rebuild { p.build(false, Tracing::silent())?; } diff --git a/crates/aiken/src/cmd/blueprint/apply.rs b/crates/aiken/src/cmd/blueprint/apply.rs index a25c3073..58088506 100644 --- a/crates/aiken/src/cmd/blueprint/apply.rs +++ b/crates/aiken/src/cmd/blueprint/apply.rs @@ -47,7 +47,7 @@ pub fn exec( validator, }: Args, ) -> miette::Result<()> { - with_project(None, false, |p| { + with_project(None, u32::default(), false, |p| { let title = module.as_ref().map(|m| { format!( "{m}{}", diff --git a/crates/aiken/src/cmd/blueprint/hash.rs b/crates/aiken/src/cmd/blueprint/hash.rs index ef67e702..40ddd152 100644 --- a/crates/aiken/src/cmd/blueprint/hash.rs +++ b/crates/aiken/src/cmd/blueprint/hash.rs @@ -29,7 +29,7 @@ pub fn exec( rebuild, }: Args, ) -> miette::Result<()> { - with_project(directory.as_deref(), false, |p| { + with_project(directory.as_deref(), u32::default(), false, |p| { if rebuild { p.build(false, Tracing::silent())?; } diff --git a/crates/aiken/src/cmd/blueprint/policy.rs b/crates/aiken/src/cmd/blueprint/policy.rs index 80c90d4c..76f74a89 100644 --- a/crates/aiken/src/cmd/blueprint/policy.rs +++ b/crates/aiken/src/cmd/blueprint/policy.rs @@ -29,7 +29,7 @@ pub fn exec( rebuild, }: Args, ) -> miette::Result<()> { - with_project(directory.as_deref(), false, |p| { + with_project(directory.as_deref(), u32::default(), false, |p| { if rebuild { p.build(false, Tracing::silent())?; } diff --git a/crates/aiken/src/cmd/build.rs b/crates/aiken/src/cmd/build.rs index 81a4afb4..6949b2a3 100644 --- a/crates/aiken/src/cmd/build.rs +++ b/crates/aiken/src/cmd/build.rs @@ -1,7 +1,6 @@ use aiken_lang::ast::{TraceLevel, Tracing}; use aiken_project::watch::{self, watch_project, with_project}; -use clap::builder::MapValueParser; -use clap::builder::{PossibleValuesParser, TypedValueParser}; +use clap::builder::{MapValueParser, PossibleValuesParser, TypedValueParser}; use std::{path::PathBuf, process}; #[derive(clap::Args)] @@ -52,17 +51,23 @@ pub fn exec( }: Args, ) -> miette::Result<()> { let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { - p.build( - uplc, - match filter_traces { - Some(filter_traces) => filter_traces(trace_level), - None => Tracing::All(trace_level), - }, - ) - }) + watch_project( + directory.as_deref(), + watch::default_filter, + u32::default(), + 500, + |p| { + p.build( + uplc, + match filter_traces { + Some(filter_traces) => filter_traces(trace_level), + None => Tracing::All(trace_level), + }, + ) + }, + ) } else { - with_project(directory.as_deref(), deny, |p| { + with_project(directory.as_deref(), u32::default(), deny, |p| { p.build( uplc, match filter_traces { @@ -77,8 +82,8 @@ pub fn exec( } #[allow(clippy::type_complexity)] -pub fn filter_traces_parser( -) -> MapValueParser fn(TraceLevel) -> Tracing> { +pub fn filter_traces_parser() +-> MapValueParser fn(TraceLevel) -> Tracing> { PossibleValuesParser::new(["user-defined", "compiler-generated", "all"]).map( |s: String| match s.as_str() { "user-defined" => Tracing::UserDefined, diff --git a/crates/aiken/src/cmd/check.rs b/crates/aiken/src/cmd/check.rs index 207a796a..e0dcbd63 100644 --- a/crates/aiken/src/cmd/check.rs +++ b/crates/aiken/src/cmd/check.rs @@ -1,6 +1,7 @@ use super::build::{filter_traces_parser, trace_level_parser}; use aiken_lang::ast::{TraceLevel, Tracing}; use aiken_project::watch::{self, watch_project, with_project}; +use rand::prelude::*; use std::{path::PathBuf, process}; #[derive(clap::Args)] @@ -25,6 +26,10 @@ pub struct Args { #[clap(long)] watch: bool, + /// An initial seed to initialize the pseudo-random generator for property-tests. + #[clap(long)] + seed: Option, + /// Only run tests if they match any of these strings. /// You can match a module with `-m aiken/list` or `-m list`. /// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"` @@ -66,28 +71,41 @@ pub fn exec( watch, filter_traces, trace_level, + seed, }: Args, ) -> miette::Result<()> { + let mut rng = rand::thread_rng(); + + let seed = seed.unwrap_or_else(|| rng.gen()); + let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { - p.check( - skip_tests, - match_tests.clone(), - debug, - exact_match, - match filter_traces { - Some(filter_traces) => filter_traces(trace_level), - None => Tracing::All(trace_level), - }, - ) - }) + watch_project( + directory.as_deref(), + watch::default_filter, + seed, + 500, + |p| { + p.check( + skip_tests, + match_tests.clone(), + debug, + exact_match, + seed, + match filter_traces { + Some(filter_traces) => filter_traces(trace_level), + None => Tracing::All(trace_level), + }, + ) + }, + ) } else { - with_project(directory.as_deref(), deny, |p| { + with_project(directory.as_deref(), seed, deny, |p| { p.check( skip_tests, match_tests.clone(), debug, exact_match, + seed, match filter_traces { Some(filter_traces) => filter_traces(trace_level), None => Tracing::All(trace_level), diff --git a/crates/aiken/src/cmd/docs.rs b/crates/aiken/src/cmd/docs.rs index 1abc27db..75407294 100644 --- a/crates/aiken/src/cmd/docs.rs +++ b/crates/aiken/src/cmd/docs.rs @@ -29,11 +29,17 @@ pub fn exec( }: Args, ) -> miette::Result<()> { let result = if watch { - watch_project(directory.as_deref(), watch::default_filter, 500, |p| { + watch_project( + directory.as_deref(), + watch::default_filter, + u32::default(), + 500, + |p| p.docs(destination.clone()), + ) + } else { + with_project(directory.as_deref(), u32::default(), deny, |p| { p.docs(destination.clone()) }) - } else { - with_project(directory.as_deref(), deny, |p| p.docs(destination.clone())) }; result.map_err(|_| process::exit(1))