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

SettingsTest.pudu

Pudu135 lines8.5 KB

GitHub ↗
1/** @Test.Domain.Settings.Suite — key-value settings read into a plan */2module PuduLangLog.Domain.SettingsTest34import Std.Io as Io5import Std.Result as Result6import Std.Test as Test7import PuduLangLog.Domain.Settings as Settings8import PuduLangLog as Log910/// The plan of the settings.11fn planOf(pairs: Array[(Str, Str)]) -> Settings.Plan { Settings.read(&pairs).plan }1213/// The problems of the settings.14fn problemsOf(pairs: Array[(Str, Str)]) -> Array[Str] { Settings.read(&pairs).problems }1516/// Runs the suite.17fn main() -> Int {18  let none: Array[Str] = []19  let full = Settings.read(&[20      ("MinimumLevel:Default", "Debug"),21      ("minimumlevel:override:App.Web", "Warning"),22      ("MinimumLevel:Override:App.Jobs", "$jobs"),23      ("LevelSwitches:$jobs", "Error"),24      ("LevelSwitches:$root", "Verbose"),25      ("MinimumLevel:ControlledBy", "$root"),26      ("Properties:Application", "Shop"),27      ("Properties:application", "Store"),28      ("Enrich:0", "MachineName"),29      ("Enrich:1:Name", "EnvironmentName"),30      ("Enrich:1:Args:variable", "APP_ENV"),31      ("WriteTo:0:Name", "Console"),32      ("WriteTo:0:Args:theme", "none"),33      ("WriteTo:File:Name", "File"),34      ("WriteTo:File:Args:path", "log.txt"),35      ("WriteTo:File:Args:levelSwitch", "$jobs"),36      ("WriteTo:0:Args:Theme", "code"),37      ("AuditTo:0", "Http"),38      ("Filter:0:ByExcluding", "RequestPath like '/health%'"),39      ("Filter:1:ByIncludingOnly", "@l <> 'Verbose'"),40      ("Destructure:MaximumDepth", "4"),41      ("Destructure:MaximumStringLength", "100"),42      ("Destructure:MaximumCollectionCount", "10"),43      ("Destructure:AsScalar:0", "Money")44    ])45  let plan = full.plan46  let checks = Test.suite("Domain.Settings", &[47      Test.equals("a full set of settings reads without problems", &full.problems, &none),48      Test.equals("levels", &(plan.minimum, plan.controlledBy), &(Some(Log.Debug), Some("root"))),49      Test.equals("level switches, their names without `$` and in lower case", &plan.switches, &[("jobs", Log.Error), ("root", Log.Verbose)]),50      Test.equals("overrides keep levels and switch references", &plan.overrides, &[("App.Web", "Warning"), ("App.Jobs", "$jobs")]),51      Test.equals("a later property replaces an earlier one ignoring case", &plan.properties, &[("application", "Store")]),52      Test.equals("enrichers in order, with arguments", &plan.enrichers, &[Settings.Target{name: "MachineName", arguments: []}, Settings.Target{name: "EnvironmentName", arguments: [("variable", "APP_ENV")]}]),53      Test.equals("sinks in first-seen order, arguments replaced ignoring case", &plan.sinks, &[Settings.Target{name: "Console", arguments: [("Theme", "code")]}, Settings.Target{name: "File", arguments: [("path", "log.txt"), ("levelSwitch", "$jobs")]}]),54      Test.equals("audit sinks", &plan.audits, &[Settings.Target{name: "Http", arguments: []}]),55      Test.equals("filters", &plan.filters, &[Settings.Filter{including: false, expression: "RequestPath like '/health%'"}, Settings.Filter{including: true, expression: "@l <> 'Verbose'"}]),56      Test.equals("capture limits and scalar tags", &(plan.maximumDepth, plan.maximumStringLength, plan.maximumCollectionCount, plan.scalars), &(Some(4), Some(100), Some(10), ["Money"])),57      Test.equals("a bare minimum level", &planOf([("MinimumLevel", "warning")]).minimum, &Some(Log.Warning)),58      Test.equals("nothing decided", &planOf([]), &Settings.empty()),59      Test.equals("malformed and unknown settings", &problemsOf([60            ("MinimumLevel", "Loud"),61            ("MinimumLevel:Override", "Debug"),62            ("MinimumLevel:Override:App", "Loud"),63            ("MinimumLevel:Other", "x"),64            ("MinimumLevel:Default:Extra", "Debug"),65            ("LevelSwitches:$a", "Loud"),66            ("LevelSwitches", "Debug"),67            ("Properties", "x"),68            ("Enrich", "x"),69            ("WriteTo:0:Args", "x"),70            ("WriteTo:0:Other", "x"),71            ("WriteTo:0:Args:", "x"),72            ("Filter:0:Somehow", "x"),73            ("Filter:0", "x"),74            ("Destructure:MaximumDepth", "-1"),75            ("Destructure:MaximumDepth", "deep"),76            ("Destructure:Other", "1"),77            ("Destructure", "1"),78            ("Destructure:Other:x", "1"),79            ("Destructure:AsScalar", "Money"),80            ("Using:0", "Something")81          ]), &[82          "Unknown or malformed setting `MinimumLevel`",83          "Unknown or malformed setting `MinimumLevel:Override`",84          "Unknown or malformed setting `MinimumLevel:Override:App`",85          "Unknown or malformed setting `MinimumLevel:Other`",86          "Unknown or malformed setting `MinimumLevel:Default:Extra`",87          "Unknown or malformed setting `LevelSwitches:$a`",88          "Unknown or malformed setting `LevelSwitches`",89          "Unknown or malformed setting `Properties`",90          "Unknown or malformed setting `Enrich`",91          "Unknown or malformed setting `WriteTo:0:Args`",92          "Unknown or malformed setting `WriteTo:0:Other`",93          "Unknown or malformed setting `WriteTo:0:Args:`",94          "Unknown or malformed setting `Filter:0:Somehow`",95          "Unknown or malformed setting `Filter:0`",96          "Unknown or malformed setting `Destructure:MaximumDepth`",97          "Unknown or malformed setting `Destructure:MaximumDepth`",98          "Unknown or malformed setting `Destructure:Other`",99          "Unknown or malformed setting `Destructure`",100          "Unknown or malformed setting `Destructure:Other:x`",101          "Unknown or malformed setting `Destructure:AsScalar`",102          "Unknown or malformed setting `Using:0`"103        ]),104      Test.equals("references to undeclared switches and unnamed entries", &problemsOf([105            ("MinimumLevel:ControlledBy", "$missing"),106            ("MinimumLevel:Override:App", "$other"),107            ("WriteTo:0:Args:levelSwitch", "$third"),108            ("AuditTo:0:Args:x", "1"),109            ("Enrich:0:Args:x", "1")110          ]), &[111          "An entry of `Enrich` names nothing",112          "An entry of `WriteTo` names nothing",113          "An entry of `AuditTo` names nothing",114          "The level switch `missing` is not declared",115          "The level switch `other` is not declared",116          "The level switch `third` is not declared"117        ]),118      Test.equals("JSON under a section", &Settings.fromJson("\{\"Other\":1,\"log\":\{\"MinimumLevel\":\{\"Default\":\"Debug\"\},\"WriteTo\":[\{\"Name\":\"Console\",\"Args\":\{\"buffered\":true,\"limit\":5,\"ratio\":0.5,\"none\":null\}\}]\}\}", "Log"), &Ok([("MinimumLevel:Default", "Debug"), ("WriteTo:0:Name", "Console"), ("WriteTo:0:Args:buffered", "true"), ("WriteTo:0:Args:limit", "5"), ("WriteTo:0:Args:ratio", "0.5"), ("WriteTo:0:Args:none", "")])),119      Test.equals("JSON lists are numbered from zero", &Settings.fromJson("\{\"Enrich\":[\"A\",\"B\",\"C\"]\}", ""), &Ok([("Enrich:0", "A"), ("Enrich:1", "B"), ("Enrich:2", "C")])),120      Test.equals("JSON without a section, or missing it", &(Settings.fromJson("\{\"Properties\":\{\"A\":false\}\}", ""), Settings.fromJson("\{\"A\":1\}", "Log"), Settings.fromJson("[1]", "Log")), &(Ok([("Properties:A", "false")]), Ok([]), Ok([]))),121      Test.that("text that is not JSON is refused", Result.isErr(&Settings.fromJson("\{", "Log"))),122      Test.equals("environment variable names", &[Settings.environmentKey("APP__MinimumLevel__Default", "APP"), Settings.environmentKey("app__WriteTo__0__Name", "App"), Settings.environmentKey("APP__", "APP"), Settings.environmentKey("OTHER__X", "APP"), Settings.environmentKey("APPX__Y", "APP")], &[Some("MinimumLevel:Default"), Some("WriteTo:0:Name"), None, None, None]),123      Test.equals("rolling intervals", &[Settings.intervalOf("day"), Settings.intervalOf(" Hour "), Settings.intervalOf("INFINITE"), Settings.intervalOf("week")], &[Some(Log.Day), Some(Log.Hour), Some(Log.Infinite), None]),124      Test.equals("counts", &[Settings.count("0"), Settings.count(" 12 "), Settings.count("-1"), Settings.count("x")], &[Some(0), Some(12), None, None]),125      Test.equals("flags", &[Settings.flag("TRUE"), Settings.flag(" false"), Settings.flag("yes")], &[Some(true), Some(false), None]),126      Test.equals("switch names", &[Settings.switchName(" $Jobs "), Settings.switchName("jobs")], &["jobs", "jobs"]),127      Test.equals("arguments by name ignoring case", &(Settings.argument(&Settings.Target{name: "x", arguments: [("Path", "a")]}, "path"), Settings.argument(&Settings.Target{name: "x", arguments: []}, "path")), &(Some("a"), None))128    ])129  let ran = Test.run(&checks)130  for failure in Test.failuresOf(&ran) {131    let _reported = Io.writeErrorLine(failure)132  }133  Test.report(&ran)134}135