Skip to content

Commit 839bcd4

Browse files
authored
feat(json)!: Multi-error test support (#111)
2 parents f3968c3 + 10674e4 commit 839bcd4

12 files changed

Lines changed: 323 additions & 110 deletions

File tree

DESIGN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Decisions
3737
- Terse and pretty progress indicators are too nebulous to render (see their notifiers)
3838
- There is likely not enough value add in the failure message
3939
- This puts more of a burden on custom test harnesses for their implementation than is strictly needed
40+
- Report failures separate from test-complete so we can have multiple
4041

4142
### Prior Art
4243

crates/libtest-json/event.schema.json

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@
6767
"event"
6868
]
6969
},
70+
{
71+
"type": "object",
72+
"properties": {
73+
"event": {
74+
"type": "string",
75+
"const": "case_message"
76+
}
77+
},
78+
"$ref": "#/$defs/CaseMessage",
79+
"required": [
80+
"event"
81+
]
82+
},
7083
{
7184
"type": "object",
7285
"properties": {
@@ -200,35 +213,49 @@
200213
"name"
201214
]
202215
},
203-
"RunStatus": {
216+
"MessageKind": {
204217
"type": "string",
205218
"enum": [
206-
"ignored",
207-
"failed"
219+
"error",
220+
"ignored"
208221
]
209222
},
210-
"CaseComplete": {
223+
"CaseMessage": {
211224
"type": "object",
212225
"properties": {
213226
"name": {
214227
"type": "string"
215228
},
216-
"status": {
217-
"description": "`None` means success",
229+
"kind": {
230+
"$ref": "#/$defs/MessageKind"
231+
},
232+
"message": {
233+
"type": [
234+
"string",
235+
"null"
236+
]
237+
},
238+
"elapsed_s": {
218239
"anyOf": [
219240
{
220-
"$ref": "#/$defs/RunStatus"
241+
"$ref": "#/$defs/Elapsed"
221242
},
222243
{
223244
"type": "null"
224245
}
225246
]
226-
},
227-
"message": {
228-
"type": [
229-
"string",
230-
"null"
231-
]
247+
}
248+
},
249+
"required": [
250+
"name",
251+
"kind"
252+
]
253+
},
254+
"CaseComplete": {
255+
"type": "object",
256+
"properties": {
257+
"name": {
258+
"type": "string"
232259
},
233260
"elapsed_s": {
234261
"anyOf": [

crates/libtest-json/src/event.rs

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub enum Event {
99
DiscoverComplete(DiscoverComplete),
1010
RunStart(RunStart),
1111
CaseStart(CaseStart),
12+
CaseMessage(CaseMessage),
1213
CaseComplete(CaseComplete),
1314
RunComplete(RunComplete),
1415
}
@@ -22,6 +23,7 @@ impl Event {
2223
Self::DiscoverComplete(event) => event.to_jsonline(),
2324
Self::RunStart(event) => event.to_jsonline(),
2425
Self::CaseStart(event) => event.to_jsonline(),
26+
Self::CaseMessage(event) => event.to_jsonline(),
2527
Self::CaseComplete(event) => event.to_jsonline(),
2628
Self::RunComplete(event) => event.to_jsonline(),
2729
}
@@ -58,6 +60,12 @@ impl From<CaseStart> for Event {
5860
}
5961
}
6062

63+
impl From<CaseMessage> for Event {
64+
fn from(inner: CaseMessage) -> Self {
65+
Self::CaseMessage(inner)
66+
}
67+
}
68+
6169
impl From<CaseComplete> for Event {
6270
fn from(inner: CaseComplete) -> Self {
6371
Self::CaseComplete(inner)
@@ -296,14 +304,9 @@ impl CaseStart {
296304
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
297305
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
298306
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
299-
pub struct CaseComplete {
307+
pub struct CaseMessage {
300308
pub name: String,
301-
/// `None` means success
302-
#[cfg_attr(
303-
feature = "serde",
304-
serde(default, skip_serializing_if = "Option::is_none")
305-
)]
306-
pub status: Option<RunStatus>,
309+
pub kind: MessageKind,
307310
#[cfg_attr(
308311
feature = "serde",
309312
serde(default, skip_serializing_if = "Option::is_none")
@@ -316,7 +319,7 @@ pub struct CaseComplete {
316319
pub elapsed_s: Option<Elapsed>,
317320
}
318321

319-
impl CaseComplete {
322+
impl CaseMessage {
320323
#[cfg(feature = "json")]
321324
pub fn to_jsonline(&self) -> String {
322325
use json_write::JsonWrite as _;
@@ -326,19 +329,17 @@ impl CaseComplete {
326329

327330
buffer.key("event").unwrap();
328331
buffer.keyval_sep().unwrap();
329-
buffer.value("case_complete").unwrap();
332+
buffer.value("case_message").unwrap();
330333

331334
buffer.val_sep().unwrap();
332335
buffer.key("name").unwrap();
333336
buffer.keyval_sep().unwrap();
334337
buffer.value(&self.name).unwrap();
335338

336-
if let Some(status) = self.status {
337-
buffer.val_sep().unwrap();
338-
buffer.key("status").unwrap();
339-
buffer.keyval_sep().unwrap();
340-
buffer.value(status.as_str()).unwrap();
341-
}
339+
buffer.val_sep().unwrap();
340+
buffer.key("kind").unwrap();
341+
buffer.keyval_sep().unwrap();
342+
buffer.value(self.kind.as_str()).unwrap();
342343

343344
if let Some(message) = &self.message {
344345
buffer.val_sep().unwrap();
@@ -360,6 +361,49 @@ impl CaseComplete {
360361
}
361362
}
362363

364+
#[derive(Clone, Debug)]
365+
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
366+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
367+
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
368+
pub struct CaseComplete {
369+
pub name: String,
370+
#[cfg_attr(
371+
feature = "serde",
372+
serde(default, skip_serializing_if = "Option::is_none")
373+
)]
374+
pub elapsed_s: Option<Elapsed>,
375+
}
376+
377+
impl CaseComplete {
378+
#[cfg(feature = "json")]
379+
pub fn to_jsonline(&self) -> String {
380+
use json_write::JsonWrite as _;
381+
382+
let mut buffer = String::new();
383+
buffer.open_object().unwrap();
384+
385+
buffer.key("event").unwrap();
386+
buffer.keyval_sep().unwrap();
387+
buffer.value("case_complete").unwrap();
388+
389+
buffer.val_sep().unwrap();
390+
buffer.key("name").unwrap();
391+
buffer.keyval_sep().unwrap();
392+
buffer.value(&self.name).unwrap();
393+
394+
if let Some(elapsed_s) = self.elapsed_s {
395+
buffer.val_sep().unwrap();
396+
buffer.key("elapsed_s").unwrap();
397+
buffer.keyval_sep().unwrap();
398+
buffer.value(String::from(elapsed_s)).unwrap();
399+
}
400+
401+
buffer.close_object().unwrap();
402+
403+
buffer
404+
}
405+
}
406+
363407
#[derive(Clone, Debug)]
364408
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
365409
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -397,10 +441,12 @@ impl RunComplete {
397441
}
398442
}
399443

444+
#[cfg(feature = "serde")]
400445
fn true_default() -> bool {
401446
true
402447
}
403448

449+
#[cfg(feature = "serde")]
404450
fn is_true(yes: &bool) -> bool {
405451
*yes
406452
}
@@ -423,25 +469,27 @@ impl RunMode {
423469
}
424470
}
425471

472+
#[cfg(any(feature = "serde", feature = "json"))]
426473
fn is_default(&self) -> bool {
427474
*self == Default::default()
428475
}
429476
}
430477

431-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
478+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
432479
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
433480
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
434481
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
435-
pub enum RunStatus {
482+
pub enum MessageKind {
483+
// Highest precedent items for determining test status last
484+
Error,
436485
Ignored,
437-
Failed,
438486
}
439487

440-
impl RunStatus {
488+
impl MessageKind {
441489
pub fn as_str(&self) -> &str {
442490
match self {
491+
Self::Error => "error",
443492
Self::Ignored => "ignored",
444-
Self::Failed => "failed",
445493
}
446494
}
447495
}

crates/libtest-json/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ pub mod event;
99

1010
pub use event::Elapsed;
1111
pub use event::Event;
12+
pub use event::MessageKind;
1213
pub use event::RunMode;
13-
pub use event::RunStatus;
1414

1515
#[doc = include_str!("../README.md")]
1616
#[cfg(doctest)]

crates/libtest-json/tests/roundtrip.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,30 +102,49 @@ fn case_start() {
102102
}
103103

104104
#[test]
105-
fn case_complete() {
105+
fn case_message() {
106106
t(
107-
libtest_json::event::CaseComplete {
107+
libtest_json::event::CaseMessage {
108108
name: "Hello\tworld!".to_owned(),
109-
status: None,
109+
kind: libtest_json::MessageKind::Error,
110110
message: None,
111111
elapsed_s: None,
112112
},
113-
str![[r#"{"event":"case_complete","name":"Hello\tworld!"}"#]],
113+
str![[r#"{"event":"case_message","name":"Hello\tworld!","kind":"error"}"#]],
114114
);
115115

116116
t(
117-
libtest_json::event::CaseComplete {
117+
libtest_json::event::CaseMessage {
118118
name: "Hello\tworld!".to_owned(),
119-
status: Some(libtest_json::RunStatus::Ignored),
119+
kind: libtest_json::MessageKind::Ignored,
120120
message: Some("This\tfailed".to_owned()),
121121
elapsed_s: Some(libtest_json::Elapsed(Default::default())),
122122
},
123123
str![[
124-
r#"{"event":"case_complete","name":"Hello\tworld!","status":"ignored","message":"This\tfailed","elapsed_s":"0"}"#
124+
r#"{"event":"case_message","name":"Hello\tworld!","kind":"ignored","message":"This\tfailed","elapsed_s":"0"}"#
125125
]],
126126
);
127127
}
128128

129+
#[test]
130+
fn case_complete() {
131+
t(
132+
libtest_json::event::CaseComplete {
133+
name: "Hello\tworld!".to_owned(),
134+
elapsed_s: None,
135+
},
136+
str![[r#"{"event":"case_complete","name":"Hello\tworld!"}"#]],
137+
);
138+
139+
t(
140+
libtest_json::event::CaseComplete {
141+
name: "Hello\tworld!".to_owned(),
142+
elapsed_s: Some(libtest_json::Elapsed(Default::default())),
143+
},
144+
str![[r#"{"event":"case_complete","name":"Hello\tworld!","elapsed_s":"0"}"#]],
145+
);
146+
}
147+
129148
#[test]
130149
fn suite_complete() {
131150
t(

crates/libtest2-harness/src/case.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ pub type RunResult = Result<(), RunError>;
5353

5454
#[derive(Debug)]
5555
pub struct RunError {
56-
status: notify::RunStatus,
56+
status: notify::MessageKind,
5757
cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
5858
}
5959

6060
impl RunError {
6161
pub fn with_cause(cause: impl std::error::Error + Send + Sync + 'static) -> Self {
6262
Self {
63-
status: notify::RunStatus::Failed,
63+
status: notify::MessageKind::Error,
6464
cause: Some(Box::new(cause)),
6565
}
6666
}
@@ -71,19 +71,19 @@ impl RunError {
7171

7272
pub(crate) fn ignore() -> Self {
7373
Self {
74-
status: notify::RunStatus::Ignored,
74+
status: notify::MessageKind::Ignored,
7575
cause: None,
7676
}
7777
}
7878

7979
pub(crate) fn ignore_for(reason: String) -> Self {
8080
Self {
81-
status: notify::RunStatus::Ignored,
81+
status: notify::MessageKind::Ignored,
8282
cause: Some(Box::new(Message(reason))),
8383
}
8484
}
8585

86-
pub(crate) fn status(&self) -> notify::RunStatus {
86+
pub(crate) fn status(&self) -> notify::MessageKind {
8787
self.status
8888
}
8989

0 commit comments

Comments
 (0)