#![forbid(unsafe_code)]
use std::fs;
use std::path::PathBuf;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use fsci_runtime::{RuntimeMode, TestLogEntry, TestLogLevel, TestResult};
use serde::Serialize;
use serde_json::Value;
const PACKET_ID: &str = "FSCI-P2C-007";
#[derive(Debug, Clone, Serialize)]
struct CaseDiff {
case_id: String,
pass: bool,
note: String,
}
#[derive(Debug, Clone, Serialize)]
struct DiffLog {
test_id: String,
category: String,
case_count: usize,
pass: bool,
timestamp_ms: u128,
duration_ns: u128,
cases: Vec<CaseDiff>,
}
fn output_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("fixtures/artifacts/{PACKET_ID}/diff"))
}
fn ensure_output_dir() {
fs::create_dir_all(output_dir()).expect("create test_log_entry diff dir");
}
fn timestamp_ms() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis())
}
fn emit_log(log: &DiffLog) {
ensure_output_dir();
let path = output_dir().join(format!("{}.json", log.test_id));
let json = serde_json::to_string_pretty(log).expect("serialize log");
fs::write(path, json).expect("write log");
}
#[test]
fn diff_runtime_test_log_entry_builder() {
let start = Instant::now();
let mut diffs: Vec<CaseDiff> = Vec::new();
let mut check = |id: &str, ok: bool, note: String| {
diffs.push(CaseDiff {
case_id: id.into(),
pass: ok,
note,
});
};
{
let entry = TestLogEntry::new("t1", "m1", "msg1");
check(
"new_test_id",
entry.test_id == "t1",
format!("test_id={}", entry.test_id),
);
check(
"new_module",
entry.module == "m1",
format!("module={}", entry.module),
);
check(
"new_message",
entry.message == "msg1",
format!("message={}", entry.message),
);
check(
"new_level_info",
matches!(entry.level, TestLogLevel::Info),
format!("level={:?}", entry.level),
);
check(
"new_timestamp_recent",
entry.timestamp_ms > 1_700_000_000_000,
format!("ts={}", entry.timestamp_ms),
);
check("new_seed_none", entry.seed.is_none(), String::new());
check(
"new_fixture_none",
entry.fixture_id.is_none(),
String::new(),
);
check("new_mode_none", entry.mode.is_none(), String::new());
check("new_result_none", entry.result.is_none(), String::new());
}
{
let entry = TestLogEntry::new("t2", "m2", "msg2")
.with_result(TestResult::Pass)
.with_seed(42)
.with_mode(RuntimeMode::Hardened)
.with_fixture("fixture-abc");
check(
"builder_result_set",
entry.result == Some(TestResult::Pass),
format!("result={:?}", entry.result),
);
check(
"builder_seed_set",
entry.seed == Some(42),
format!("seed={:?}", entry.seed),
);
check(
"builder_mode_set",
entry.mode == Some(RuntimeMode::Hardened),
format!("mode={:?}", entry.mode),
);
check(
"builder_fixture_set",
entry.fixture_id.as_deref() == Some("fixture-abc"),
format!("fixture_id={:?}", entry.fixture_id),
);
}
{
let entry = TestLogEntry::new("t3", "m3", "msg3").with_result(TestResult::Fail);
let line = entry.to_json_line();
check(
"json_single_line",
!line.contains('\n'),
format!("line={line}"),
);
let v: Value = serde_json::from_str(&line).expect("parse");
check(
"json_test_id_field",
v.get("test_id").and_then(|x| x.as_str()) == Some("t3"),
format!("v={v:?}"),
);
check(
"json_result_field",
v.get("result").and_then(|x| x.as_str()) == Some("fail"),
format!("result={:?}", v.get("result")),
);
check(
"json_level_info",
v.get("level").and_then(|x| x.as_str()) == Some("info"),
format!("level={:?}", v.get("level")),
);
}
{
let entry = TestLogEntry::new("t4", "m4", "msg4");
let line = entry.to_json_line();
let v: Value = serde_json::from_str(&line).expect("parse");
let obj = v.as_object().expect("object");
check(
"json_seed_elided",
!obj.contains_key("seed"),
format!("keys={:?}", obj.keys().collect::<Vec<_>>()),
);
check(
"json_fixture_elided",
!obj.contains_key("fixture_id"),
String::new(),
);
check("json_mode_elided", !obj.contains_key("mode"), String::new());
check(
"json_result_elided",
!obj.contains_key("result"),
String::new(),
);
check(
"json_artifact_refs_elided",
!obj.contains_key("artifact_refs"),
String::new(),
);
}
{
let entry = TestLogEntry::new("t5", "m5", "msg5")
.with_seed(7)
.with_fixture("fix5")
.with_mode(RuntimeMode::Strict)
.with_result(TestResult::Skip);
let line = entry.to_json_line();
let v: Value = serde_json::from_str(&line).expect("parse");
let obj = v.as_object().expect("object");
check(
"json_seed_present",
obj.get("seed").and_then(|x| x.as_u64()) == Some(7),
String::new(),
);
check(
"json_fixture_present",
obj.get("fixture_id").and_then(|x| x.as_str()) == Some("fix5"),
String::new(),
);
check("json_mode_present", obj.contains_key("mode"), String::new());
check(
"json_result_skip",
obj.get("result").and_then(|x| x.as_str()) == Some("skip"),
String::new(),
);
}
{
let original = TestLogEntry::new("t6", "m6", "msg6")
.with_seed(99)
.with_mode(RuntimeMode::Hardened)
.with_result(TestResult::Warn)
.with_fixture("rt-fix");
let line = original.to_json_line();
let parsed: TestLogEntry = serde_json::from_str(&line).expect("round-trip parse");
check(
"roundtrip_test_id",
parsed.test_id == original.test_id,
String::new(),
);
check(
"roundtrip_module",
parsed.module == original.module,
String::new(),
);
check(
"roundtrip_seed",
parsed.seed == original.seed,
String::new(),
);
check(
"roundtrip_mode",
parsed.mode == original.mode,
String::new(),
);
check(
"roundtrip_result",
parsed.result == original.result,
String::new(),
);
check(
"roundtrip_fixture",
parsed.fixture_id == original.fixture_id,
String::new(),
);
}
let all_pass = diffs.iter().all(|d| d.pass);
let log = DiffLog {
test_id: "diff_runtime_test_log_entry_builder".into(),
category: "fsci_runtime::TestLogEntry builder + JSON serialization".into(),
case_count: diffs.len(),
pass: all_pass,
timestamp_ms: timestamp_ms(),
duration_ns: start.elapsed().as_nanos(),
cases: diffs.clone(),
};
emit_log(&log);
for d in &diffs {
if !d.pass {
eprintln!("test_log_entry mismatch: {} — {}", d.case_id, d.note);
}
}
assert!(
all_pass,
"TestLogEntry builder coverage failed: {} cases",
diffs.len(),
);
}