306 lines
9.6 KiB
Rust
306 lines
9.6 KiB
Rust
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());
|
|
}
|
|
Event::FinishedBenchmarks { benchmarks, seed } => {
|
|
let benchmark_results: Vec<_> = benchmarks
|
|
.into_iter()
|
|
.filter_map(|test| {
|
|
if let TestResult::BenchmarkResult(result) = test {
|
|
Some(serde_json::json!({
|
|
"name": result.bench.name,
|
|
"module": result.bench.module,
|
|
"measures": result.measures
|
|
.into_iter()
|
|
.map(|measure| serde_json::json!({
|
|
"size": measure.0,
|
|
"memory": measure.1.mem,
|
|
"cpu": measure.1.cpu
|
|
}))
|
|
.collect::<Vec<_>>()
|
|
}))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let json = serde_json::json!({
|
|
"benchmarks": benchmark_results,
|
|
"seed": seed,
|
|
});
|
|
|
|
println!("{}", serde_json::to_string_pretty(&json).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
|
|
}
|
|
TestResult::BenchmarkResult(_) => unreachable!("benchmark returned in JSON output"),
|
|
};
|
|
|
|
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()}),
|
|
};
|
|
}
|
|
TestResult::BenchmarkResult(_) => unreachable!("benchmark returned in JSON output"),
|
|
}
|
|
|
|
if !result.logs().is_empty() {
|
|
test["traces"] = json!(result.logs());
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
pub fn json_schema() -> serde_json::Value {
|
|
let definitions = json!({
|
|
"Summary": {
|
|
"type": "object",
|
|
"required": ["total", "passed", "failed", "kind"],
|
|
"properties": {
|
|
"total": { "type": "integer" },
|
|
"passed": { "type": "integer" },
|
|
"failed": { "type": "integer" },
|
|
"kind": {
|
|
"type": "object",
|
|
"required": ["unit", "property"],
|
|
"properties": {
|
|
"unit": { "type": "integer" },
|
|
"property": { "type": "integer" }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"Status": {
|
|
"type": "string",
|
|
"enum": [ "pass", "fail" ]
|
|
},
|
|
"OnFailure": {
|
|
"type": "string",
|
|
"enum": [
|
|
"fail_immediately",
|
|
"succeed_immediately",
|
|
"succeed_eventually"
|
|
]
|
|
}
|
|
});
|
|
|
|
let unit_test = json!({
|
|
"type": "object",
|
|
"required": [
|
|
"kind",
|
|
"title",
|
|
"status",
|
|
"on_failure",
|
|
"execution_units"
|
|
],
|
|
"properties": {
|
|
"kind": {
|
|
"type": "string",
|
|
"enum": [ "unit" ]
|
|
},
|
|
"title": { "type": "string" },
|
|
"status": { "$ref": "#/properties/definitions/Status" },
|
|
"on_failure": { "$ref": "#/properties/definitions/OnFailure" },
|
|
"execution_units": {
|
|
"type": "object",
|
|
"properties": {
|
|
"mem": { "type": "integer" },
|
|
"cpu": { "type": "integer" }
|
|
}
|
|
},
|
|
"assertion": { "type": "string" },
|
|
}
|
|
});
|
|
|
|
let property_test = json!({
|
|
"type": "object",
|
|
"required": [
|
|
"kind",
|
|
"title",
|
|
"status",
|
|
"on_failure",
|
|
"iterations",
|
|
"counterexample"
|
|
],
|
|
"properties": {
|
|
"kind": {
|
|
"type": "string",
|
|
"enum": [ "property" ]
|
|
},
|
|
"title": { "type": "string" },
|
|
"status": { "$ref": "#/properties/definitions/Status" },
|
|
"on_failure": { "$ref": "#/properties/definitions/OnFailure" },
|
|
"iterations": { "type": "integer" },
|
|
"labels": {
|
|
"type": "object",
|
|
"additionalProperties": { "type": "integer" }
|
|
},
|
|
"counterexample": {
|
|
"oneOf": [
|
|
{ "type": "string" },
|
|
{ "type": "null" },
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"error": { "type": "string" }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
});
|
|
|
|
json!({
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$vocabulary": {
|
|
"https://json-schema.org/draft/2020-12/vocab/core": true,
|
|
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
|
|
"https://json-schema.org/draft/2020-12/vocab/validation": true
|
|
},
|
|
"title": "Aiken CLI JSON Schema",
|
|
"type": "object",
|
|
"properties": {
|
|
"command[check]": {
|
|
"seed": { "type": "integer" },
|
|
"summary": { "$ref": "#/properties/definitions/Summary" },
|
|
"modules": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": { "type": "string" },
|
|
"summary": { "$ref": "#/properties/definitions/Summary" },
|
|
"test": {
|
|
"type": "array",
|
|
"items": {
|
|
"oneOf": [ unit_test, property_test ]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"definitions": definitions
|
|
}
|
|
})
|
|
}
|