195 lines
8.2 KiB
Rust
195 lines
8.2 KiB
Rust
// file: crates/ksp-logging-lib/tests/runtime.rs
|
|
// version: 3
|
|
|
|
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
|
|
|
|
const LOGGING_TARGET: &str = "ksp-logging-lib";
|
|
const OTHER_KSP_TARGET: &str = "ksp-store-lib";
|
|
const EXTERNAL_TARGET: &str = "sqlx";
|
|
|
|
fn logging_trace_enabled() -> bool {
|
|
return tracing::enabled!(target: LOGGING_TARGET, tracing::Level::TRACE);
|
|
}
|
|
|
|
fn other_ksp_info_enabled() -> bool {
|
|
return tracing::enabled!(target: OTHER_KSP_TARGET, tracing::Level::INFO);
|
|
}
|
|
|
|
fn other_ksp_debug_enabled() -> bool {
|
|
return tracing::enabled!(target: OTHER_KSP_TARGET, tracing::Level::DEBUG);
|
|
}
|
|
|
|
fn external_error_enabled() -> bool {
|
|
return tracing::enabled!(target: EXTERNAL_TARGET, tracing::Level::ERROR);
|
|
}
|
|
|
|
fn test_root_directory() -> std::path::PathBuf {
|
|
return std::env::temp_dir().join(format!("ksp-logging-lib-runtime-{}", std::process::id()));
|
|
}
|
|
|
|
fn reset_directory(path: &std::path::Path) {
|
|
if path.exists() {
|
|
let remove_result = std::fs::remove_dir_all(path);
|
|
assert!(remove_result.is_ok());
|
|
}
|
|
}
|
|
|
|
fn read_directory_text(path: &std::path::Path) -> std::string::String {
|
|
let read_result = std::fs::read_dir(path);
|
|
let entries = match read_result {
|
|
std::result::Result::Ok(entries) => entries,
|
|
std::result::Result::Err(_) => return std::string::String::new(),
|
|
};
|
|
let mut output = std::string::String::new();
|
|
for entry_result in entries {
|
|
let entry = match entry_result {
|
|
std::result::Result::Ok(entry) => entry,
|
|
std::result::Result::Err(_) => continue,
|
|
};
|
|
let file_type = match entry.file_type() {
|
|
std::result::Result::Ok(file_type) => file_type,
|
|
std::result::Result::Err(_) => continue,
|
|
};
|
|
if !file_type.is_file() {
|
|
continue;
|
|
}
|
|
let content = match std::fs::read_to_string(entry.path()) {
|
|
std::result::Result::Ok(content) => content,
|
|
std::result::Result::Err(_) => continue,
|
|
};
|
|
output.push_str(content.as_str());
|
|
}
|
|
return output;
|
|
}
|
|
|
|
fn exercise_concurrent_reload(guard: &mut ksp_logging_lib::LoggingGuard, disabled: &ksp_logging_lib::LoggingSettings) {
|
|
let quiet_console = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Off,
|
|
ksp_logging_lib::SpanEvents::Off,
|
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
|
std::option::Option::None,
|
|
);
|
|
let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
|
let barrier = std::sync::Arc::new(std::sync::Barrier::new(5));
|
|
let mut threads = std::vec::Vec::new();
|
|
for worker_index in 0..4_u32 {
|
|
let worker_stop = std::sync::Arc::clone(&stop);
|
|
let worker_barrier = std::sync::Arc::clone(&barrier);
|
|
threads.push(std::thread::spawn(move || {
|
|
worker_barrier.wait();
|
|
let mut sequence = 0_u64;
|
|
while !worker_stop.load(std::sync::atomic::Ordering::Relaxed) {
|
|
ksp_logging_lib::trace!(target: LOGGING_TARGET, worker_index, sequence, "concurrent reload probe");
|
|
sequence = sequence.wrapping_add(1);
|
|
}
|
|
return;
|
|
}));
|
|
}
|
|
barrier.wait();
|
|
let mut reloads_succeeded = true;
|
|
for reload_index in 0..32_u32 {
|
|
let settings = if reload_index % 2 == 0 { &quiet_console } else { disabled };
|
|
let reload_result = ksp_logging_lib::reinitialize(guard, settings);
|
|
if reload_result.is_err() {
|
|
reloads_succeeded = false;
|
|
break;
|
|
}
|
|
}
|
|
stop.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
let mut joins_succeeded = true;
|
|
for thread in threads {
|
|
if thread.join().is_err() {
|
|
joins_succeeded = false;
|
|
}
|
|
}
|
|
assert!(reloads_succeeded);
|
|
assert!(joins_succeeded);
|
|
}
|
|
|
|
#[test]
|
|
fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_initialization() {
|
|
let root = test_root_directory();
|
|
reset_directory(root.as_path());
|
|
let disabled = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Info,
|
|
ksp_logging_lib::SpanEvents::Off,
|
|
std::option::Option::None,
|
|
std::option::Option::None,
|
|
);
|
|
let initialize_result = ksp_logging_lib::initialize(&disabled);
|
|
assert!(initialize_result.is_ok());
|
|
let mut guard = match initialize_result {
|
|
std::result::Result::Ok(guard) => guard,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
assert_eq!(guard.dropped_lines(), ksp_logging_lib::DroppedLines::zero());
|
|
assert!(!logging_trace_enabled());
|
|
assert!(!other_ksp_info_enabled());
|
|
assert!(!external_error_enabled());
|
|
let console_enabled = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Info,
|
|
ksp_logging_lib::SpanEvents::NewAndClose,
|
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
|
std::option::Option::None,
|
|
)
|
|
.with_target_filter(ksp_logging_lib::TargetFilter::new(LOGGING_TARGET, ksp_logging_lib::LogFilterLevel::Trace));
|
|
let reload_result = ksp_logging_lib::reinitialize(&mut guard, &console_enabled);
|
|
assert!(reload_result.is_ok());
|
|
assert_eq!(guard.settings(), &console_enabled);
|
|
assert!(logging_trace_enabled());
|
|
assert!(other_ksp_info_enabled());
|
|
assert!(!other_ksp_debug_enabled());
|
|
assert!(!external_error_enabled());
|
|
let blocked_directory = root.join("not-a-directory");
|
|
let create_root = std::fs::create_dir_all(root.as_path());
|
|
assert!(create_root.is_ok());
|
|
let create_blocker = std::fs::write(blocked_directory.as_path(), b"file blocks directory creation");
|
|
assert!(create_blocker.is_ok());
|
|
let invalid_file = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Error,
|
|
ksp_logging_lib::SpanEvents::Full,
|
|
std::option::Option::None,
|
|
std::option::Option::Some(ksp_logging_lib::FileSettings::new(blocked_directory.as_path(), "invalid", ksp_logging_lib::FileRotation::Daily)),
|
|
);
|
|
let failed_reload = ksp_logging_lib::reinitialize(&mut guard, &invalid_file);
|
|
assert!(failed_reload.is_err());
|
|
let file_error = match failed_reload {
|
|
std::result::Result::Ok(()) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(file_error.code(), ksp_logging_lib::ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED);
|
|
assert_eq!(guard.settings(), &console_enabled);
|
|
assert!(logging_trace_enabled());
|
|
assert!(!external_error_enabled());
|
|
exercise_concurrent_reload(&mut guard, &disabled);
|
|
let log_directory = root.join("logs");
|
|
let file_enabled = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Info,
|
|
ksp_logging_lib::SpanEvents::Off,
|
|
std::option::Option::None,
|
|
std::option::Option::Some(ksp_logging_lib::FileSettings::new(log_directory.as_path(), "runtime-test.log", ksp_logging_lib::FileRotation::Never)),
|
|
);
|
|
let file_reload = ksp_logging_lib::reinitialize(&mut guard, &file_enabled);
|
|
assert!(file_reload.is_ok());
|
|
ksp_logging_lib::info!(target: LOGGING_TARGET, "file \x1b[31moutput\x1b[0m marker");
|
|
tracing::error!(target: EXTERNAL_TARGET, "external marker must remain silent");
|
|
let disable_after_file = ksp_logging_lib::reinitialize(&mut guard, &disabled);
|
|
assert!(disable_after_file.is_ok());
|
|
let file_text = read_directory_text(log_directory.as_path());
|
|
assert!(file_text.contains("file output marker"));
|
|
assert!(file_text.contains(LOGGING_TARGET));
|
|
assert!(file_text.contains("runtime.rs"));
|
|
assert!(!file_text.contains("\x1b["));
|
|
assert!(!file_text.contains("external marker must remain silent"));
|
|
let dropped = guard.dropped_lines();
|
|
assert_eq!(dropped.total(), dropped.console().saturating_add(dropped.file()));
|
|
let second_initialize = ksp_logging_lib::initialize(&disabled);
|
|
assert!(second_initialize.is_err());
|
|
let error = match second_initialize {
|
|
std::result::Result::Ok(_) => return,
|
|
std::result::Result::Err(error) => error,
|
|
};
|
|
assert_eq!(error.code(), ksp_logging_lib::ERROR_CODE_ALREADY_INITIALIZED);
|
|
reset_directory(root.as_path());
|
|
}
|