Rework unit test report to leverage new reification
And also provide slightly better errors when traces, or trace-if-false operators are present.
This commit is contained in:
parent
59996850c1
commit
bff822ea7f
|
@ -8,11 +8,13 @@
|
|||
- **aiken-lang**: New types `PRNG` and `Fuzzer` in the prelude. @KtorZ
|
||||
- **aiken-lang**: Test definitions now accept an (optional) argument alongside a new keyword `via` to specify fuzzers. @KtorZ
|
||||
- **aiken-project**: Property-based testing framework with integrated shrinking. @KtorZ
|
||||
- **aiken-project**: Unit tests now show assertion operands as Aiken expression instead of raw UPLC . @KtorZ
|
||||
- **aiken**: The `check` command now accept an extra arg `--seed` to provide an initial seed for the pseudo-random generator of properties. @KtorZ
|
||||
|
||||
### Fixed
|
||||
|
||||
- **uplc**: `serialise_data` builtin wrongly encoding some larger ints as tagged CBOR bigints, instead of plain integers over 9 bytes. @KtorZ
|
||||
- **aiken-project**: Unit tests reports are now inline with the test with less noise. @KtorZ
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
@ -156,6 +156,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"toml",
|
||||
"uplc",
|
||||
"vec1",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
|
@ -520,10 +521,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.88"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -535,9 +537,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.34"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
@ -564,9 +566,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
||||
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -574,9 +576,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -1254,9 +1256,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -1443,9 +1445,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.35.1"
|
||||
version = "1.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2"
|
||||
checksum = "0a7c22c4d34ef4788c351e971c52bfdfe7ea2766f8c5466bc175dd46e52ac22e"
|
||||
dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
|
@ -1494,10 +1496,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.68"
|
||||
name = "jobserver"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
|
||||
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -2230,18 +2241,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
|
||||
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2531,9 +2542,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -3570,9 +3581,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
|
@ -3595,9 +3606,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -3605,9 +3616,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -3620,9 +3631,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
|
||||
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -3632,9 +3643,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -3642,9 +3653,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3655,15 +3666,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.91"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.68"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
|
||||
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
|
@ -273,56 +273,6 @@ impl From<TypedTest> for TypedFunction {
|
|||
}
|
||||
}
|
||||
|
||||
impl TypedTest {
|
||||
pub fn test_hint(&self) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
||||
if self.arguments.is_empty() {
|
||||
do_test_hint(&self.body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_test_hint(body: &TypedExpr) -> Option<(BinOp, Box<TypedExpr>, Box<TypedExpr>)> {
|
||||
match body {
|
||||
TypedExpr::BinOp {
|
||||
name,
|
||||
tipo,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} if tipo == &bool() => Some((*name, left.clone(), right.clone())),
|
||||
TypedExpr::Sequence { expressions, .. } | TypedExpr::Pipeline { expressions, .. } => {
|
||||
if let Some((binop, left, right)) = do_test_hint(&expressions[expressions.len() - 1]) {
|
||||
let mut new_left_expressions = expressions.clone();
|
||||
new_left_expressions.pop();
|
||||
new_left_expressions.push(*left);
|
||||
|
||||
let mut new_right_expressions = expressions.clone();
|
||||
new_right_expressions.pop();
|
||||
new_right_expressions.push(*right);
|
||||
|
||||
Some((
|
||||
binop,
|
||||
TypedExpr::Sequence {
|
||||
expressions: new_left_expressions,
|
||||
location: Span::empty(),
|
||||
}
|
||||
.into(),
|
||||
TypedExpr::Sequence {
|
||||
expressions: new_right_expressions,
|
||||
location: Span::empty(),
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TypeAlias<T> {
|
||||
pub alias: String,
|
||||
|
|
|
@ -45,6 +45,7 @@ aiken-lang = { path = "../aiken-lang", version = "1.0.24-alpha" }
|
|||
uplc = { path = '../uplc', version = "1.0.24-alpha" }
|
||||
num-bigint = "0.4.4"
|
||||
cryptoxide = "0.4.4"
|
||||
vec1 = "1.10.1"
|
||||
|
||||
[dev-dependencies]
|
||||
blst = "0.3.11"
|
||||
|
|
|
@ -93,7 +93,6 @@ pub enum Error {
|
|||
path: PathBuf,
|
||||
verbose: bool,
|
||||
src: String,
|
||||
assertion: Option<String>,
|
||||
},
|
||||
|
||||
#[error(
|
||||
|
@ -331,10 +330,7 @@ impl Diagnostic for Error {
|
|||
)),
|
||||
Error::TomlLoading { .. } => None,
|
||||
Error::Format { .. } => None,
|
||||
Error::TestFailure { assertion, .. } => match assertion {
|
||||
None => None,
|
||||
Some(hint) => Some(Box::new(hint.to_string())),
|
||||
},
|
||||
Error::TestFailure { .. } => None,
|
||||
Error::Http(_) => None,
|
||||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
|
|
|
@ -843,8 +843,6 @@ where
|
|||
.into_par_iter()
|
||||
.map(|test| match test {
|
||||
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(seed),
|
||||
})
|
||||
.collect::<Vec<TestResult<PlutusData>>>()
|
||||
|
|
|
@ -305,14 +305,10 @@ fn fmt_test(
|
|||
);
|
||||
}
|
||||
TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
|
||||
test = pretty::pad_right(
|
||||
format!(
|
||||
test = format!(
|
||||
"{test} [after {} test{}]",
|
||||
pretty::pad_left(iterations.to_string(), max_iter, " "),
|
||||
if *iterations > 1 { "s" } else { "" }
|
||||
),
|
||||
18 + max_mem + max_cpu + max_iter,
|
||||
" ",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -325,6 +321,22 @@ fn fmt_test(
|
|||
.to_string())
|
||||
);
|
||||
|
||||
// Annotations
|
||||
match result {
|
||||
TestResult::UnitTestResult(UnitTestResult {
|
||||
test: unit_test, ..
|
||||
}) if !result.is_success() => {
|
||||
if let Some(ref assertion) = unit_test.assertion {
|
||||
test = format!(
|
||||
"{test}\n{}{new_line}",
|
||||
assertion.to_string(Stderr, unit_test.can_error),
|
||||
new_line = if result.logs().is_empty() { "\n" } else { "" },
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// CounterExample
|
||||
if let TestResult::PropertyTestResult(PropertyTestResult {
|
||||
counterexample: Some(counterexample),
|
||||
|
@ -334,7 +346,7 @@ fn fmt_test(
|
|||
let is_expected_failure = result.is_success();
|
||||
|
||||
test = format!(
|
||||
"{test}\n{}\n{}\n",
|
||||
"{test}\n{}\n{}{new_line}",
|
||||
if is_expected_failure {
|
||||
"★ counterexample"
|
||||
.if_supports_color(Stderr, |s| s.green())
|
||||
|
@ -362,31 +374,20 @@ fn fmt_test(
|
|||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
.join("\n"),
|
||||
new_line = if result.logs().is_empty() { "\n" } else { "" },
|
||||
);
|
||||
}
|
||||
|
||||
// Traces
|
||||
if !result.logs().is_empty() {
|
||||
test = format!(
|
||||
"{test}\n{logs}",
|
||||
"{test}\n{title}\n{logs}\n",
|
||||
title = "· with traces".if_supports_color(Stderr, |s| s.bold()),
|
||||
logs = result
|
||||
.logs()
|
||||
.iter()
|
||||
.map(|line| {
|
||||
format!(
|
||||
"{arrow} {styled_line}",
|
||||
arrow = "↳".if_supports_color(Stderr, |s| s.bright_yellow()),
|
||||
styled_line = line
|
||||
.split('\n')
|
||||
.map(|l| format!(
|
||||
"{}",
|
||||
l.if_supports_color(Stderr, |s| s.bright_black())
|
||||
))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
})
|
||||
.map(|line| { format!("| {line}",) })
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
use crate::pretty;
|
||||
use aiken_lang::{
|
||||
ast::{Arg, BinOp, DataTypeKey, TypedDataType, TypedTest},
|
||||
expr::UntypedExpr,
|
||||
ast::{Arg, BinOp, DataTypeKey, IfBranch, Span, TypedDataType, TypedTest},
|
||||
builtins::bool,
|
||||
expr::{TypedExpr, UntypedExpr},
|
||||
format::Formatter,
|
||||
gen_uplc::{builder::convert_opaque_type, CodeGenerator},
|
||||
tipo::Type,
|
||||
};
|
||||
use cryptoxide::{blake2b::Blake2b, digest::Digest};
|
||||
use indexmap::IndexMap;
|
||||
use owo_colors::{OwoColorize, Stream};
|
||||
use pallas::ledger::primitives::alonzo::{Constr, PlutusData};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{self, Display},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{borrow::Borrow, convert::TryFrom, path::PathBuf, rc::Rc};
|
||||
use uplc::{
|
||||
ast::{Constant, Data, Name, NamedDeBruijn, Program, Term},
|
||||
machine::{cost_model::ExBudget, eval_result::EvalResult},
|
||||
};
|
||||
use vec1::{vec1, Vec1};
|
||||
|
||||
/// ----- Test -----------------------------------------------------------------
|
||||
///
|
||||
|
@ -46,20 +44,50 @@ unsafe impl Send for Test {}
|
|||
|
||||
impl Test {
|
||||
pub fn unit_test(
|
||||
generator: &mut CodeGenerator<'_>,
|
||||
test: TypedTest,
|
||||
module_name: String,
|
||||
input_path: PathBuf,
|
||||
module: String,
|
||||
name: String,
|
||||
can_error: bool,
|
||||
program: Program<NamedDeBruijn>,
|
||||
assertion: Option<Assertion>,
|
||||
) -> Test {
|
||||
let data_types = generator.data_types().clone();
|
||||
|
||||
let program = generator.generate_raw(&test.body, &[], &module_name);
|
||||
|
||||
let assertion = match test.body.try_into() {
|
||||
Err(..) => None,
|
||||
Ok(Assertion { bin_op, head, tail }) => {
|
||||
let as_constant = |generator: &mut CodeGenerator<'_>, side| {
|
||||
Program::<NamedDeBruijn>::try_from(generator.generate_raw(
|
||||
&side,
|
||||
&[],
|
||||
&module_name,
|
||||
))
|
||||
.expect("failed to convert assertion operaand to NamedDeBruijn")
|
||||
.eval(ExBudget::max())
|
||||
.unwrap_constant()
|
||||
.map(|cst| {
|
||||
UntypedExpr::reify_constant(&data_types, cst, &side.tipo())
|
||||
.expect("failed to reify assertion operand?")
|
||||
})
|
||||
};
|
||||
|
||||
Some(Assertion {
|
||||
bin_op,
|
||||
head: as_constant(generator, head.expect("cannot be Err at this point")),
|
||||
tail: tail
|
||||
.expect("cannot be Err at this point")
|
||||
.try_mapped(|e| as_constant(generator, e)),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Test::UnitTest(UnitTest {
|
||||
input_path,
|
||||
module,
|
||||
name,
|
||||
module: module_name,
|
||||
name: test.name,
|
||||
program,
|
||||
can_error,
|
||||
assertion,
|
||||
can_error: test.can_error,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -88,39 +116,7 @@ impl Test {
|
|||
input_path: PathBuf,
|
||||
) -> Test {
|
||||
if test.arguments.is_empty() {
|
||||
let program = generator.generate_raw(&test.body, &[], &module_name);
|
||||
|
||||
// TODO: Check whether we really need to clone the _entire_ generator, or whether we
|
||||
// can mostly copy the generator and only clone parts that matters.
|
||||
let assertion = test.test_hint().map(|(bin_op, left_src, right_src)| {
|
||||
let left = generator
|
||||
.clone()
|
||||
.generate_raw(&left_src, &[], &module_name)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let right = generator
|
||||
.clone()
|
||||
.generate_raw(&right_src, &[], &module_name)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
Assertion {
|
||||
bin_op,
|
||||
left,
|
||||
right,
|
||||
can_error: test.can_error,
|
||||
}
|
||||
});
|
||||
|
||||
Self::unit_test(
|
||||
input_path,
|
||||
module_name,
|
||||
test.name,
|
||||
test.can_error,
|
||||
program.try_into().unwrap(),
|
||||
assertion,
|
||||
)
|
||||
Self::unit_test(generator, test, module_name, input_path)
|
||||
} else {
|
||||
let parameter = test.arguments.first().unwrap().to_owned();
|
||||
|
||||
|
@ -168,18 +164,23 @@ pub struct UnitTest {
|
|||
pub module: String,
|
||||
pub name: String,
|
||||
pub can_error: bool,
|
||||
pub program: Program<NamedDeBruijn>,
|
||||
pub assertion: Option<Assertion>,
|
||||
pub program: Program<Name>,
|
||||
pub assertion: Option<Assertion<UntypedExpr>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for UnitTest {}
|
||||
|
||||
impl UnitTest {
|
||||
pub fn run<T>(self) -> TestResult<T> {
|
||||
let mut eval_result = self.program.clone().eval(ExBudget::max());
|
||||
let mut eval_result = Program::<NamedDeBruijn>::try_from(self.program.clone())
|
||||
.unwrap()
|
||||
.eval(ExBudget::max());
|
||||
|
||||
let success = !eval_result.failed(self.can_error);
|
||||
|
||||
TestResult::UnitTestResult(UnitTestResult {
|
||||
success,
|
||||
test: self.to_owned(),
|
||||
success: !eval_result.failed(self.can_error),
|
||||
spent_budget: eval_result.cost(),
|
||||
logs: eval_result.logs(),
|
||||
output: eval_result.result().ok(),
|
||||
|
@ -399,8 +400,10 @@ impl Prng {
|
|||
fn as_prng(cst: &PlutusData) -> Prng {
|
||||
if let PlutusData::Constr(Constr { tag, fields, .. }) = cst {
|
||||
if *tag == 121 + Prng::SEEDED {
|
||||
if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] =
|
||||
&fields[..]
|
||||
if let [
|
||||
PlutusData::BoundedBytes(bytes),
|
||||
PlutusData::BoundedBytes(choices),
|
||||
] = &fields[..]
|
||||
{
|
||||
return Prng::Seeded {
|
||||
choices: choices.to_vec(),
|
||||
|
@ -741,24 +744,21 @@ impl<T> TestResult<T> {
|
|||
}
|
||||
|
||||
pub fn into_error(&self, verbose: bool) -> crate::Error {
|
||||
let (name, path, assertion, src) = match self {
|
||||
let (name, path, src) = match self {
|
||||
TestResult::UnitTestResult(UnitTestResult { test, .. }) => (
|
||||
test.name.to_string(),
|
||||
test.input_path.to_path_buf(),
|
||||
test.assertion.as_ref().map(|hint| hint.to_string()),
|
||||
test.program.to_pretty(),
|
||||
),
|
||||
TestResult::PropertyTestResult(PropertyTestResult { test, .. }) => (
|
||||
test.name.to_string(),
|
||||
test.input_path.to_path_buf(),
|
||||
None,
|
||||
test.program.to_pretty(),
|
||||
),
|
||||
};
|
||||
crate::Error::TestFailure {
|
||||
name,
|
||||
path,
|
||||
assertion,
|
||||
src,
|
||||
verbose,
|
||||
}
|
||||
|
@ -809,74 +809,233 @@ impl PropertyTestResult<PlutusData> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Assertion {
|
||||
pub struct Assertion<T> {
|
||||
pub bin_op: BinOp,
|
||||
pub left: Program<NamedDeBruijn>,
|
||||
pub right: Program<NamedDeBruijn>,
|
||||
pub can_error: bool,
|
||||
pub head: Result<T, ()>,
|
||||
pub tail: Result<Vec1<T>, ()>,
|
||||
}
|
||||
|
||||
impl Display for Assertion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let unlimited_budget = ExBudget {
|
||||
mem: i64::MAX,
|
||||
cpu: i64::MAX,
|
||||
impl TryFrom<TypedExpr> for Assertion<TypedExpr> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(body: TypedExpr) -> Result<Self, Self::Error> {
|
||||
match body {
|
||||
TypedExpr::BinOp {
|
||||
name,
|
||||
tipo,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} if tipo == bool() => {
|
||||
// 'and' and 'or' are left-associative operators.
|
||||
match (*right).clone().try_into() {
|
||||
Ok(Assertion {
|
||||
bin_op,
|
||||
head: Ok(head),
|
||||
tail: Ok(tail),
|
||||
..
|
||||
}) if bin_op == name => {
|
||||
let mut both = vec1![head];
|
||||
both.extend(tail);
|
||||
Ok(Assertion {
|
||||
bin_op: name,
|
||||
head: Ok(*left),
|
||||
tail: Ok(both),
|
||||
})
|
||||
}
|
||||
_ => Ok(Assertion {
|
||||
bin_op: name,
|
||||
head: Ok(*left),
|
||||
tail: Ok(vec1![*right]),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE drill through trace-if-false operators for better errors.
|
||||
TypedExpr::If {
|
||||
branches,
|
||||
final_else,
|
||||
..
|
||||
} => {
|
||||
if let [
|
||||
IfBranch {
|
||||
condition, body, ..
|
||||
},
|
||||
] = &branches[..]
|
||||
{
|
||||
let then_is_true = match body {
|
||||
TypedExpr::Var {
|
||||
name, constructor, ..
|
||||
} => name == "True" && constructor.tipo == bool(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let left = pretty::boxed(
|
||||
"left",
|
||||
&match self.left.clone().eval(unlimited_budget).result() {
|
||||
Ok(term) => format!("{term}"),
|
||||
Err(err) => format!("{err}"),
|
||||
let else_is_wrapped_false = match *final_else {
|
||||
TypedExpr::Trace { then, .. } => match *then {
|
||||
TypedExpr::Var {
|
||||
name, constructor, ..
|
||||
} => name == "False" && constructor.tipo == bool(),
|
||||
_ => false,
|
||||
},
|
||||
);
|
||||
let right = pretty::boxed(
|
||||
"right",
|
||||
&match self.right.clone().eval(unlimited_budget).result() {
|
||||
Ok(term) => format!("{term}"),
|
||||
Err(err) => format!("{err}"),
|
||||
},
|
||||
);
|
||||
let msg = if self.can_error {
|
||||
match self.bin_op {
|
||||
BinOp::And => Some(format!(
|
||||
"{left}\n\nand\n\n{right}\n\nare both true but shouldn't."
|
||||
)),
|
||||
BinOp::Or => Some(format!(
|
||||
"neither\n\n{left}\n\nnor\n\n{right}\n\nshould be true."
|
||||
)),
|
||||
BinOp::Eq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
||||
BinOp::NotEq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
||||
BinOp::LtInt => Some(format!(
|
||||
"{left}\n\nshould be greater than or equal to\n\n{right}"
|
||||
)),
|
||||
BinOp::LtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
||||
BinOp::GtEqInt => Some(format!(
|
||||
"{left}\n\nshould be lower than or equal\n\n{right}"
|
||||
)),
|
||||
BinOp::GtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
||||
_ => None,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if then_is_true && else_is_wrapped_false {
|
||||
return condition.to_owned().try_into();
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
TypedExpr::Trace { then, .. } => (*then).try_into(),
|
||||
|
||||
TypedExpr::Sequence { expressions, .. } | TypedExpr::Pipeline { expressions, .. } => {
|
||||
if let Ok(Assertion {
|
||||
bin_op,
|
||||
head: Ok(head),
|
||||
tail: Ok(tail),
|
||||
}) = expressions.last().unwrap().to_owned().try_into()
|
||||
{
|
||||
let replace = |expr| {
|
||||
let mut expressions = expressions.clone();
|
||||
expressions.pop();
|
||||
expressions.push(expr);
|
||||
TypedExpr::Sequence {
|
||||
expressions,
|
||||
location: Span::empty(),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Assertion {
|
||||
bin_op,
|
||||
head: Ok(replace(head)),
|
||||
tail: Ok(tail.mapped(replace)),
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Assertion<UntypedExpr> {
|
||||
#[allow(clippy::just_underscores_and_digits)]
|
||||
pub fn to_string(&self, stream: Stream, expect_failure: bool) -> String {
|
||||
let red = |s: &str| {
|
||||
format!("× {s}")
|
||||
.if_supports_color(stream, |s| s.red())
|
||||
.if_supports_color(stream, |s| s.bold())
|
||||
.to_string()
|
||||
};
|
||||
|
||||
if self.head.is_err() {
|
||||
return red("program failed");
|
||||
}
|
||||
|
||||
fn fmt_side(side: &UntypedExpr, stream: Stream) -> String {
|
||||
let __ = "│".if_supports_color(stream, |s| s.red());
|
||||
|
||||
Formatter::new()
|
||||
.expr(side, false)
|
||||
.to_pretty_string(60)
|
||||
.lines()
|
||||
.map(|line| format!("{__} {line}"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
let left = fmt_side(self.head.as_ref().unwrap(), stream);
|
||||
|
||||
let tail = self.tail.as_ref().unwrap();
|
||||
|
||||
let right = fmt_side(tail.first(), stream);
|
||||
|
||||
format!(
|
||||
"{}{}{}",
|
||||
red("expected"),
|
||||
if expect_failure && self.bin_op == BinOp::Or {
|
||||
" neither\n"
|
||||
.if_supports_color(stream, |s| s.red())
|
||||
.if_supports_color(stream, |s| s.bold())
|
||||
.to_string()
|
||||
} else {
|
||||
"\n".to_string()
|
||||
},
|
||||
if expect_failure {
|
||||
match self.bin_op {
|
||||
BinOp::And => [
|
||||
left,
|
||||
red("and"),
|
||||
[
|
||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
||||
.join(format!("\n{}\n", red("and")).as_str()),
|
||||
if tail.len() > 1 {
|
||||
red("to not all be true")
|
||||
} else {
|
||||
red("to not both be true")
|
||||
},
|
||||
]
|
||||
.join("\n"),
|
||||
],
|
||||
BinOp::Or => [
|
||||
left,
|
||||
red("nor"),
|
||||
[
|
||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
||||
.join(format!("\n{}\n", red("nor")).as_str()),
|
||||
red("to be true"),
|
||||
]
|
||||
.join("\n"),
|
||||
],
|
||||
BinOp::Eq => [left, red("to not equal"), right],
|
||||
BinOp::NotEq => [left, red("to not be different"), right],
|
||||
BinOp::LtInt => [left, red("to not be lower than"), right],
|
||||
BinOp::LtEqInt => [left, red("to not be lower than or equal to"), right],
|
||||
BinOp::GtInt => [left, red("to not be greater than"), right],
|
||||
BinOp::GtEqInt => [left, red("to not be greater than or equal to"), right],
|
||||
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
||||
}
|
||||
.join("\n")
|
||||
} else {
|
||||
match self.bin_op {
|
||||
BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")),
|
||||
BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")),
|
||||
BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")),
|
||||
BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")),
|
||||
BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")),
|
||||
BinOp::LtEqInt => Some(format!(
|
||||
"{left}\n\nshould be lower than or equal to\n\n{right}"
|
||||
)),
|
||||
BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")),
|
||||
BinOp::GtInt => Some(format!(
|
||||
"{left}\n\nshould be greater than or equal to\n\n{right}"
|
||||
)),
|
||||
_ => None,
|
||||
BinOp::And => [
|
||||
left,
|
||||
red("and"),
|
||||
[
|
||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
||||
.join(format!("\n{}\n", red("and")).as_str()),
|
||||
if tail.len() > 1 {
|
||||
red("to all be true")
|
||||
} else {
|
||||
red("to both be true")
|
||||
},
|
||||
]
|
||||
.join("\n"),
|
||||
],
|
||||
BinOp::Or => [
|
||||
left,
|
||||
red("or"),
|
||||
[
|
||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
||||
.join(format!("\n{}\n", red("or")).as_str()),
|
||||
red("to be true"),
|
||||
]
|
||||
.join("\n"),
|
||||
],
|
||||
BinOp::Eq => [left, red("to equal"), right],
|
||||
BinOp::NotEq => [left, red("to not equal"), right],
|
||||
BinOp::LtInt => [left, red("to be lower than"), right],
|
||||
BinOp::LtEqInt => [left, red("to be lower than or equal to"), right],
|
||||
BinOp::GtInt => [left, red("to be greater than"), right],
|
||||
BinOp::GtEqInt => [left, red("to be greater than or equal to"), right],
|
||||
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
||||
}
|
||||
.join("\n")
|
||||
}
|
||||
.ok_or(fmt::Error)?;
|
||||
|
||||
f.write_str(&msg)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{telemetry::Terminal, Error, Project};
|
||||
use crate::{telemetry::Terminal, Project};
|
||||
use miette::{Diagnostic, IntoDiagnostic};
|
||||
use notify::{Event, RecursiveMode, Watcher};
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
|
@ -108,7 +108,6 @@ where
|
|||
err.report()
|
||||
}
|
||||
|
||||
if !errs.iter().any(|e| matches!(e, Error::TestFailure { .. })) {
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
|
@ -116,7 +115,6 @@ where
|
|||
error_count: errs.len(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return Err(ExitFailure::into_report());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::ast::{Constant, NamedDeBruijn, Term};
|
||||
|
||||
use super::{cost_model::ExBudget, Error};
|
||||
use crate::ast::{Constant, NamedDeBruijn, Term};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EvalResult {
|
||||
|
@ -44,6 +43,14 @@ impl EvalResult {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn unwrap_constant(self) -> Result<Constant, ()> {
|
||||
match self.result {
|
||||
Ok(Term::Constant(cst)) => Ok(cst.as_ref().to_owned()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn result(&self) -> Result<Term<NamedDeBruijn>, Error> {
|
||||
self.result.clone()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue