Accept an optional --seed parameter for check, otherwise default to random.

Also, show the seed on failure.
This commit is contained in:
KtorZ 2024-03-03 20:36:01 +01:00
parent a7b9d4bb22
commit 7a2537432a
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
14 changed files with 100 additions and 55 deletions

1
Cargo.lock generated vendored
View File

@ -67,6 +67,7 @@ dependencies = [
"ordinal", "ordinal",
"owo-colors", "owo-colors",
"pallas", "pallas",
"rand",
"regex", "regex",
"serde_json", "serde_json",
"thiserror", "thiserror",

View File

@ -1,7 +1,6 @@
use std::{collections::HashMap, path::PathBuf};
use aiken_lang::{ast::Tracing, line_numbers::LineNumbers}; use aiken_lang::{ast::Tracing, line_numbers::LineNumbers};
use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project}; use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project};
use std::{collections::HashMap, path::PathBuf};
#[derive(Debug)] #[derive(Debug)]
pub struct SourceInfo { pub struct SourceInfo {
@ -30,9 +29,9 @@ impl LspProject {
pub fn compile(&mut self) -> Result<(), Vec<ProjectError>> { pub fn compile(&mut self) -> Result<(), Vec<ProjectError>> {
let checkpoint = self.project.checkpoint(); let checkpoint = self.project.checkpoint();
let result = self let result =
.project self.project
.check(true, None, false, false, Tracing::silent()); .check(true, None, false, false, u32::default(), Tracing::silent());
self.project.restore(checkpoint); self.project.restore(checkpoint);

View File

@ -226,6 +226,7 @@ where
match_tests: Option<Vec<String>>, match_tests: Option<Vec<String>>,
verbose: bool, verbose: bool,
exact_match: bool, exact_match: bool,
seed: u32,
tracing: Tracing, tracing: Tracing,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let options = Options { let options = Options {
@ -237,6 +238,7 @@ where
match_tests, match_tests,
verbose, verbose,
exact_match, exact_match,
seed,
} }
}, },
}; };
@ -322,6 +324,7 @@ where
match_tests, match_tests,
verbose, verbose,
exact_match, exact_match,
seed,
} => { } => {
let tests = let tests =
self.collect_tests(verbose, match_tests, exact_match, options.tracing)?; self.collect_tests(verbose, match_tests, exact_match, options.tracing)?;
@ -330,7 +333,7 @@ where
self.event_listener.handle_event(Event::RunningTests); self.event_listener.handle_event(Event::RunningTests);
} }
let tests = self.run_tests(tests); let tests = self.run_tests(tests, seed);
let errors: Vec<Error> = tests let errors: Vec<Error> = tests
.iter() .iter()
@ -786,7 +789,7 @@ where
Ok(tests) Ok(tests)
} }
fn run_tests(&self, tests: Vec<Test>) -> Vec<TestResult<UntypedExpr>> { fn run_tests(&self, tests: Vec<Test>, seed: u32) -> Vec<TestResult<UntypedExpr>> {
use rayon::prelude::*; use rayon::prelude::*;
let data_types = utils::indexmap::as_ref_values(&self.data_types); let data_types = utils::indexmap::as_ref_values(&self.data_types);
@ -797,7 +800,7 @@ where
Test::UnitTest(unit_test) => unit_test.run(), Test::UnitTest(unit_test) => unit_test.run(),
// TODO: Get the seed from the command-line, defaulting to a random one when not // TODO: Get the seed from the command-line, defaulting to a random one when not
// provided. // provided.
Test::PropertyTest(property_test) => property_test.run(42), Test::PropertyTest(property_test) => property_test.run(seed),
}) })
.collect::<Vec<TestResult<PlutusData>>>() .collect::<Vec<TestResult<PlutusData>>>()
.into_iter() .into_iter()

View File

