1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 21:50:00 -05:00

fix(test): improve how --fail-fast shuts down when hitting limit (#16956)

Closes #15650
This commit is contained in:
David Sherret 2022-12-05 16:17:49 -05:00 committed by GitHub
parent 3863aaf8ae
commit 2fab4583ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 31 deletions

View file

@ -130,6 +130,10 @@ mod ts {
path_dts.join(format!("lib.{}.d.ts", name)).display() path_dts.join(format!("lib.{}.d.ts", name)).display()
); );
} }
println!(
"cargo:rerun-if-changed={}",
cwd.join("js").join("40_testing.js").display()
);
// create a copy of the vector that includes any op crate libs to be passed // create a copy of the vector that includes any op crate libs to be passed
// to the JavaScript compiler to build into the snapshot // to the JavaScript compiler to build into the snapshot

View file

@ -1086,6 +1086,9 @@
} }
for (const desc of filtered) { for (const desc of filtered) {
if (ops.op_tests_should_stop()) {
break;
}
ops.op_dispatch_test_event({ wait: desc.id }); ops.op_dispatch_test_event({ wait: desc.id });
const earlier = DateNow(); const earlier = DateNow();
const result = await runTest(desc); const result = await runTest(desc);

View file

@ -13,6 +13,7 @@ use crate::lsp::logging::lsp_log;
use crate::ops; use crate::ops;
use crate::proc_state; use crate::proc_state;
use crate::tools::test; use crate::tools::test;
use crate::tools::test::FailFastTracker;
use crate::tools::test::TestEventSender; use crate::tools::test::TestEventSender;
use crate::util::checksum; use crate::util::checksum;
use crate::worker::create_main_worker_for_test_or_bench; use crate::worker::create_main_worker_for_test_or_bench;
@ -144,25 +145,29 @@ impl LspTestFilter {
} }
} }
#[allow(clippy::too_many_arguments)]
async fn test_specifier( async fn test_specifier(
ps: proc_state::ProcState, ps: proc_state::ProcState,
permissions: Permissions, permissions: Permissions,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
mode: test::TestMode, mode: test::TestMode,
sender: &TestEventSender, sender: TestEventSender,
fail_fast_tracker: FailFastTracker,
token: CancellationToken, token: CancellationToken,
filter: test::TestFilter, filter: test::TestFilter,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if !token.is_cancelled() { if !token.is_cancelled() {
let stdout = StdioPipe::File(sender.stdout());
let stderr = StdioPipe::File(sender.stderr());
let mut worker = create_main_worker_for_test_or_bench( let mut worker = create_main_worker_for_test_or_bench(
&ps, &ps,
specifier.clone(), specifier.clone(),
permissions, permissions,
vec![ops::testing::init(sender.clone(), filter)], vec![ops::testing::init(sender, fail_fast_tracker, filter)],
Stdio { Stdio {
stdin: StdioPipe::Inherit, stdin: StdioPipe::Inherit,
stdout: StdioPipe::File(sender.stdout()), stdout,
stderr: StdioPipe::File(sender.stderr()), stderr,
}, },
) )
.await?; .await?;
@ -262,19 +267,17 @@ impl TestRun {
) )
.await?; .await?;
let (sender, mut receiver) = mpsc::unbounded_channel::<test::TestEvent>();
let sender = TestEventSender::new(sender);
let (concurrent_jobs, fail_fast) = let (concurrent_jobs, fail_fast) =
if let DenoSubcommand::Test(test_flags) = ps.options.sub_command() { if let DenoSubcommand::Test(test_flags) = ps.options.sub_command() {
( (test_flags.concurrent_jobs.into(), test_flags.fail_fast)
test_flags.concurrent_jobs.into(),
test_flags.fail_fast.map(|count| count.into()),
)
} else { } else {
unreachable!("Should always be Test subcommand."); unreachable!("Should always be Test subcommand.");
}; };
let (sender, mut receiver) = mpsc::unbounded_channel::<test::TestEvent>();
let sender = TestEventSender::new(sender);
let fail_fast_tracker = FailFastTracker::new(fail_fast);
let mut queue = self.queue.iter().collect::<Vec<&ModuleSpecifier>>(); let mut queue = self.queue.iter().collect::<Vec<&ModuleSpecifier>>();
queue.sort(); queue.sort();
@ -288,6 +291,7 @@ impl TestRun {
let ps = ps.clone(); let ps = ps.clone();
let permissions = permissions.clone(); let permissions = permissions.clone();
let mut sender = sender.clone(); let mut sender = sender.clone();
let fail_fast_tracker = fail_fast_tracker.clone();
let lsp_filter = self.filters.get(&specifier); let lsp_filter = self.filters.get(&specifier);
let filter = test::TestFilter { let filter = test::TestFilter {
substring: None, substring: None,
@ -305,13 +309,17 @@ impl TestRun {
let tests = tests_.clone(); let tests = tests_.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
if fail_fast_tracker.should_stop() {
return Ok(());
}
let origin = specifier.to_string(); let origin = specifier.to_string();
let file_result = run_local(test_specifier( let file_result = run_local(test_specifier(
ps, ps,
permissions, permissions,
specifier, specifier,
test::TestMode::Executable, test::TestMode::Executable,
&sender, sender.clone(),
fail_fast_tracker,
token, token,
filter, filter,
)); ));
@ -427,12 +435,6 @@ impl TestRun {
); );
} }
} }
if let Some(count) = fail_fast {
if summary.failed >= count {
break;
}
}
} }
let elapsed = Instant::now().duration_since(earlier); let elapsed = Instant::now().duration_since(earlier);

