Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/adaptive/tests/unit/plugin_component_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@ fn response_cache_backend_validation_uses_the_default_backend_kind() {

#[test]
fn adaptive_to_plugin_error_maps_all_non_redis_variants() {
assert_adaptive_config_and_lookup_errors();
assert_adaptive_internal_and_serialization_errors();
}

fn assert_adaptive_config_and_lookup_errors() {
assert!(matches!(
adaptive_to_plugin_error(AdaptiveError::InvalidConfig("bad".into())),
nemo_relay::plugin::PluginError::InvalidConfig(message) if message == "bad"
Expand All @@ -283,6 +288,9 @@ fn adaptive_to_plugin_error_maps_all_non_redis_variants() {
adaptive_to_plugin_error(AdaptiveError::Storage("store".into())),
nemo_relay::plugin::PluginError::Internal(message) if message == "store"
));
}

fn assert_adaptive_internal_and_serialization_errors() {
assert!(matches!(
adaptive_to_plugin_error(AdaptiveError::Internal("internal".into())),
nemo_relay::plugin::PluginError::Internal(message) if message == "internal"
Expand Down
87 changes: 87 additions & 0 deletions crates/adaptive/tests/unit/response_cache/intercept_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,90 @@ async fn write_behind_returns_eof_before_cache_commit_completes() {
.expect("detached cache commit must resume after release")
.expect("detached cache commit must run to completion");
}

#[test]
fn response_cache_fidelity_helpers_cover_all_rejection_shapes() {
assert_uncollected_response_field_shapes();
assert_aggregate_replay_and_stream_completion();
assert_inband_error_and_content_detection();
assert_error_response_and_bypass_detection();
}

fn assert_uncollected_response_field_shapes() {
for chunk in [
json!(false),
json!({"choices": false}),
json!({"choices": [false]}),
json!({"choices": [{"index": "zero"}]}),
json!({"choices": [{"finish_reason": false}]}),
json!({"choices": [{"delta": false}]}),
json!({"choices": [{"logprobs": {}}]}),
json!({"choices": [{"extension": true}]}),
json!({"choices": [{"delta": {"tool_calls": [false]}}]}),
json!({"choices": [{"delta": {"tool_calls": [{"index": "zero"}]}}]}),
json!({"choices": [{"delta": {"tool_calls": [{"id": false}]}}]}),
json!({"choices": [{"delta": {"tool_calls": [{"function": false}]}}]}),
json!({"choices": [{"delta": {"tool_calls": [{"extension": true}]}}]}),
] {
assert!(chunk_has_uncollected_response_fields(&chunk), "{chunk}");
}
assert!(!chunk_has_uncollected_response_fields(&json!({
"choices": null,
"metadata": null
})));
}

fn assert_aggregate_replay_and_stream_completion() {
assert!(aggregate_replay_lossy(&json!({
"content": [{"type": "thinking"}]
})));
assert!(aggregate_replay_lossy(&json!({
"choices": [{"message": {"content": null, "tool_calls": []}}]
})));
assert!(!aggregate_replay_lossy(&json!({
"choices": [{"message": {"content": "answer"}}]
})));

let mut completion = StreamCompletion::default();
completion.observe(&json!({
"choices": [
{"index": 0, "finish_reason": "stop"},
{"index": 1, "finish_reason": null}
]
}));
assert!(!completion.is_terminal());
completion.observe(&json!({"choices": [{"index": 1, "finish_reason": "stop"}]}));
assert!(completion.is_terminal());

let mut stopped = StreamCompletion::default();
stopped.observe(&json!({"type": "response.completed"}));
assert!(stopped.is_terminal());
}

fn assert_inband_error_and_content_detection() {
assert!(chunk_is_inband_error(&json!({"error": "bad"})));
assert!(chunk_is_inband_error(&json!({"type": "response.failed"})));
assert!(!chunk_is_inband_error(&json!({"error": null})));
assert!(aggregate_has_no_content(&json!({})));
assert!(!aggregate_has_no_content(&json!({"output": [1]})));
}

fn assert_error_response_and_bypass_detection() {
assert!(!is_error_response(&json!(false)));
assert!(is_error_response(&json!({"error": "bad"})));
for status in [
"failed",
"cancelled",
"canceled",
"incomplete",
"in_progress",
"queued",
] {
assert!(is_error_response(&json!({"status": status})));
}
assert!(!is_error_response(&json!({"status": "completed"})));
assert!(!should_bypass(0.0));
assert!(should_bypass(1.0));
let unit = next_unit_f64();
assert!((0.0..1.0).contains(&unit), "{unit}");
}
20 changes: 20 additions & 0 deletions crates/adaptive/tests/unit/response_cache/store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,23 @@ async fn an_entry_larger_than_the_budget_is_not_cached_and_keeps_existing_entrie
);
assert_eq!(store.total_bytes(), 0);
}

#[tokio::test]
async fn repeated_replacement_compacts_stale_insertion_order_nodes() {
let store = InMemoryCacheStore::new(BIG);
for generation in 0..70 {
store
.set(
"stable-key",
entry("stable-key", generation, u64::MAX),
Duration::MAX,
)
.await
.unwrap();
}

let guard = store.inner.lock().unwrap();
assert_eq!(guard.map.len(), 1);
assert_eq!(guard.order.len(), 4);
assert_eq!(guard.next_generation, 70);
}
30 changes: 30 additions & 0 deletions crates/adaptive/tests/unit/trie/builder_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,36 @@ fn test_score_to_sensitivity_no_samples() {
assert_eq!(score_to_sensitivity(&acc, 5), None);
}

#[test]
fn sensitivity_helpers_cover_empty_zero_duration_and_parallel_edges() {
let mut contexts = Vec::new();
compute_sensitivity_scores(&mut contexts, &SensitivityConfig::default());
assert!(compute_logical_positions(&contexts).is_empty());

let context = LlmCallContext {
path: vec!["edge".into()],
call_index: 0,
remaining_calls: 0,
time_to_next_ms: None,
output_tokens: 0,
call_duration_s: 0.0,
workflow_duration_s: 0.0,
parallel_slack_ratio: 0.4,
sensitivity_score: 0.0,
span_start_time: 0.0,
span_end_time: 0.0,
};
assert_eq!(critical_path_weight(&context), 1.0);
assert_eq!(fanout_score(0, 0), 0.0);
assert_eq!(position_score(0, 1), 1.0);
assert_eq!(parallel_penalty(0.4, &HashMap::from([(0, 2)]), 0), 0.45);

let mut run = make_test_run(1, 0);
run.ended_at = None;
run.calls[0].ended_at = None;
assert!(extract_llm_contexts(&run).is_empty());
}

// -----------------------------------------------------------------------
// PredictionTrieBuilder integration tests
// -----------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions crates/core/src/logging/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,7 @@ pub(crate) fn rotated_log_path(base_path: &Path, index: usize) -> PathBuf {
}
base_path.with_file_name(file_name)
}

#[cfg(test)]
#[path = "../../tests/coverage/logging_rotation_tests.rs"]
mod tests;
57 changes: 57 additions & 0 deletions crates/core/tests/coverage/logging_rotation_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use super::*;

#[test]
fn rotating_writer_reports_missing_file_state() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("events.log");
let mut writer = SizeRotatingFileWriter::new(path, 1, 1).unwrap();
writer.file = None;
writer.current_size = 1;

let rotate_error = writer
.write(b"next")
.expect_err("rotation requires an open active file");
assert_eq!(rotate_error.kind(), io::ErrorKind::Other);

writer.current_size = 0;
let write_error = writer
.write(b"next")
.expect_err("writing requires an open active file");
assert_eq!(write_error.kind(), io::ErrorKind::Other);

let flush_error = writer
.flush()
.expect_err("flushing requires an open active file");
assert_eq!(flush_error.kind(), io::ErrorKind::Other);
}