@ -10,6 +10,7 @@ pub enum CodeGenMode {
match_tests: Option<Vec<String>>, match_tests: Option<Vec<String>>,
verbose: bool, verbose: bool,
exact_match: bool, exact_match: bool,
seed: u32,
}, },
Build(bool), Build(bool),
NoOp, NoOp,

View File

@ -267,8 +267,6 @@ impl PropertyTest {
} = next_prng } = next_prng
{ {
if result.failed(self.can_error) { if result.failed(self.can_error) {
println!("{:#?}", result.result());
let mut counterexample = Counterexample { let mut counterexample = Counterexample {
value, value,
choices: next_prng.choices(), choices: next_prng.choices(),

View File

@ -1,4 +1,4 @@
use crate::{telemetry::Terminal, Project}; use crate::{telemetry::Terminal, Error, Project};
use miette::{Diagnostic, IntoDiagnostic}; use miette::{Diagnostic, IntoDiagnostic};
use notify::{Event, RecursiveMode, Watcher}; use notify::{Event, RecursiveMode, Watcher};
use owo_colors::{OwoColorize, Stream::Stderr}; use owo_colors::{OwoColorize, Stream::Stderr};
@ -75,7 +75,12 @@ pub fn default_filter(evt: &Event) -> bool {
} }
} }
pub fn with_project<A>(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()> pub fn with_project<A>(
directory: Option<&Path>,
seed: u32,
deny: bool,
mut action: A,
) -> miette::Result<()>
where where
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>, A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
{ {
@ -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()); 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 { if warning_count > 0 && deny {
Err(ExitFailure::into_report()) Err(ExitFailure::into_report())
} else { } else {
@ -148,6 +160,7 @@ where
pub fn watch_project<F, A>( pub fn watch_project<F, A>(
directory: Option<&Path>, directory: Option<&Path>,
filter: F, filter: F,
seed: u32,
debounce: u32, debounce: u32,
mut action: A, mut action: A,
) -> miette::Result<()> ) -> miette::Result<()>
@ -219,7 +232,7 @@ where
.if_supports_color(Stderr, |s| s.bold()) .if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.purple()), .if_supports_color(Stderr, |s| s.purple()),
); );
with_project(directory, false, &mut action).unwrap_or(()) with_project(directory, seed, false, &mut action).unwrap_or(())
} }
} }
} }

View File

@ -38,3 +38,4 @@ clap_complete = "4.3.2"
inquire = "0.6.2" inquire = "0.6.2"
num-bigint = "0.4.3" num-bigint = "0.4.3"
ordinal = "0.3.2" ordinal = "0.3.2"
rand = "0.8.5"

View File

@ -39,7 +39,7 @@ pub fn exec(
mainnet, mainnet,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), u32::default(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::silent())?; p.build(false, Tracing::silent())?;
} }

View File

