Skip to content

Commit 8f54c9a

Browse files
authored
Merge pull request #328 from kirtchev-adacore/wip/issue-326-escape-backslashes-on-windows
JSON emitter: escape backslashes on Windows
2 parents 7ea50e1 + 750807b commit 8f54c9a

File tree

2 files changed

+123
-26
lines changed

2 files changed

+123
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
### Fixed
1414

15+
* the JSON output now properly escapes backslashes on Windows
16+
1517
### Changed
1618

1719
### Removed

src/status_emitter/json.rs

Lines changed: 121 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,54 +13,67 @@ use bstr::ByteSlice;
1313

1414
// MAINTENANCE REGION START
1515

16-
// When integrating with a new libtest version, update all emit_xxx functions.
16+
// When integrating with a new libtest version, update all xxx_event functions.
1717

18-
fn emit_suite_end(failed: usize, filtered_out: usize, ignored: usize, passed: usize, status: &str) {
18+
fn suite_end_event(
19+
failed: usize,
20+
filtered_out: usize,
21+
ignored: usize,
22+
passed: usize,
23+
status: &str,
24+
) -> String {
1925
// Adapted from test::formatters::json::write_run_finish().
20-
println!(
26+
format!(
2127
r#"{{ "type": "suite", "event": "{status}", "passed": {passed}, "failed": {failed}, "ignored": {ignored}, "measured": 0, "filtered_out": {filtered_out} }}"#
22-
);
28+
)
2329
}
2430

25-
fn emit_suite_start() {
31+
fn suite_start_event() -> String {
2632
// Adapted from test::formatters::json::write_run_start().
27-
println!(r#"{{ "type": "suite", "event": "started" }}"#);
33+
String::from(r#"{ "type": "suite", "event": "started" }"#)
2834
}
2935

30-
fn emit_test_end(name: &String, revision: &String, path: &Path, status: &str, diags: &str) {
31-
let displayed_path = path.display();
32-
let stdout = if diags.is_empty() {
33-
String::new()
34-
} else {
35-
let triaged_diags = serde_json::to_string(diags).unwrap();
36-
format!(r#", "stdout": {triaged_diags}"#)
37-
};
36+
fn test_end_event(name: &str, revision: &str, path: &Path, status: &str, diags: &str) -> String {
37+
let name_attribute = make_name_attribute(name, revision, path);
38+
let stdout_attribute = make_stdout_attribute(diags);
3839

3940
// Adapted from test::formatters::json::write_event().
40-
println!(
41-
r#"{{ "type": "test", "event": "{status}", "name": "{name} ({revision}) - {displayed_path}"{stdout} }}"#
42-
);
41+
format!(r#"{{ "type": "test", "event": "{status}"{name_attribute}{stdout_attribute} }}"#)
4342
}
4443

45-
fn emit_test_start(name: &String, revision: &String, path: &Path) {
46-
let displayed_path = path.display();
44+
fn test_start_event(name: &str, revision: &str, path: &Path) -> String {
45+
let name_attribute = make_name_attribute(name, revision, path);
4746

4847
// Adapted from test::formatters::json::write_test_start().
49-
println!(
50-
r#"{{ "type": "test", "event": "started", "name": "{name} ({revision}) - {displayed_path}" }}"#
51-
);
48+
format!(r#"{{ "type": "test", "event": "started"{name_attribute} }}"#)
5249
}
5350

5451
// MAINTENANCE REGION END
5552

53+
fn make_name_attribute(name: &str, revision: &str, path: &Path) -> String {
54+
let path_display = path.display();
55+
let escaped_value =
56+
serde_json::to_string(&format!("{name} ({revision}) - {path_display}")).unwrap();
57+
format!(r#", "name": {escaped_value}"#)
58+
}
59+
60+
fn make_stdout_attribute(diags: &str) -> String {
61+
if diags.is_empty() {
62+
String::new()
63+
} else {
64+
let escaped_diags = serde_json::to_string(diags).unwrap();
65+
format!(r#", "stdout": {escaped_diags}"#)
66+
}
67+
}
68+
5669
/// A JSON output emitter.
5770
#[derive(Clone)]
5871
pub struct JSON {}
5972

6073
impl JSON {
6174
/// Create a new instance of a JSON output emitter.
6275
pub fn new() -> Self {
63-
emit_suite_start();
76+
println!("{}", suite_start_event());
6477

6578
JSON {}
6679
}
@@ -88,7 +101,10 @@ impl StatusEmitter for JSON {
88101
"ok"
89102
};
90103

91-
emit_suite_end(failed, filtered, ignored, succeeded, status);
104+
println!(
105+
"{}",
106+
suite_end_event(failed, filtered, ignored, succeeded, status)
107+
);
92108

93109
Box::new(())
94110
}
@@ -99,7 +115,7 @@ impl StatusEmitter for JSON {
99115
let name = path.to_str().unwrap().to_string();
100116
let revision = String::new();
101117

102-
emit_test_start(&name, &revision, &path);
118+
println!("{}", test_start_event(&name, &revision, &path));
103119

104120
Box::new(JSONStatus {
105121
name,
@@ -140,7 +156,10 @@ impl TestStatus for JSONStatus {
140156
String::new()
141157
};
142158

143-
emit_test_end(&self.name, &self.revision, self.path(), status, &diags);
159+
println!(
160+
"{}",
161+
test_end_event(&self.name, &self.revision, self.path(), status, &diags)
162+
);
144163
}
145164

146165
/// Invoked before each failed test prints its errors along with a drop guard that can
@@ -186,3 +205,79 @@ impl TestStatus for JSONStatus {
186205
&self.revision
187206
}
188207
}
208+
209+
#[test]
210+
fn suite_end_event_constructs_event() {
211+
assert_eq!(
212+
suite_end_event(
213+
12, // failed
214+
34, // filtered_out
215+
56, // ignored
216+
78, // passed
217+
"status"
218+
),
219+
r#"{ "type": "suite", "event": "status", "passed": 78, "failed": 12, "ignored": 56, "measured": 0, "filtered_out": 34 }"#
220+
);
221+
}
222+
223+
#[test]
224+
fn suite_start_event_constructs_event() {
225+
assert_eq!(
226+
suite_start_event(),
227+
r#"{ "type": "suite", "event": "started" }"#
228+
);
229+
}
230+
231+
#[test]
232+
fn test_end_event_constructs_event() {
233+
assert_eq!(
234+
test_end_event("name", "revision", Path::new("aaa/bbb"), "status", "diags"),
235+
r#"{ "type": "test", "event": "status", "name": "name (revision) - aaa/bbb", "stdout": "diags" }"#
236+
);
237+
}
238+
239+
#[test]
240+
fn test_end_event_constructs_event_with_escapes() {
241+
assert_eq!(
242+
test_end_event(
243+
r#"aaa\bbb"#,
244+
r#"ccc ddd\eee"#,
245+
Path::new(r#"fff\ggg"#),
246+
"status",
247+
r#""rustc" "--error-format=json""#
248+
),
249+
r#"{ "type": "test", "event": "status", "name": "aaa\\bbb (ccc ddd\\eee) - fff\\ggg", "stdout": "\"rustc\" \"--error-format=json\"" }"#
250+
);
251+
}
252+
253+
#[test]
254+
fn test_end_event_constructs_event_without_revision() {
255+
assert_eq!(
256+
test_end_event("name", "", Path::new("aaa/bbb"), "status", "diags"),
257+
r#"{ "type": "test", "event": "status", "name": "name () - aaa/bbb", "stdout": "diags" }"#
258+
);
259+
}
260+
261+
#[test]
262+
fn test_end_event_constructs_event_without_stdout() {
263+
assert_eq!(
264+
test_end_event("name", "revision", Path::new("aaa/bbb"), "status", ""),
265+
r#"{ "type": "test", "event": "status", "name": "name (revision) - aaa/bbb" }"#
266+
);
267+
}
268+
269+
#[test]
270+
fn test_start_event_constructs_event() {
271+
assert_eq!(
272+
test_start_event("name", "revision", Path::new("aaa/bbb")),
273+
r#"{ "type": "test", "event": "started", "name": "name (revision) - aaa/bbb" }"#
274+
);
275+
}
276+
277+
#[test]
278+
fn test_start_event_constructs_event_with_escapes() {
279+
assert_eq!(
280+
test_start_event(r#"aaa\bbb"#, r#"ccc ddd\eee"#, Path::new(r#"fff\ggg"#)),
281+
r#"{ "type": "test", "event": "started", "name": "aaa\\bbb (ccc ddd\\eee) - fff\\ggg" }"#
282+
);
283+
}

0 commit comments

Comments
 (0)