Cherry picked cef3276521ba005fa30af46130dba2836347b1ae
This commit is contained in:
parent
cd42f51f1a
commit
8ac09025f5
|
@ -38,6 +38,7 @@ impl LspProject {
|
|||
PropertyTest::DEFAULT_MAX_SUCCESS,
|
||||
Tracing::verbose(),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
self.project.restore(checkpoint);
|
||||
|
|
|
@ -197,11 +197,13 @@ where
|
|||
uplc: bool,
|
||||
tracing: Tracing,
|
||||
env: Option<String>,
|
||||
json: bool,
|
||||
) -> Result<(), Vec<Error>> {
|
||||
let options = Options {
|
||||
code_gen_mode: CodeGenMode::Build(uplc),
|
||||
tracing,
|
||||
env,
|
||||
json,
|
||||
};
|
||||
|
||||
self.compile(options)
|
||||
|
@ -225,7 +227,7 @@ where
|
|||
|
||||
let mut modules = self.parse_sources(self.config.name.clone())?;
|
||||
|
||||
self.type_check(&mut modules, Tracing::silent(), None, false)?;
|
||||
self.type_check(&mut modules, Tracing::silent(), None, false, false)?;
|
||||
|
||||
let destination = destination.unwrap_or_else(|| self.root.join("docs"));
|
||||
|
||||
|
@ -267,6 +269,7 @@ where
|
|||
property_max_success: usize,
|
||||
tracing: Tracing,
|
||||
env: Option<String>,
|
||||
json: bool,
|
||||
) -> Result<(), Vec<Error>> {
|
||||
let options = Options {
|
||||
tracing,
|
||||
|
@ -282,6 +285,7 @@ where
|
|||
property_max_success,
|
||||
}
|
||||
},
|
||||
json: json,
|
||||
};
|
||||
|
||||
self.compile(options)
|
||||
|
@ -343,6 +347,7 @@ where
|
|||
root: self.root.clone(),
|
||||
name: self.config.name.to_string(),
|
||||
version: self.config.version.clone(),
|
||||
json: options.json,
|
||||
});
|
||||
|
||||
let env = options.env.as_deref();
|
||||
|
@ -353,7 +358,7 @@ where
|
|||
|
||||
let mut modules = self.parse_sources(self.config.name.clone())?;
|
||||
|
||||
self.type_check(&mut modules, options.tracing, env, true)?;
|
||||
self.type_check(&mut modules, options.tracing, env, true, options.json)?;
|
||||
|
||||
match options.code_gen_mode {
|
||||
CodeGenMode::Build(uplc_dump) => {
|
||||
|
@ -400,7 +405,8 @@ where
|
|||
self.collect_tests(verbose, match_tests, exact_match, options.tracing)?;
|
||||
|
||||
if !tests.is_empty() {
|
||||
self.event_listener.handle_event(Event::RunningTests);
|
||||
self.event_listener
|
||||
.handle_event(Event::RunningTests { json: options.json });
|
||||
}
|
||||
|
||||
let tests = self.run_tests(tests, seed, property_max_success);
|
||||
|
@ -427,8 +433,11 @@ where
|
|||
})
|
||||
.collect();
|
||||
|
||||
self.event_listener
|
||||
.handle_event(Event::FinishedTests { seed, tests });
|
||||
self.event_listener.handle_event(Event::FinishedTests {
|
||||
seed,
|
||||
tests,
|
||||
json: options.json,
|
||||
});
|
||||
|
||||
if !errors.is_empty() {
|
||||
Err(errors)
|
||||
|
@ -637,7 +646,11 @@ where
|
|||
Ok(blueprint)
|
||||
}
|
||||
|
||||
fn with_dependencies(&mut self, parsed_packages: &mut ParsedModules) -> Result<(), Vec<Error>> {
|
||||
fn with_dependencies(
|
||||
&mut self,
|
||||
parsed_packages: &mut ParsedModules,
|
||||
json: bool,
|
||||
) -> Result<(), Vec<Error>> {
|
||||
let manifest = deps::download(&self.event_listener, &self.root, &self.config)?;
|
||||
|
||||
for package in manifest.packages {
|
||||
|
@ -648,6 +661,7 @@ where
|
|||
root: lib.clone(),
|
||||
name: package.name.to_string(),
|
||||
version: package.version.clone(),
|
||||
json,
|
||||
});
|
||||
|
||||
self.read_package_source_files(&lib.join("lib"))?;
|
||||
|
@ -836,10 +850,11 @@ where
|
|||
tracing: Tracing,
|
||||
env: Option<&str>,
|
||||
validate_module_name: bool,
|
||||
json: bool,
|
||||
) -> Result<(), Vec<Error>> {
|
||||
let our_modules: BTreeSet<String> = modules.keys().cloned().collect();
|
||||
|
||||
self.with_dependencies(modules)?;
|
||||
self.with_dependencies(modules, json)?;
|
||||
|
||||
for name in modules.sequence(&our_modules)? {
|
||||
if let Some(module) = modules.remove(&name) {
|
||||
|
|
|
@ -4,6 +4,7 @@ pub struct Options {
|
|||
pub code_gen_mode: CodeGenMode,
|
||||
pub tracing: Tracing,
|
||||
pub env: Option<String>,
|
||||
pub json: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
|
@ -12,6 +13,7 @@ impl Default for Options {
|
|||
code_gen_mode: CodeGenMode::NoOp,
|
||||
tracing: Tracing::silent(),
|
||||
env: None,
|
||||
json: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use aiken_lang::{
|
|||
test_framework::{PropertyTestResult, TestResult, UnitTestResult},
|
||||
};
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
use serde_json::json;
|
||||
use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
|
||||
use uplc::machine::cost_model::ExBudget;
|
||||
|
||||
|
@ -18,6 +19,7 @@ pub enum Event {
|
|||
name: String,
|
||||
version: String,
|
||||
root: PathBuf,
|
||||
json: bool,
|
||||
},
|
||||
BuildingDocumentation {
|
||||
name: String,
|
||||
|
@ -37,10 +39,13 @@ pub enum Event {
|
|||
name: String,
|
||||
path: PathBuf,
|
||||
},
|
||||
RunningTests,
|
||||
RunningTests {
|
||||
json: bool,
|
||||
},
|
||||
FinishedTests {
|
||||
seed: u32,
|
||||
tests: Vec<TestResult<UntypedExpr, UntypedExpr>>,
|
||||
json: bool,
|
||||
},
|
||||
WaitingForBuildDirLock,
|
||||
ResolvingPackages {
|
||||
|
@ -81,17 +86,20 @@ impl EventListener for Terminal {
|
|||
name,
|
||||
version,
|
||||
root,
|
||||
json,
|
||||
} => {
|
||||
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())
|
||||
);
|
||||
if !json {
|
||||
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,
|
||||
|
@ -169,53 +177,70 @@ impl EventListener for Terminal {
|
|||
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::RunningTests { json } => {
|
||||
if !json {
|
||||
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 } => {
|
||||
Event::FinishedTests { seed, tests, json } => {
|
||||
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();
|
||||
if json {
|
||||
let json_output = serde_json::json!({
|
||||
"seed": seed,
|
||||
"modules": group_by_module(&tests).iter().map(|(module, results)| {
|
||||
serde_json::json!({
|
||||
"name": module,
|
||||
"tests": results.iter().map(|r| fmt_test_json(r, max_mem, max_cpu, max_iter)).collect::<Vec<_>>(),
|
||||
"summary": fmt_test_summary_json(results)
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"summary": fmt_overall_summary_json(&tests)
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&json_output).unwrap());
|
||||
} else {
|
||||
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 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 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
|
||||
)
|
||||
);
|
||||
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() {
|
||||
|
@ -495,7 +520,105 @@ fn fmt_test_summary<T>(tests: &[&TestResult<T, T>], styled: bool) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
fn group_by_module<T>(results: &Vec<TestResult<T, T>>) -> BTreeMap<String, Vec<&TestResult<T, T>>> {
|
||||
fn fmt_test_json(
|
||||
result: &TestResult<UntypedExpr, UntypedExpr>,
|
||||
max_mem: usize,
|
||||
max_cpu: usize,
|
||||
max_iter: usize,
|
||||
) -> serde_json::Value {
|
||||
let mut test = json!({
|
||||
"name": result.title(),
|
||||
"status": if result.is_success() { "PASS" } else { "FAIL" },
|
||||
});
|
||||
|
||||
match result {
|
||||
TestResult::UnitTestResult(UnitTestResult {
|
||||
spent_budget,
|
||||
assertion,
|
||||
test: unit_test,
|
||||
..
|
||||
}) => {
|
||||
test["execution_units"] = json!({
|
||||
"memory": spent_budget.mem,
|
||||
"cpu": spent_budget.cpu,
|
||||
});
|
||||
if !result.is_success() {
|
||||
if let Some(assertion) = assertion {
|
||||
test["assertion"] = json!({
|
||||
"message": assertion.to_string(Stderr, false),
|
||||
"expected_to_fail": matches!(unit_test.on_test_failure, OnTestFailure::SucceedEventually | OnTestFailure::SucceedImmediately),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
TestResult::PropertyTestResult(PropertyTestResult {
|
||||
iterations,
|
||||
labels,
|
||||
counterexample,
|
||||
..
|
||||
}) => {
|
||||
test["iterations"] = json!(iterations);
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
fn fmt_overall_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;
|
||||
|
||||
let modules = group_by_module(tests);
|
||||
let module_count = modules.len();
|
||||
|
||||
let (max_mem, max_cpu, max_iter) = find_max_execution_units(tests);
|
||||
|
||||
json!({
|
||||
"total_tests": total,
|
||||
"passed_tests": passed,
|
||||
"failed_tests": failed,
|
||||
"module_count": module_count,
|
||||
"max_execution_units": {
|
||||
"memory": max_mem,
|
||||
"cpu": max_cpu,
|
||||
},
|
||||
"max_iterations": max_iter,
|
||||
"modules": modules.into_iter().map(|(module, results)| {
|
||||
json!({
|
||||
"name": module,
|
||||
"tests": results.iter().map(|r| fmt_test_json(r, max_mem, max_cpu, max_iter)).collect::<Vec<_>>(),
|
||||
"summary": fmt_test_summary_json(&results),
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
|
||||
fn group_by_module(
|
||||
results: &[TestResult<UntypedExpr, UntypedExpr>],
|
||||
) -> BTreeMap<String, Vec<&TestResult<UntypedExpr, UntypedExpr>>> {
|
||||
let mut modules = BTreeMap::new();
|
||||
for r in results {
|
||||
let xs: &mut Vec<&TestResult<_, _>> = modules.entry(r.module().to_string()).or_default();
|
||||
|
|
|
@ -88,7 +88,12 @@ pub fn default_filter(evt: &Event) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_project<A>(directory: Option<&Path>, deny: bool, mut action: A) -> miette::Result<()>
|
||||
pub fn with_project<A>(
|
||||
directory: Option<&Path>,
|
||||
deny: bool,
|
||||
json: bool,
|
||||
mut action: A,
|
||||
) -> miette::Result<()>
|
||||
where
|
||||
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
|
||||
{
|
||||
|
@ -116,36 +121,38 @@ where
|
|||
|
||||
let warning_count = warnings.len();
|
||||
|
||||
for warning in &warnings {
|
||||
warning.report()
|
||||
}
|
||||
if !json {
|
||||
for warning in &warnings {
|
||||
warning.report()
|
||||
}
|
||||
|
||||
if let Err(errs) = build_result {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
if let Err(errs) = build_result {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count: project.checks_count,
|
||||
warning_count,
|
||||
error_count: errs.len(),
|
||||
}
|
||||
);
|
||||
|
||||
return Err(ExitFailure::into_report());
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count: project.checks_count,
|
||||
warning_count,
|
||||
error_count: errs.len(),
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
|
||||
return Err(ExitFailure::into_report());
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count: project.checks_count,
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
|
||||
if warning_count > 0 && deny {
|
||||
Err(ExitFailure::into_report())
|
||||
} else {
|
||||
|
@ -239,7 +246,7 @@ where
|
|||
.if_supports_color(Stderr, |s| s.bold())
|
||||
.if_supports_color(Stderr, |s| s.purple()),
|
||||
);
|
||||
with_project(directory, false, &mut action).unwrap_or(())
|
||||
with_project(directory, false, false, &mut action).unwrap_or(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn exec(
|
|||
mainnet,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
with_project(directory.as_deref(), false, false, |p| {
|
||||
let title = module.as_ref().map(|m| {
|
||||
format!(
|
||||
"{m}{}",
|
||||
|
|
|
@ -54,7 +54,7 @@ pub fn exec(
|
|||
validator,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(None, false, |p| {
|
||||
with_project(None, false, false, |p| {
|
||||
let title = module.as_ref().map(|m| {
|
||||
format!(
|
||||
"{m}{}",
|
||||
|
|
|
@ -23,7 +23,7 @@ pub fn exec(
|
|||
validator,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
with_project(directory.as_deref(), false, false, |p| {
|
||||
let title = module.as_ref().map(|m| {
|
||||
format!(
|
||||
"{m}{}",
|
||||
|
|
|
@ -23,7 +23,7 @@ pub fn exec(
|
|||
validator,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
with_project(directory.as_deref(), false, false, |p| {
|
||||
let title = module.as_ref().map(|m| {
|
||||
format!(
|
||||
"{m}{}",
|
||||
|
|
|
@ -79,10 +79,11 @@ pub fn exec(
|
|||
None => Tracing::All(trace_level),
|
||||
},
|
||||
env.clone(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
with_project(directory.as_deref(), deny, |p| {
|
||||
with_project(directory.as_deref(), deny, false, |p| {
|
||||
p.build(
|
||||
uplc,
|
||||
match trace_filter {
|
||||
|
@ -90,6 +91,7 @@ pub fn exec(
|
|||
None => Tracing::All(trace_level),
|
||||
},
|
||||
env.clone(),
|
||||
false,
|
||||
)
|
||||
})
|
||||
};
|
||||
|
|
|
@ -84,6 +84,10 @@ pub struct Args {
|
|||
/// [optional]
|
||||
#[clap(short, long, value_parser=trace_level_parser(), default_value_t=TraceLevel::Verbose, verbatim_doc_comment)]
|
||||
trace_level: TraceLevel,
|
||||
|
||||
/// Output JSON (useful for scripting & automation)
|
||||
#[clap(long)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
|
@ -100,6 +104,7 @@ pub fn exec(
|
|||
seed,
|
||||
max_success,
|
||||
env,
|
||||
json,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
@ -120,10 +125,11 @@ pub fn exec(
|
|||
None => Tracing::All(trace_level),
|
||||
},
|
||||
env.clone(),
|
||||
json,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
with_project(directory.as_deref(), deny, |p| {
|
||||
with_project(directory.as_deref(), deny, json, |p| {
|
||||
p.check(
|
||||
skip_tests,
|
||||
match_tests.clone(),
|
||||
|
@ -136,6 +142,7 @@ pub fn exec(
|
|||
None => Tracing::All(trace_level),
|
||||
},
|
||||
env.clone(),
|
||||
json,
|
||||
)
|
||||
})
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@ pub fn exec(
|
|||
p.docs(destination.clone(), include_dependencies)
|
||||
})
|
||||
} else {
|
||||
with_project(directory.as_deref(), deny, |p| {
|
||||
with_project(directory.as_deref(), deny, false, |p| {
|
||||
p.docs(destination.clone(), include_dependencies)
|
||||
})
|
||||
};
|
||||
|
|
|
@ -61,7 +61,7 @@ pub fn exec(
|
|||
trace_level,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory.as_deref(), false, |p| {
|
||||
with_project(directory.as_deref(), false, false, |p| {
|
||||
p.compile(Options::default())?;
|
||||
|
||||
let export = p.export(
|
||||
|
|
Loading…
Reference in New Issue