#[test]
fn rotation_helpers_handle_relative_paths_and_missing_generations() {
let temp = tempfile::tempdir().unwrap();
let base = temp.path().join("relay");
assert_eq!(rotated_log_path(&base, 3), temp.path().join("relay.3"));

rotate_files(&base, 3).unwrap();
assert!(!rotated_log_path(&base, 1).exists());

create_parent_directory(Path::new("relay.log")).unwrap();
}

#[test]
fn failed_rotation_reopens_the_active_file() {
let temp = tempfile::tempdir().unwrap();
let base = temp.path().join("relay.log");
let backup = rotated_log_path(&base, 1);
fs::create_dir(&backup).unwrap();

let mut writer = SizeRotatingFileWriter::new(base.clone(), 1, 1).unwrap();
writer.write_all(b"first").unwrap();
let _error = writer
.write(b"second")
.expect_err("an existing backup directory prevents rotation");
assert!(writer.file.is_some());
assert_eq!(writer.current_size, fs::metadata(base).unwrap().len());
}
122 changes: 119 additions & 3 deletions crates/core/tests/coverage/logging_sink_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

use super::{
DROP_REPORT_INTERVAL_MILLIS, DropNoticeRateLimiter, dropped_record_error_handler,
log_level_filter, now_millis, spdlog_level, stderr_error_handler,
DROP_REPORT_INTERVAL_MILLIS, DropNoticeRateLimiter, build_logger, dropped_record_error_handler,
log_level_filter, logging_path_identity, normalize_path_components, now_millis,
reserved_sink_paths, resolve_log_path, spdlog_level, stderr_error_handler,
};
use crate::logging::LogLevel;
use crate::logging::{
FileLogRotationConfig, FileLogSinkConfig, LogLevel, LogSinkConfig, LoggingConfig,
MAX_FILE_SINK_QUEUE_ENTRIES,
};
use std::path::{Path, PathBuf};

