use std::{
ffi::{OsStr, OsString},
path::PathBuf,
time::Duration,
};
use rootcause::Report;
use crate::{CargoLeptosMode, LeptosBrowserTestError, SiteScheme, app::LeptosTestApp};
const DEFAULT_STARTUP_LOG_TAIL_LINES: usize = 200;
const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(60 * 10);
const DEFAULT_STARTUP_TIMEOUT_REASON: &str =
"default — generous bound for a cold cargo-leptos compile of server + wasm";
const DEFAULT_INTERRUPT_TIMEOUT: Duration = Duration::from_secs(3);
const DEFAULT_TERMINATE_TIMEOUT: Duration = Duration::from_secs(8);
const DEFAULT_TERMINATION_TIMEOUTS_REASON: &str =
"default — graceful cargo-leptos shutdown before SIGKILL";
#[derive(Debug, Clone)]
pub struct LeptosTestAppConfig {
pub(crate) app_dir: PathBuf,
pub(crate) app_name: String,
pub(crate) mode: CargoLeptosMode,
pub(crate) cargo_bin: Option<OsString>,
pub(crate) site_scheme: SiteScheme,
pub(crate) site_addr: Option<String>,
pub(crate) reload_port: Option<u16>,
pub(crate) startup_line: Option<String>,
pub(crate) startup_timeout: Duration,
pub(crate) startup_timeout_reason: String,
pub(crate) startup_log_tail_lines: usize,
pub(crate) interrupt_timeout: Duration,
pub(crate) terminate_timeout: Duration,
pub(crate) termination_timeouts_reason: String,
pub(crate) forward_logs: bool,
pub(crate) extra_env: Vec<(OsString, OsString)>,
}
impl LeptosTestAppConfig {
#[must_use]
pub fn new(app_dir: impl Into<PathBuf>) -> Self {
Self {
app_dir: app_dir.into(),
app_name: "Leptos test app".to_owned(),
mode: CargoLeptosMode::Serve,
cargo_bin: None,
site_scheme: SiteScheme::Http,
site_addr: None,
reload_port: None,
startup_line: None,
startup_timeout: DEFAULT_STARTUP_TIMEOUT,
startup_timeout_reason: DEFAULT_STARTUP_TIMEOUT_REASON.to_owned(),
startup_log_tail_lines: DEFAULT_STARTUP_LOG_TAIL_LINES,
interrupt_timeout: DEFAULT_INTERRUPT_TIMEOUT,
terminate_timeout: DEFAULT_TERMINATE_TIMEOUT,
termination_timeouts_reason: DEFAULT_TERMINATION_TIMEOUTS_REASON.to_owned(),
forward_logs: true,
extra_env: Vec::new(),
}
}
#[must_use]
pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
self.app_name = app_name.into();
self
}
#[must_use]
pub const fn with_mode(mut self, mode: CargoLeptosMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub fn with_cargo(mut self, cargo_bin: impl Into<OsString>) -> Self {
self.cargo_bin = Some(cargo_bin.into());
self
}
#[must_use]
pub const fn with_site_scheme(mut self, site_scheme: SiteScheme) -> Self {
self.site_scheme = site_scheme;
self
}
#[must_use]
pub fn with_site_addr(mut self, site_addr: impl Into<String>) -> Self {
self.site_addr = Some(site_addr.into());
self
}
#[must_use]
pub const fn with_reload_port(mut self, reload_port: u16) -> Self {
self.reload_port = Some(reload_port);
self
}
#[must_use]
pub fn with_startup_line(mut self, startup_line: impl Into<String>) -> Self {
self.startup_line = Some(startup_line.into());
self
}
#[must_use]
pub fn with_startup_timeout(mut self, timeout: Duration, reason: impl Into<String>) -> Self {
self.startup_timeout = timeout;
self.startup_timeout_reason = reason.into();
self
}
#[must_use]
pub const fn with_startup_log_tail_lines(mut self, lines: usize) -> Self {
self.startup_log_tail_lines = lines;
self
}
#[must_use]
pub fn with_termination_timeouts(
mut self,
interrupt_timeout: Duration,
terminate_timeout: Duration,
reason: impl Into<String>,
) -> Self {
self.interrupt_timeout = interrupt_timeout;
self.terminate_timeout = terminate_timeout;
self.termination_timeouts_reason = reason.into();
self
}
#[must_use]
pub fn with_env(mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self {
self.extra_env
.push((key.as_ref().to_owned(), value.as_ref().to_owned()));
self
}
#[must_use]
pub const fn with_forward_logs(mut self, forward_logs: bool) -> Self {
self.forward_logs = forward_logs;
self
}
pub async fn start(self) -> Result<LeptosTestApp, Report<LeptosBrowserTestError>> {
crate::app::start_configured_app(self).await
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use assertr::prelude::*;
use super::{
DEFAULT_INTERRUPT_TIMEOUT, DEFAULT_STARTUP_LOG_TAIL_LINES, DEFAULT_STARTUP_TIMEOUT,
DEFAULT_STARTUP_TIMEOUT_REASON, DEFAULT_TERMINATE_TIMEOUT,
DEFAULT_TERMINATION_TIMEOUTS_REASON, LeptosTestAppConfig,
};
use crate::{CargoLeptosMode, SiteScheme};
#[test]
fn new_uses_documented_defaults() {
let config = LeptosTestAppConfig::new("./test-app");
assert_that!(config.app_name).is_equal_to("Leptos test app");
assert_that!(config.mode).is_equal_to(CargoLeptosMode::Serve);
assert_that!(config.cargo_bin).is_equal_to(None);
assert_that!(config.site_scheme).is_equal_to(SiteScheme::Http);
assert_that!(config.site_addr).is_equal_to(None);
assert_that!(config.reload_port).is_equal_to(None);
assert_that!(config.startup_line).is_equal_to(None);
assert_that!(config.startup_timeout).is_equal_to(DEFAULT_STARTUP_TIMEOUT);
assert_that!(config.startup_timeout_reason).is_equal_to(DEFAULT_STARTUP_TIMEOUT_REASON);
assert_that!(config.startup_log_tail_lines).is_equal_to(DEFAULT_STARTUP_LOG_TAIL_LINES);
assert_that!(config.interrupt_timeout).is_equal_to(DEFAULT_INTERRUPT_TIMEOUT);
assert_that!(config.terminate_timeout).is_equal_to(DEFAULT_TERMINATE_TIMEOUT);
assert_that!(config.termination_timeouts_reason)
.is_equal_to(DEFAULT_TERMINATION_TIMEOUTS_REASON);
assert_that!(config.forward_logs).is_true();
assert_that!(config.extra_env.is_empty()).is_true();
}
#[test]
fn default_constants_match_documented_values() {
assert_that!(DEFAULT_STARTUP_LOG_TAIL_LINES).is_equal_to(200);
assert_that!(DEFAULT_STARTUP_TIMEOUT).is_equal_to(Duration::from_secs(60 * 10));
assert_that!(DEFAULT_INTERRUPT_TIMEOUT).is_equal_to(Duration::from_secs(3));
assert_that!(DEFAULT_TERMINATE_TIMEOUT).is_equal_to(Duration::from_secs(8));
}
#[test]
fn setters_override_defaults() {
let config = LeptosTestAppConfig::new("./test-app")
.with_app_name("custom")
.with_mode(CargoLeptosMode::Watch)
.with_cargo("/opt/cargo")
.with_site_scheme(SiteScheme::Https)
.with_site_addr("127.0.0.1:4000")
.with_reload_port(4001)
.with_startup_line("ready")
.with_startup_timeout(
Duration::from_secs(5),
"tight bound for unit-style smoke test",
)
.with_startup_log_tail_lines(10)
.with_termination_timeouts(
Duration::from_millis(100),
Duration::from_millis(500),
"test fixture exits immediately on SIGTERM",
)
.with_forward_logs(false)
.with_env("FOO", "bar");
assert_that!(config.app_name).is_equal_to("custom");
assert_that!(config.mode).is_equal_to(CargoLeptosMode::Watch);
assert_that!(config.cargo_bin).is_equal_to(Some(std::ffi::OsString::from("/opt/cargo")));
assert_that!(config.site_scheme).is_equal_to(SiteScheme::Https);
assert_that!(config.site_addr).is_equal_to(Some("127.0.0.1:4000".to_owned()));
assert_that!(config.reload_port).is_equal_to(Some(4001));
assert_that!(config.startup_line).is_equal_to(Some("ready".to_owned()));
assert_that!(config.startup_timeout).is_equal_to(Duration::from_secs(5));
assert_that!(config.startup_timeout_reason)
.is_equal_to("tight bound for unit-style smoke test");
assert_that!(config.startup_log_tail_lines).is_equal_to(10);
assert_that!(config.interrupt_timeout).is_equal_to(Duration::from_millis(100));
assert_that!(config.terminate_timeout).is_equal_to(Duration::from_millis(500));
assert_that!(config.termination_timeouts_reason)
.is_equal_to("test fixture exits immediately on SIGTERM");
assert_that!(config.forward_logs).is_false();
assert_that!(config.extra_env.len()).is_equal_to(1);
}
}