Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-log

Structured event logging for Pudu: message templates, enrichment, filtering, formatting, and sinks

0.1.0Apache-2.01

InstallClose

DisplayTest.pudu

Pudu73 lines6.7 KB

GitHub ↗
1/** @Test.Domain.Display.Suite — display text of values, messages, and failures */2module PuduLangLog.Domain.DisplayTest34import Std.Io as Io5import Std.Test as Test6import PuduLangLog.Domain.Display as Display7import PuduLangLog.Domain.Parser as Parser8import PuduLangLog.Domain.Properties as Properties9import PuduLangLog as Log1011/// A text scalar value.12fn text(held: Str) -> Log.Value { Log.Scalar(Log.Text(held)) }1314/// An integer scalar value.15fn int(held: Int) -> Log.Value { Log.Scalar(Log.Integer(held)) }1617/// A value's display text under a format.18fn shown(held: Log.Value, format: Option[Str]) -> Str { Display.plain(&Display.value(&held, &format)) }1920/// A template rendered with properties under a message format.21fn rendered(template: Str, properties: Array[Log.Property], format: Option[Str]) -> Str {22  Display.plain(&Display.message(&Parser.parse(template), &properties, &format))23}2425/// A property.26fn property(name: Str, held: Log.Value) -> Log.Property { Log.Property{name: name, value: held} }2728/// Runs the suite.29fn main() -> Int {30  let user = Log.Structure(Log.Structure{tag: Some("User"), properties: [property("Name", text("Ada")), property("Tags", Log.Sequence([text("x")]))]})31  let props = [property("User", text("Ada")), property("Count", int(3)), property("Price", Log.Scalar(Log.Real(2.5))), property("Account", user)]32  let failure = Log.Failure{kind: "IoError", message: "disk full", trace: ["Store.save", "Main.main"], causes: [Log.Failure{kind: "Quota", message: "", trace: [], causes: []}]}33  let checks = Test.suite("Domain.Display", &[34      Test.equals("scalars", &[shown(Log.Scalar(Log.Null), None), shown(Log.Scalar(Log.Boolean(false)), None), shown(int(-7), None), shown(Log.Scalar(Log.Exact(1.50d)), None), shown(Log.Scalar(Log.Real(1.0e20)), None)], &["null", "false", "-7", "1.50", "1E+20"]),35      Test.equals("text is quoted with inner quotes escaped", &shown(text("say \"hi\""), None), &"\"say \\\"hi\\\"\""),36      Test.equals("the literal format drops quotes", &shown(text("plain"), Some("l")), &"plain"),37      Test.equals("number formats apply", &(shown(int(5), Some("000")), shown(Log.Scalar(Log.Real(2.345)), Some("0.00")), shown(Log.Scalar(Log.Exact(1234d)), Some("N0"))), &("005", "2.35", "1,234")),38      Test.equals("an unknown number format falls back", &shown(int(5), Some("Q")), &"5"),39      Test.equals("time formats apply", &(shown(Log.Scalar(Log.Moment(Log.Timestamp{millis: 0, offset: 0})), Some("yyyy")), shown(Log.Scalar(Log.Span(61000)), None)), &("1970", "00:01:01")),40      Test.equals("sequences pass their format to elements", &shown(Log.Sequence([int(1), int(2)]), Some("00")), &"[01, 02]"),41      Test.equals("empty sequences", &shown(Log.Sequence([]), None), &"[]"),42      Test.equals("structures", &shown(user, None), &"User \{ Name: \"Ada\", Tags: [\"x\"] \}"),43      Test.equals("untagged and empty structures", &(shown(Log.Structure(Log.Structure{tag: None, properties: [property("A", int(1))]}), None), shown(Log.Structure(Log.Structure{tag: None, properties: []}), None)), &("\{ A: 1 \}", "\{ \}")),44      Test.equals("dictionaries", &shown(Log.Dictionary([Log.Entry{key: Log.Text("a"), value: int(1)}, Log.Entry{key: Log.Integer(2), value: text("b")}]), None), &"[(\"a\": 1), (2: \"b\")]"),45      Test.equals("empty dictionaries", &shown(Log.Dictionary([]), None), &"[]"),46      Test.equals("a message with its values", &rendered("\{User\} has \{Count\} items", props, None), &"\"Ada\" has 3 items"),47      Test.equals("the literal message format", &rendered("\{User\} has \{Count\} items", props, Some("l")), &"Ada has 3 items"),48      Test.equals("the JSON message format", &rendered("\{User\} owns \{Account\}", props, Some("j")), &"\"Ada\" owns \{\"Name\":\"Ada\",\"Tags\":[\"x\"],\"$type\":\"User\"\}"),49      Test.equals("literal and JSON together", &rendered("\{User\} owns \{Account\}", props, Some("lj")), &"Ada owns \{\"Name\":\"Ada\",\"Tags\":[\"x\"],\"$type\":\"User\"\}"),50      Test.equals("a hole's own format wins over JSON", &rendered("\{Price:0.00\}", props, Some("j")), &"2.50"),51      Test.equals("a missing property keeps its raw text", &rendered("\{Missing,5:u\} and \{Count\}", props, None), &"\{Missing,5:u\} and 3"),52      Test.equals("alignment pads rendered values", &(rendered("[\{Count,3\}]", props, None), rendered("[\{Count,-3\}]", props, None), rendered("[\{User,2\}]", props, None)), &("[  3]", "[3  ]", "[\"Ada\"]")),53      Test.equals("literal text keeps unescaped braces", &rendered("\{\{x\}\} \{Count\}", props, None), &"\{x\} 3"),54      Test.equals("styles mark each kind", &Display.message(&Parser.parse("n=\{Count\} \{Nope\}"), &props, &None).map(|piece: Display.Piece| piece.style), &[Display.Plain, Display.NumberStyle, Display.Plain, Display.Invalid]),55      Test.equals("structure styles", &Display.value(&user, &None).map(|piece: Display.Piece| piece.style), &[Display.NameStyle, Display.Tertiary, Display.NameStyle, Display.Tertiary, Display.StringStyle, Display.Tertiary, Display.NameStyle, Display.Tertiary, Display.Tertiary, Display.StringStyle, Display.Tertiary, Display.Tertiary]),56      Test.equals("text already as wide as its alignment gains no padding piece", &Display.aligned(&[Display.Piece{style: Display.NumberStyle, text: "12"}], &Some(Log.Alignment{left: true, width: 2})), &[Display.Piece{style: Display.NumberStyle, text: "12"}]),57      Test.equals("left padding follows the value", &Display.aligned(&[Display.Piece{style: Display.NumberStyle, text: "3"}], &Some(Log.Alignment{left: true, width: 2})), &[Display.Piece{style: Display.NumberStyle, text: "3"}, Display.Piece{style: Display.Plain, text: " "}]),58      Test.equals("failures with trace and causes", &Display.failure(&failure), &"IoError: disk full\n   at Store.save\n   at Main.main\n ---> Quota"),59      Test.equals("property lookup", &(Properties.find(&props, "Count"), Properties.find(&props, "None")), &(Some(int(3)), None)),60      Test.equals("put replaces in place", &Properties.put(&[property("A", int(1)), property("B", int(2))], "A", int(9)), &[property("A", int(9)), property("B", int(2))]),61      Test.equals("put adds at the end", &Properties.put(&[property("A", int(1))], "B", int(2)), &[property("A", int(1)), property("B", int(2))]),62      Test.equals("putIfAbsent keeps an existing value", &Properties.putIfAbsent(&[property("A", int(1))], "A", int(2)), &[property("A", int(1))]),63      Test.equals("putIfAbsent adds a new value", &Properties.putIfAbsent(&[property("A", int(1))], "B", int(2)), &[property("A", int(1)), property("B", int(2))]),64      Test.equals("remove", &Properties.remove(&[property("A", int(1)), property("B", int(2))], "A"), &[property("B", int(2))]),65      Test.that("has", Properties.has(&props, "User") && !Properties.has(&props, "user"))66    ])67  let ran = Test.run(&checks)68  for problem in Test.failuresOf(&ran) {69    let _reported = Io.writeErrorLine(problem)70  }71  Test.report(&ran)72}73