v0.1.2-pre.005
This commit is contained in:
47
crates/ksp-logging-lib/tests/overhead.rs
Normal file
47
crates/ksp-logging-lib/tests/overhead.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
// file: crates/ksp-logging-lib/tests/overhead.rs
|
||||
// version: 1
|
||||
|
||||
//! Diagnostic gross-overhead probe for the reload layer used by KSP Logging.
|
||||
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
|
||||
const TEST_TARGET: &str = "ksp-logging-lib";
|
||||
const ITERATIONS: u64 = 200_000;
|
||||
|
||||
fn emit_probe_events() {
|
||||
for sequence in 0..ITERATIONS {
|
||||
tracing::trace!(target: TEST_TARGET, sequence, "reload overhead probe");
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_filter() -> tracing_subscriber::filter::Targets {
|
||||
return tracing_subscriber::filter::Targets::new()
|
||||
.with_default(tracing_subscriber::filter::LevelFilter::OFF)
|
||||
.with_target(TEST_TARGET, tracing_subscriber::filter::LevelFilter::TRACE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "diagnostic timing probe; run explicitly with --ignored --nocapture"]
|
||||
fn reload_layer_overhead_remains_within_a_gross_regression_guardrail() {
|
||||
let baseline_subscriber = tracing_subscriber::registry().with(trace_filter());
|
||||
let baseline_start = std::time::Instant::now();
|
||||
tracing::subscriber::with_default(baseline_subscriber, || {
|
||||
emit_probe_events();
|
||||
return;
|
||||
});
|
||||
let baseline_elapsed = baseline_start.elapsed();
|
||||
let (reload_layer, _reload_handle) = tracing_subscriber::reload::Layer::new(trace_filter());
|
||||
let reload_subscriber = tracing_subscriber::registry().with(reload_layer);
|
||||
let reload_start = std::time::Instant::now();
|
||||
tracing::subscriber::with_default(reload_subscriber, || {
|
||||
emit_probe_events();
|
||||
return;
|
||||
});
|
||||
let reload_elapsed = reload_start.elapsed();
|
||||
let gross_ceiling = baseline_elapsed.saturating_mul(100).saturating_add(std::time::Duration::from_millis(100));
|
||||
println!("KSP reload overhead probe: baseline={baseline_elapsed:?}, reload={reload_elapsed:?}, iterations={ITERATIONS}");
|
||||
assert!(
|
||||
reload_elapsed <= gross_ceiling,
|
||||
"reload layer exceeded the gross regression guardrail: baseline={baseline_elapsed:?}, reload={reload_elapsed:?}"
|
||||
);
|
||||
}
|
||||
81
crates/ksp-logging-lib/tests/ownership.rs
Normal file
81
crates/ksp-logging-lib/tests/ownership.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// file: crates/ksp-logging-lib/tests/ownership.rs
|
||||
// version: 1
|
||||
|
||||
//! Integration audit ensuring KSP crates do not bypass the logging facade.
|
||||
|
||||
fn collect_rust_files(directory: &std::path::Path, files: &mut std::vec::Vec<std::path::PathBuf>) {
|
||||
let entries_result = std::fs::read_dir(directory);
|
||||
let entries = match entries_result {
|
||||
std::result::Result::Ok(entries) => entries,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
for entry_result in entries {
|
||||
let entry = match entry_result {
|
||||
std::result::Result::Ok(entry) => entry,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
collect_rust_files(path.as_path(), files);
|
||||
continue;
|
||||
}
|
||||
if path.extension().and_then(std::ffi::OsStr::to_str) == std::option::Option::Some("rs") {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_crates_do_not_bypass_ksp_logging_facade() {
|
||||
let logging_manifest_directory = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = match logging_manifest_directory.parent().and_then(std::path::Path::parent) {
|
||||
std::option::Option::Some(root) => root,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let crates_directory = workspace_root.join("crates");
|
||||
let entries_result = std::fs::read_dir(crates_directory.as_path());
|
||||
assert!(entries_result.is_ok(), "unable to inspect workspace crates at {}", crates_directory.display());
|
||||
let entries = match entries_result {
|
||||
std::result::Result::Ok(entries) => entries,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
for entry_result in entries {
|
||||
let entry = match entry_result {
|
||||
std::result::Result::Ok(entry) => entry,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
let crate_path = entry.path();
|
||||
if !crate_path.is_dir() || entry.file_name() == std::ffi::OsStr::new("ksp-logging-lib") {
|
||||
continue;
|
||||
}
|
||||
let manifest_path = crate_path.join("Cargo.toml");
|
||||
if manifest_path.exists() {
|
||||
let manifest_result = std::fs::read_to_string(manifest_path.as_path());
|
||||
assert!(manifest_result.is_ok(), "unable to read {}", manifest_path.display());
|
||||
let manifest = match manifest_result {
|
||||
std::result::Result::Ok(manifest) => manifest,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
assert!(
|
||||
!manifest.contains("tracing.workspace") && !manifest.contains("\ntracing =") && !manifest.contains("[dependencies.tracing]"),
|
||||
"{} depends directly on tracing",
|
||||
manifest_path.display(),
|
||||
);
|
||||
assert!(!manifest.contains("tracing-subscriber"), "{} depends directly on tracing-subscriber", manifest_path.display());
|
||||
assert!(!manifest.contains("tracing-appender"), "{} depends directly on tracing-appender", manifest_path.display());
|
||||
}
|
||||
let mut rust_files = std::vec::Vec::new();
|
||||
collect_rust_files(crate_path.as_path(), &mut rust_files);
|
||||
for rust_file in rust_files {
|
||||
let source_result = std::fs::read_to_string(rust_file.as_path());
|
||||
assert!(source_result.is_ok(), "unable to read {}", rust_file.display());
|
||||
let source = match source_result {
|
||||
std::result::Result::Ok(source) => source,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
assert!(!source.contains("tracing::"), "{} bypasses ksp-logging-lib via tracing", rust_file.display());
|
||||
assert!(!source.contains("tracing_subscriber::"), "{} bypasses ksp-logging-lib via tracing-subscriber", rust_file.display());
|
||||
assert!(!source.contains("tracing_appender::"), "{} bypasses ksp-logging-lib via tracing-appender", rust_file.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/tests/runtime.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
|
||||
|
||||
@@ -62,6 +62,50 @@ fn read_directory_text(path: &std::path::Path) -> std::string::String {
|
||||
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();
|
||||
@@ -117,6 +161,7 @@ fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_i
|
||||
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,
|
||||
|
||||
73
crates/ksp-logging-lib/tests/span_lifecycle.rs
Normal file
73
crates/ksp-logging-lib/tests/span_lifecycle.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
// file: crates/ksp-logging-lib/tests/span_lifecycle.rs
|
||||
// version: 1
|
||||
|
||||
//! Integration tests for formatted KSP span lifecycle timing output.
|
||||
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
|
||||
const TEST_TARGET: &str = "ksp-logging-lib";
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SharedWriter {
|
||||
buffer: std::sync::Arc<std::sync::Mutex<std::vec::Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl SharedWriter {
|
||||
fn new(buffer: std::sync::Arc<std::sync::Mutex<std::vec::Vec<u8>>>) -> Self {
|
||||
return Self { buffer };
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for SharedWriter {
|
||||
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
|
||||
let lock_result = self.buffer.lock();
|
||||
let mut buffer = match lock_result {
|
||||
std::result::Result::Ok(buffer) => buffer,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(std::io::Error::other("span test buffer is poisoned")),
|
||||
};
|
||||
buffer.extend_from_slice(bytes);
|
||||
return std::result::Result::Ok(bytes.len());
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
fn captured_text(buffer: &std::sync::Arc<std::sync::Mutex<std::vec::Vec<u8>>>) -> std::string::String {
|
||||
let lock_result = buffer.lock();
|
||||
let bytes = match lock_result {
|
||||
std::result::Result::Ok(bytes) => bytes.clone(),
|
||||
std::result::Result::Err(error) => error.into_inner().clone(),
|
||||
};
|
||||
return std::string::String::from_utf8_lossy(bytes.as_slice()).into_owned();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_and_close_span_events_expose_busy_and_idle_timing_fields() {
|
||||
let buffer = std::sync::Arc::new(std::sync::Mutex::new(std::vec::Vec::new()));
|
||||
let writer_buffer = std::sync::Arc::clone(&buffer);
|
||||
let layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(move || -> SharedWriter {
|
||||
return SharedWriter::new(std::sync::Arc::clone(&writer_buffer));
|
||||
})
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::NEW | tracing_subscriber::fmt::format::FmtSpan::CLOSE);
|
||||
let subscriber = tracing_subscriber::registry().with(layer);
|
||||
tracing::subscriber::with_default(subscriber, || {
|
||||
let span = ksp_logging_lib::trace_span!(target: TEST_TARGET, "timed_scope", domain = "logging");
|
||||
span.in_scope(|| {
|
||||
std::hint::black_box(42_u32);
|
||||
return;
|
||||
});
|
||||
drop(span);
|
||||
return;
|
||||
});
|
||||
let text = captured_text(&buffer);
|
||||
assert!(text.contains("timed_scope"));
|
||||
assert!(text.contains("new"));
|
||||
assert!(text.contains("close"));
|
||||
assert!(text.contains("time.busy"));
|
||||
assert!(text.contains("time.idle"));
|
||||
}
|
||||
Reference in New Issue
Block a user