use serde::{Deserialize, Deserializer};
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GlobalTimeout {
pub(crate) period: Duration,
}
impl<'de> Deserialize<'de> for GlobalTimeout {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(GlobalTimeout {
period: humantime_serde::deserialize(deserializer)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
config::{core::NextestConfig, utils::test_helpers::*},
run_mode::NextestRunMode,
};
use camino_tempfile::tempdir;
use indoc::indoc;
use nextest_filtering::ParseContext;
use test_case::test_case;
#[test_case(
"",
Ok(GlobalTimeout { period: Duration::from_secs(946728000) }),
None
; "empty config is expected to use the hardcoded values"
)]
#[test_case(
indoc! {r#"
[profile.default]
global-timeout = "30s"
"#},
Ok(GlobalTimeout { period: Duration::from_secs(30) }),
None
; "overrides the default profile"
)]
#[test_case(
indoc! {r#"
[profile.default]
global-timeout = "30s"
[profile.ci]
global-timeout = "60s"
"#},
Ok(GlobalTimeout { period: Duration::from_secs(30) }),
Some(GlobalTimeout { period: Duration::from_secs(60) })
; "adds a custom profile 'ci'"
)]
fn globaltimeout_adheres_to_hierarchy(
config_contents: &str,
expected_default: Result<GlobalTimeout, &str>,
maybe_expected_ci: Option<GlobalTimeout>,
) {
let workspace_dir = tempdir().unwrap();
let graph = temp_workspace(&workspace_dir, config_contents);
let pcx = ParseContext::new(&graph);
let nextest_config_result = NextestConfig::from_sources(
graph.workspace().root(),
&pcx,
None,
&[][..],
&Default::default(),
);
match expected_default {
Ok(expected_default) => {
let nextest_config = nextest_config_result.expect("config file should parse");
assert_eq!(
nextest_config
.profile("default")
.expect("default profile should exist")
.apply_build_platforms(&build_platforms())
.global_timeout(NextestRunMode::Test),
expected_default,
);
if let Some(expected_ci) = maybe_expected_ci {
assert_eq!(
nextest_config
.profile("ci")
.expect("ci profile should exist")
.apply_build_platforms(&build_platforms())
.global_timeout(NextestRunMode::Test),
expected_ci,
);
}
}
Err(expected_err_str) => {
let err_str = format!("{:?}", nextest_config_result.unwrap_err());
assert!(
err_str.contains(expected_err_str),
"expected error string not found: {err_str}",
)
}
}
}
const DEFAULT_GLOBAL_TIMEOUT: GlobalTimeout = GlobalTimeout {
period: Duration::from_secs(946728000),
};
#[derive(Debug)]
enum ExpectedBenchGlobalTimeout {
Exact(GlobalTimeout),
VeryLarge,
}
#[test_case(
"",
DEFAULT_GLOBAL_TIMEOUT,
ExpectedBenchGlobalTimeout::VeryLarge
; "empty config uses defaults for both modes"
)]
#[test_case(
indoc! {r#"
[profile.default]
global-timeout = "10s"
"#},
GlobalTimeout { period: Duration::from_secs(10) },
// bench.global-timeout should still be 30 years (default).
ExpectedBenchGlobalTimeout::VeryLarge
; "global-timeout does not affect bench.global-timeout"
)]
#[test_case(
indoc! {r#"
[profile.default]
bench.global-timeout = "20s"
"#},
// global-timeout should still be 30 years (default).
DEFAULT_GLOBAL_TIMEOUT,
ExpectedBenchGlobalTimeout::Exact(GlobalTimeout {
period: Duration::from_secs(20),
})
; "bench.global-timeout does not affect global-timeout"
)]
#[test_case(
indoc! {r#"
[profile.default]
global-timeout = "10s"
bench.global-timeout = "20s"
"#},
GlobalTimeout { period: Duration::from_secs(10) },
ExpectedBenchGlobalTimeout::Exact(GlobalTimeout {
period: Duration::from_secs(20),
})
; "both global-timeout and bench.global-timeout can be set independently"
)]
fn bench_globaltimeout_is_independent(
config_contents: &str,
expected_test_timeout: GlobalTimeout,
expected_bench_timeout: ExpectedBenchGlobalTimeout,
) {
let workspace_dir = tempdir().unwrap();
let graph = temp_workspace(&workspace_dir, config_contents);
let pcx = ParseContext::new(&graph);
let nextest_config = NextestConfig::from_sources(
graph.workspace().root(),
&pcx,
None,
&[][..],
&Default::default(),
)
.expect("config file should parse");
let profile = nextest_config
.profile("default")
.expect("default profile should exist")
.apply_build_platforms(&build_platforms());
assert_eq!(
profile.global_timeout(NextestRunMode::Test),
expected_test_timeout,
"Test mode global-timeout mismatch"
);
let actual_bench_timeout = profile.global_timeout(NextestRunMode::Benchmark);
match expected_bench_timeout {
ExpectedBenchGlobalTimeout::Exact(expected) => {
assert_eq!(
actual_bench_timeout, expected,
"Benchmark mode global-timeout mismatch"
);
}
ExpectedBenchGlobalTimeout::VeryLarge => {
assert!(
actual_bench_timeout.period >= DEFAULT_GLOBAL_TIMEOUT.period,
"Benchmark mode global-timeout should be >= default, got {:?}",
actual_bench_timeout.period
);
}
}
}
}