// file: crates/ksp-logging-lib/tests/runtime.rs // version: 2 //! 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; } #[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()); 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()); }