///|
pub(all) struct InvariantCheck {
name : String
passed : Bool
detail : String
}
///|
pub(all) struct InvariantReport {
subject : String
checks : Array[InvariantCheck]
}
///|
pub fn invariant_check(
name : String,
passed : Bool,
detail? : String = "",
) -> InvariantCheck {
{ name, passed, detail }
}
///|
pub fn InvariantReport::new(subject : String) -> InvariantReport {
{ subject, checks: [] }
}
///|
pub fn InvariantReport::add(
self : InvariantReport,
check : InvariantCheck,
) -> InvariantReport {
self.checks.push(check)
self
}
///|
pub fn InvariantReport::passed(self : InvariantReport) -> Bool {
self.failed_count() == 0
}
///|
pub fn InvariantReport::failed_count(self : InvariantReport) -> Int {
let mut failed = 0
for check in self.checks {
if !check.passed {
failed += 1
}
}
failed
}
///|
pub fn InvariantReport::passed_count(self : InvariantReport) -> Int {
self.checks.length() - self.failed_count()
}
///|
pub fn InvariantReport::check_count(self : InvariantReport) -> Int {
self.checks.length()
}
///|
pub fn InvariantReport::is_empty(self : InvariantReport) -> Bool {
self.checks.length() == 0
}
///|
pub fn InvariantReport::has_check(
self : InvariantReport,
name : String,
) -> Bool {
for check in self.checks {
if check.name == name {
return true
}
}
false
}
///|
pub fn InvariantReport::merge(
self : InvariantReport,
other : InvariantReport,
) -> InvariantReport {
let merged = InvariantReport::new(self.subject + "+" + other.subject)
for check in self.checks {
ignore(merged.add(check))
}
for check in other.checks {
ignore(merged.add(check))
}
merged
}
///|
pub fn InvariantReport::only_failures(
self : InvariantReport,
) -> InvariantReport {
let failures = InvariantReport::new(self.subject + ":failures")
for check in self.checks {
if !check.passed {
ignore(failures.add(check))
}
}
failures
}
///|
pub fn InvariantReport::summary(self : InvariantReport) -> String {
let status = if self.passed() { "PASS" } else { "FAIL" }
status +
" invariants=" +
self.subject +
" checks=" +
self.checks.length().to_string() +
" failed=" +
self.failed_count().to_string()
}
///|
pub fn InvariantReport::render(self : InvariantReport) -> String {
let buf = StringBuilder::new()
buf.write_string(self.summary())
for check in self.checks {
buf.write_string("\n- ")
buf.write_string(if check.passed { "PASS " } else { "FAIL " })
buf.write_string(check.name)
if check.detail != "" {
buf.write_string(": " + check.detail)
}
}
buf.to_string()
}
///|
pub fn check_sim_invariants(sim : Sim) -> InvariantReport {
InvariantReport::new("sim")
.add(
invariant_check(
"non_negative_time",
sim.time() >= 0,
detail=sim.time().to_string(),
),
)
.add(
invariant_check(
"valid_pending_events",
sim.validate().passed(),
detail=sim.validate().summary(),
),
)
.add(
invariant_check(
"trace_monotonic",
expect_trace_monotonic_ticks(sim.trace()).passed,
),
)
}
///|
pub fn check_counter_at_least(
sim : Sim,
name : String,
minimum : Int,
) -> InvariantCheck {
let value = sim.metrics().counter(name)
invariant_check(
"counter_at_least:" + name,
value >= minimum,
detail=value.to_string() + ">=" + minimum.to_string(),
)
}
///|
pub fn check_no_pending(sim : Sim) -> InvariantCheck {
invariant_check(
"no_pending_events",
sim.pending_count() == 0,
detail=sim.pending_count().to_string(),
)
}