Skip to content

Commit 9d0eac4

Browse files
committedMar 20, 2023
Auto merge of rust-lang#108148 - parthopdas:master, r=oli-obk
Implementing "<test_binary> --list --format json" for use by IDE test explorers / runners Fixes rust-lang#107307 PR 1 of 2 - wiring up just the new information + implement the command line changes i.e. --format json + tests upcoming: PR 2 of 2 - clean up "#[cfg(not(bootstrap))]" from PR 1 As per the discussions on - MCP: https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/Implementing.20.22.3Ctest_binary.3E.20--list.20--form.E2.80.A6.20compiler-team.23592/near/328747548 - preRFC: https://internals.rust-lang.org/t/pre-rfc-implementing-test-binary-list-format-json-for-use-by-ide-test-explorers-runners/18308 - FYI on Discord: https://discord.com/channels/442252698964721669/459149169546887178/1075581549409484820

23 files changed

+554
-46
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Session.vim
2121
.project
2222
.favorites.json
2323
.settings/
24+
.vs/
2425

2526
## Tool
2627
.valgrindrc

‎compiler/rustc_builtin_macros/src/test.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_errors::Applicability;
88
use rustc_expand::base::*;
99
use rustc_session::Session;
1010
use rustc_span::symbol::{sym, Ident, Symbol};
11-
use rustc_span::Span;
11+
use rustc_span::{FileNameDisplayPreference, Span};
1212
use std::iter;
1313
use thin_vec::{thin_vec, ThinVec};
1414

@@ -231,6 +231,8 @@ pub fn expand_test_or_bench(
231231
&item.ident,
232232
));
233233

234+
let location_info = get_location_info(cx, &item);
235+
234236
let mut test_const = cx.item(
235237
sp,
236238
Ident::new(item.ident.name, sp),
@@ -280,6 +282,16 @@ pub fn expand_test_or_bench(
280282
cx.expr_none(sp)
281283
},
282284
),
285+
// source_file: <relative_path_of_source_file>
286+
field("source_file", cx.expr_str(sp, location_info.0)),
287+
// start_line: start line of the test fn identifier.
288+
field("start_line", cx.expr_usize(sp, location_info.1)),
289+
// start_col: start column of the test fn identifier.
290+
field("start_col", cx.expr_usize(sp, location_info.2)),
291+
// end_line: end line of the test fn identifier.
292+
field("end_line", cx.expr_usize(sp, location_info.3)),
293+
// end_col: end column of the test fn identifier.
294+
field("end_col", cx.expr_usize(sp, location_info.4)),
283295
// compile_fail: true | false
284296
field("compile_fail", cx.expr_bool(sp, false)),
285297
// no_run: true | false
@@ -364,6 +376,19 @@ pub fn expand_test_or_bench(
364376
}
365377
}
366378

