// Log replay utilities — for testing and verification
///|
pub(all) struct LogReplay {
records : Array[LogRecord]
position : Int
}
///|
pub fn LogReplay::from_logger(logger : Logger) -> LogReplay {
LogReplay::{ records: logger.records(), position: 0 }
}
///|
pub fn LogReplay::from_records(records : Array[LogRecord]) -> LogReplay {
LogReplay::{ records: copy_replay_records(records, 0, []), position: 0 }
}
///|
fn copy_replay_records(
records : Array[LogRecord],
index : Int,
result : Array[LogRecord],
) -> Array[LogRecord] {
if index >= records.length() {
result
} else {
result.push(records[index].copy())
copy_replay_records(records, index + 1, result)
}
}
///|
pub fn LogReplay::next(self : LogReplay) -> (LogReplay, LogRecord) {
match self.next_record() {
(next_replay, Some(record)) => (next_replay, record)
(next_replay, None) =>
(next_replay, LogRecord::new(Level::INFO, "EOF", [], 0))
}
}
///|
pub fn LogReplay::next_record(self : LogReplay) -> (LogReplay, LogRecord?) {
if self.position >= self.records.length() {
(self, None)
} else {
let record = self.records[self.position].copy()
(LogReplay::{ ..self, position: self.position + 1 }, Some(record))
}
}
///|
pub fn LogReplay::peek(self : LogReplay) -> LogRecord? {
if self.position >= self.records.length() {
None
} else {
Some(self.records[self.position].copy())
}
}
///|
pub fn LogReplay::has_more(self : LogReplay) -> Bool {
self.position < self.records.length()
}
///|
pub fn LogReplay::total(self : LogReplay) -> Int {
self.records.length()
}
///|
pub fn LogReplay::remaining(self : LogReplay) -> Int {
let count = self.records.length() - self.position
if count < 0 {
0
} else {
count
}
}
///|
pub fn LogReplay::reset(self : LogReplay) -> LogReplay {
LogReplay::{ ..self, position: 0 }
}
///|
pub fn LogReplay::seek(self : LogReplay, position : Int) -> LogReplay {
let target = if position < 0 {
0
} else if position > self.records.length() {
self.records.length()
} else {
position
}
LogReplay::{ ..self, position: target }
}
///|
pub fn LogReplay::current_position(self : LogReplay) -> Int {
self.position
}
///|
pub fn LogReplay::replay_into(
self : LogReplay,
logger : Logger,
) -> (LogReplay, Logger) {
replay_remaining(self.records, self.position, logger)
}
///|
fn replay_remaining(
records : Array[LogRecord],
position : Int,
logger : Logger,
) -> (LogReplay, Logger) {
if position >= records.length() {
(
LogReplay::{ records: copy_replay_records(records, 0, []), position },
logger,
)
} else {
replay_remaining(
records,
position + 1,
logger._append_record(records[position]),
)
}
}
// Log assertion utilities for testing
///|
pub fn assert_log_contains(logger : Logger, substring : String) -> Bool {
log_contains_at(logger.logs(), substring, 0)
}
///|
fn log_contains_at(logs : Array[String], substring : String, idx : Int) -> Bool {
if idx >= logs.length() {
false
} else if logs[idx].contains(substring) {
true
} else {
log_contains_at(logs, substring, idx + 1)
}
}
///|
pub fn assert_log_count(logger : Logger, expected : Int) -> Bool {
logger.logs().length() == expected
}
///|
pub fn assert_log_level_count(logger : Logger, level : Level) -> Int {
count_by_level(logger.records(), level, 0, 0)
}
///|
fn count_by_level(
records : Array[LogRecord],
level : Level,
idx : Int,
count : Int,
) -> Int {
if idx >= records.length() {
count
} else {
let increment = if level_ordinal(records[idx].level) >= level_ordinal(level) {
1
} else {
0
}
count_by_level(records, level, idx + 1, count + increment)
}
}