compiletest/executor/
json.rs

1//! Collects statistics and emits suite/test events as JSON messages, using
2//! the same JSON format as libtest's JSON formatter.
3//!
4//! These messages are then parsed by bootstrap, which replaces them with
5//! user-friendly terminal output.
6
7use std::time::Instant;
8
9use serde_json::json;
10
11use crate::executor::{CollectedTest, TestCompletion, TestOutcome};
12
13pub(crate) struct Listener {
14    suite_start: Option<Instant>,
15    passed: usize,
16    failed: usize,
17    ignored: usize,
18    filtered_out: usize,
19}
20
21impl Listener {
22    pub(crate) fn new() -> Self {
23        Self { suite_start: None, passed: 0, failed: 0, ignored: 0, filtered_out: 0 }
24    }
25
26    fn print_message(&self, message: &serde_json::Value) {
27        println!("{message}");
28    }
29
30    fn now(&self) -> Instant {
31        Instant::now()
32    }
33
34    pub(crate) fn suite_started(&mut self, test_count: usize, filtered_out: usize) {
35        self.suite_start = Some(self.now());
36        self.filtered_out = filtered_out;
37        let message = json!({ "type": "suite", "event": "started", "test_count": test_count });
38        self.print_message(&message);
39    }
40
41    pub(crate) fn test_started(&mut self, test: &CollectedTest) {
42        let name = test.desc.name.as_str();
43        let message = json!({ "type": "test", "event": "started", "name": name });
44        self.print_message(&message);
45    }
46
47    pub(crate) fn test_timed_out(&mut self, test: &CollectedTest) {
48        let name = test.desc.name.as_str();
49        let message = json!({ "type": "test", "event": "timeout", "name": name });
50        self.print_message(&message);
51    }
52
53    pub(crate) fn test_finished(&mut self, test: &CollectedTest, completion: &TestCompletion) {
54        let event;
55        let name = test.desc.name.as_str();
56        let mut maybe_message = None;
57        let maybe_stdout = completion.stdout.as_deref().map(String::from_utf8_lossy);
58
59        match completion.outcome {
60            TestOutcome::Succeeded => {
61                self.passed += 1;
62                event = "ok";
63            }
64            TestOutcome::Failed { message } => {
65                self.failed += 1;
66                maybe_message = message;
67                event = "failed";
68            }
69            TestOutcome::Ignored => {
70                self.ignored += 1;
71                maybe_message = test.desc.ignore_message.as_deref();
72                event = "ignored";
73            }
74        };
75
76        // This emits optional fields as `null`, instead of omitting them
77        // completely as libtest does, but bootstrap can parse the result
78        // either way.
79        let json = json!({
80            "type": "test",
81            "event": event,
82            "name": name,
83            "message": maybe_message,
84            "stdout": maybe_stdout,
85        });
86
87        self.print_message(&json);
88    }
89
90    pub(crate) fn suite_finished(&mut self) -> bool {
91        let exec_time = self.suite_start.map(|start| (self.now() - start).as_secs_f64());
92        let suite_passed = self.failed == 0;
93
94        let event = if suite_passed { "ok" } else { "failed" };
95        let message = json!({
96            "type": "suite",
97            "event": event,
98            "passed": self.passed,
99            "failed": self.failed,
100            "ignored": self.ignored,
101            // Compiletest doesn't run any benchmarks, but we still need to set this
102            // field to 0 so that bootstrap's JSON parser can read our message.
103            "measured": 0,
104            "filtered_out": self.filtered_out,
105            "exec_time": exec_time,
106        });
107
108        self.print_message(&message);
109        suite_passed
110    }
111}