Cherry picked cef3276521ba005fa30af46130dba2836347b1ae

This commit is contained in:
Riley-Kilgore 2024-09-16 08:29:40 -07:00 committed by KtorZ
parent cd42f51f1a
commit 8ac09025f5
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
13 changed files with 246 additions and 89 deletions

View File

@ -38,6 +38,7 @@ impl LspProject {
PropertyTest::DEFAULT_MAX_SUCCESS, PropertyTest::DEFAULT_MAX_SUCCESS,
Tracing::verbose(), Tracing::verbose(),
None, None,
false,
); );
self.project.restore(checkpoint); self.project.restore(checkpoint);

View File

@ -197,11 +197,13 @@ where
uplc: bool, uplc: bool,
tracing: Tracing, tracing: Tracing,
env: Option<String>, env: Option<String>,
json: bool,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let options = Options { let options = Options {
code_gen_mode: CodeGenMode::Build(uplc), code_gen_mode: CodeGenMode::Build(uplc),
tracing, tracing,
env, env,
json,
}; };
self.compile(options) self.compile(options)
@ -225,7 +227,7 @@ where
let mut modules = self.parse_sources(self.config.name.clone())?; 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")); let destination = destination.unwrap_or_else(|| self.root.join("docs"));
@ -267,6 +269,7 @@ where
property_max_success: usize, property_max_success: usize,
tracing: Tracing, tracing: Tracing,
env: Option<String>, env: Option<String>,
json: bool,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let options = Options { let options = Options {
tracing, tracing,
@ -282,6 +285,7 @@ where
property_max_success, property_max_success,
} }
}, },
json: json,
}; };
self.compile(options) self.compile(options)
@ -343,6 +347,7 @@ where
root: self.root.clone(), root: self.root.clone(),
name: self.config.name.to_string(), name: self.config.name.to_string(),
version: self.config.version.clone(), version: self.config.version.clone(),
json: options.json,
}); });
let env = options.env.as_deref(); let env = options.env.as_deref();
@ -353,7 +358,7 @@ where
let mut modules = self.parse_sources(self.config.name.clone())?; 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 { match options.code_gen_mode {
CodeGenMode::Build(uplc_dump) => { CodeGenMode::Build(uplc_dump) => {
@ -400,7 +405,8 @@ where
self.collect_tests(verbose, match_tests, exact_match, options.tracing)?; self.collect_tests(verbose, match_tests, exact_match, options.tracing)?;
if !tests.is_empty() { 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); let tests = self.run_tests(tests, seed, property_max_success);
@ -427,8 +433,11 @@ where
}) })
.collect(); .collect();
self.event_listener self.event_listener.handle_event(Event::FinishedTests {
.handle_event(Event::FinishedTests { seed, tests }); seed,
tests,
json: options.json,
});
if !errors.is_empty() { if !errors.is_empty() {
Err(errors) Err(errors)
@ -637,7 +646,11 @@ where
Ok(blueprint) 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)?; let manifest = deps::download(&self.event_listener, &self.root, &self.config)?;
for package in manifest.packages { for package in manifest.packages {
@ -648,6 +661,7 @@ where
root: lib.clone(), root: lib.clone(),
name: package.name.to_string(), name: package.name.to_string(),
version: package.version.clone(), version: package.version.clone(),
json,
}); });
self.read_package_source_files(&lib.join("lib"))?; self.read_package_source_files(&lib.join("lib"))?;
@ -836,10 +850,11 @@ where
tracing: Tracing, tracing: Tracing,
env: Option<&str>, env: Option<&str>,
validate_module_name: bool, validate_module_name: bool,
json: bool,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let our_modules: BTreeSet<String> = modules.keys().cloned().collect(); 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)? { for name in modules.sequence(&our_modules)? {
if let Some(module) = modules.remove(&name) { if let Some(module) = modules.remove(&name) {

View File

@ -4,6 +4,7 @@ pub struct Options {
pub code_gen_mode: CodeGenMode, pub code_gen_mode: CodeGenMode,
pub tracing: Tracing, pub tracing: Tracing,
pub env: Option<String>, pub env: Option<String>,
pub json: bool,
} }
impl Default for Options { impl Default for Options {
@ -12,6 +13,7 @@ impl Default for Options {
code_gen_mode: CodeGenMode::NoOp, code_gen_mode: CodeGenMode::NoOp,
tracing: Tracing::silent(), tracing: Tracing::silent(),
env: None, env: None,
json: false,
} }
} }
} }

View File

