Skip to content

Commit fb0a6e0

Browse files
committed
[nextest-runner] make leak timeout configurable
Keep the same setting but make it configurable, both as a default setting and via overrides.
1 parent 9c97e9d commit fb0a6e0

File tree

3 files changed

+37
-1
lines changed

3 files changed

+37
-1
lines changed

nextest-runner/default-config.toml

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ fail-fast = true
6767
# Example: slow-timeout = { period = "60s", terminate-after = 2 }
6868
slow-timeout = { period = "60s" }
6969

70+
# Treat a test as leaky if after the process is shut down, standard output and standard error
71+
# aren't closed by this time.
72+
#
73+
# This usually happens in case of a test that creates a child process and lets it inherit those
74+
# handles, but doesn't clean the child process up (especially when it fails).
75+
leak-timeout = "100ms"
76+
7077
[profile.default.junit]
7178
# Output a JUnit report into the given file inside 'store.dir/<profile-name>'.
7279
# If unspecified, JUnit is not written out.

nextest-runner/src/config.rs

+25
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,14 @@ impl<'cfg> NextestProfile<'cfg> {
204204
.unwrap_or(self.default_profile.slow_timeout)
205205
}
206206

207+
/// Returns the time after which a child process that hasn't closed its handles is marked as
208+
/// leaky.
209+
pub fn leak_timeout(&self) -> Duration {
210+
self.custom_profile
211+
.and_then(|profile| profile.leak_timeout)
212+
.unwrap_or(self.default_profile.leak_timeout)
213+
}
214+
207215
/// Returns the test status level.
208216
pub fn status_level(&self) -> StatusLevel {
209217
self.custom_profile
@@ -243,6 +251,7 @@ impl<'cfg> NextestProfile<'cfg> {
243251
pub fn overrides_for(&self, query: &TestQuery<'_>) -> ProfileOverrides {
244252
let mut retries = None;
245253
let mut slow_timeout = None;
254+
let mut leak_timeout = None;
246255

247256
for &override_ in &self.overrides {
248257
if !override_.expr.matches_test(query) {
@@ -254,11 +263,15 @@ impl<'cfg> NextestProfile<'cfg> {
254263
if slow_timeout.is_none() && override_.data.slow_timeout.is_some() {
255264
slow_timeout = override_.data.slow_timeout;
256265
}
266+
if leak_timeout.is_none() && override_.data.leak_timeout.is_some() {
267+
leak_timeout = override_.data.leak_timeout;
268+
}
257269
}
258270

259271
ProfileOverrides {
260272
retries,
261273
slow_timeout,
274+
leak_timeout,
262275
}
263276
}
264277

@@ -288,6 +301,7 @@ impl<'cfg> NextestProfile<'cfg> {
288301
pub struct ProfileOverrides {
289302
retries: Option<usize>,
290303
slow_timeout: Option<SlowTimeout>,
304+
leak_timeout: Option<Duration>,
291305
}
292306

293307
impl ProfileOverrides {
@@ -300,6 +314,11 @@ impl ProfileOverrides {
300314
pub fn slow_timeout(&self) -> Option<SlowTimeout> {
301315
self.slow_timeout
302316
}
317+
318+
/// Returns the leak timeout for this test.
319+
pub fn leak_timeout(&self) -> Option<Duration> {
320+
self.leak_timeout
321+
}
303322
}
304323

305324
/// JUnit configuration for nextest, returned by a [`NextestProfile`].
@@ -376,6 +395,8 @@ struct DefaultProfileImpl {
376395
fail_fast: bool,
377396
#[serde(deserialize_with = "require_deserialize_slow_timeout")]
378397
slow_timeout: SlowTimeout,
398+
#[serde(with = "humantime_serde")]
399+
leak_timeout: Duration,
379400
#[serde(default)]
380401
overrides: Vec<ProfileOverrideSource>,
381402
junit: DefaultJunitImpl,
@@ -547,6 +568,8 @@ struct CustomProfileImpl {
547568
fail_fast: Option<bool>,
548569
#[serde(default, deserialize_with = "deserialize_slow_timeout")]
549570
slow_timeout: Option<SlowTimeout>,
571+
#[serde(default, with = "humantime_serde::option")]
572+
leak_timeout: Option<Duration>,
550573
#[serde(default)]
551574
overrides: Vec<ProfileOverrideSource>,
552575
#[serde(default)]
@@ -571,6 +594,8 @@ struct ProfileOverrideData {
571594
retries: Option<usize>,
572595
#[serde(default, deserialize_with = "deserialize_slow_timeout")]
573596
slow_timeout: Option<SlowTimeout>,
597+
#[serde(default)]
598+
leak_timeout: Option<Duration>,
574599
}
575600

576601
#[derive(Clone, Debug, Default)]

nextest-runner/src/runner.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl TestRunnerBuilder {
9393
};
9494
let fail_fast = self.fail_fast.unwrap_or_else(|| profile.fail_fast());
9595
let slow_timeout = profile.slow_timeout();
96+
let leak_timeout = profile.leak_timeout();
9697

9798
let runtime = Runtime::new().map_err(TestRunnerBuildError::TokioRuntimeCreate)?;
9899
let _guard = runtime.enter();
@@ -110,6 +111,7 @@ impl TestRunnerBuilder {
110111
ignore_retry_overrides,
111112
fail_fast,
112113
slow_timeout,
114+
leak_timeout,
113115
test_list,
114116
target_runner,
115117
runtime,
@@ -165,6 +167,7 @@ struct TestRunnerInner<'a> {
165167
ignore_retry_overrides: bool,
166168
fail_fast: bool,
167169
slow_timeout: crate::config::SlowTimeout,
170+
leak_timeout: Duration,
168171
test_list: &'a TestList<'a>,
169172
target_runner: TargetRunner,
170173
runtime: Runtime,
@@ -426,6 +429,7 @@ impl<'a> TestRunnerInner<'a> {
426429

427430
let mut status: Option<ExecutionResult> = None;
428431
let slow_timeout = overrides.slow_timeout().unwrap_or(self.slow_timeout);
432+
let leak_timeout = overrides.leak_timeout().unwrap_or(self.leak_timeout);
429433
let mut is_slow = false;
430434

431435
let mut interval = tokio::time::interval(slow_timeout.period);
@@ -558,7 +562,7 @@ impl<'a> TestRunnerInner<'a> {
558562
// Previously, this used to hang if spawned grandchildren inherited stdout/stderr but
559563
// didn't shut down properly. Now, this detects those cases and marks them as leaked.
560564
let leaked = loop {
561-
let sleep = tokio::time::sleep(Duration::from_millis(100));
565+
let sleep = tokio::time::sleep(leak_timeout);
562566

563567
tokio::select! {
564568
res = &mut stdout_fut, if !stdout_done => {

0 commit comments

Comments
 (0)