-
-
Notifications
You must be signed in to change notification settings - Fork 0
Output Formatting And Testing
Out.rendered(&Out.stdout(), &["compiled", "3 modules"])
Fmt.int(&Fmt.grouped(&Fmt.spec(), ","), 12000)
Test.run(&Test.suite("parser", &checks))
Status: Std.Out, Std.Fmt, Std.Test, and Std.Test.Property ship with the compiler and execute
through the interpreter.
These modules keep decisions in values. Rendering is separate from writing, shaping is separate from rendering, and running a suite is separate from presenting its report. A program can therefore test each stage by comparing ordinary values.
print takes text and ends the line. Std.Out.Printer names the decisions beyond that: stream,
separator, ending, and per-line prefix.
Std.Out holds those decisions as a Printer.
import Std.Out as Out
let out = Out.stdout()
let csv = Out.separatedBy(&out, ",")
let prompt = Out.endingWith(&out, "")
Out.rendered(&out, &["compiled", "3 modules"]) // "compiled 3 modules\n"
Out.rendered(&csv, &["a", "b", "c"]) // "a,b,c\n"
Out.rendered(&prompt, &["Name: "]) // "Name: "
Pudu has no keyword or variadic arguments. A positional print(x, sep, end) API would repeat every
choice at every call. A Printer can instead be named, transformed, and passed to the function that
reports through it.
rendered answers exactly the text write sends to its selected stream. write returns
Result[(), Str]; callers handle or propagate that result like any other recoverable effect.
fn report(printer: &Out.Printer) -> Result[(), Str] {
Out.write(printer, &["compiled", "3 modules"]) ?
Ok(())
}
Every writing function is stated in terms of it, and every write answers a Result.
A prefix applies to every line of a piece, not only the first, so indenting a block indents all
of it and indentedBy nests rather than replaces.
Std.Fmt.Spec holds width, fill, alignment, sign, and digit grouping. The module parses no format
strings. A malformed choice is rejected as ordinary Pudu syntax or typing rather than discovered
while rendering.
import Std.Fmt as Fmt
let column = Fmt.rightAligned(&Fmt.width(&Fmt.spec(), 8))
Fmt.text(&column, "42") // " 42"
Fmt.hex(&column, 255) // " ff"
Fmt.zeroPadded(&Fmt.width(&Fmt.spec(), 4), 42) // "0042"
A width is a least width: losing characters is worse than losing a column, and truncated is how
the other behaviour is asked for by name.
fixed takes a Decimal and never a Float. A figure shown to two places is money or a
measurement, and admitting the type that cannot hold those exactly would reintroduce at the last step
the error Std.Decimal exists to prevent.
Std.Test makes a check a value. run is a pure function from a Suite to a Report.
import Std.Test as Test
let suite = Test.suite("orders", &[
Test.equals("total", &42, &42),
Test.that("is paid", true),
Test.todo("refunds", "not implemented"),
])
let report = Test.run(&suite)
Nothing throws, so one run reports every failure rather than the first. A failure says what it expected, what it found, and the path that reaches it — a suite answering "thirty-nine of forty" has told the reader the least useful true thing about their program.
A pending check is counted apart from both passes and failures. Counting it as a pass lets a suite quietly shrink by deleting what has not been done; this module's own fixture caught exactly that defect in it.
A table of cases becomes one named check per row, named by a function rather than by a template with holes in it:
Test.each(&cases, fn(c: Case) -> Str => c.name, fn(c: Case) -> Bool => c.holds)
A template carrying %s and %d is a small language inside a string that nothing checks; a function
is checked against the row's own type.
Skipping leaves a check pending rather than absent, so a suite cannot quietly shrink on the machine where a condition is false. Focusing is a filter over a suite rather than a mark on a check, which a value does not need.
Std.Test.Property generates values and reduces a counterexample before reporting it.
import Std.Test.Property as Property
Property.forAllInts(
"generated values stay below the bound",
7u64,
200,
50,
fn(n: Int) -> Bool => n < 50
)
Shrinking halves toward zero rather than stepping, so a failure at a million reduces in about twenty tries. The seed is given, not clocked: a property check is a pure function of that seed, reports it beside the counterexample, and can therefore be repeated exactly rather than run again and hoped about.
- No format strings.
- No captured standard output in tests. Everything these modules do is reachable through a pure rendering function.
- No
concurrentorsequentialtest modes: Pudu has no threads. - No setup/teardown fixtures. They exist to sequence effects, and a check here is a value.
- Modules And Imports
- Values, Bindings, And Blocks
- Types And Inference
- Records, Sums, And Tuples
- Functions Generics And Traits
- Pattern Language
- Control Flow And Patterns
- Iteration And Loops
- Failure And Propagation
- Numbers And Collections
- Sets Maps And Sequences
- Keyed Structures
- Trees And Hierarchies
- Standard Library
- Output Formatting And Testing
- Tasks And Scopes
- Compile-Time Evaluation
- Typed Macros
- References And Unsafe
- Worked Programs