mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
refactor(test): support custom writer in PrettyTestReporter (#20783)
This commit is contained in:
parent
fd4fc2d818
commit
551a081450
7 changed files with 291 additions and 191 deletions
|
@ -404,6 +404,7 @@ impl TestRun {
|
|||
);
|
||||
}
|
||||
}
|
||||
test::TestEvent::ForceEndReport => {}
|
||||
test::TestEvent::Sigint => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
use super::*;
|
||||
|
||||
pub fn to_relative_path_or_remote_url(cwd: &Url, path_or_url: &str) -> String {
|
||||
let url = Url::parse(path_or_url).unwrap();
|
||||
let Ok(url) = Url::parse(path_or_url) else {
|
||||
return "<anonymous>".to_string();
|
||||
};
|
||||
if url.scheme() == "file" {
|
||||
if let Some(mut r) = cwd.make_relative(&url) {
|
||||
if !r.starts_with("../") {
|
||||
|
|
|
@ -50,6 +50,7 @@ use deno_runtime::fmt_errors::format_js_error;
|
|||
use deno_runtime::permissions::Permissions;
|
||||
use deno_runtime::permissions::PermissionsContainer;
|
||||
use deno_runtime::tokio_util::create_and_run_current_thread;
|
||||
use deno_runtime::worker::MainWorker;
|
||||
use indexmap::IndexMap;
|
||||
use indexmap::IndexSet;
|
||||
use log::Level;
|
||||
|
@ -77,11 +78,12 @@ use std::time::Instant;
|
|||
use std::time::SystemTime;
|
||||
use tokio::signal;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::mpsc::WeakUnboundedSender;
|
||||
|
||||
pub mod fmt;
|
||||
mod reporters;
|
||||
pub mod reporters;
|
||||
|
||||
pub use fmt::format_test_error;
|
||||
use reporters::CompoundTestReporter;
|
||||
|
@ -313,6 +315,7 @@ pub enum TestEvent {
|
|||
StepRegister(TestStepDescription),
|
||||
StepWait(usize),
|
||||
StepResult(usize, TestStepResult, u64),
|
||||
ForceEndReport,
|
||||
Sigint,
|
||||
}
|
||||
|
||||
|
@ -342,7 +345,7 @@ struct TestSpecifiersOptions {
|
|||
junit_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TestSpecifierOptions {
|
||||
pub shuffle: Option<u64>,
|
||||
pub filter: TestFilter,
|
||||
|
@ -379,6 +382,7 @@ fn get_test_reporter(options: &TestSpecifiersOptions) -> Box<dyn TestReporter> {
|
|||
parallel,
|
||||
options.log_level != Some(Level::Error),
|
||||
options.filter,
|
||||
false,
|
||||
)),
|
||||
TestReporterConfig::Junit => {
|
||||
Box::new(JunitTestReporter::new("-".to_string()))
|
||||
|
@ -453,10 +457,35 @@ pub async fn test_specifier(
|
|||
|
||||
worker.dispatch_load_event(located_script_name!())?;
|
||||
|
||||
let tests = {
|
||||
run_tests_for_worker(&mut worker, &specifier, &options, &fail_fast_tracker)
|
||||
.await?;
|
||||
|
||||
// Ignore `defaultPrevented` of the `beforeunload` event. We don't allow the
|
||||
// event loop to continue beyond what's needed to await results.
|
||||
worker.dispatch_beforeunload_event(located_script_name!())?;
|
||||
worker.dispatch_unload_event(located_script_name!())?;
|
||||
|
||||
if let Some(coverage_collector) = coverage_collector.as_mut() {
|
||||
worker
|
||||
.with_event_loop(coverage_collector.stop_collecting().boxed_local())
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_tests_for_worker(
|
||||
worker: &mut MainWorker,
|
||||
specifier: &ModuleSpecifier,
|
||||
options: &TestSpecifierOptions,
|
||||
fail_fast_tracker: &FailFastTracker,
|
||||
) -> Result<(), AnyError> {
|
||||
let (tests, mut sender) = {
|
||||
let state_rc = worker.js_runtime.op_state();
|
||||
let mut state = state_rc.borrow_mut();
|
||||
std::mem::take(&mut state.borrow_mut::<ops::testing::TestContainer>().0)
|
||||
(
|
||||
std::mem::take(&mut state.borrow_mut::<ops::testing::TestContainer>().0),
|
||||
state.borrow::<TestEventSender>().clone(),
|
||||
)
|
||||
};
|
||||
let unfiltered = tests.len();
|
||||
let tests = tests
|
||||
|
@ -532,17 +561,6 @@ pub async fn test_specifier(
|
|||
let elapsed = SystemTime::now().duration_since(earlier)?.as_millis();
|
||||
sender.send(TestEvent::Result(desc.id, result, elapsed as u64))?;
|
||||
}
|
||||
|
||||
// Ignore `defaultPrevented` of the `beforeunload` event. We don't allow the
|
||||
// event loop to continue beyond what's needed to await results.
|
||||
worker.dispatch_beforeunload_event(located_script_name!())?;
|
||||
worker.dispatch_unload_event(located_script_name!())?;
|
||||
|
||||
if let Some(coverage_collector) = coverage_collector.as_mut() {
|
||||
worker
|
||||
.with_event_loop(coverage_collector.stop_collecting().boxed_local())
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -810,7 +828,7 @@ async fn test_specifiers(
|
|||
specifiers
|
||||
};
|
||||
|
||||
let (sender, mut receiver) = unbounded_channel::<TestEvent>();
|
||||
let (sender, receiver) = unbounded_channel::<TestEvent>();
|
||||
let sender = TestEventSender::new(sender);
|
||||
let concurrent_jobs = options.concurrent_jobs;
|
||||
|
||||
|
@ -820,7 +838,7 @@ async fn test_specifiers(
|
|||
sender_.upgrade().map(|s| s.send(TestEvent::Sigint).ok());
|
||||
});
|
||||
HAS_TEST_RUN_SIGINT_HANDLER.store(true, Ordering::Relaxed);
|
||||
let mut reporter = get_test_reporter(&options);
|
||||
let reporter = get_test_reporter(&options);
|
||||
let fail_fast_tracker = FailFastTracker::new(options.fail_fast);
|
||||
|
||||
let join_handles = specifiers.into_iter().map(move |specifier| {
|
||||
|
@ -840,144 +858,149 @@ async fn test_specifiers(
|
|||
))
|
||||
})
|
||||
});
|
||||
|
||||
let join_stream = stream::iter(join_handles)
|
||||
.buffer_unordered(concurrent_jobs.get())
|
||||
.collect::<Vec<Result<Result<(), AnyError>, tokio::task::JoinError>>>();
|
||||
|
||||
let handler = {
|
||||
spawn(async move {
|
||||
let earlier = Instant::now();
|
||||
let mut tests = IndexMap::new();
|
||||
let mut test_steps = IndexMap::new();
|
||||
let mut tests_started = HashSet::new();
|
||||
let mut tests_with_result = HashSet::new();
|
||||
let mut used_only = false;
|
||||
let mut failed = false;
|
||||
|
||||
while let Some(event) = receiver.recv().await {
|
||||
match event {
|
||||
TestEvent::Register(description) => {
|
||||
reporter.report_register(&description);
|
||||
tests.insert(description.id, description);
|
||||
}
|
||||
|
||||
TestEvent::Plan(plan) => {
|
||||
if plan.used_only {
|
||||
used_only = true;
|
||||
}
|
||||
|
||||
reporter.report_plan(&plan);
|
||||
}
|
||||
|
||||
TestEvent::Wait(id) => {
|
||||
if tests_started.insert(id) {
|
||||
reporter.report_wait(tests.get(&id).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
TestEvent::Output(output) => {
|
||||
reporter.report_output(&output);
|
||||
}
|
||||
|
||||
TestEvent::Result(id, result, elapsed) => {
|
||||
if tests_with_result.insert(id) {
|
||||
match result {
|
||||
TestResult::Failed(_) | TestResult::Cancelled => {
|
||||
failed = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
reporter.report_result(tests.get(&id).unwrap(), &result, elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
TestEvent::UncaughtError(origin, error) => {
|
||||
failed = true;
|
||||
reporter.report_uncaught_error(&origin, error);
|
||||
}
|
||||
|
||||
TestEvent::StepRegister(description) => {
|
||||
reporter.report_step_register(&description);
|
||||
test_steps.insert(description.id, description);
|
||||
}
|
||||
|
||||
TestEvent::StepWait(id) => {
|
||||
if tests_started.insert(id) {
|
||||
reporter.report_step_wait(test_steps.get(&id).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
TestEvent::StepResult(id, result, duration) => {
|
||||
if tests_with_result.insert(id) {
|
||||
reporter.report_step_result(
|
||||
test_steps.get(&id).unwrap(),
|
||||
&result,
|
||||
duration,
|
||||
&tests,
|
||||
&test_steps,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TestEvent::Sigint => {
|
||||
let elapsed = Instant::now().duration_since(earlier);
|
||||
reporter.report_sigint(
|
||||
&tests_started
|
||||
.difference(&tests_with_result)
|
||||
.copied()
|
||||
.collect(),
|
||||
&tests,
|
||||
&test_steps,
|
||||
);
|
||||
if let Err(err) =
|
||||
reporter.flush_report(&elapsed, &tests, &test_steps)
|
||||
{
|
||||
eprint!("Test reporter failed to flush: {}", err)
|
||||
}
|
||||
std::process::exit(130);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sigint_handler_handle.abort();
|
||||
HAS_TEST_RUN_SIGINT_HANDLER.store(false, Ordering::Relaxed);
|
||||
|
||||
let elapsed = Instant::now().duration_since(earlier);
|
||||
reporter.report_summary(&elapsed, &tests, &test_steps);
|
||||
if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) {
|
||||
return Err(generic_error(format!(
|
||||
"Test reporter failed to flush: {}",
|
||||
err
|
||||
)));
|
||||
}
|
||||
|
||||
if used_only {
|
||||
return Err(generic_error(
|
||||
"Test failed because the \"only\" option was used",
|
||||
));
|
||||
}
|
||||
|
||||
if failed {
|
||||
return Err(generic_error("Test failed"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
let handler = spawn(async move { report_tests(receiver, reporter).await.0 });
|
||||
|
||||
let (join_results, result) = future::join(join_stream, handler).await;
|
||||
|
||||
// propagate any errors
|
||||
sigint_handler_handle.abort();
|
||||
HAS_TEST_RUN_SIGINT_HANDLER.store(false, Ordering::Relaxed);
|
||||
for join_result in join_results {
|
||||
join_result??;
|
||||
}
|
||||
|
||||
result??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gives receiver back in case it was ended with `TestEvent::ForceEndReport`.
|
||||
pub async fn report_tests(
|
||||
mut receiver: UnboundedReceiver<TestEvent>,
|
||||
mut reporter: Box<dyn TestReporter>,
|
||||
) -> (Result<(), AnyError>, UnboundedReceiver<TestEvent>) {
|
||||
let mut tests = IndexMap::new();
|
||||
let mut test_steps = IndexMap::new();
|
||||
let mut tests_started = HashSet::new();
|
||||
let mut tests_with_result = HashSet::new();
|
||||
let mut start_time = None;
|
||||
let mut had_plan = false;
|
||||
let mut used_only = false;
|
||||
let mut failed = false;
|
||||
|
||||
while let Some(event) = receiver.recv().await {
|
||||
match event {
|
||||
TestEvent::Register(description) => {
|
||||
reporter.report_register(&description);
|
||||
tests.insert(description.id, description);
|
||||
}
|
||||
TestEvent::Plan(plan) => {
|
||||
if !had_plan {
|
||||
start_time = Some(Instant::now());
|
||||
had_plan = true;
|
||||
}
|
||||
if plan.used_only {
|
||||
used_only = true;
|
||||
}
|
||||
reporter.report_plan(&plan);
|
||||
}
|
||||
TestEvent::Wait(id) => {
|
||||
if tests_started.insert(id) {
|
||||
reporter.report_wait(tests.get(&id).unwrap());
|
||||
}
|
||||
}
|
||||
TestEvent::Output(output) => {
|
||||
reporter.report_output(&output);
|
||||
}
|
||||
TestEvent::Result(id, result, elapsed) => {
|
||||
if tests_with_result.insert(id) {
|
||||
match result {
|
||||
TestResult::Failed(_) | TestResult::Cancelled => {
|
||||
failed = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
reporter.report_result(tests.get(&id).unwrap(), &result, elapsed);
|
||||
}
|
||||
}
|
||||
TestEvent::UncaughtError(origin, error) => {
|
||||
failed = true;
|
||||
reporter.report_uncaught_error(&origin, error);
|
||||
}
|
||||
TestEvent::StepRegister(description) => {
|
||||
reporter.report_step_register(&description);
|
||||
test_steps.insert(description.id, description);
|
||||
}
|
||||
TestEvent::StepWait(id) => {
|
||||
if tests_started.insert(id) {
|
||||
reporter.report_step_wait(test_steps.get(&id).unwrap());
|
||||
}
|
||||
}
|
||||
TestEvent::StepResult(id, result, duration) => {
|
||||
if tests_with_result.insert(id) {
|
||||
reporter.report_step_result(
|
||||
test_steps.get(&id).unwrap(),
|
||||
&result,
|
||||
duration,
|
||||
&tests,
|
||||
&test_steps,
|
||||
);
|
||||
}
|
||||
}
|
||||
TestEvent::ForceEndReport => {
|
||||
break;
|
||||
}
|
||||
TestEvent::Sigint => {
|
||||
let elapsed = start_time
|
||||
.map(|t| Instant::now().duration_since(t))
|
||||
.unwrap_or_default();
|
||||
reporter.report_sigint(
|
||||
&tests_started
|
||||
.difference(&tests_with_result)
|
||||
.copied()
|
||||
.collect(),
|
||||
&tests,
|
||||
&test_steps,
|
||||
);
|
||||
if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) {
|
||||
eprint!("Test reporter failed to flush: {}", err)
|
||||
}
|
||||
std::process::exit(130);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let elapsed = start_time
|
||||
.map(|t| Instant::now().duration_since(t))
|
||||
.unwrap_or_default();
|
||||
reporter.report_summary(&elapsed, &tests, &test_steps);
|
||||
if let Err(err) = reporter.flush_report(&elapsed, &tests, &test_steps) {
|
||||
return (
|
||||
Err(generic_error(format!(
|
||||
"Test reporter failed to flush: {}",
|
||||
err
|
||||
))),
|
||||
receiver,
|
||||
);
|
||||
}
|
||||
|
||||
if used_only {
|
||||
return (
|
||||
Err(generic_error(
|
||||
"Test failed because the \"only\" option was used",
|
||||
)),
|
||||
receiver,
|
||||
);
|
||||
}
|
||||
|
||||
if failed {
|
||||
return (Err(generic_error("Test failed")), receiver);
|
||||
}
|
||||
|
||||
(Ok(()), receiver)
|
||||
}
|
||||
|
||||
/// Checks if the path has a basename and extension Deno supports for tests.
|
||||
pub(crate) fn is_supported_test_path(path: &Path) -> bool {
|
||||
if let Some(name) = path.file_stem() {
|
||||
|
@ -1300,7 +1323,7 @@ pub async fn run_tests_with_watch(
|
|||
|
||||
/// Tracks failures for the `--fail-fast` argument in
|
||||
/// order to tell when to stop running tests.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FailFastTracker {
|
||||
max_count: Option<usize>,
|
||||
failure_count: Arc<AtomicUsize>,
|
||||
|
|
|
@ -66,6 +66,7 @@ pub fn format_test_step_for_summary(
|
|||
}
|
||||
|
||||
pub(super) fn report_sigint(
|
||||
writer: &mut dyn std::io::Write,
|
||||
cwd: &Url,
|
||||
tests_pending: &HashSet<usize>,
|
||||
tests: &IndexMap<usize, TestDescription>,
|
||||
|
@ -84,17 +85,20 @@ pub(super) fn report_sigint(
|
|||
.insert(format_test_step_for_summary(cwd, desc, tests, test_steps));
|
||||
}
|
||||
}
|
||||
println!(
|
||||
writeln!(
|
||||
writer,
|
||||
"\n{} The following tests were pending:\n",
|
||||
colors::intense_blue("SIGINT")
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
for entry in formatted_pending {
|
||||
println!("{}", entry);
|
||||
writeln!(writer, "{}", entry).unwrap();
|
||||
}
|
||||
println!();
|
||||
writeln!(writer).unwrap();
|
||||
}
|
||||
|
||||
pub(super) fn report_summary(
|
||||
writer: &mut dyn std::io::Write,
|
||||
cwd: &Url,
|
||||
summary: &TestSummary,
|
||||
elapsed: &Duration,
|
||||
|
@ -120,14 +124,20 @@ pub(super) fn report_summary(
|
|||
}
|
||||
|
||||
// note: the trailing whitespace is intentional to get a red background
|
||||
println!("\n{}\n", colors::white_bold_on_red(" ERRORS "));
|
||||
writeln!(writer, "\n{}\n", colors::white_bold_on_red(" ERRORS ")).unwrap();
|
||||
for (origin, (failures, uncaught_error)) in failures_by_origin {
|
||||
for (description, failure) in failures {
|
||||
if !failure.hide_in_summary() {
|
||||
let failure_title = format_test_for_summary(cwd, description);
|
||||
println!("{}", &failure_title);
|
||||
println!("{}: {}", colors::red_bold("error"), failure.to_string());
|
||||
println!();
|
||||
writeln!(writer, "{}", &failure_title).unwrap();
|
||||
writeln!(
|
||||
writer,
|
||||
"{}: {}",
|
||||
colors::red_bold("error"),
|
||||
failure.to_string()
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(writer).unwrap();
|
||||
failure_titles.push(failure_title);
|
||||
}
|
||||
}
|
||||
|
@ -136,22 +146,24 @@ pub(super) fn report_summary(
|
|||
"{} (uncaught error)",
|
||||
to_relative_path_or_remote_url(cwd, &origin)
|
||||
);
|
||||
println!("{}", &failure_title);
|
||||
println!(
|
||||
writeln!(writer, "{}", &failure_title).unwrap();
|
||||
writeln!(
|
||||
writer,
|
||||
"{}: {}",
|
||||
colors::red_bold("error"),
|
||||
format_test_error(js_error)
|
||||
);
|
||||
println!("This error was not caught from a test and caused the test runner to fail on the referenced module.");
|
||||
println!("It most likely originated from a dangling promise, event/timeout handler or top-level code.");
|
||||
println!();
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(writer, "This error was not caught from a test and caused the test runner to fail on the referenced module.").unwrap();
|
||||
writeln!(writer, "It most likely originated from a dangling promise, event/timeout handler or top-level code.").unwrap();
|
||||
writeln!(writer).unwrap();
|
||||
failure_titles.push(failure_title);
|
||||
}
|
||||
}
|
||||
// note: the trailing whitespace is intentional to get a red background
|
||||
println!("{}\n", colors::white_bold_on_red(" FAILURES "));
|
||||
writeln!(writer, "{}\n", colors::white_bold_on_red(" FAILURES ")).unwrap();
|
||||
for failure_title in failure_titles {
|
||||
println!("{failure_title}");
|
||||
writeln!(writer, "{failure_title}").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,10 +213,12 @@ pub(super) fn report_summary(
|
|||
write!(summary_result, " | {} filtered out", summary.filtered_out).unwrap()
|
||||
};
|
||||
|
||||
println!(
|
||||
writeln!(
|
||||
writer,
|
||||
"\n{} | {} {}\n",
|
||||
status,
|
||||
summary_result,
|
||||
colors::gray(format!("({})", display::human_elapsed(elapsed.as_millis()))),
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -184,7 +184,12 @@ impl TestReporter for DotTestReporter {
|
|||
_tests: &IndexMap<usize, TestDescription>,
|
||||
_test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) {
|
||||
common::report_summary(&self.cwd, &self.summary, elapsed);
|
||||
common::report_summary(
|
||||
&mut std::io::stdout(),
|
||||
&self.cwd,
|
||||
&self.summary,
|
||||
elapsed,
|
||||
);
|
||||
}
|
||||
|
||||
fn report_sigint(
|
||||
|
@ -193,7 +198,13 @@ impl TestReporter for DotTestReporter {
|
|||
tests: &IndexMap<usize, TestDescription>,
|
||||
test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) {
|
||||
common::report_sigint(&self.cwd, tests_pending, tests, test_steps);
|
||||
common::report_sigint(
|
||||
&mut std::io::stdout(),
|
||||
&self.cwd,
|
||||
tests_pending,
|
||||
tests,
|
||||
test_steps,
|
||||
);
|
||||
}
|
||||
|
||||
fn flush_report(
|
||||
|
|
|
@ -9,6 +9,7 @@ pub struct PrettyTestReporter {
|
|||
echo_output: bool,
|
||||
in_new_line: bool,
|
||||
filter: bool,
|
||||
repl: bool,
|
||||
scope_test_id: Option<usize>,
|
||||
cwd: Url,
|
||||
did_have_user_output: bool,
|
||||
|
@ -16,6 +17,7 @@ pub struct PrettyTestReporter {
|
|||
child_results_buffer:
|
||||
HashMap<usize, IndexMap<usize, (TestStepDescription, TestStepResult, u64)>>,
|
||||
summary: TestSummary,
|
||||
writer: Box<dyn std::io::Write>,
|
||||
}
|
||||
|
||||
impl PrettyTestReporter {
|
||||
|
@ -23,35 +25,40 @@ impl PrettyTestReporter {
|
|||
parallel: bool,
|
||||
echo_output: bool,
|
||||
filter: bool,
|
||||
repl: bool,
|
||||
) -> PrettyTestReporter {
|
||||
PrettyTestReporter {
|
||||
parallel,
|
||||
echo_output,
|
||||
in_new_line: true,
|
||||
filter,
|
||||
repl,
|
||||
scope_test_id: None,
|
||||
cwd: Url::from_directory_path(std::env::current_dir().unwrap()).unwrap(),
|
||||
did_have_user_output: false,
|
||||
started_tests: false,
|
||||
child_results_buffer: Default::default(),
|
||||
summary: TestSummary::new(),
|
||||
writer: Box::new(std::io::stdout()),
|
||||
}
|
||||
}
|
||||
|
||||
fn force_report_wait(&mut self, description: &TestDescription) {
|
||||
if !self.in_new_line {
|
||||
println!();
|
||||
writeln!(&mut self.writer).unwrap();
|
||||
}
|
||||
if self.parallel {
|
||||
print!(
|
||||
write!(
|
||||
&mut self.writer,
|
||||
"{}",
|
||||
colors::gray(format!(
|
||||
"{} => ",
|
||||
to_relative_path_or_remote_url(&self.cwd, &description.origin)
|
||||
))
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
print!("{} ...", description.name);
|
||||
write!(&mut self.writer, "{} ...", description.name).unwrap();
|
||||
self.in_new_line = false;
|
||||
// flush for faster feedback when line buffered
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
@ -61,9 +68,15 @@ impl PrettyTestReporter {
|
|||
fn force_report_step_wait(&mut self, description: &TestStepDescription) {
|
||||
self.write_output_end();
|
||||
if !self.in_new_line {
|
||||
println!();
|
||||
writeln!(&mut self.writer).unwrap();
|
||||
}
|
||||
print!("{}{} ...", " ".repeat(description.level), description.name);
|
||||
write!(
|
||||
&mut self.writer,
|
||||
"{}{} ...",
|
||||
" ".repeat(description.level),
|
||||
description.name
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = false;
|
||||
// flush for faster feedback when line buffered
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
@ -99,19 +112,21 @@ impl PrettyTestReporter {
|
|||
TestStepResult::Ignored => colors::yellow("ignored").to_string(),
|
||||
TestStepResult::Failed(failure) => failure.format_label(),
|
||||
};
|
||||
print!(" {}", status);
|
||||
write!(&mut self.writer, " {}", status).unwrap();
|
||||
if let TestStepResult::Failed(failure) = result {
|
||||
if let Some(inline_summary) = failure.format_inline_summary() {
|
||||
print!(" ({})", inline_summary)
|
||||
write!(&mut self.writer, " ({})", inline_summary).unwrap()
|
||||
}
|
||||
}
|
||||
if !matches!(result, TestStepResult::Failed(TestFailure::Incomplete)) {
|
||||
print!(
|
||||
write!(
|
||||
&mut self.writer,
|
||||
" {}",
|
||||
colors::gray(format!("({})", display::human_elapsed(elapsed.into())))
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
println!();
|
||||
writeln!(&mut self.writer).unwrap();
|
||||
self.in_new_line = true;
|
||||
if self.parallel {
|
||||
self.scope_test_id = None;
|
||||
|
@ -127,7 +142,12 @@ impl PrettyTestReporter {
|
|||
|
||||
fn write_output_end(&mut self) {
|
||||
if self.did_have_user_output {
|
||||
println!("{}", colors::gray("----- output end -----"));
|
||||
writeln!(
|
||||
&mut self.writer,
|
||||
"{}",
|
||||
colors::gray("----- output end -----")
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = true;
|
||||
self.did_have_user_output = false;
|
||||
}
|
||||
|
@ -139,11 +159,15 @@ impl TestReporter for PrettyTestReporter {
|
|||
fn report_plan(&mut self, plan: &TestPlan) {
|
||||
self.summary.total += plan.total;
|
||||
self.summary.filtered_out += plan.filtered_out;
|
||||
if self.repl {
|
||||
return;
|
||||
}
|
||||
if self.parallel || (self.filter && plan.total == 0) {
|
||||
return;
|
||||
}
|
||||
let inflection = if plan.total == 1 { "test" } else { "tests" };
|
||||
println!(
|
||||
writeln!(
|
||||
&mut self.writer,
|
||||
"{}",
|
||||
colors::gray(format!(
|
||||
"running {} {} from {}",
|
||||
|
@ -151,7 +175,8 @@ impl TestReporter for PrettyTestReporter {
|
|||
inflection,
|
||||
to_relative_path_or_remote_url(&self.cwd, &plan.origin)
|
||||
))
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = true;
|
||||
}
|
||||
|
||||
|
@ -170,9 +195,14 @@ impl TestReporter for PrettyTestReporter {
|
|||
if !self.did_have_user_output && self.started_tests {
|
||||
self.did_have_user_output = true;
|
||||
if !self.in_new_line {
|
||||
println!();
|
||||
writeln!(&mut self.writer).unwrap();
|
||||
}
|
||||
println!("{}", colors::gray("------- output -------"));
|
||||
writeln!(
|
||||
&mut self.writer,
|
||||
"{}",
|
||||
colors::gray("------- output -------")
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = true;
|
||||
}
|
||||
|
||||
|
@ -221,16 +251,18 @@ impl TestReporter for PrettyTestReporter {
|
|||
TestResult::Failed(failure) => failure.format_label(),
|
||||
TestResult::Cancelled => colors::gray("cancelled").to_string(),
|
||||
};
|
||||
print!(" {}", status);
|
||||
write!(&mut self.writer, " {}", status).unwrap();
|
||||
if let TestResult::Failed(failure) = result {
|
||||
if let Some(inline_summary) = failure.format_inline_summary() {
|
||||
print!(" ({})", inline_summary)
|
||||
write!(&mut self.writer, " ({})", inline_summary).unwrap();
|
||||
}
|
||||
}
|
||||
println!(
|
||||
writeln!(
|
||||
&mut self.writer,
|
||||
" {}",
|
||||
colors::gray(format!("({})", display::human_elapsed(elapsed.into())))
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = true;
|
||||
self.scope_test_id = None;
|
||||
}
|
||||
|
@ -243,13 +275,15 @@ impl TestReporter for PrettyTestReporter {
|
|||
.push((origin.to_string(), error));
|
||||
|
||||
if !self.in_new_line {
|
||||
println!();
|
||||
writeln!(&mut self.writer).unwrap();
|
||||
}
|
||||
println!(
|
||||
writeln!(
|
||||
&mut self.writer,
|
||||
"Uncaught error from {} {}",
|
||||
to_relative_path_or_remote_url(&self.cwd, origin),
|
||||
colors::red("FAILED")
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = true;
|
||||
self.did_have_user_output = false;
|
||||
}
|
||||
|
@ -295,14 +329,16 @@ impl TestReporter for PrettyTestReporter {
|
|||
|
||||
if self.parallel {
|
||||
self.write_output_end();
|
||||
print!(
|
||||
write!(
|
||||
&mut self.writer,
|
||||
"{} {} ...",
|
||||
colors::gray(format!(
|
||||
"{} =>",
|
||||
to_relative_path_or_remote_url(&self.cwd, &desc.origin)
|
||||
)),
|
||||
common::format_test_step_ancestry(desc, tests, test_steps)
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
self.in_new_line = false;
|
||||
self.scope_test_id = Some(desc.id);
|
||||
self.force_report_step_result(desc, result, elapsed);
|
||||
|
@ -331,7 +367,7 @@ impl TestReporter for PrettyTestReporter {
|
|||
_tests: &IndexMap<usize, TestDescription>,
|
||||
_test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) {
|
||||
common::report_summary(&self.cwd, &self.summary, elapsed);
|
||||
common::report_summary(&mut self.writer, &self.cwd, &self.summary, elapsed);
|
||||
self.in_new_line = true;
|
||||
}
|
||||
|
||||
|
@ -341,7 +377,13 @@ impl TestReporter for PrettyTestReporter {
|
|||
tests: &IndexMap<usize, TestDescription>,
|
||||
test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) {
|
||||
common::report_sigint(&self.cwd, tests_pending, tests, test_steps);
|
||||
common::report_sigint(
|
||||
&mut self.writer,
|
||||
&self.cwd,
|
||||
tests_pending,
|
||||
tests,
|
||||
test_steps,
|
||||
);
|
||||
self.in_new_line = true;
|
||||
}
|
||||
|
||||
|
@ -351,6 +393,7 @@ impl TestReporter for PrettyTestReporter {
|
|||
_tests: &IndexMap<usize, TestDescription>,
|
||||
_test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.writer.flush().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,7 +227,13 @@ impl TestReporter for TapTestReporter {
|
|||
test_steps: &IndexMap<usize, TestStepDescription>,
|
||||
) {
|
||||
println!("Bail out! SIGINT received.");
|
||||
common::report_sigint(&self.cwd, tests_pending, tests, test_steps);
|
||||
common::report_sigint(
|
||||
&mut std::io::stdout(),
|
||||
&self.cwd,
|
||||
tests_pending,
|
||||
tests,
|
||||
test_steps,
|
||||
);
|
||||
}
|
||||
|
||||
fn flush_report(
|
||||
|
|
Loading…
Reference in a new issue