Something went wrong. Try again.
A lightweight test helper package
Something went wrong. Try again.
test config.go
4.4 kB · 142 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143package test
import ( "errors" "fmt" "math" "strings")
const ( defaultFloatEqualityThreshold = 1e-8)
// config holds test-specific configuration including additional context// and how the caller wants this library to behave.type config struct { title string // Title of the test, shown as a header in the failure log context string // Additional context passed by the caller reason string // Concise reason why the test has failed, only used sparingly and not in a user option floatEqualityThreshold float64 // The difference threshold below which two floats are considered equal}
// defaultConfig returns a default configuration.func defaultConfig() config { return config{ floatEqualityThreshold: defaultFloatEqualityThreshold, }}
// failure represents a test failure, including any set config.type failure[T any] struct { got T // The actual value want T // Expected value cfg config // Test config}
// String implements [fmt.Stringer] for failure, allowing it to print itself in the test log.func (f failure[T]) String() string { s := &strings.Builder{} s.WriteByte('\n')
s.WriteString(f.cfg.title) s.WriteByte('\n') s.WriteString(strings.Repeat("-", len(f.cfg.title))) s.WriteString("\n\n")
fmt.Fprintf(s, "Got:\t%+v\n", f.got) fmt.Fprintf(s, "Wanted:\t%+v\n", f.want)
if f.cfg.context != "" { fmt.Fprintf(s, "\n(%s)\n", f.cfg.context) }
if f.cfg.reason != "" { fmt.Fprintf(s, "\nBecause: %s\n", f.cfg.reason) }
return s.String()}
// Option is a configuration option for a test.type Option interface { // Apply the option to the test config, returning an error if the option // cannot be applied for whatever reason. apply(*config) error}
// option is a function adapter implementing the Option interface, analogous// to how http.HandlerFunc implements the Handler interface.type option func(*config) error
// apply applies the option, implementing the Option interface for the option// function adapter.func (o option) apply(cfg *config) error { return o(cfg)}
// FloatEqualityThreshold is an [Option] to set the maximum difference allowed between// two floating point numbers before they are considered equal. This setting is only// used in [NearlyEqual] and [NotNearlyEqual].//// Setting threshold to ±math.Inf is an error and will fail the test.//// The default is 1e-8, a sensible default for most cases.func FloatEqualityThreshold(threshold float64) Option { f := func(cfg *config) error { if math.IsInf(threshold, 0) { return errors.New("cannot set floating point equality threshold to ±infinity") } cfg.floatEqualityThreshold = threshold return nil } return option(f)}
// Title is an [Option] that sets the title of the test in the test failure log.//// The title is shown as an underlined header in the test failure, below which the// actual and expected values will be shown.//// By default this will be named sensibly after the test function being called, for// example [Equal] has a default title "Not Equal".//// Setting title explicitly to the empty string "" is an error and will fail the test.//// test.Equal(t, "apples", "oranges", test.Title("Wrong fruits!"))func Title(title string) Option { f := func(cfg *config) error { if title == "" { return errors.New("cannot set title to an empty string") } cfg.title = strings.TrimSpace(title) return nil } return option(f)}
// Context is an [Option] that allows the caller to inject useful contextual information// as to why the test failed. This can be a useful addition to the test failure output log.//// The signature of context allows the use of fmt print verbs to format the message in the// same way one might use [fmt.Sprintf].//// It is not necessary to include a newline character at the end of format.//// Setting context explicitly to the empty string "" is an error and will fail the test.//// For example://// err := doSomethingComplicated()// test.Ok(t, err, test.Context("something complicated failed"))func Context(format string, args ...any) Option { f := func(cfg *config) error { if format == "" { return errors.New("cannot set context to an empty string") } context := fmt.Sprintf(format, args...) cfg.context = strings.TrimSpace(context) return nil } return option(f)}