Merge branch 'json-check-output-2'
This commit is contained in:
commit
c523b0153d
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **aiken**: Optionally provide blueprint file location when using `blueprint apply` @Riley-Kilgore
|
- **aiken**: Optionally provide blueprint file location when using `blueprint apply`. @Riley-Kilgore
|
||||||
|
- **aiken**: Output test results as structured JSON when the target output is not a TTY terminal. @Riley-Kilgore, @KtorZ
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
- **aiken-project**: Fix `aiken docs` wrongly formatting list constants as tuples. See [#1048](https://github.com/aiken-lang/aiken/issues/1048). @KtorZ
|
- **aiken-project**: Fix `aiken docs` wrongly formatting list constants as tuples. See [#1048](https://github.com/aiken-lang/aiken/issues/1048). @KtorZ
|
||||||
- **aiken-project**: Fix `aiken docs` source linking crashing when generating docs for config modules. See [#1044](https://github.com/aiken-lang/aiken/issues/1044). @KtorZ
|
- **aiken-project**: Fix `aiken docs` source linking crashing when generating docs for config modules. See [#1044](https://github.com/aiken-lang/aiken/issues/1044). @KtorZ
|
||||||
- **aiken-project**: Fix `aiken docs` generating very long lines for constants. @KtorZ
|
- **aiken-project**: Fix `aiken docs` generating very long lines for constants. @KtorZ
|
||||||
|
- **aiken-lang**: Leverage [Decision Trees](https://www.cs.tufts.edu/comp/150FP/archive/luc-maranget/jun08.pdf) for compiling pattern matches to UPLC. @MicroProofs
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ dependencies = [
|
||||||
"aiken-project",
|
"aiken-project",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
"color-print",
|
||||||
"hex",
|
"hex",
|
||||||
"ignore",
|
"ignore",
|
||||||
"indoc",
|
"indoc",
|
||||||
|
@ -590,6 +591,27 @@ version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-print"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4"
|
||||||
|
dependencies = [
|
||||||
|
"color-print-proc-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-print-proc-macro"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.77",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
|
@ -1201,28 +1201,50 @@ impl TryFrom<TypedExpr> for Assertion<TypedExpr> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AssertionStyleOptions<'a> {
|
||||||
|
red: Box<dyn Fn(String) -> String + 'a>,
|
||||||
|
bold: Box<dyn Fn(String) -> String + 'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AssertionStyleOptions<'a> {
|
||||||
|
pub fn new(stream: Option<&'a Stream>) -> Self {
|
||||||
|
match stream {
|
||||||
|
Some(stream) => Self {
|
||||||
|
red: Box::new(|s| {
|
||||||
|
s.if_supports_color(stream.to_owned(), |s| s.red())
|
||||||
|
.to_string()
|
||||||
|
}),
|
||||||
|
bold: Box::new(|s| {
|
||||||
|
s.if_supports_color(stream.to_owned(), |s| s.bold())
|
||||||
|
.to_string()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
None => Self {
|
||||||
|
red: Box::new(|s| s),
|
||||||
|
bold: Box::new(|s| s),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Assertion<UntypedExpr> {
|
impl Assertion<UntypedExpr> {
|
||||||
#[allow(clippy::just_underscores_and_digits)]
|
#[allow(clippy::just_underscores_and_digits)]
|
||||||
pub fn to_string(&self, stream: Stream, expect_failure: bool) -> String {
|
pub fn to_string(&self, expect_failure: bool, style: &AssertionStyleOptions) -> String {
|
||||||
let red = |s: &str| {
|
let red = |s: &str| style.red.as_ref()(s.to_string());
|
||||||
format!("× {s}")
|
let x = |s: &str| style.red.as_ref()(style.bold.as_ref()(format!("× {s}")));
|
||||||
.if_supports_color(stream, |s| s.red())
|
|
||||||
.if_supports_color(stream, |s| s.bold())
|
|
||||||
.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// head did not map to a constant
|
// head did not map to a constant
|
||||||
if self.head.is_err() {
|
if self.head.is_err() {
|
||||||
return red("program failed");
|
return x("program failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// any value in tail did not map to a constant
|
// any value in tail did not map to a constant
|
||||||
if self.tail.is_err() {
|
if self.tail.is_err() {
|
||||||
return red("program failed");
|
return x("program failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_side(side: &UntypedExpr, stream: Stream) -> String {
|
fn fmt_side(side: &UntypedExpr, red: &dyn Fn(&str) -> String) -> String {
|
||||||
let __ = "│".if_supports_color(stream, |s| s.red());
|
let __ = red("│");
|
||||||
|
|
||||||
Formatter::new()
|
Formatter::new()
|
||||||
.expr(side, false)
|
.expr(side, false)
|
||||||
|
@ -1233,20 +1255,17 @@ impl Assertion<UntypedExpr> {
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
let left = fmt_side(self.head.as_ref().unwrap(), stream);
|
let left = fmt_side(self.head.as_ref().unwrap(), &red);
|
||||||
|
|
||||||
let tail = self.tail.as_ref().unwrap();
|
let tail = self.tail.as_ref().unwrap();
|
||||||
|
|
||||||
let right = fmt_side(tail.first(), stream);
|
let right = fmt_side(tail.first(), &red);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"{}{}{}",
|
"{}{}{}",
|
||||||
red("expected"),
|
x("expected"),
|
||||||
if expect_failure && self.bin_op == BinOp::Or {
|
if expect_failure && self.bin_op == BinOp::Or {
|
||||||
" neither\n"
|
x(" neither\n")
|
||||||
.if_supports_color(stream, |s| s.red())
|
|
||||||
.if_supports_color(stream, |s| s.bold())
|
|
||||||
.to_string()
|
|
||||||
} else {
|
} else {
|
||||||
"\n".to_string()
|
"\n".to_string()
|
||||||
},
|
},
|
||||||
|
@ -1254,34 +1273,34 @@ impl Assertion<UntypedExpr> {
|
||||||
match self.bin_op {
|
match self.bin_op {
|
||||||
BinOp::And => [
|
BinOp::And => [
|
||||||
left,
|
left,
|
||||||
red("and"),
|
x("and"),
|
||||||
[
|
[
|
||||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
tail.mapped_ref(|s| fmt_side(s, &red))
|
||||||
.join(format!("\n{}\n", red("and")).as_str()),
|
.join(format!("\n{}\n", x("and")).as_str()),
|
||||||
if tail.len() > 1 {
|
if tail.len() > 1 {
|
||||||
red("to not all be true")
|
x("to not all be true")
|
||||||
} else {
|
} else {
|
||||||
red("to not both be true")
|
x("to not both be true")
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
],
|
],
|
||||||
BinOp::Or => [
|
BinOp::Or => [
|
||||||
left,
|
left,
|
||||||
red("nor"),
|
x("nor"),
|
||||||
[
|
[
|
||||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
tail.mapped_ref(|s| fmt_side(s, &red))
|
||||||
.join(format!("\n{}\n", red("nor")).as_str()),
|
.join(format!("\n{}\n", x("nor")).as_str()),
|
||||||
red("to be true"),
|
x("to be true"),
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
],
|
],
|
||||||
BinOp::Eq => [left, red("to not equal"), right],
|
BinOp::Eq => [left, x("to not equal"), right],
|
||||||
BinOp::NotEq => [left, red("to not be different"), right],
|
BinOp::NotEq => [left, x("to not be different"), right],
|
||||||
BinOp::LtInt => [left, red("to not be lower than"), right],
|
BinOp::LtInt => [left, x("to not be lower than"), right],
|
||||||
BinOp::LtEqInt => [left, red("to not be lower than or equal to"), right],
|
BinOp::LtEqInt => [left, x("to not be lower than or equal to"), right],
|
||||||
BinOp::GtInt => [left, red("to not be greater than"), right],
|
BinOp::GtInt => [left, x("to not be greater than"), right],
|
||||||
BinOp::GtEqInt => [left, red("to not be greater than or equal to"), right],
|
BinOp::GtEqInt => [left, x("to not be greater than or equal to"), right],
|
||||||
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
||||||
}
|
}
|
||||||
.join("\n")
|
.join("\n")
|
||||||
|
@ -1289,34 +1308,34 @@ impl Assertion<UntypedExpr> {
|
||||||
match self.bin_op {
|
match self.bin_op {
|
||||||
BinOp::And => [
|
BinOp::And => [
|
||||||
left,
|
left,
|
||||||
red("and"),
|
x("and"),
|
||||||
[
|
[
|
||||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
tail.mapped_ref(|s| fmt_side(s, &red))
|
||||||
.join(format!("\n{}\n", red("and")).as_str()),
|
.join(format!("\n{}\n", x("and")).as_str()),
|
||||||
if tail.len() > 1 {
|
if tail.len() > 1 {
|
||||||
red("to all be true")
|
x("to all be true")
|
||||||
} else {
|
} else {
|
||||||
red("to both be true")
|
x("to both be true")
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
],
|
],
|
||||||
BinOp::Or => [
|
BinOp::Or => [
|
||||||
left,
|
left,
|
||||||
red("or"),
|
x("or"),
|
||||||
[
|
[
|
||||||
tail.mapped_ref(|s| fmt_side(s, stream))
|
tail.mapped_ref(|s| fmt_side(s, &red))
|
||||||
.join(format!("\n{}\n", red("or")).as_str()),
|
.join(format!("\n{}\n", x("or")).as_str()),
|
||||||
red("to be true"),
|
x("to be true"),
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
],
|
],
|
||||||
BinOp::Eq => [left, red("to equal"), right],
|
BinOp::Eq => [left, x("to equal"), right],
|
||||||
BinOp::NotEq => [left, red("to not equal"), right],
|
BinOp::NotEq => [left, x("to not equal"), right],
|
||||||
BinOp::LtInt => [left, red("to be lower than"), right],
|
BinOp::LtInt => [left, x("to be lower than"), right],
|
||||||
BinOp::LtEqInt => [left, red("to be lower than or equal to"), right],
|
BinOp::LtEqInt => [left, x("to be lower than or equal to"), right],
|
||||||
BinOp::GtInt => [left, red("to be greater than"), right],
|
BinOp::GtInt => [left, x("to be greater than"), right],
|
||||||
BinOp::GtEqInt => [left, red("to be greater than or equal to"), right],
|
BinOp::GtEqInt => [left, x("to be greater than or equal to"), right],
|
||||||
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
_ => unreachable!("unexpected non-boolean binary operator in assertion?"),
|
||||||
}
|
}
|
||||||
.join("\n")
|
.join("\n")
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
use crate::pretty;
|
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::OnTestFailure,
|
|
||||||
expr::UntypedExpr,
|
expr::UntypedExpr,
|
||||||
format::Formatter,
|
|
||||||
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
||||||
};
|
};
|
||||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
pub use json::Json;
|
||||||
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
use std::{
|
||||||
use uplc::machine::cost_model::ExBudget;
|
collections::BTreeMap,
|
||||||
|
fmt::Display,
|
||||||
|
io::{self, IsTerminal},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
pub use terminal::Terminal;
|
||||||
|
|
||||||
|
mod json;
|
||||||
|
mod terminal;
|
||||||
|
|
||||||
pub trait EventListener {
|
pub trait EventListener {
|
||||||
fn handle_event(&self, _event: Event) {}
|
fn handle_event(&self, _event: Event) {}
|
||||||
|
@ -57,6 +62,30 @@ pub enum Event {
|
||||||
ResolvingVersions,
|
ResolvingVersions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum EventTarget {
|
||||||
|
Json(Json),
|
||||||
|
Terminal(Terminal),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EventTarget {
|
||||||
|
fn default() -> Self {
|
||||||
|
if io::stdout().is_terminal() {
|
||||||
|
EventTarget::Terminal(Terminal)
|
||||||
|
} else {
|
||||||
|
EventTarget::Json(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventListener for EventTarget {
|
||||||
|
fn handle_event(&self, event: Event) {
|
||||||
|
match self {
|
||||||
|
EventTarget::Terminal(term) => term.handle_event(event),
|
||||||
|
EventTarget::Json(json) => json.handle_event(event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum DownloadSource {
|
pub enum DownloadSource {
|
||||||
Network,
|
Network,
|
||||||
Cache,
|
Cache,
|
||||||
|
@ -71,431 +100,9 @@ impl Display for DownloadSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
pub(crate) fn group_by_module(
|
||||||
pub struct Terminal;
|
results: &[TestResult<UntypedExpr, UntypedExpr>],
|
||||||
|
) -> BTreeMap<String, Vec<&TestResult<UntypedExpr, UntypedExpr>>> {
|
||||||
impl EventListener for Terminal {
|
|
||||||
fn handle_event(&self, event: Event) {
|
|
||||||
match event {
|
|
||||||
Event::StartingCompilation {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
root,
|
|
||||||
} => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} {} ({})",
|
|
||||||
" Compiling"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
name.if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
version,
|
|
||||||
root.display()
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::BuildingDocumentation {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
root,
|
|
||||||
} => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} for {} {} ({})",
|
|
||||||
" Generating"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"documentation".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
name.if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
version,
|
|
||||||
root.to_str()
|
|
||||||
.unwrap_or("")
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::WaitingForBuildDirLock => {
|
|
||||||
eprintln!(
|
|
||||||
"{}",
|
|
||||||
"Waiting for build directory lock ..."
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::DumpingUPLC { path } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} ({})",
|
|
||||||
" Exporting"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"UPLC".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
path.display()
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::GeneratingBlueprint { path } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} ({})",
|
|
||||||
" Generating"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
path.display()
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::GeneratingDocFiles { output_path } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} to {}",
|
|
||||||
" Writing"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"documentation files".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
output_path
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or("")
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::GeneratingUPLCFor { name, path } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {} {}.{{{}}}",
|
|
||||||
" Generating"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"UPLC for"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.white()),
|
|
||||||
path.to_str()
|
|
||||||
.unwrap_or("")
|
|
||||||
.if_supports_color(Stderr, |s| s.blue()),
|
|
||||||
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::RunningTests => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {}",
|
|
||||||
" Testing"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
"...".if_supports_color(Stderr, |s| s.bold())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::FinishedTests { seed, tests } => {
|
|
||||||
let (max_mem, max_cpu, max_iter) = find_max_execution_units(&tests);
|
|
||||||
|
|
||||||
for (module, results) in &group_by_module(&tests) {
|
|
||||||
let title = module
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.blue())
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let tests = results
|
|
||||||
.iter()
|
|
||||||
.map(|r| fmt_test(r, max_mem, max_cpu, max_iter, true))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
let seed_info = if results
|
|
||||||
.iter()
|
|
||||||
.any(|t| matches!(t, TestResult::PropertyTestResult { .. }))
|
|
||||||
{
|
|
||||||
format!(
|
|
||||||
"with {opt}={seed} → ",
|
|
||||||
opt = "--seed".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
seed = format!("{seed}").if_supports_color(Stderr, |s| s.bold())
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let summary = format!("{}{}", seed_info, fmt_test_summary(results, true));
|
|
||||||
println!(
|
|
||||||
"\n{}",
|
|
||||||
pretty::indent(
|
|
||||||
&pretty::open_box(&title, &tests, &summary, |border| border
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_black())
|
|
||||||
.to_string()),
|
|
||||||
4
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tests.is_empty() {
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::ResolvingPackages { name } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {}",
|
|
||||||
" Resolving"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
name.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Event::PackageResolveFallback { name } => {
|
|
||||||
eprintln!(
|
|
||||||
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
|
|
||||||
" Using"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.yellow()),
|
|
||||||
format!("uncertain local version for {name}")
|
|
||||||
.if_supports_color(Stderr, |s| s.yellow())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Event::PackagesDownloaded {
|
|
||||||
start,
|
|
||||||
count,
|
|
||||||
source,
|
|
||||||
} => {
|
|
||||||
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
|
||||||
|
|
||||||
let msg = match count {
|
|
||||||
1 => format!("1 package in {elapsed}"),
|
|
||||||
_ => format!("{count} packages in {elapsed}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!(
|
|
||||||
"{} {} from {source}",
|
|
||||||
match source {
|
|
||||||
DownloadSource::Network => " Downloaded",
|
|
||||||
DownloadSource::Cache => " Fetched",
|
|
||||||
}
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
msg.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Event::ResolvingVersions => {
|
|
||||||
eprintln!(
|
|
||||||
"{}",
|
|
||||||
" Resolving dependencies"
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.purple()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_test(
|
|
||||||
result: &TestResult<UntypedExpr, UntypedExpr>,
|
|
||||||
max_mem: usize,
|
|
||||||
max_cpu: usize,
|
|
||||||
max_iter: usize,
|
|
||||||
styled: bool,
|
|
||||||
) -> String {
|
|
||||||
// Status
|
|
||||||
let mut test = if result.is_success() {
|
|
||||||
pretty::style_if(styled, "PASS".to_string(), |s| {
|
|
||||||
s.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.green())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
|
||||||
s.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.if_supports_color(Stderr, |s| s.red())
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execution units / iteration steps
|
|
||||||
match result {
|
|
||||||
TestResult::UnitTestResult(UnitTestResult { spent_budget, .. }) => {
|
|
||||||
let ExBudget { mem, cpu } = spent_budget;
|
|
||||||
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
|
||||||
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
|
||||||
|
|
||||||
test = format!(
|
|
||||||
"{test} [mem: {mem_unit}, cpu: {cpu_unit}]",
|
|
||||||
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.cyan())
|
|
||||||
.to_string()),
|
|
||||||
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.cyan())
|
|
||||||
.to_string()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
|
|
||||||
test = format!(
|
|
||||||
"{test} [after {} test{}]",
|
|
||||||
pretty::pad_left(
|
|
||||||
if *iterations == 0 {
|
|
||||||
"?".to_string()
|
|
||||||
} else {
|
|
||||||
iterations.to_string()
|
|
||||||
},
|
|
||||||
max_iter,
|
|
||||||
" "
|
|
||||||
),
|
|
||||||
if *iterations > 1 { "s" } else { "" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title
|
|
||||||
test = format!(
|
|
||||||
"{test} {title}",
|
|
||||||
title = pretty::style_if(styled, result.title().to_string(), |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_blue())
|
|
||||||
.to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Annotations
|
|
||||||
match result {
|
|
||||||
TestResult::UnitTestResult(UnitTestResult {
|
|
||||||
assertion: Some(assertion),
|
|
||||||
test: unit_test,
|
|
||||||
..
|
|
||||||
}) if !result.is_success() => {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{}",
|
|
||||||
assertion.to_string(
|
|
||||||
Stderr,
|
|
||||||
match unit_test.on_test_failure {
|
|
||||||
OnTestFailure::FailImmediately => false,
|
|
||||||
OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately =>
|
|
||||||
true,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// CounterExamples
|
|
||||||
if let TestResult::PropertyTestResult(PropertyTestResult { counterexample, .. }) = result {
|
|
||||||
match counterexample {
|
|
||||||
Err(err) => {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{}\n{}",
|
|
||||||
"× fuzzer failed unexpectedly"
|
|
||||||
.if_supports_color(Stderr, |s| s.red())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
format!("| {err}").if_supports_color(Stderr, |s| s.red())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None) => {
|
|
||||||
if !result.is_success() {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{}",
|
|
||||||
"× no counterexample found"
|
|
||||||
.if_supports_color(Stderr, |s| s.red())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(counterexample)) => {
|
|
||||||
let is_expected_failure = result.is_success();
|
|
||||||
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{}\n{}",
|
|
||||||
if is_expected_failure {
|
|
||||||
"★ counterexample"
|
|
||||||
.if_supports_color(Stderr, |s| s.green())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
"× counterexample"
|
|
||||||
.if_supports_color(Stderr, |s| s.red())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.to_string()
|
|
||||||
},
|
|
||||||
&Formatter::new()
|
|
||||||
.expr(counterexample, false)
|
|
||||||
.to_pretty_string(60)
|
|
||||||
.lines()
|
|
||||||
.map(|line| {
|
|
||||||
format!(
|
|
||||||
"{} {}",
|
|
||||||
"│".if_supports_color(Stderr, |s| if is_expected_failure {
|
|
||||||
s.green().to_string()
|
|
||||||
} else {
|
|
||||||
s.red().to_string()
|
|
||||||
}),
|
|
||||||
line
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Labels
|
|
||||||
if let TestResult::PropertyTestResult(PropertyTestResult { labels, .. }) = result {
|
|
||||||
if !labels.is_empty() && result.is_success() {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{title}",
|
|
||||||
title = "· with coverage".if_supports_color(Stderr, |s| s.bold())
|
|
||||||
);
|
|
||||||
let mut total = 0;
|
|
||||||
let mut pad = 0;
|
|
||||||
for (k, v) in labels {
|
|
||||||
total += v;
|
|
||||||
if k.len() > pad {
|
|
||||||
pad = k.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut labels = labels.iter().collect::<Vec<_>>();
|
|
||||||
labels.sort_by(|a, b| b.1.cmp(a.1));
|
|
||||||
|
|
||||||
for (k, v) in labels {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n| {} {:>5.1}%",
|
|
||||||
pretty::pad_right(k.to_owned(), pad, " ")
|
|
||||||
.if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
100.0 * (*v as f64) / (total as f64),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traces
|
|
||||||
if !result.traces().is_empty() {
|
|
||||||
test = format!(
|
|
||||||
"{test}\n{title}\n{traces}",
|
|
||||||
title = "· with traces".if_supports_color(Stderr, |s| s.bold()),
|
|
||||||
traces = result
|
|
||||||
.traces()
|
|
||||||
.iter()
|
|
||||||
.map(|line| { format!("| {line}",) })
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
test
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_test_summary<T>(tests: &[&TestResult<T, T>], styled: bool) -> String {
|
|
||||||
let (n_passed, n_failed) = tests.iter().fold((0, 0), |(n_passed, n_failed), result| {
|
|
||||||
if result.is_success() {
|
|
||||||
(n_passed + 1, n_failed)
|
|
||||||
} else {
|
|
||||||
(n_passed, n_failed + 1)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
format!(
|
|
||||||
"{} | {} | {}",
|
|
||||||
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.to_string()),
|
|
||||||
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_green())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.to_string()),
|
|
||||||
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
|
|
||||||
.if_supports_color(Stderr, |s| s.bright_red())
|
|
||||||
.if_supports_color(Stderr, |s| s.bold())
|
|
||||||
.to_string()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group_by_module<T>(results: &Vec<TestResult<T, T>>) -> BTreeMap<String, Vec<&TestResult<T, T>>> {
|
|
||||||
let mut modules = BTreeMap::new();
|
let mut modules = BTreeMap::new();
|
||||||
for r in results {
|
for r in results {
|
||||||
let xs: &mut Vec<&TestResult<_, _>> = modules.entry(r.module().to_string()).or_default();
|
let xs: &mut Vec<&TestResult<_, _>> = modules.entry(r.module().to_string()).or_default();
|
||||||
|
@ -504,7 +111,7 @@ fn group_by_module<T>(results: &Vec<TestResult<T, T>>) -> BTreeMap<String, Vec<&
|
||||||
modules
|
modules
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_max_execution_units<T>(xs: &[TestResult<T, T>]) -> (usize, usize, usize) {
|
pub(crate) fn find_max_execution_units<T>(xs: &[TestResult<T, T>]) -> (usize, usize, usize) {
|
||||||
let (max_mem, max_cpu, max_iter) =
|
let (max_mem, max_cpu, max_iter) =
|
||||||
xs.iter()
|
xs.iter()
|
||||||
.fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test {
|
.fold((0, 0, 0), |(max_mem, max_cpu, max_iter), test| match test {
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
use super::{group_by_module, Event, EventListener};
|
||||||
|
use aiken_lang::{
|
||||||
|
ast::OnTestFailure,
|
||||||
|
expr::UntypedExpr,
|
||||||
|
format::Formatter,
|
||||||
|
test_framework::{AssertionStyleOptions, PropertyTestResult, TestResult, UnitTestResult},
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct Json;
|
||||||
|
|
||||||
|
impl EventListener for Json {
|
||||||
|
fn handle_event(&self, event: Event) {
|
||||||
|
match event {
|
||||||
|
Event::FinishedTests { seed, tests, .. } => {
|
||||||
|
let total = tests.len();
|
||||||
|
let passed = tests.iter().filter(|t| t.is_success()).count();
|
||||||
|
let failed = total - passed;
|
||||||
|
|
||||||
|
let json_output = serde_json::json!({
|
||||||
|
"seed": seed,
|
||||||
|
"summary": json!({
|
||||||
|
"total": total,
|
||||||
|
"passed": passed,
|
||||||
|
"failed": failed,
|
||||||
|
"kind": json!({
|
||||||
|
"unit": count_unit_tests(tests.iter()),
|
||||||
|
"property": count_property_tests(tests.iter()),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
"modules": group_by_module(&tests).iter().map(|(module, results)| {
|
||||||
|
serde_json::json!({
|
||||||
|
"name": module,
|
||||||
|
"summary": fmt_test_summary_json(results),
|
||||||
|
"tests": results.iter().map(|r| fmt_test_json(r)).collect::<Vec<_>>(),
|
||||||
|
})
|
||||||
|
}).collect::<Vec<_>>(),
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&json_output).unwrap());
|
||||||
|
}
|
||||||
|
_ => super::Terminal.handle_event(event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_test_json(result: &TestResult<UntypedExpr, UntypedExpr>) -> serde_json::Value {
|
||||||
|
let on_test_failure = match result {
|
||||||
|
TestResult::UnitTestResult(UnitTestResult { ref test, .. }) => &test.on_test_failure,
|
||||||
|
TestResult::PropertyTestResult(PropertyTestResult { ref test, .. }) => {
|
||||||
|
&test.on_test_failure
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut test = json!({
|
||||||
|
"title": result.title(),
|
||||||
|
"status": if result.is_success() { "pass" } else { "fail" },
|
||||||
|
"on_failure": match on_test_failure {
|
||||||
|
OnTestFailure::FailImmediately => "fail_immediately" ,
|
||||||
|
OnTestFailure::SucceedEventually => "succeed_eventually" ,
|
||||||
|
OnTestFailure::SucceedImmediately => "succeed_immediately",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match result {
|
||||||
|
TestResult::UnitTestResult(UnitTestResult {
|
||||||
|
spent_budget,
|
||||||
|
assertion,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
test["execution_units"] = json!({
|
||||||
|
"mem": spent_budget.mem,
|
||||||
|
"cpu": spent_budget.cpu,
|
||||||
|
});
|
||||||
|
if !result.is_success() {
|
||||||
|
if let Some(assertion) = assertion {
|
||||||
|
test["assertion"] =
|
||||||
|
json!(assertion.to_string(false, &AssertionStyleOptions::new(None)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TestResult::PropertyTestResult(PropertyTestResult {
|
||||||
|
iterations,
|
||||||
|
labels,
|
||||||
|
counterexample,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
test["iterations"] = json!(iterations);
|
||||||
|
if !labels.is_empty() {
|
||||||
|
test["labels"] = json!(labels);
|
||||||
|
}
|
||||||
|
test["counterexample"] = match counterexample {
|
||||||
|
Ok(Some(expr)) => json!(Formatter::new().expr(expr, false).to_pretty_string(60)),
|
||||||
|
Ok(None) => json!(null),
|
||||||
|
Err(err) => json!({"error": err.to_string()}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.traces().is_empty() {
|
||||||
|
test["traces"] = json!(result.traces());
|
||||||
|
}
|
||||||
|
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_test_summary_json(tests: &[&TestResult<UntypedExpr, UntypedExpr>]) -> serde_json::Value {
|
||||||
|
let total = tests.len();
|
||||||
|
let passed = tests.iter().filter(|t| t.is_success()).count();
|
||||||
|
let failed = total - passed;
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"total": total,
|
||||||
|
"passed": passed,
|
||||||
|
"failed": failed,
|
||||||
|
"kind": json!({
|
||||||
|
"unit": count_unit_tests(tests.iter().copied()),
|
||||||
|
"property": count_property_tests(tests.iter().copied())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_unit_tests<'a, I>(tests: I) -> usize
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a TestResult<UntypedExpr, UntypedExpr>>,
|
||||||
|
{
|
||||||
|
tests
|
||||||
|
.filter(|t| matches!(t, TestResult::UnitTestResult { .. }))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_property_tests<'a, I>(tests: I) -> usize
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a TestResult<UntypedExpr, UntypedExpr>>,
|
||||||
|
{
|
||||||
|
tests
|
||||||
|
.filter(|t| matches!(t, TestResult::PropertyTestResult { .. }))
|
||||||
|
.count()
|
||||||
|
}
|
|
@ -0,0 +1,434 @@
|
||||||
|
use super::{find_max_execution_units, group_by_module, DownloadSource, Event, EventListener};
|
||||||
|
use crate::pretty;
|
||||||
|
use aiken_lang::{
|
||||||
|
ast::OnTestFailure,
|
||||||
|
expr::UntypedExpr,
|
||||||
|
format::Formatter,
|
||||||
|
test_framework::{AssertionStyleOptions, PropertyTestResult, TestResult, UnitTestResult},
|
||||||
|
};
|
||||||
|
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||||
|
use uplc::machine::cost_model::ExBudget;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct Terminal;
|
||||||
|
|
||||||
|
impl EventListener for Terminal {
|
||||||
|
fn handle_event(&self, event: Event) {
|
||||||
|
match event {
|
||||||
|
Event::StartingCompilation {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
root,
|
||||||
|
} => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} {} ({})",
|
||||||
|
" Compiling"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
name.if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
version,
|
||||||
|
root.display()
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::BuildingDocumentation {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
root,
|
||||||
|
} => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} for {} {} ({})",
|
||||||
|
" Generating"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"documentation".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
name.if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
version,
|
||||||
|
root.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::WaitingForBuildDirLock => {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
"Waiting for build directory lock ..."
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::DumpingUPLC { path } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} ({})",
|
||||||
|
" Exporting"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"UPLC".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
path.display()
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::GeneratingBlueprint { path } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} ({})",
|
||||||
|
" Generating"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"project's blueprint".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
path.display()
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::GeneratingDocFiles { output_path } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} to {}",
|
||||||
|
" Writing"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"documentation files".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
output_path
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::GeneratingUPLCFor { name, path } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {} {}.{{{}}}",
|
||||||
|
" Generating"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"UPLC for"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.white()),
|
||||||
|
path.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.if_supports_color(Stderr, |s| s.blue()),
|
||||||
|
name.if_supports_color(Stderr, |s| s.bright_blue()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::RunningTests => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {}\n",
|
||||||
|
" Testing"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
"...".if_supports_color(Stderr, |s| s.bold())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::FinishedTests { seed, tests } => {
|
||||||
|
let (max_mem, max_cpu, max_iter) = find_max_execution_units(&tests);
|
||||||
|
|
||||||
|
for (module, results) in &group_by_module(&tests) {
|
||||||
|
let title = module
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.blue())
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let tests = results
|
||||||
|
.iter()
|
||||||
|
.map(|r| fmt_test(r, max_mem, max_cpu, max_iter, true))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let seed_info = if results
|
||||||
|
.iter()
|
||||||
|
.any(|t| matches!(t, TestResult::PropertyTestResult { .. }))
|
||||||
|
{
|
||||||
|
format!(
|
||||||
|
"with {opt}={seed} → ",
|
||||||
|
opt = "--seed".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
seed = format!("{seed}").if_supports_color(Stderr, |s| s.bold())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let summary = format!("{}{}", seed_info, fmt_test_summary(results, true));
|
||||||
|
println!(
|
||||||
|
"{}\n",
|
||||||
|
pretty::indent(
|
||||||
|
&pretty::open_box(&title, &tests, &summary, |border| border
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_black())
|
||||||
|
.to_string()),
|
||||||
|
4
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tests.is_empty() {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::ResolvingPackages { name } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {}",
|
||||||
|
" Resolving"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
name.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::PackageResolveFallback { name } => {
|
||||||
|
eprintln!(
|
||||||
|
"{} {}\n ↳ You're seeing this message because the package version is unpinned and the network is not accessible.",
|
||||||
|
" Using"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.yellow()),
|
||||||
|
format!("uncertain local version for {name}")
|
||||||
|
.if_supports_color(Stderr, |s| s.yellow())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::PackagesDownloaded {
|
||||||
|
start,
|
||||||
|
count,
|
||||||
|
source,
|
||||||
|
} => {
|
||||||
|
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
||||||
|
|
||||||
|
let msg = match count {
|
||||||
|
1 => format!("1 package in {elapsed}"),
|
||||||
|
_ => format!("{count} packages in {elapsed}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"{} {} from {source}",
|
||||||
|
match source {
|
||||||
|
DownloadSource::Network => " Downloaded",
|
||||||
|
DownloadSource::Cache => " Fetched",
|
||||||
|
}
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
msg.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Event::ResolvingVersions => {
|
||||||
|
eprintln!(
|
||||||
|
"{}",
|
||||||
|
" Resolving dependencies"
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.purple()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_test(
|
||||||
|
result: &TestResult<UntypedExpr, UntypedExpr>,
|
||||||
|
max_mem: usize,
|
||||||
|
max_cpu: usize,
|
||||||
|
max_iter: usize,
|
||||||
|
styled: bool,
|
||||||
|
) -> String {
|
||||||
|
// Status
|
||||||
|
let mut test = if result.is_success() {
|
||||||
|
pretty::style_if(styled, "PASS".to_string(), |s| {
|
||||||
|
s.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.green())
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
pretty::style_if(styled, "FAIL".to_string(), |s| {
|
||||||
|
s.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execution units / iteration steps
|
||||||
|
match result {
|
||||||
|
TestResult::UnitTestResult(UnitTestResult { spent_budget, .. }) => {
|
||||||
|
let ExBudget { mem, cpu } = spent_budget;
|
||||||
|
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
||||||
|
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
||||||
|
|
||||||
|
test = format!(
|
||||||
|
"{test} [mem: {mem_unit}, cpu: {cpu_unit}]",
|
||||||
|
mem_unit = pretty::style_if(styled, mem_pad, |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.cyan())
|
||||||
|
.to_string()),
|
||||||
|
cpu_unit = pretty::style_if(styled, cpu_pad, |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.cyan())
|
||||||
|
.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TestResult::PropertyTestResult(PropertyTestResult { iterations, .. }) => {
|
||||||
|
test = format!(
|
||||||
|
"{test} [after {} test{}]",
|
||||||
|
pretty::pad_left(
|
||||||
|
if *iterations == 0 {
|
||||||
|
"?".to_string()
|
||||||
|
} else {
|
||||||
|
iterations.to_string()
|
||||||
|
},
|
||||||
|
max_iter,
|
||||||
|
" "
|
||||||
|
),
|
||||||
|
if *iterations > 1 { "s" } else { "" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
test = format!(
|
||||||
|
"{test} {title}",
|
||||||
|
title = pretty::style_if(styled, result.title().to_string(), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_blue())
|
||||||
|
.to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Annotations
|
||||||
|
match result {
|
||||||
|
TestResult::UnitTestResult(UnitTestResult {
|
||||||
|
assertion: Some(assertion),
|
||||||
|
test: unit_test,
|
||||||
|
..
|
||||||
|
}) if !result.is_success() => {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{}",
|
||||||
|
assertion.to_string(
|
||||||
|
match unit_test.on_test_failure {
|
||||||
|
OnTestFailure::FailImmediately => false,
|
||||||
|
OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately =>
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
&AssertionStyleOptions::new(Some(&Stderr))
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// CounterExamples
|
||||||
|
if let TestResult::PropertyTestResult(PropertyTestResult { counterexample, .. }) = result {
|
||||||
|
match counterexample {
|
||||||
|
Err(err) => {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{}\n{}",
|
||||||
|
"× fuzzer failed unexpectedly"
|
||||||
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
format!("| {err}").if_supports_color(Stderr, |s| s.red())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None) => {
|
||||||
|
if !result.is_success() {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{}",
|
||||||
|
"× no counterexample found"
|
||||||
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(counterexample)) => {
|
||||||
|
let is_expected_failure = result.is_success();
|
||||||
|
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{}\n{}",
|
||||||
|
if is_expected_failure {
|
||||||
|
"★ counterexample"
|
||||||
|
.if_supports_color(Stderr, |s| s.green())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
"× counterexample"
|
||||||
|
.if_supports_color(Stderr, |s| s.red())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()
|
||||||
|
},
|
||||||
|
&Formatter::new()
|
||||||
|
.expr(counterexample, false)
|
||||||
|
.to_pretty_string(60)
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
format!(
|
||||||
|
"{} {}",
|
||||||
|
"│".if_supports_color(Stderr, |s| if is_expected_failure {
|
||||||
|
s.green().to_string()
|
||||||
|
} else {
|
||||||
|
s.red().to_string()
|
||||||
|
}),
|
||||||
|
line
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
if let TestResult::PropertyTestResult(PropertyTestResult { labels, .. }) = result {
|
||||||
|
if !labels.is_empty() && result.is_success() {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{title}",
|
||||||
|
title = "· with coverage".if_supports_color(Stderr, |s| s.bold())
|
||||||
|
);
|
||||||
|
let mut total = 0;
|
||||||
|
let mut pad = 0;
|
||||||
|
for (k, v) in labels {
|
||||||
|
total += v;
|
||||||
|
if k.len() > pad {
|
||||||
|
pad = k.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut labels = labels.iter().collect::<Vec<_>>();
|
||||||
|
labels.sort_by(|a, b| b.1.cmp(a.1));
|
||||||
|
|
||||||
|
for (k, v) in labels {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n| {} {:>5.1}%",
|
||||||
|
pretty::pad_right(k.to_owned(), pad, " ")
|
||||||
|
.if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
100.0 * (*v as f64) / (total as f64),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traces
|
||||||
|
if !result.traces().is_empty() {
|
||||||
|
test = format!(
|
||||||
|
"{test}\n{title}\n{traces}",
|
||||||
|
title = "· with traces".if_supports_color(Stderr, |s| s.bold()),
|
||||||
|
traces = result
|
||||||
|
.traces()
|
||||||
|
.iter()
|
||||||
|
.map(|line| { format!("| {line}",) })
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_test_summary<T>(tests: &[&TestResult<T, T>], styled: bool) -> String {
|
||||||
|
let (n_passed, n_failed) = tests.iter().fold((0, 0), |(n_passed, n_failed), result| {
|
||||||
|
if result.is_success() {
|
||||||
|
(n_passed + 1, n_failed)
|
||||||
|
} else {
|
||||||
|
(n_passed, n_failed + 1)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
format!(
|
||||||
|
"{} | {} | {}",
|
||||||
|
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()),
|
||||||
|
pretty::style_if(styled, format!("{n_passed} passed"), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_green())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()),
|
||||||
|
pretty::style_if(styled, format!("{n_failed} failed"), |s| s
|
||||||
|
.if_supports_color(Stderr, |s| s.bright_red())
|
||||||
|
.if_supports_color(Stderr, |s| s.bold())
|
||||||
|
.to_string()),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{telemetry::Terminal, Project};
|
use crate::{telemetry::EventTarget, 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};
|
||||||
|
@ -88,9 +88,14 @@ 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>,
|
||||||
|
deny: bool,
|
||||||
|
json: bool,
|
||||||
|
mut action: A,
|
||||||
|
) -> miette::Result<()>
|
||||||
where
|
where
|
||||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
A: FnMut(&mut Project<EventTarget>) -> Result<(), Vec<crate::error::Error>>,
|
||||||
{
|
{
|
||||||
let project_path = if let Some(d) = directory {
|
let project_path = if let Some(d) = directory {
|
||||||
d.to_path_buf()
|
d.to_path_buf()
|
||||||
|
@ -102,7 +107,7 @@ where
|
||||||
current_dir
|
current_dir
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut project = match Project::new(project_path, Terminal) {
|
let mut project = match Project::new(project_path, EventTarget::default()) {
|
||||||
Ok(p) => Ok(p),
|
Ok(p) => Ok(p),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
e.report();
|
e.report();
|
||||||
|
@ -116,6 +121,7 @@ where
|
||||||
|
|
||||||
let warning_count = warnings.len();
|
let warning_count = warnings.len();
|
||||||
|
|
||||||
|
if !json {
|
||||||
for warning in &warnings {
|
for warning in &warnings {
|
||||||
warning.report()
|
warning.report()
|
||||||
}
|
}
|
||||||
|
@ -145,6 +151,7 @@ where
|
||||||
warning_count
|
warning_count
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if warning_count > 0 && deny {
|
if warning_count > 0 && deny {
|
||||||
Err(ExitFailure::into_report())
|
Err(ExitFailure::into_report())
|
||||||
|
@ -159,7 +166,7 @@ where
|
||||||
/// // Note: doctest disabled, because aiken_project doesn't have an implementation of EventListener I can use
|
/// // Note: doctest disabled, because aiken_project doesn't have an implementation of EventListener I can use
|
||||||
/// use aiken_project::watch::{watch_project, default_filter};
|
/// use aiken_project::watch::{watch_project, default_filter};
|
||||||
/// use aiken_project::{Project};
|
/// use aiken_project::{Project};
|
||||||
/// watch_project(None, Terminal, default_filter, 500, |project| {
|
/// watch_project(None, default_filter, 500, |project| {
|
||||||
/// println!("Project changed!");
|
/// println!("Project changed!");
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// });
|
/// });
|
||||||
|
@ -172,7 +179,7 @@ pub fn watch_project<F, A>(
|
||||||
) -> miette::Result<()>
|
) -> miette::Result<()>
|
||||||
where
|
where
|
||||||
F: Fn(&Event) -> bool,
|
F: Fn(&Event) -> bool,
|
||||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
A: FnMut(&mut Project<EventTarget>) -> Result<(), Vec<crate::error::Error>>,
|
||||||
{
|
{
|
||||||
let project_path = directory
|
let project_path = directory
|
||||||
.map(|p| p.to_path_buf())
|
.map(|p| p.to_path_buf())
|
||||||
|
@ -239,7 +246,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, false, false, &mut action).unwrap_or(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ clap = { version = "4.1.8", features = [
|
||||||
"string",
|
"string",
|
||||||
] }
|
] }
|
||||||
clap_complete = "4.3.2"
|
clap_complete = "4.3.2"
|
||||||
|
color-print = "0.3.7"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
ignore = "0.4.20"
|
ignore = "0.4.20"
|
||||||
indoc = "2.0"
|
indoc = "2.0"
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn exec(
|
||||||
mainnet,
|
mainnet,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), false, false, |p| {
|
||||||
let title = module.as_ref().map(|m| {
|
let title = module.as_ref().map(|m| {
|
||||||
format!(
|
format!(
|
||||||
"{m}{}",
|
"{m}{}",
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub fn exec(
|
||||||
validator,
|
validator,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(None, false, |p| {
|
with_project(None, false, false, |p| {
|
||||||
let title = module.as_ref().map(|m| {
|
let title = module.as_ref().map(|m| {
|
||||||
format!(
|
format!(
|
||||||
"{m}{}",
|
"{m}{}",
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn exec(
|
||||||
validator,
|
validator,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), false, false, |p| {
|
||||||
let title = module.as_ref().map(|m| {
|
let title = module.as_ref().map(|m| {
|
||||||
format!(
|
format!(
|
||||||
"{m}{}",
|
"{m}{}",
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn exec(
|
||||||
validator,
|
validator,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), false, false, |p| {
|
||||||
let title = module.as_ref().map(|m| {
|
let title = module.as_ref().map(|m| {
|
||||||
format!(
|
format!(
|
||||||
"{m}{}",
|
"{m}{}",
|
||||||
|
|
|
@ -82,7 +82,7 @@ pub fn exec(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
with_project(directory.as_deref(), deny, |p| {
|
with_project(directory.as_deref(), deny, false, |p| {
|
||||||
p.build(
|
p.build(
|
||||||
uplc,
|
uplc,
|
||||||
match trace_filter {
|
match trace_filter {
|
||||||
|
|
|
@ -5,10 +5,108 @@ use aiken_lang::{
|
||||||
};
|
};
|
||||||
use aiken_project::watch::{self, watch_project, with_project};
|
use aiken_project::watch::{self, watch_project, with_project};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{path::PathBuf, process};
|
use std::{
|
||||||
|
io::{self, IsTerminal},
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
/// Type-check an Aiken project
|
#[command(
|
||||||
|
verbatim_doc_comment,
|
||||||
|
about = color_print::cstr!(r#"
|
||||||
|
Type-check an Aiken project and run any tests found.
|
||||||
|
|
||||||
|
Test results are printed as stylized outputs when `stdout` is a TTY-capable terminal. If it
|
||||||
|
isn't, (e.g. because you are redirecting the output to a file), test results are printed as
|
||||||
|
a JSON structured object. Use `--help` to see the whole schema.
|
||||||
|
"#),
|
||||||
|
after_long_help = color_print::cstr!(r#"<bold><underline>Output JSON schema:</underline></bold>
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>seed</bold>: <cyan>&type_integer</cyan>
|
||||||
|
<bold>type</bold>: integer
|
||||||
|
<bold>summary</bold>:
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>properties</bold>: <cyan>&type_summary</cyan>
|
||||||
|
<bold>total</bold>: *type_integer
|
||||||
|
<bold>passed</bold>: *type_integer
|
||||||
|
<bold>failed</bold>: *type_integer
|
||||||
|
<bold>kind</bold>:
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>unit</bold>: *type_integer
|
||||||
|
<bold>property</bold>: *type_integer
|
||||||
|
<bold>modules</bold>:
|
||||||
|
<bold>type</bold>: array
|
||||||
|
<bold>items</bold>:
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>name</bold>: <cyan>&type_string</cyan>
|
||||||
|
<bold>type</bold>: string
|
||||||
|
<bold>summary</bold>: *type_summary
|
||||||
|
<bold>test</bold>:
|
||||||
|
<bold>type</bold>: array
|
||||||
|
<bold>items</bold>:
|
||||||
|
<bold>oneOf</bold>:
|
||||||
|
- <bold>type</bold>: object
|
||||||
|
<bold>required</bold>:
|
||||||
|
- kind
|
||||||
|
- title
|
||||||
|
- status
|
||||||
|
- on_failure
|
||||||
|
- execution_units
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>kind</bold>
|
||||||
|
<bold>type</bold>: string
|
||||||
|
<bold>enum</bold>: [ "unit" ]
|
||||||
|
<bold>title</bold>: *type_string
|
||||||
|
<bold>status</bold>: <cyan>&type_status</cyan>
|
||||||
|
<bold>type</bold>: string
|
||||||
|
<bold>enum</bold>: [ "pass", "fail" ]
|
||||||
|
<bold>on_failure</bold>: <cyan>&type_on_failure</cyan>
|
||||||
|
<bold>type</bold>: string
|
||||||
|
<bold>enum</bold>:
|
||||||
|
- fail_immediately
|
||||||
|
- succeed_immediately
|
||||||
|
- succeed_eventually
|
||||||
|
<bold>execution_units</bold>:
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>mem</bold>: *type_integer
|
||||||
|
<bold>cpu</bold>: *type_integer
|
||||||
|
<bold>assertion</bold>: *type_string
|
||||||
|
- <bold>type</bold>: object
|
||||||
|
<bold>required</bold>:
|
||||||
|
- kind
|
||||||
|
- title
|
||||||
|
- status
|
||||||
|
- on_failure
|
||||||
|
- iterations
|
||||||
|
- counterexample
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>kind</bold>
|
||||||
|
<bold>type</bold>: string
|
||||||
|
<bold>enum</bold>: [ "property" ]
|
||||||
|
<bold>title</bold>: *type_string
|
||||||
|
<bold>status</bold>: *type_status
|
||||||
|
<bold>on_failure</bold>: *type_on_failure
|
||||||
|
<bold>iterations</bold>: *type_integer
|
||||||
|
<bold>labels</bold>:
|
||||||
|
<bold>type</bold>: object
|
||||||
|
<bold>additionalProperties</bold>: *type_integer
|
||||||
|
<bold>counterexample</bold>:
|
||||||
|
<bold>oneOf</bold>:
|
||||||
|
- *type_string
|
||||||
|
- <bold>type</bold>: "null"
|
||||||
|
- <bold>type</bold>: object
|
||||||
|
<bold>properties</bold>:
|
||||||
|
<bold>error</bold>: *type_string
|
||||||
|
|
||||||
|
<bold><underline>Note:</underline></bold>
|
||||||
|
You are seeing the extended help. Use `-h` instead of `--help` for a more compact view.
|
||||||
|
"#
|
||||||
|
))]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Path to project
|
/// Path to project
|
||||||
directory: Option<PathBuf>,
|
directory: Option<PathBuf>,
|
||||||
|
@ -40,7 +138,7 @@ pub struct Args {
|
||||||
/// 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}"`
|
||||||
#[clap(short, long)]
|
#[clap(short, long, verbatim_doc_comment)]
|
||||||
match_tests: Option<Vec<String>>,
|
match_tests: Option<Vec<String>>,
|
||||||
|
|
||||||
/// This is meant to be used with `--match-tests`.
|
/// This is meant to be used with `--match-tests`.
|
||||||
|
@ -72,14 +170,9 @@ pub struct Args {
|
||||||
|
|
||||||
/// Choose the verbosity level of traces:
|
/// Choose the verbosity level of traces:
|
||||||
///
|
///
|
||||||
/// - silent:
|
/// - silent: disable traces altogether
|
||||||
/// disable traces altogether
|
/// - compact: only culprit line numbers are shown on failures
|
||||||
///
|
/// - verbose: enable full verbose traces as provided by the user or the compiler
|
||||||
/// - compact:
|
|
||||||
/// only culprit line numbers are shown on failures
|
|
||||||
///
|
|
||||||
/// - verbose:
|
|
||||||
/// enable full verbose traces as provided by the user or the compiler
|
|
||||||
///
|
///
|
||||||
/// [optional]
|
/// [optional]
|
||||||
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Verbose, verbatim_doc_comment)]
|
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Verbose, verbatim_doc_comment)]
|
||||||
|
@ -123,7 +216,11 @@ pub fn exec(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
with_project(directory.as_deref(), deny, |p| {
|
with_project(
|
||||||
|
directory.as_deref(),
|
||||||
|
deny,
|
||||||
|
!io::stdout().is_terminal(),
|
||||||
|
|p| {
|
||||||
p.check(
|
p.check(
|
||||||
skip_tests,
|
skip_tests,
|
||||||
match_tests.clone(),
|
match_tests.clone(),
|
||||||
|
@ -137,7 +234,8 @@ pub fn exec(
|
||||||
},
|
},
|
||||||
env.clone(),
|
env.clone(),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
result.map_err(|_| process::exit(1))
|
result.map_err(|_| process::exit(1))
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub fn exec(
|
||||||
p.docs(destination.clone(), include_dependencies)
|
p.docs(destination.clone(), include_dependencies)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
with_project(directory.as_deref(), deny, |p| {
|
with_project(directory.as_deref(), deny, false, |p| {
|
||||||
p.docs(destination.clone(), include_dependencies)
|
p.docs(destination.clone(), include_dependencies)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub fn exec(
|
||||||
trace_level,
|
trace_level,
|
||||||
}: Args,
|
}: Args,
|
||||||
) -> miette::Result<()> {
|
) -> miette::Result<()> {
|
||||||
with_project(directory.as_deref(), false, |p| {
|
with_project(directory.as_deref(), false, false, |p| {
|
||||||
p.compile(Options::default())?;
|
p.compile(Options::default())?;
|
||||||
|
|
||||||
let export = p.export(
|
let export = p.export(
|
||||||
|
|
Loading…
Reference in New Issue