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

CaptureTest.pudu

Pudu97 lines9.7 KB

GitHub ↗
1/** @Test.Domain.Capture.Suite — capture hints, limits, policies, and binding */2module PuduLangLog.Domain.CaptureTest34import Std.Bytes as Bytes5import Std.Io as Io6import Std.Test as Test7import PuduLangLog.Domain.Capture as Capture8import PuduLangLog.Domain.Parser as Parser9import 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 property.18fn property(name: Str, held: Log.Value) -> Log.Property { Log.Property{name: name, value: held} }1920/// A tagged structure.21fn structure(tag: Str, properties: Array[Log.Property]) -> Log.Value { Log.Structure(Log.Structure{tag: Some(tag), properties: properties}) }2223/// A structure nested `levels` deep around an integer.24fn nested(levels: Int) -> Log.Value {25  var held = int(1)26  var count = 027  while count < levels {28    held = structure("N", [property("Inner", held)])29    count = count + 130  }31  held32}3334/// The properties bound by default policy.35fn bound(template: Str, arguments: Array[Log.Value]) -> Capture.Bound {36  Capture.bind(&Capture.defaults(), &Parser.parse(template), &arguments)37}3839/// Runs the suite.40fn main() -> Int {41  let policy = Capture.defaults()42  let user = structure("User", [property("Name", text("Ada")), property("Password", text("secret"))])43  let limited = Capture.Policy{..Capture.defaults(), maximumStringLength: 5, maximumCollectionCount: 2}44  let shallow = Capture.Policy{..Capture.defaults(), maximumDepth: 2}45  let redacting = Capture.Policy{..Capture.defaults(), transforms: [Capture.Transform{tag: "User", apply: |_held: Log.Value| structure("Redacted", [property("Name", text("Ada"))])}]}46  let scalarUsers = Capture.Policy{..Capture.defaults(), scalars: ["User"]}47  let custom = Capture.Policy{..Capture.defaults(), custom: [fn(held: &Log.Value) -> Option[Log.Value] { if *held == user { Some(text("custom")) } else { None } }]}48  let long = Bytes.fromArray(&Bytes.toArray(&Bytes.fromText("0123456789abcdef")).concat(Bytes.toArray(&Bytes.fromText("x".repeat(1100)))))49  let checks = Test.suite("Domain.Capture", &[50      Test.equals("scalars are kept", &Capture.capture(&policy, &int(3), Log.Default), &int(3)),51      Test.equals("structures render to text by default", &Capture.capture(&policy, &user, Log.Default), &text("User \{ Name: \"Ada\", Password: \"secret\" \}")),52      Test.equals("destructuring keeps structures", &Capture.capture(&policy, &user, Log.Destructure), &user),53      Test.equals("stringifying renders to text", &Capture.capture(&policy, &Log.Sequence([int(1), text("a")]), Log.Stringify), &text("[1, a]")),54      Test.equals("stringified text is itself", &Capture.capture(&policy, &text("x"), Log.Stringify), &text("x")),55      Test.equals("sequences are kept by default", &Capture.capture(&policy, &Log.Sequence([int(1)]), Log.Default), &Log.Sequence([int(1)])),56      Test.equals("structures inside sequences render by default", &Capture.capture(&policy, &Log.Sequence([structure("P", [])]), Log.Default), &Log.Sequence([text("P \{ \}")])),57      Test.equals("destructuring reaches into sequences", &Capture.capture(&policy, &Log.Sequence([structure("P", [])]), Log.Destructure), &Log.Sequence([structure("P", [])])),58      Test.equals("long text is cut with an ellipsis", &Capture.capture(&limited, &text("abcdefgh"), Log.Default), &text("abcd…")),59      Test.equals("text at the limit is kept", &Capture.capture(&limited, &text("abcde"), Log.Default), &text("abcde")),60      Test.equals("stringified text is cut too", &Capture.capture(&limited, &int(1234567), Log.Stringify), &text("1234…")),61      Test.equals("collections are cut to the count", &Capture.capture(&limited, &Log.Sequence([int(1), int(2), int(3)]), Log.Default), &Log.Sequence([int(1), int(2)])),62      Test.equals("dictionaries are cut to the count", &Capture.capture(&limited, &Log.Dictionary([Log.Entry{key: Log.Text("abcdefg"), value: int(1)}, Log.Entry{key: Log.Integer(2), value: int(2)}, Log.Entry{key: Log.Integer(3), value: int(3)}]), Log.Default), &Log.Dictionary([Log.Entry{key: Log.Text("abcd…"), value: int(1)}, Log.Entry{key: Log.Integer(2), value: int(2)}])),63      Test.equals("nesting past the depth becomes null", &Capture.capture(&shallow, &nested(2), Log.Destructure), &structure("N", [property("Inner", structure("N", [property("Inner", Log.Scalar(Log.Null))]))])),64      Test.equals("nesting within the depth is kept", &Capture.capture(&shallow, &nested(1), Log.Destructure), &nested(1)),65      Test.equals("the default depth keeps ten levels", &(Capture.capture(&policy, &nested(9), Log.Destructure) == nested(9), Capture.capture(&policy, &nested(10), Log.Destructure) == nested(10)), &(true, false)),66      Test.equals("a transform replaces its tag", &Capture.capture(&redacting, &user, Log.Destructure), &structure("Redacted", [property("Name", text("Ada"))])),67      Test.equals("a transform applies only when destructuring", &Capture.capture(&redacting, &user, Log.Default), &Capture.capture(&policy, &user, Log.Default)),68      Test.equals("scalar tags render to text even when destructured", &Capture.capture(&scalarUsers, &user, Log.Destructure), &Capture.capture(&policy, &user, Log.Default)),69      Test.equals("custom policies come first", &Capture.capture(&custom, &user, Log.Destructure), &text("custom")),70      Test.equals("custom policies pass over what they decline", &Capture.capture(&custom, &structure("Other", []), Log.Destructure), &structure("Other", [])),71      Test.equals("long byte strings keep a prefix and their length", &Capture.capture(&policy, &Log.Scalar(Log.Binary(long)), Log.Default), &text("30313233343536373839616263646566... (1116 bytes)")),72      Test.equals("short byte strings are kept", &Capture.capture(&policy, &Log.Scalar(Log.Binary(Bytes.fromText("ab"))), Log.Default), &Log.Scalar(Log.Binary(Bytes.fromText("ab")))),73      Test.equals("named holes bind left to right", &bound("\{A\} \{B\}", [int(1), int(2)]), &Capture.Bound{properties: [property("A", int(1)), property("B", int(2))], problems: []}),74      Test.equals("hints apply while binding", &bound("\{@U\} \{$V\}", [user, int(5)]).properties, &[property("U", user), property("V", text("5"))]),75      Test.equals("extra arguments are kept by index", &bound("\{A\}", [int(1), int(2), int(3)]), &Capture.Bound{properties: [property("A", int(1)), property("__1", int(2)), property("__2", int(3))], problems: ["Named property count does not match parameter count: \{A\}"]}),76      Test.equals("missing arguments leave holes unbound", &bound("\{A\} \{B\}", [int(1)]), &Capture.Bound{properties: [property("A", int(1))], problems: ["Named property count does not match parameter count: \{A\} \{B\}"]}),77      Test.equals("no arguments for holes", &bound("\{A\}", []), &Capture.Bound{properties: [], problems: ["Required properties not provided for: \{A\}"]}),78      Test.equals("no arguments and no holes", &bound("plain", []), &Capture.Bound{properties: [], problems: []}),79      Test.equals("arguments without holes are dropped", &bound("plain", [int(1)]), &Capture.Bound{properties: [], problems: ["Parameters provided for message template with no properties: plain"]}),80      Test.equals("a repeated name keeps its last value", &bound("\{A\} \{A\}", [int(1), int(2)]).properties, &[property("A", int(2))]),81      Test.equals("numbered holes bind by number", &bound("\{1\} \{0\}", [int(10), int(11)]), &Capture.Bound{properties: [property("0", int(10)), property("1", int(11))], problems: []}),82      Test.equals("a number past the arguments is reported", &bound("\{0\} \{2\}", [int(10), int(11)]), &Capture.Bound{properties: [property("0", int(10))], problems: ["Unassigned positional value 2 in: \{0\} \{2\}", "Positional property count does not match parameter count: \{0\} \{2\}"]}),83      Test.equals("a repeated number binds once", &bound("\{0\} \{0\}", [int(10)]).properties, &[property("0", int(10))]),84      Test.equals("mixed holes bind by name and are reported", &bound("\{0\} \{Name\}", [int(1), int(2)]), &Capture.Bound{properties: [property("0", int(1)), property("Name", int(2))], problems: ["Message template is malformed: \{0\} \{Name\}"]}),85      Test.equals("sequences past the depth hold null and are reported", &Capture.bind(&shallow, &Parser.parse("\{L\}"), &[Log.Sequence([Log.Sequence([Log.Sequence([int(1)])]), int(2)])]), &Capture.Bound{properties: [property("L", Log.Sequence([Log.Sequence([Log.Scalar(Log.Null)]), int(2)]))], problems: ["Maximum destructuring depth reached."]}),86      Test.equals("dictionaries past the depth hold null and are reported", &Capture.bind(&shallow, &Parser.parse("\{D\}"), &[Log.Dictionary([Log.Entry{key: Log.Text("a"), value: Log.Sequence([Log.Sequence([int(1)])])}, Log.Entry{key: Log.Text("b"), value: int(2)}])]), &Capture.Bound{properties: [property("D", Log.Dictionary([Log.Entry{key: Log.Text("a"), value: Log.Sequence([Log.Scalar(Log.Null)])}, Log.Entry{key: Log.Text("b"), value: int(2)}]))], problems: ["Maximum destructuring depth reached."]}),87      Test.equals("shallow values bind without problems", &Capture.bind(&shallow, &Parser.parse("\{$S\} \{L\} \{D\} \{U\} \{@V\}"), &[nested(5), Log.Sequence([int(1)]), Log.Dictionary([Log.Entry{key: Log.Text("k"), value: int(1)}]), nested(5), structure("P", [property("A", int(1))])]).problems, &[]),88      Test.equals("byte strings of exactly the limit are kept", &Capture.capture(&policy, &Log.Scalar(Log.Binary(Bytes.fromText("y".repeat(1024)))), Log.Default), &Log.Scalar(Log.Binary(Bytes.fromText("y".repeat(1024))))),89      Test.equals("binding past the depth is reported", &Capture.bind(&shallow, &Parser.parse("\{@N\}"), &[nested(3)]).problems, &["Maximum destructuring depth reached."])90    ])91  let ran = Test.run(&checks)92  for failure in Test.failuresOf(&ran) {93    let _reported = Io.writeErrorLine(failure)94  }95  Test.report(&ran)96}97