@ -6,6 +6,7 @@ use aiken_lang::{
test_framework::{PropertyTestResult, TestResult, UnitTestResult}, test_framework::{PropertyTestResult, TestResult, UnitTestResult},
}; };
use owo_colors::{OwoColorize, Stream::Stderr}; use owo_colors::{OwoColorize, Stream::Stderr};
use serde_json::json;
use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
use uplc::machine::cost_model::ExBudget; use uplc::machine::cost_model::ExBudget;
@ -18,6 +19,7 @@ pub enum Event {
name: String, name: String,
version: String, version: String,
root: PathBuf, root: PathBuf,
json: bool,
}, },
BuildingDocumentation { BuildingDocumentation {
name: String, name: String,
@ -37,10 +39,13 @@ pub enum Event {
name: String, name: String,
path: PathBuf, path: PathBuf,
}, },
RunningTests, RunningTests {
json: bool,
},
FinishedTests { FinishedTests {
seed: u32, seed: u32,
tests: Vec<TestResult<UntypedExpr, UntypedExpr>>, tests: Vec<TestResult<UntypedExpr, UntypedExpr>>,
json: bool,
}, },
WaitingForBuildDirLock, WaitingForBuildDirLock,
ResolvingPackages { ResolvingPackages {
@ -81,7 +86,9 @@ impl EventListener for Terminal {
name, name,
version, version,
root, root,
json,
} => { } => {
if !json {
eprintln!( eprintln!(
"{} {} {} ({})", "{} {} {} ({})",
" Compiling" " Compiling"
@ -93,6 +100,7 @@ impl EventListener for Terminal {
.if_supports_color(Stderr, |s| s.bright_blue()) .if_supports_color(Stderr, |s| s.bright_blue())
); );
} }
}
Event::BuildingDocumentation { Event::BuildingDocumentation {
name, name,
version, version,
@ -169,18 +177,34 @@ impl EventListener for Terminal {
name.if_supports_color(Stderr, |s| s.bright_blue()), name.if_supports_color(Stderr, |s| s.bright_blue()),
); );
} }
Event::RunningTests => { Event::RunningTests { json } => {
if !json {
eprintln!( eprintln!(
"{} {}", "{} {}\n",
" Testing" " Testing"
.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()),
"...".if_supports_color(Stderr, |s| s.bold()) "...".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); let (max_mem, max_cpu, max_iter) = find_max_execution_units(&tests);
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) { for (module, results) in &group_by_module(&tests) {
let title = module let title = module
.if_supports_color(Stderr, |s| s.bold()) .if_supports_color(Stderr, |s| s.bold())
@ -208,7 +232,7 @@ impl EventListener for Terminal {
let summary = format!("{}{}", seed_info, fmt_test_summary(results, true)); let summary = format!("{}{}", seed_info, fmt_test_summary(results, true));
println!( println!(
"\n{}", "{}\n",
pretty::indent( pretty::indent(
&pretty::open_box(&title, &tests, &summary, |border| border &pretty::open_box(&title, &tests, &summary, |border| border
.if_supports_color(Stderr, |s| s.bright_black()) .if_supports_color(Stderr, |s| s.bright_black())
@ -217,6 +241,7 @@ impl EventListener for Terminal {
) )
); );
} }
}
if !tests.is_empty() { if !tests.is_empty() {
println!(); println!();
@ -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(); 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();

View File

@ -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 where
A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>, A: FnMut(&mut Project<Terminal>) -> Result<(), Vec<crate::error::Error>>,
{ {
@ -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())
@ -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(())
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -79,10 +79,11 @@ pub fn exec(
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(), env.clone(),
false,
) )
}) })
} 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 {
@ -90,6 +91,7 @@ pub fn exec(
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(), env.clone(),
false,
) )
}) })
}; };

View File

@ -84,6 +84,10 @@ pub struct Args {
/// [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)]
trace_level: TraceLevel, trace_level: TraceLevel,
/// Output JSON (useful for scripting & automation)
#[clap(long)]
json: bool,
} }
pub fn exec( pub fn exec(
@ -100,6 +104,7 @@ pub fn exec(
seed, seed,
max_success, max_success,
env, env,
json,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -120,10 +125,11 @@ pub fn exec(
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(), env.clone(),
json,
) )
}) })
} else { } else {
with_project(directory.as_deref(), deny, |p| { with_project(directory.as_deref(), deny, json, |p| {
p.check( p.check(
skip_tests, skip_tests,
match_tests.clone(), match_tests.clone(),
@ -136,6 +142,7 @@ pub fn exec(
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(), env.clone(),
json,
) )
}) })
}; };

View File

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

View File

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