#[test]
fn drop_notice_rate_limiter_reports_immediately_then_once_per_interval() {
Expand Down Expand Up @@ -33,3 +38,114 @@ fn sink_helpers_cover_boundary_levels_time_and_emergency_handlers() {
"expected test error",
)));
}

#[test]
fn sink_path_helpers_cover_rotation_and_normalization_edges() {
assert!(resolve_log_path(Path::new("")).is_err());
assert_eq!(
normalize_path_components(Path::new("alpha/./beta/../gamma")),
PathBuf::from("alpha/gamma")
);
assert_eq!(
logging_path_identity(Path::new("relay.log")),
PathBuf::from("relay.log")
);

let temp = tempfile::tempdir().unwrap();
let base = temp.path().join("relay.log");
std::fs::write(&base, "existing").unwrap();
assert_eq!(
logging_path_identity(&base),
std::fs::canonicalize(&base).unwrap()
);

let rotation = FileLogRotationConfig::new(1_024, 2).unwrap();
let paths = reserved_sink_paths(&base, Some(rotation));
assert_eq!(paths.len(), 3);
assert_eq!(paths[0], base);
assert!(paths[1].ends_with("relay.1.log"));
assert!(paths[2].ends_with("relay.2.log"));
assert_eq!(reserved_sink_paths(&paths[0], None), vec![paths[0].clone()]);
}

#[test]
fn sink_level_helpers_cover_all_intermediate_levels() {
for (level, spdlog_level_expected, log_level_expected) in [
(LogLevel::Warn, spdlog::Level::Warn, log::LevelFilter::Warn),
(LogLevel::Info, spdlog::Level::Info, log::LevelFilter::Info),
(
LogLevel::Debug,
spdlog::Level::Debug,
log::LevelFilter::Debug,
),
] {
assert_eq!(spdlog_level(level), spdlog_level_expected);
assert_eq!(log_level_filter(level), log_level_expected);
}
}

fn file_sink(path: PathBuf) -> FileLogSinkConfig {
FileLogSinkConfig {
path,
..FileLogSinkConfig::default()
}
}

fn build_logger_error(config: &LoggingConfig) -> String {
match build_logger(config, "root".into()) {
Ok(_) => panic!("expected logger construction to fail"),
Err(error) => error.to_string(),
}
}

#[test]
fn logger_builder_rejects_duplicate_reserved_and_invalid_queue_sinks() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("relay.log");

let mut config = LoggingConfig {
sinks: vec![
LogSinkConfig::File(file_sink(path.clone())),
LogSinkConfig::File(file_sink(path.clone())),
],
..LoggingConfig::default()
};
assert!(build_logger_error(&config).contains("duplicate"));

let mut rotating = file_sink(path.clone());
rotating.rotation = Some(FileLogRotationConfig::new(1_024, 1).unwrap());
config.sinks = vec![
LogSinkConfig::File(rotating),
LogSinkConfig::File(file_sink(temp.path().join("relay.1.log"))),
];
assert!(build_logger_error(&config).contains("conflicts"));

for (capacity, expected) in [
(0, "must be greater than 0"),
(MAX_FILE_SINK_QUEUE_ENTRIES + 1, "exceeds maximum"),
] {
let mut sink = file_sink(temp.path().join(format!("queue-{capacity}.log")));
sink.queue_capacity = capacity;
config.sinks = vec![LogSinkConfig::File(sink)];
assert!(build_logger_error(&config).contains(expected));
}
}

#[test]
fn logger_builder_reports_file_and_rotating_file_open_errors() {
let temp = tempfile::tempdir().unwrap();
let blocked_parent = temp.path().join("not-a-directory");
std::fs::write(&blocked_parent, "file").unwrap();
let mut config = LoggingConfig {
sinks: vec![LogSinkConfig::File(file_sink(
blocked_parent.join("relay.log"),
))],
..LoggingConfig::default()
};
assert!(build_logger_error(&config).contains("failed to open logging sink"));

let mut rotating = file_sink(blocked_parent.join("rotating.log"));
rotating.rotation = Some(FileLogRotationConfig::new(1_024, 1).unwrap());
config.sinks = vec![LogSinkConfig::File(rotating)];
assert!(build_logger_error(&config).contains("failed to open rotating logging sink"));
}
Loading
Loading