aiken/crates/aiken-project/src/telemetry/json.rs

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