1use std::io::{BufRead, BufReader, Read, Write};
10use std::process::{ChildStdout, Stdio};
11use std::time::Duration;
12
13use termcolor::{Color, ColorSpec, WriteColor};
14
15use crate::core::builder::Builder;
16use crate::utils::exec::BootstrapCommand;
17
18const TERSE_TESTS_PER_LINE: usize = 88;
19
20pub(crate) fn add_flags_and_try_run_tests(
21 builder: &Builder<'_>,
22 cmd: &mut BootstrapCommand,
23) -> bool {
24 if !cmd.get_args().any(|arg| arg == "--") {
25 cmd.arg("--");
26 }
27 cmd.args(["-Z", "unstable-options", "--format", "json"]);
28
29 try_run_tests(builder, cmd, false)
30}
31
32pub(crate) fn try_run_tests(
33 builder: &Builder<'_>,
34 cmd: &mut BootstrapCommand,
35 stream: bool,
36) -> bool {
37 if builder.config.dry_run() {
38 cmd.mark_as_executed();
39 return true;
40 }
41
42 if !run_tests(builder, cmd, stream) {
43 if builder.fail_fast {
44 crate::exit!(1);
45 } else {
46 builder.config.exec_ctx().add_to_delay_failure(format!("{cmd:?}"));
47 false
48 }
49 } else {
50 true
51 }
52}
53
54fn run_tests(builder: &Builder<'_>, cmd: &mut BootstrapCommand, stream: bool) -> bool {
55 let cmd = cmd.as_command_mut();
56 cmd.stdout(Stdio::piped());
57
58 builder.verbose(|| println!("running: {cmd:?}"));
59
60 let mut process = cmd.spawn().unwrap();
61
62 let renderer = Renderer::new(process.stdout.take().unwrap(), builder);
65 if stream {
66 renderer.stream_all();
67 } else {
68 renderer.render_all();
69 }
70
71 let result = process.wait_with_output().unwrap();
72 if !result.status.success() && builder.is_verbose() {
73 println!(
74 "\n\ncommand did not execute successfully: {cmd:?}\n\
75 expected success, got: {}",
76 result.status
77 );
78 }
79
80 result.status.success()
81}
82
83struct Renderer<'a> {
84 stdout: BufReader<ChildStdout>,
85 failures: Vec<TestOutcome>,
86 benches: Vec<BenchOutcome>,
87 builder: &'a Builder<'a>,
88 tests_count: Option<usize>,
89 executed_tests: usize,
90 up_to_date_tests: usize,
93 ignored_tests: usize,
94 terse_tests_in_line: usize,
95 ci_latest_logged_percentage: f64,
96}
97
98impl<'a> Renderer<'a> {
99 fn new(stdout: ChildStdout, builder: &'a Builder<'a>) -> Self {
100 Self {
101 stdout: BufReader::new(stdout),
102 benches: Vec::new(),
103 failures: Vec::new(),
104 builder,
105 tests_count: None,
106 executed_tests: 0,
107 up_to_date_tests: 0,
108 ignored_tests: 0,
109 terse_tests_in_line: 0,
110 ci_latest_logged_percentage: 0.0,
111 }
112 }
113
114 fn render_all(mut self) {
115 let mut line = Vec::new();
116 loop {
117 line.clear();
118 match self.stdout.read_until(b'\n', &mut line) {
119 Ok(_) => {}
120 Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break,
121 Err(err) => panic!("failed to read output of test runner: {err}"),
122 }
123 if line.is_empty() {
124 break;
125 }
126
127 match serde_json::from_slice(&line) {
128 Ok(parsed) => self.render_message(parsed),
129 Err(_err) => {
130 let mut stdout = std::io::stdout();
132 stdout.write_all(&line).unwrap();
133 let _ = stdout.flush();
134 }
135 }
136 }
137
138 if self.up_to_date_tests > 0 {
139 let n = self.up_to_date_tests;
140 let s = if n > 1 { "s" } else { "" };
141 println!("help: ignored {n} up-to-date test{s}; use `--force-rerun` to prevent this\n");
142 }
143 }
144
145 fn stream_all(mut self) {
147 let mut buffer = [0; 1];
148 loop {
149 match self.stdout.read(&mut buffer) {
150 Ok(0) => break,
151 Ok(_) => {
152 let mut stdout = std::io::stdout();
153 stdout.write_all(&buffer).unwrap();
154 let _ = stdout.flush();
155 }
156 Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break,
157 Err(err) => panic!("failed to read output of test runner: {err}"),
158 }
159 }
160 }
161
162 fn render_test_outcome(&mut self, outcome: Outcome<'_>, test: &TestOutcome) {
163 self.executed_tests += 1;
164
165 if let Outcome::Ignored { reason } = outcome {
166 self.ignored_tests += 1;
167 if reason == Some("up-to-date") {
169 self.up_to_date_tests += 1;
170 }
171 }
172
173 #[cfg(feature = "build-metrics")]
174 self.builder.metrics.record_test(
175 &test.name,
176 match outcome {
177 Outcome::Ok | Outcome::BenchOk => build_helper::metrics::TestOutcome::Passed,
178 Outcome::Failed => build_helper::metrics::TestOutcome::Failed,
179 Outcome::Ignored { reason } => build_helper::metrics::TestOutcome::Ignored {
180 ignore_reason: reason.map(|s| s.to_string()),
181 },
182 },
183 self.builder,
184 );
185
186 if self.builder.config.verbose_tests {
187 self.render_test_outcome_verbose(outcome, test);
188 } else if self.builder.config.is_running_on_ci {
189 self.render_test_outcome_ci(outcome, test);
190 } else {
191 self.render_test_outcome_terse(outcome, test);
192 }
193 }
194
195 fn render_test_outcome_verbose(&self, outcome: Outcome<'_>, test: &TestOutcome) {
196 print!("test {} ... ", test.name);
197 self.builder.colored_stdout(|stdout| outcome.write_long(stdout)).unwrap();
198 if let Some(exec_time) = test.exec_time {
199 print!(" ({exec_time:.2?})");
200 }
201 println!();
202 }
203
204 fn render_test_outcome_terse(&mut self, outcome: Outcome<'_>, test: &TestOutcome) {
205 if self.terse_tests_in_line != 0 && self.terse_tests_in_line % TERSE_TESTS_PER_LINE == 0 {
206 if let Some(total) = self.tests_count {
207 let total = total.to_string();
208 let executed = format!("{:>width$}", self.executed_tests - 1, width = total.len());
209 print!(" {executed}/{total}");
210 }
211 println!();
212 self.terse_tests_in_line = 0;
213 }
214
215 self.terse_tests_in_line += 1;
216 self.builder.colored_stdout(|stdout| outcome.write_short(stdout, &test.name)).unwrap();
217 let _ = std::io::stdout().flush();
218 }
219
220 fn render_test_outcome_ci(&mut self, outcome: Outcome<'_>, test: &TestOutcome) {
221 if let Some(total) = self.tests_count {
222 let percent = self.executed_tests as f64 / total as f64;
223
224 if self.ci_latest_logged_percentage + 0.10 < percent {
225 let total = total.to_string();
226 let executed = format!("{:>width$}", self.executed_tests, width = total.len());
227 let pretty_percent = format!("{:.0}%", percent * 100.0);
228 let passed_tests = self.executed_tests - (self.failures.len() + self.ignored_tests);
229 println!(
230 "{:<4} -- {executed}/{total}, {:>total_indent$} passed, {} failed, {} ignored",
231 pretty_percent,
232 passed_tests,
233 self.failures.len(),
234 self.ignored_tests,
235 total_indent = total.len()
236 );
237 self.ci_latest_logged_percentage += 0.10;
238 }
239 }
240
241 self.builder.colored_stdout(|stdout| outcome.write_ci(stdout, &test.name)).unwrap();
242 let _ = std::io::stdout().flush();
243 }
244
245 fn render_suite_outcome(&self, outcome: Outcome<'_>, suite: &SuiteOutcome) {
246 if !self.builder.config.verbose_tests {
248 println!();
249 }
250
251 if !self.failures.is_empty() {
252 println!("\nfailures:\n");
253 for failure in &self.failures {
254 if failure.stdout.is_some() || failure.message.is_some() {
255 println!("---- {} stdout ----", failure.name);
256 if let Some(stdout) = &failure.stdout {
257 println!("{stdout}");
258 }
259 if let Some(message) = &failure.message {
260 println!("NOTE: {message}");
261 }
262 }
263 }
264
265 println!("\nfailures:");
266 for failure in &self.failures {
267 println!(" {}", failure.name);
268 }
269 }
270
271 if !self.benches.is_empty() {
272 println!("\nbenchmarks:");
273
274 let mut rows = Vec::new();
275 for bench in &self.benches {
276 rows.push((
277 &bench.name,
278 format!("{:.2?}ns/iter", bench.median),
279 format!("+/- {:.2?}", bench.deviation),
280 ));
281 }
282
283 let max_0 = rows.iter().map(|r| r.0.len()).max().unwrap_or(0);
284 let max_1 = rows.iter().map(|r| r.1.len()).max().unwrap_or(0);
285 let max_2 = rows.iter().map(|r| r.2.len()).max().unwrap_or(0);
286 for row in &rows {
287 println!(" {:<max_0$} {:>max_1$} {:>max_2$}", row.0, row.1, row.2);
288 }
289 }
290
291 print!("\ntest result: ");
292 self.builder.colored_stdout(|stdout| outcome.write_long(stdout)).unwrap();
293 println!(
294 ". {} passed; {} failed; {} ignored; {} measured; {} filtered out{time}\n",
295 suite.passed,
296 suite.failed,
297 suite.ignored,
298 suite.measured,
299 suite.filtered_out,
300 time = match suite.exec_time {
301 Some(t) => format!("; finished in {:.2?}", Duration::from_secs_f64(t)),
302 None => String::new(),
303 }
304 );
305 }
306
307 fn render_message(&mut self, message: Message) {
308 match message {
309 Message::Suite(SuiteMessage::Started { test_count }) => {
310 println!("\nrunning {test_count} tests");
311 self.benches = vec![];
312 self.failures = vec![];
313 self.ignored_tests = 0;
314 self.executed_tests = 0;
315 self.terse_tests_in_line = 0;
316 self.tests_count = Some(test_count);
317 }
318 Message::Suite(SuiteMessage::Ok(outcome)) => {
319 self.render_suite_outcome(Outcome::Ok, &outcome);
320 }
321 Message::Suite(SuiteMessage::Failed(outcome)) => {
322 self.render_suite_outcome(Outcome::Failed, &outcome);
323 }
324 Message::Bench(outcome) => {
325 let fake_test_outcome = TestOutcome {
331 name: outcome.name.clone(),
332 exec_time: None,
333 stdout: None,
334 message: None,
335 };
336 self.render_test_outcome(Outcome::BenchOk, &fake_test_outcome);
337 self.benches.push(outcome);
338 }
339 Message::Test(TestMessage::Ok(outcome)) => {
340 self.render_test_outcome(Outcome::Ok, &outcome);
341 }
342 Message::Test(TestMessage::Ignored(outcome)) => {
343 self.render_test_outcome(
344 Outcome::Ignored { reason: outcome.message.as_deref() },
345 &outcome,
346 );
347 }
348 Message::Test(TestMessage::Failed(outcome)) => {
349 self.render_test_outcome(Outcome::Failed, &outcome);
350 self.failures.push(outcome);
351 }
352 Message::Test(TestMessage::Timeout { name }) => {
353 println!("test {name} has been running for a long time");
354 }
355 Message::Test(TestMessage::Started) => {} }
357 }
358}
359
360enum Outcome<'a> {
361 Ok,
362 BenchOk,
363 Failed,
364 Ignored { reason: Option<&'a str> },
365}
366
367impl Outcome<'_> {
368 fn write_short(&self, writer: &mut dyn WriteColor, name: &str) -> Result<(), std::io::Error> {
369 match self {
370 Outcome::Ok => {
371 writer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
372 write!(writer, ".")?;
373 }
374 Outcome::BenchOk => {
375 writer.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
376 write!(writer, "b")?;
377 }
378 Outcome::Failed => {
379 writeln!(writer)?;
382 writer.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
383 writeln!(writer, "{name} ... F")?;
384 }
385 Outcome::Ignored { .. } => {
386 writer.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
387 write!(writer, "i")?;
388 }
389 }
390 writer.reset()
391 }
392
393 fn write_long(&self, writer: &mut dyn WriteColor) -> Result<(), std::io::Error> {
394 match self {
395 Outcome::Ok => {
396 writer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
397 write!(writer, "ok")?;
398 }
399 Outcome::BenchOk => {
400 writer.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
401 write!(writer, "benchmarked")?;
402 }
403 Outcome::Failed => {
404 writer.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
405 write!(writer, "FAILED")?;
406 }
407 Outcome::Ignored { reason } => {
408 writer.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
409 write!(writer, "ignored")?;
410 if let Some(reason) = reason {
411 write!(writer, ", {reason}")?;
412 }
413 }
414 }
415 writer.reset()
416 }
417
418 fn write_ci(&self, writer: &mut dyn WriteColor, name: &str) -> Result<(), std::io::Error> {
419 match self {
420 Outcome::Ok | Outcome::BenchOk | Outcome::Ignored { .. } => {}
421 Outcome::Failed => {
422 writer.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
423 writeln!(writer, " {name} ... FAILED")?;
424 }
425 }
426 writer.reset()
427 }
428}
429
430#[derive(serde_derive::Deserialize)]
431#[serde(tag = "type", rename_all = "snake_case")]
432enum Message {
433 Suite(SuiteMessage),
434 Test(TestMessage),
435 Bench(BenchOutcome),
436}
437
438#[derive(serde_derive::Deserialize)]
439#[serde(tag = "event", rename_all = "snake_case")]
440enum SuiteMessage {
441 Ok(SuiteOutcome),
442 Failed(SuiteOutcome),
443 Started { test_count: usize },
444}
445
446#[derive(serde_derive::Deserialize)]
447struct SuiteOutcome {
448 passed: usize,
449 failed: usize,
450 ignored: usize,
451 measured: usize,
452 filtered_out: usize,
453 exec_time: Option<f64>,
456}
457
458#[derive(serde_derive::Deserialize)]
459#[serde(tag = "event", rename_all = "snake_case")]
460enum TestMessage {
461 Ok(TestOutcome),
462 Failed(TestOutcome),
463 Ignored(TestOutcome),
464 Timeout { name: String },
465 Started,
466}
467
468#[derive(serde_derive::Deserialize)]
469struct BenchOutcome {
470 name: String,
471 median: f64,
472 deviation: f64,
473}
474
475#[derive(serde_derive::Deserialize)]
476struct TestOutcome {
477 name: String,
478 exec_time: Option<f64>,
479 stdout: Option<String>,
480 message: Option<String>,
481}