use super::*;
use anyhow::{Result, ensure};
use netsuke::localization::keys;
use rstest::rstest;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use tracing_subscriber::{fmt, registry::Registry};
#[rstest]
#[case(DiagMode::Human, false, LevelFilter::WARN)]
#[case(DiagMode::Human, true, LevelFilter::TRACE)]
#[case(DiagMode::Json, false, LevelFilter::OFF)]
#[case(DiagMode::Json, true, LevelFilter::OFF)]
fn the_startup_filter_matches_the_mode(
#[case] mode: DiagMode,
#[case] verbose: bool,
#[case] expected: LevelFilter,
) {
assert_eq!(startup_filter(mode, verbose), expected);
}
struct EmptyEnv;
impl locale_resolution::LocaleEnvProvider for EmptyEnv {
fn var(&self, _key: &str) -> Option<String> {
None
}
}
struct NoSystemLocale;
impl locale_resolution::SystemLocale for NoSystemLocale {
fn system_locale(&self) -> Option<String> {
None
}
}
fn record_startup(locale: &str) -> Result<(StartupWriter, String)> {
let args: Vec<OsString> = ["netsuke", "--locale", locale]
.into_iter()
.map(OsString::from)
.collect();
record_startup_with(&args, &EmptyEnv)
}
fn record_startup_with<E: locale_resolution::LocaleEnvProvider>(
args: &[OsString],
env: &E,
) -> Result<(StartupWriter, String)> {
let _lock = test_support::localizer_test_lock()
.map_err(|error| anyhow::anyhow!("localizer test lock poisoned: {error}"))?;
let writer = StartupWriter::buffering();
let subscriber = Registry::default()
.with(LevelFilter::WARN)
.with(fmt::layer().with_writer(writer.clone()).with_ansi(false));
let previous = localization::localizer();
tracing::subscriber::with_default(subscriber, || {
drop(startup_localizer(args, env, &NoSystemLocale));
});
localization::set_localizer(previous);
let recorded = String::from_utf8_lossy(&writer.buffered()).into_owned();
Ok((writer, recorded))
}
#[test]
fn an_unsupported_startup_locale_is_recorded_before_parsing() -> Result<()> {
let (_writer, recorded) = record_startup("is-IS")?;
ensure!(
recorded.contains("falling back to the source locale"),
"the startup path must record the fallback, got {recorded:?}"
);
ensure!(
recorded.contains("is-IS"),
"the record must name the requested locale, got {recorded:?}"
);
Ok(())
}
struct EnvWithLocale(&'static str);
impl locale_resolution::LocaleEnvProvider for EnvWithLocale {
fn var(&self, key: &str) -> Option<String> {
(key == "NETSUKE_LOCALE").then(|| self.0.to_owned())
}
}
#[test]
fn the_startup_path_resolves_the_locale_from_the_environment() -> Result<()> {
let args = vec![OsString::from("netsuke")];
let (_writer, recorded) = record_startup_with(&args, &EnvWithLocale("is-IS"))?;
ensure!(
recorded.contains("is-IS"),
"the environment locale must reach the localizer, got {recorded:?}"
);
Ok(())
}
#[rstest]
#[case(DiagMode::Human, "human mode must release the buffer to stderr")]
#[case(DiagMode::Json, "JSON mode must drop the buffer")]
fn settling_empties_the_startup_buffer(
#[case] mode: DiagMode,
#[case] expectation: &str,
) -> Result<()> {
let (writer, recorded) = record_startup("is-IS")?;
ensure!(
!recorded.is_empty(),
"expected startup to record a warning before settlement"
);
settle_startup_diagnostics(&writer, mode);
ensure!(writer.buffered().is_empty(), "{expectation}");
Ok(())
}
#[test]
fn a_supported_startup_locale_records_nothing() -> Result<()> {
let (_writer, recorded) = record_startup("fr")?;
ensure!(
recorded.is_empty(),
"a shipped catalogue must not warn at startup, got {recorded:?}"
);
Ok(())
}
#[test]
fn startup_installs_and_restores_the_global_localizer() -> Result<()> {
let _lock = test_support::localizer_test_lock()
.map_err(|error| anyhow::anyhow!("localizer test lock poisoned: {error}"))?;
let before = localization::message(keys::CLI_ABOUT).to_string();
let writer = StartupWriter::buffering();
let subscriber = Registry::default()
.with(LevelFilter::WARN)
.with(fmt::layer().with_writer(writer.clone()).with_ansi(false));
let args: Vec<OsString> = ["netsuke", "--locale", "fr"]
.into_iter()
.map(OsString::from)
.collect();
let previous = localization::localizer();
let restore = localization::set_localizer_for_tests(Arc::clone(&previous));
let installed = Arc::new(Barrier::new(2));
let emitted = Arc::new(Barrier::new(2));
let during = Arc::new(Mutex::new(String::new()));
let (thread_installed, thread_emitted, thread_during, thread_writer) = (
Arc::clone(&installed),
Arc::clone(&emitted),
Arc::clone(&during),
writer.clone(),
);
let observer = thread::spawn(move || {
thread_installed.wait();
let rendered = localization::message(keys::CLI_ABOUT).to_string();
if let Ok(mut slot) = thread_during.lock() {
*slot = rendered;
}
let observer_subscriber = Registry::default()
.with(LevelFilter::WARN)
.with(fmt::layer().with_writer(thread_writer).with_ansi(false));
tracing::subscriber::with_default(observer_subscriber, || {
tracing::warn!(target: "concurrent", "observed during startup");
});
thread_emitted.wait();
});
tracing::subscriber::with_default(subscriber, || {
drop(startup_localizer(&args, &EmptyEnv, &NoSystemLocale));
installed.wait();
emitted.wait();
});
observer
.join()
.map_err(|_| anyhow::anyhow!("observer thread panicked"))?;
let rendered_during_startup = during
.lock()
.map_err(|error| anyhow::anyhow!("observation lock poisoned: {error}"))?
.clone();
ensure!(
rendered_during_startup != before,
"the concurrent observer must see the installed French localizer, \
got {rendered_during_startup:?}"
);
let buffered = String::from_utf8_lossy(&writer.buffered()).into_owned();
ensure!(
buffered.contains("observed during startup"),
"the concurrent thread's event must reach the shared writer, got {buffered:?}"
);
drop(restore);
let after = localization::message(keys::CLI_ABOUT).to_string();
ensure!(
after == before,
"the previous localizer must be restored, got {after:?} rather than {before:?}"
);
Ok(())
}