View file

@ -1,10 +1,12 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::tools::test::FailFastTracker;
use crate::tools::test::TestDescription; use crate::tools::test::TestDescription;
use crate::tools::test::TestEvent; use crate::tools::test::TestEvent;
use crate::tools::test::TestEventSender; use crate::tools::test::TestEventSender;
use crate::tools::test::TestFilter; use crate::tools::test::TestFilter;
use crate::tools::test::TestLocation; use crate::tools::test::TestLocation;
use crate::tools::test::TestResult;
use crate::tools::test::TestStepDescription; use crate::tools::test::TestStepDescription;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -23,7 +25,11 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use uuid::Uuid; use uuid::Uuid;
pub fn init(sender: TestEventSender, filter: TestFilter) -> Extension { pub fn init(
sender: TestEventSender,
fail_fast_tracker: FailFastTracker,
filter: TestFilter,
) -> Extension {
Extension::builder() Extension::builder()
.ops(vec![ .ops(vec![
op_pledge_test_permissions::decl(), op_pledge_test_permissions::decl(),
@ -32,9 +38,11 @@ pub fn init(sender: TestEventSender, filter: TestFilter) -> Extension {
op_register_test::decl(), op_register_test::decl(),
op_register_test_step::decl(), op_register_test_step::decl(),
op_dispatch_test_event::decl(), op_dispatch_test_event::decl(),
op_tests_should_stop::decl(),
]) ])
.state(move |state| { .state(move |state| {
state.put(sender.clone()); state.put(sender.clone());
state.put(fail_fast_tracker.clone());
state.put(filter.clone()); state.put(filter.clone());
Ok(()) Ok(())
}) })
@ -178,7 +186,18 @@ fn op_dispatch_test_event(
state: &mut OpState, state: &mut OpState,
event: TestEvent, event: TestEvent,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
if matches!(
event,
TestEvent::Result(_, TestResult::Cancelled | TestResult::Failed(_), _)
) {
state.borrow::<FailFastTracker>().add_failure();
}
let mut sender = state.borrow::<TestEventSender>().clone(); let mut sender = state.borrow::<TestEventSender>().clone();
sender.send(event).ok(); sender.send(event).ok();
Ok(()) Ok(())
} }
#[op]
fn op_tests_should_stop(state: &mut OpState) -> bool {
state.borrow::<FailFastTracker>().should_stop()
}

View file

@ -53,6 +53,7 @@ use std::io::Write;
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; use std::time::Instant;
@ -713,18 +714,25 @@ async fn test_specifier(
permissions: Permissions, permissions: Permissions,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
mode: TestMode, mode: TestMode,
sender: &TestEventSender, sender: TestEventSender,
fail_fast_tracker: FailFastTracker,
options: TestSpecifierOptions, options: TestSpecifierOptions,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let stdout = StdioPipe::File(sender.stdout());
let stderr = StdioPipe::File(sender.stderr());
let mut worker = create_main_worker_for_test_or_bench( let mut worker = create_main_worker_for_test_or_bench(
&ps, &ps,
specifier.clone(), specifier.clone(),
permissions, permissions,
vec![ops::testing::init(sender.clone(), options.filter.clone())], vec![ops::testing::init(
sender,
fail_fast_tracker,
options.filter.clone(),
)],
Stdio { Stdio {
stdin: StdioPipe::Inherit, stdin: StdioPipe::Inherit,
stdout: StdioPipe::File(sender.stdout()), stdout,
stderr: StdioPipe::File(sender.stderr()), stderr,
}, },
) )
.await?; .await?;
@ -999,9 +1007,9 @@ async fn test_specifiers(
}; };
let (sender, mut receiver) = unbounded_channel::<TestEvent>(); let (sender, mut receiver) = unbounded_channel::<TestEvent>();
let fail_fast_tracker = FailFastTracker::new(options.fail_fast);
let sender = TestEventSender::new(sender); let sender = TestEventSender::new(sender);
let concurrent_jobs = options.concurrent_jobs; let concurrent_jobs = options.concurrent_jobs;
let fail_fast = options.fail_fast;
let join_handles = let join_handles =
specifiers_with_mode.iter().map(move |(specifier, mode)| { specifiers_with_mode.iter().map(move |(specifier, mode)| {
@ -1011,15 +1019,21 @@ async fn test_specifiers(
let mode = mode.clone(); let mode = mode.clone();
let mut sender = sender.clone(); let mut sender = sender.clone();
let options = options.clone(); let options = options.clone();
let fail_fast_tracker = fail_fast_tracker.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
if fail_fast_tracker.should_stop() {
return Ok(());
}
let origin = specifier.to_string(); let origin = specifier.to_string();
let file_result = run_local(test_specifier( let file_result = run_local(test_specifier(
ps, ps,
permissions, permissions,
specifier, specifier,
mode, mode,
&sender, sender.clone(),
fail_fast_tracker,
options, options,
)); ));
if let Err(error) = file_result { if let Err(error) = file_result {
@ -1148,12 +1162,6 @@ async fn test_specifiers(
); );
} }
} }
if let Some(x) = fail_fast {
if summary.failed >= x.get() {
break;
}
}
} }
let elapsed = Instant::now().duration_since(earlier); let elapsed = Instant::now().duration_since(earlier);
@ -1564,6 +1572,42 @@ pub async fn run_tests_with_watch(
Ok(()) Ok(())
} }
/// Tracks failures for the `--fail-fast` argument in
/// order to tell when to stop running tests.
#[derive(Clone)]
pub struct FailFastTracker {
max_count: Option<usize>,
failure_count: Arc<AtomicUsize>,
}
impl FailFastTracker {
pub fn new(fail_fast: Option<NonZeroUsize>) -> Self {
Self {
max_count: fail_fast.map(|v| v.into()),
failure_count: Default::default(),
}
}
pub fn add_failure(&self) -> bool {
if let Some(max_count) = &self.max_count {
self
.failure_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
>= *max_count
} else {
false
}
}
pub fn should_stop(&self) -> bool {
if let Some(max_count) = &self.max_count {
self.failure_count.load(std::sync::atomic::Ordering::SeqCst) >= *max_count
} else {
false
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct TestEventSender { pub struct TestEventSender {
sender: UnboundedSender<TestEvent>, sender: UnboundedSender<TestEvent>,
@ -1596,6 +1640,7 @@ impl TestEventSender {
TestEvent::Result(_, _, _) TestEvent::Result(_, _, _)
| TestEvent::StepWait(_) | TestEvent::StepWait(_)
| TestEvent::StepResult(_, _, _) | TestEvent::StepResult(_, _, _)
| TestEvent::UncaughtError(_, _)
) { ) {
self.flush_stdout_and_stderr(); self.flush_stdout_and_stderr();
} }