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:
KtorZ 2024-03-07 01:04:50 +01:00
parent 59996850c1
commit bff822ea7f
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
10 changed files with 377 additions and 254 deletions

View File

@ -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

89
Cargo.lock generated vendored
View File

@ -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",

View File

@ -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,

View File

@ -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"

View File

@ -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,

View File

@ -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>>>()

View File

@ -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")
);

View File

@ -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)
)
}
}

View File

@ -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());
}

View File

@ -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()
}