Packages

Property-based testing and metamorphic testing combinator library for Gleam

Current section

Files

Jump to
metamon src metamon annotate.gleam
Raw

src/metamon/annotate.gleam

//// Add per-property context that is only displayed when the property
//// fails. Successful runs discard all annotations so the cost is zero
//// in the happy path.
////
//// The runner clears state at the start of every run and reads any
//// remaining state on failure.
import gleam/dynamic.{type Dynamic}
import gleam/list
import gleam/string
import metamon/internal/process_state
const annotations_key: String = "annotations"
const footnotes_key: String = "footnotes"
/// Add a free-form message to the current property's annotation buffer.
/// Successful runs discard the buffer, so this is cheap.
pub fn annotate(message: String) -> Nil {
push(annotations_key, message)
}
/// Pretty-print `value` and add it to the annotation buffer with a
/// human-readable label. The output uses Gleam's `string.inspect` so it
/// works on any type.
pub fn annotate_value(label: String, value: a) -> Nil {
let rendered = label <> ": " <> string.inspect(value)
push(annotations_key, rendered)
}
/// Add a short message to the footnote buffer (printed at the bottom
/// of the failure report).
pub fn footnote(message: String) -> Nil {
push(footnotes_key, message)
}
/// Clear annotation and footnote buffers. Called by the runner before
/// each property run.
pub fn reset() -> Nil {
process_state.erase(annotations_key)
process_state.erase(footnotes_key)
Nil
}
/// Read the annotations recorded since the last `reset`. Order is the
/// order in which they were registered.
pub fn current_annotations() -> List(String) {
read(annotations_key)
}
/// Read the footnotes recorded since the last `reset`.
pub fn current_footnotes() -> List(String) {
read(footnotes_key)
}
fn push(key: String, message: String) -> Nil {
let updated = [message, ..read(key)]
process_state.put(key, updated)
Nil
}
fn read(key: String) -> List(String) {
case process_state.get(key) {
Error(_) -> []
Ok(raw) -> coerce_string_list(raw) |> list.reverse()
}
}
@external(erlang, "metamon_ffi", "identity")
@external(javascript, "../metamon_ffi.mjs", "identity")
fn coerce_string_list(raw: Dynamic) -> List(String)