379+
fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) {
380+
let span = item.ident.span;
381+
let (source_file, lo_line, lo_col, hi_line, hi_col) =
382+
cx.sess.source_map().span_to_location_info(span);
383+
384+
let file_name = match source_file {
385+
Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(),
386+
None => "no-location".to_string(),
387+
};
388+
389+
(Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
390+
}
391+
367392
fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
368393
mod_path
369394
.iter()

‎compiler/rustc_span/src/source_map.rs

+21-10
Original file line numberDiff line numberDiff line change
@@ -448,25 +448,36 @@ impl SourceMap {
448448
sp: Span,
449449
filename_display_pref: FileNameDisplayPreference,
450450
) -> String {
451-
if self.files.borrow().source_files.is_empty() || sp.is_dummy() {
452-
return "no-location".to_string();
453-
}
451+
let (source_file, lo_line, lo_col, hi_line, hi_col) = self.span_to_location_info(sp);
452+
453+
let file_name = match source_file {
454+
Some(sf) => sf.name.display(filename_display_pref).to_string(),
455+
None => return "no-location".to_string(),
456+
};
454457

455-
let lo = self.lookup_char_pos(sp.lo());
456-
let hi = self.lookup_char_pos(sp.hi());
457458
format!(
458-
"{}:{}:{}{}",
459-
lo.file.name.display(filename_display_pref),
460-
lo.line,
461-
lo.col.to_usize() + 1,
459+
"{file_name}:{lo_line}:{lo_col}{}",
462460
if let FileNameDisplayPreference::Short = filename_display_pref {
463461
String::new()
464462
} else {
465-
format!(": {}:{}", hi.line, hi.col.to_usize() + 1)
463+
format!(": {hi_line}:{hi_col}")
466464
}
467465
)
468466
}
469467

468+
pub fn span_to_location_info(
469+
&self,
470+
sp: Span,
471+
) -> (Option<Lrc<SourceFile>>, usize, usize, usize, usize) {
472+
if self.files.borrow().source_files.is_empty() || sp.is_dummy() {
473+
return (None, 0, 0, 0, 0);
474+
}
475+
476+
let lo = self.lookup_char_pos(sp.lo());
477+
let hi = self.lookup_char_pos(sp.hi());
478+
(Some(lo.file), lo.line, lo.col.to_usize() + 1, hi.line, hi.col.to_usize() + 1)
479+
}
480+
470481
/// Format the span location suitable for embedding in build artifacts
471482
pub fn span_to_embeddable_string(&self, sp: Span) -> String {
472483
self.span_to_string(sp, FileNameDisplayPreference::Remapped)

‎library/test/src/console.rs

+57-26
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,46 @@ impl<T: Write> Write for OutputLocation<T> {
4141
}
4242
}
4343

44+
pub struct ConsoleTestDiscoveryState {
45+
pub log_out: Option<File>,
46+
pub tests: usize,
47+
pub benchmarks: usize,
48+
pub ignored: usize,
49+
pub options: Options,
50+
}
51+
52+
impl ConsoleTestDiscoveryState {
53+
pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestDiscoveryState> {
54+
let log_out = match opts.logfile {
55+
Some(ref path) => Some(File::create(path)?),
56+
None => None,
57+
};
58+
59+
Ok(ConsoleTestDiscoveryState {
60+
log_out,
61+
tests: 0,
62+
benchmarks: 0,
63+
ignored: 0,
64+
options: opts.options,
65+
})
66+
}
67+
68+
pub fn write_log<F, S>(&mut self, msg: F) -> io::Result<()>
69+
where
70+
S: AsRef<str>,
71+
F: FnOnce() -> S,
72+
{
73+
match self.log_out {
74+
None => Ok(()),
75+
Some(ref mut o) => {
76+
let msg = msg();
77+
let msg = msg.as_ref();
78+
o.write_all(msg.as_bytes())
79+
}
80+
}
81+
}
82+
}
83+
4484
pub struct ConsoleTestState {
4585
pub log_out: Option<File>,
4686
pub total: usize,
@@ -138,53 +178,44 @@ impl ConsoleTestState {
138178

139179
// List the tests to console, and optionally to logfile. Filters are honored.
140180
pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> {
141-
let mut output = match term::stdout() {
181+
let output = match term::stdout() {
142182
None => OutputLocation::Raw(io::stdout().lock()),
143183
Some(t) => OutputLocation::Pretty(t),
144184
};
145185

146-
let quiet = opts.format == OutputFormat::Terse;
147-
let mut st = ConsoleTestState::new(opts)?;
148-
149-
let mut ntest = 0;
150-
let mut nbench = 0;
186+
let mut out: Box<dyn OutputFormatter> = match opts.format {
187+
OutputFormat::Pretty | OutputFormat::Junit => {
188+
Box::new(PrettyFormatter::new(output, false, 0, false, None))
189+
}
190+
OutputFormat::Terse => Box::new(TerseFormatter::new(output, false, 0, false)),
191+
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
192+
};
193+
let mut st = ConsoleTestDiscoveryState::new(opts)?;
151194

195+
out.write_discovery_start()?;
152196
for test in filter_tests(opts, tests).into_iter() {
153197
use crate::TestFn::*;
154198

155-
let TestDescAndFn { desc: TestDesc { name, .. }, testfn } = test;
199+
let TestDescAndFn { desc, testfn } = test;
156200

157201
let fntype = match testfn {
158202
StaticTestFn(..) | DynTestFn(..) => {
159-
ntest += 1;
203+
st.tests += 1;
160204
"test"
161205
}
162206
StaticBenchFn(..) | DynBenchFn(..) => {
163-
nbench += 1;
207+
st.benchmarks += 1;
164208
"benchmark"
165209
}
166210
};
167211

168-
writeln!(output, "{name}: {fntype}")?;
169-
st.write_log(|| format!("{fntype} {name}\n"))?;
170-
}
212+
st.ignored += if desc.ignore { 1 } else { 0 };
171213

172-
fn plural(count: u32, s: &str) -> String {
173-
match count {
174-
1 => format!("1 {s}"),
175-
n => format!("{n} {s}s"),
176-
}
214+
out.write_test_discovered(&desc, fntype)?;
215+
st.write_log(|| format!("{fntype} {}\n", desc.name))?;
177216
}
178217

179-
if !quiet {
180-
if ntest != 0 || nbench != 0 {
181-
writeln!(output)?;
182-
}
183-
184-
writeln!(output, "{}, {}", plural(ntest, "test"), plural(nbench, "benchmark"))?;
185-
}
186-
187-
Ok(())
218+
out.write_discovery_finish(&st)
188219
}
189220

190221
// Updates `ConsoleTestState` depending on result of the test execution.

‎library/test/src/formatters/json.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{borrow::Cow, io, io::prelude::Write};
22

33
use super::OutputFormatter;
44
use crate::{
5-
console::{ConsoleTestState, OutputLocation},
5+
console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
66
test_result::TestResult,
77
time,
88
types::TestDesc,
@@ -60,6 +60,56 @@ impl<T: Write> JsonFormatter<T> {
6060
}
6161

6262
impl<T: Write> OutputFormatter for JsonFormatter<T> {
63+
fn write_discovery_start(&mut self) -> io::Result<()> {
64+
self.writeln_message(&format!(r#"{{ "type": "suite", "event": "discovery" }}"#))
65+
}
66+
67+
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
68+
let TestDesc {
69+
name,
70+
ignore,
71+
ignore_message,
72+
#[cfg(not(bootstrap))]
73+
source_file,
74+
#[cfg(not(bootstrap))]
75+
start_line,
76+
#[cfg(not(bootstrap))]
77+
start_col,
78+
#[cfg(not(bootstrap))]
79+
end_line,
80+
#[cfg(not(bootstrap))]
81+
end_col,
82+
..
83+
} = desc;
84+
85+
#[cfg(bootstrap)]
86+
let source_file = "";
87+
#[cfg(bootstrap)]
88+
let start_line = 0;
89+
#[cfg(bootstrap)]
90+
let start_col = 0;
91+
#[cfg(bootstrap)]
92+
let end_line = 0;
93+
#[cfg(bootstrap)]
94+
let end_col = 0;
95+
96+
self.writeln_message(&format!(
97+
r#"{{ "type": "{test_type}", "event": "discovered", "name": "{}", "ignore": {ignore}, "ignore_message": "{}", "source_path": "{}", "start_line": {start_line}, "start_col": {start_col}, "end_line": {end_line}, "end_col": {end_col} }}"#,
98+
EscapedString(name.as_slice()),
99+
ignore_message.unwrap_or(""),
100+
EscapedString(source_file),
101+
))
102+
}
103+
104+
fn write_discovery_finish(&mut self, state: &ConsoleTestDiscoveryState) -> io::Result<()> {
105+
let ConsoleTestDiscoveryState { tests, benchmarks, ignored, .. } = state;
106+
107+
let total = tests + benchmarks;
108+
self.writeln_message(&format!(
109+
r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}"#
110+
))
111+
}
112+
63113
fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> {
64114
let shuffle_seed_json = if let Some(shuffle_seed) = shuffle_seed {
65115
format!(r#", "shuffle_seed": {shuffle_seed}"#)

‎library/test/src/formatters/junit.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::time::Duration;
33

44
use super::OutputFormatter;
55
use crate::{
6-
console::{ConsoleTestState, OutputLocation},
6+
console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
77
test_result::TestResult,
88
time,
99
types::{TestDesc, TestType},
@@ -27,6 +27,18 @@ impl<T: Write> JunitFormatter<T> {
2727
}
2828

2929
impl<T: Write> OutputFormatter for JunitFormatter<T> {
30+
fn write_discovery_start(&mut self) -> io::Result<()> {
31+
Err(io::Error::new(io::ErrorKind::NotFound, "Not yet implemented!"))
32+
}
33+
34+
fn write_test_discovered(&mut self, _desc: &TestDesc, _test_type: &str) -> io::Result<()> {
35+
Err(io::Error::new(io::ErrorKind::NotFound, "Not yet implemented!"))
36+
}
37+
38+
fn write_discovery_finish(&mut self, _state: &ConsoleTestDiscoveryState) -> io::Result<()> {
39+
Err(io::Error::new(io::ErrorKind::NotFound, "Not yet implemented!"))
40+
}
41+
3042
fn write_run_start(
3143
&mut self,
3244
_test_count: usize,

‎library/test/src/formatters/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{io, io::prelude::Write};
22

33
use crate::{
4-
console::ConsoleTestState,
4+
console::{ConsoleTestDiscoveryState, ConsoleTestState},
55
test_result::TestResult,
66
time,
77
types::{TestDesc, TestName},
@@ -18,6 +18,10 @@ pub(crate) use self::pretty::PrettyFormatter;
1818
pub(crate) use self::terse::TerseFormatter;
1919

2020
pub(crate) trait OutputFormatter {
21+
fn write_discovery_start(&mut self) -> io::Result<()>;
22+
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()>;
23+
fn write_discovery_finish(&mut self, state: &ConsoleTestDiscoveryState) -> io::Result<()>;
24+
2125
fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()>;
2226
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()>;
2327
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>;

‎library/test/src/formatters/pretty.rs

+28-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{io, io::prelude::Write};
33
use super::OutputFormatter;
44
use crate::{
55
bench::fmt_bench_samples,
6-
console::{ConsoleTestState, OutputLocation},
6+
console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
77
term,
88
test_result::TestResult,
99
time,
@@ -181,6 +181,33 @@ impl<T: Write> PrettyFormatter<T> {
181181
}
182182

183183
impl<T: Write> OutputFormatter for PrettyFormatter<T> {
184+
fn write_discovery_start(&mut self) -> io::Result<()> {
185+
Ok(())
186+
}
187+
188+
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
189+
self.write_plain(format!("{}: {test_type}\n", desc.name))
190+
}
191+
192+
fn write_discovery_finish(&mut self, state: &ConsoleTestDiscoveryState) -> io::Result<()> {
193+
fn plural(count: usize, s: &str) -> String {
194+
match count {
195+
1 => format!("1 {s}"),
196+
n => format!("{n} {s}s"),
197+
}
198+
}
199+
200+
if state.tests != 0 || state.benchmarks != 0 {
201+
self.write_plain("\n")?;
202+
}
203+
204+
self.write_plain(format!(
205+
"{}, {}\n",
206+
plural(state.tests, "test"),
207+
plural(state.benchmarks, "benchmark")
208+
))
209+
}
210+
184211
fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> {
185212
let noun = if test_count != 1 { "tests" } else { "test" };
186213
let shuffle_seed_msg = if let Some(shuffle_seed) = shuffle_seed {

0 commit comments

Comments
 (0)
Please sign in to comment.