@ -47,7 +47,7 @@ pub fn exec(
validator, validator,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(None, false, |p| { with_project(None, u32::default(), false, |p| {
let title = module.as_ref().map(|m| { let title = module.as_ref().map(|m| {
format!( format!(
"{m}{}", "{m}{}",

View File

@ -29,7 +29,7 @@ pub fn exec(
rebuild, rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), u32::default(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::silent())?; p.build(false, Tracing::silent())?;
} }

View File

@ -29,7 +29,7 @@ pub fn exec(
rebuild, rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), u32::default(), false, |p| {
if rebuild { if rebuild {
p.build(false, Tracing::silent())?; p.build(false, Tracing::silent())?;
} }

View File

@ -1,7 +1,6 @@
use aiken_lang::ast::{TraceLevel, Tracing}; use aiken_lang::ast::{TraceLevel, Tracing};
use aiken_project::watch::{self, watch_project, with_project}; use aiken_project::watch::{self, watch_project, with_project};
use clap::builder::MapValueParser; use clap::builder::{MapValueParser, PossibleValuesParser, TypedValueParser};
use clap::builder::{PossibleValuesParser, TypedValueParser};
use std::{path::PathBuf, process}; use std::{path::PathBuf, process};
#[derive(clap::Args)] #[derive(clap::Args)]
@ -52,17 +51,23 @@ pub fn exec(
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let result = if watch { let result = if watch {
watch_project(directory.as_deref(), watch::default_filter, 500, |p| { watch_project(
p.build( directory.as_deref(),
uplc, watch::default_filter,
match filter_traces { u32::default(),
Some(filter_traces) => filter_traces(trace_level), 500,
None => Tracing::All(trace_level), |p| {
}, p.build(
) uplc,
}) match filter_traces {
Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level),
},
)
},
)
} else { } else {
with_project(directory.as_deref(), deny, |p| { with_project(directory.as_deref(), u32::default(), deny, |p| {
p.build( p.build(
uplc, uplc,
match filter_traces { match filter_traces {
@ -77,8 +82,8 @@ pub fn exec(
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn filter_traces_parser( pub fn filter_traces_parser()
) -> MapValueParser<PossibleValuesParser, fn(String) -> fn(TraceLevel) -> Tracing> { -> MapValueParser<PossibleValuesParser, fn(String) -> fn(TraceLevel) -> Tracing> {
PossibleValuesParser::new(["user-defined", "compiler-generated", "all"]).map( PossibleValuesParser::new(["user-defined", "compiler-generated", "all"]).map(
|s: String| match s.as_str() { |s: String| match s.as_str() {
"user-defined" => Tracing::UserDefined, "user-defined" => Tracing::UserDefined,

View File

@ -1,6 +1,7 @@
use super::build::{filter_traces_parser, trace_level_parser}; use super::build::{filter_traces_parser, trace_level_parser};
use aiken_lang::ast::{TraceLevel, Tracing}; use aiken_lang::ast::{TraceLevel, Tracing};
use aiken_project::watch::{self, watch_project, with_project}; use aiken_project::watch::{self, watch_project, with_project};
use rand::prelude::*;
use std::{path::PathBuf, process}; use std::{path::PathBuf, process};
#[derive(clap::Args)] #[derive(clap::Args)]
@ -25,6 +26,10 @@ pub struct Args {
#[clap(long)] #[clap(long)]
watch: bool, watch: bool,
/// An initial seed to initialize the pseudo-random generator for property-tests.
#[clap(long)]
seed: Option<u32>,
/// Only run tests if they match any of these strings. /// 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 module with `-m aiken/list` or `-m list`.
/// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"` /// You can match a test with `-m "aiken/list.{map}"` or `-m "aiken/option.{flatten_1}"`
@ -66,28 +71,41 @@ pub fn exec(
watch, watch,
filter_traces, filter_traces,
trace_level, trace_level,
seed,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let mut rng = rand::thread_rng();
let seed = seed.unwrap_or_else(|| rng.gen());
let result = if watch { let result = if watch {
watch_project(directory.as_deref(), watch::default_filter, 500, |p| { watch_project(
p.check( directory.as_deref(),
skip_tests, watch::default_filter,
match_tests.clone(), seed,
debug, 500,
exact_match, |p| {
match filter_traces { p.check(
Some(filter_traces) => filter_traces(trace_level), skip_tests,
None => Tracing::All(trace_level), match_tests.clone(),
}, debug,
) exact_match,
}) seed,
match filter_traces {
Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level),
},
)
},
)
} else { } else {
with_project(directory.as_deref(), deny, |p| { with_project(directory.as_deref(), seed, deny, |p| {
p.check( p.check(
skip_tests, skip_tests,
match_tests.clone(), match_tests.clone(),
debug, debug,
exact_match, exact_match,
seed,
match filter_traces { match filter_traces {
Some(filter_traces) => filter_traces(trace_level), Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level), None => Tracing::All(trace_level),

View File

@ -29,11 +29,17 @@ pub fn exec(
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let result = if watch { 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()) p.docs(destination.clone())
}) })
} else {
with_project(directory.as_deref(), deny, |p| p.docs(destination.clone()))
}; };
result.map_err(|_| process::exit(1)) result.map_err(|_| process::exit(1))