This package contains simple mechanisms used by other darvaza.org projects. It's not allowed to have dependencies outside of Go's Standard Library, and if something should be on a subdirectory, it shouldn't be here.
Generic type constraints for use with Go generics:
Signed
- signed integer types.Unsigned
- unsigned integer types.Integer
- all integer types (signed and unsigned).Float
- floating-point types.Complex
- complex number types.Bool
- boolean type.String
- string type.Ordered
- types that support ordering operations.
NewContextKey[T](name)
- creates a new type-safe key bound to specified type and friendly name.ContextKey[T].WithValue(ctx, value)
- safely attach value to context, comparable to standardcontext.WithValue()
.ContextKey[T].Get(ctx)
- extract value bound to this key in context, returns (value, found) with nil receiver safety.
WithTimeout(parent, duration)
- equivalent tocontext.WithDeadline()
but takes duration instead of absolute time. Returns parent context and no-op cancel for zero/negative durations.WithTimeoutCause(parent, duration, cause)
- equivalent tocontext.WithDeadlineCause()
but takes duration instead of absolute time. Attaches custom cause error to timeout context.
GetIPAddresses()
- get IP addresses asnetip.Addr
.GetNetIPAddresses()
- get IP addresses asnet.IP
.GetStringIPAddresses()
- get IP addresses as strings.AddrFromNetIP(ip)
- convertnet.IP
tonetip.Addr
.ParseAddr(s)
- parse string tonetip.Addr
.ParseNetIP(s)
- parse string tonet.IP
.
SplitHostPort(hostport)
- enhanced version ofnet.SplitHostPort
that accepts portless strings and validates both host and port. Supports IPv6 addresses, international domain names, and descriptive error messages.SplitAddrPort(addrport)
- splits IP address and optional port intonetip.Addr
anduint16
. Validates address format and port range (1-65535), returns zero values for portless addresses.
JoinHostPort(host, port)
- enhanced version ofnet.JoinHostPort
that validates inputs and returns portless host when port is empty. Properly handles IPv6 bracketing and international domain names.MakeHostPort(hostport, defaultPort)
- constructs validated host:port string from input with optional default port. Rejects port 0 in input, supports portless output when default is 0.AddrPort(addr, port)
- createsnetip.AddrPort
from components.
GetInterfacesNames()
- get network interface names.
Zero[T]()
- returns the zero value for type T using reflection when needed. Supports all Go types including complex generics, interfaces, and custom types.
IsZero(v)
- reports whether a value is in an uninitialized state and ready to be set. Answers the question: "Is this value uninitialized and ready to be set?"
Key semantic distinctions:
- Nil vs Empty:
[]int(nil)
returnstrue
(needs initialization),[]int{}
returnsfalse
(already initialized). - Pointer States:
(*int)(nil)
returnstrue
(can be assigned),new(int)
returnsfalse
(already points to memory). - Interface Support: Types implementing
IsZero() bool
are handled via their method, enabling custom zero semantics.
IsNil(v)
- reports whether a value is nil (typed or untyped). Answers the question: "Is this value nil?"
Key distinctions from IsZero
:
- Scope: Only checks for nil state, not zero state.
- Basic Types:
IsNil(0)
returnsfalse
(integers cannot be nil),IsZero(0)
returnstrue
(zero integer is uninitialized). - Collections:
IsNil([]int{})
returnsfalse
(empty slice is not nil),IsZero([]int{})
returnsfalse
(empty slice is initialized). - Structs:
IsNil(struct{}{})
returnsfalse
(structs cannot be nil),IsZero(struct{}{})
returnstrue
(zero struct is uninitialized).
Coalesce[T](values...)
returns the first non-zero value.IIf[T](condition, ifTrue, ifFalse)
conditional expression.
As[T,V](v)
attempts to convert value to target typeAsFn[T,V](v, fn)
converts value using a provided functionAsError[T](v)
attempts to convert value to errorAsErrors[T](v)
attempts to convert value to error slice
SliceContains[T](slice, value)
- check if slice contains value.SliceContainsFn[T](slice, value, eq)
- check containment with custom equality.SliceEqual[T](a, b)
- compare two slices for equality.SliceEqualFn[T](a, b, eq)
- compare slices with custom equality function.
SliceAs[T,V]
/SliceAsFn[T,V]
- convert slice elements to different type.SliceMap[T1,T2](slice, fn)
- transform each element with cumulative function.SliceReplaceFn[T](slice, fn)
- replace/filter elements in-place.SliceCopy[T](slice)
- create shallow copy of slice.SliceCopyFn[T](slice, fn)
- create filtered/transformed copy.
SliceMinus[T](a, b)
- elements ina
but not inb
.SliceMinusFn[T](a, b, eq)
- set difference with custom equality.SliceUnique[T](slice)
- return slice with unique elements only.SliceUniqueFn[T](slice, eq)
- unique elements with custom equality.SliceUniquify[T](ptr)
- remove duplicates in-place, modify original.SliceUniquifyFn[T](ptr, eq)
- remove duplicates with custom equality.
SliceSort[T](slice, cmp)
- sort using comparison function (returns int).SliceSortFn[T](slice, less)
- sort using less function (returns bool).SliceSortOrdered[T](slice)
- sort ordered types (int, string, float64).SliceReverse[T](slice)
- reverse slice in-place.SliceReversed[T](slice)
- return reversed copy.SliceReversedFn[T](slice, fn)
- return transformed and reversed copy.
SliceRandom[T](slice)
- select random element, returns (value, found).
ListContains[T](list, value)
- check if list contains element with default equality.ListContainsFn[T](list, value, eq)
- check if list contains element with custom equality function.
ListForEach[T](list, fn)
- iterate forward over list values until fn returns true.ListForEachElement(list, fn)
- iterate forward over list elements until fn returns true.ListForEachBackward[T](list, fn)
- iterate backward over list values until fn returns true.ListForEachBackwardElement(list, fn)
- iterate backward over list elements until fn returns true.
ListCopy[T](list)
- create shallow copy of list.ListCopyFn[T](list, fn)
- create filtered/transformed copy with helper function.
MapContains[K]()
checks if a map contains a key.MapValue[K,V]()
returns the value for a key, or a fallback value.Keys[K,T]()
returns a slice of the keys in the map.SortedKeys[K,T]()
returns a sorted slice of the keys.SortedValues[K,T]()
returns values sorted by key.SortedValuesCond[K,T]()
returns filtered values sorted by key.SortedValuesUnlikelyCond[K,T]()
likeSortedValuesCond
but more efficient.
MapListContains[K,T]
/MapListContainsFn[K,T]
MapListForEach[K,T]
/MapListForEachElement[K]
MapListInsert[K,T]
/MapListAppend[K,T]
MapListInsertUnique[K,T]
/MapListInsertUniqueFn[K,T]
MapListAppendUnique[K,T]
/MapListAppendUniqueFn[K,T]
MapListCopy[T]
/MapListCopyFn[K,V]
MapAllListContains[K,T]
/MapAllListContainsFn[K,T]
MapAllListForEach[K,T]
/MapAllListForEachElement[K]
Predefined error values for common conditions:
ErrNotImplemented
- functionality not yet implemented.ErrTODO
- placeholder for future implementation.ErrExists
- resource already exists.ErrNotExists
- resource does not exist.ErrInvalid
- invalid input or state.ErrUnknown
- unknown or unspecified error.ErrNilReceiver
- method called on nil receiver.ErrUnreachable
- indicates impossible condition.
The Unwrappable
interface represents the classic Unwrap() error
pattern,
implemented by WrappedError
. The Errors
interface represents multi-error
containers with Errors() []error
.
Error wrapping functions:
Wrap(err, note)
- wrap with simple string note.Wrapf(err, format, args...)
- wrap with formatted note.QuietWrap(err, note)
- wrap without including original error text.Unwrap(err) []error
- extract all sub-errors from wrapped errors.
The CompoundError
type aggregates multiple errors:
- Implements both
Unwrap() []error
andErrors() []error
interfaces. .AppendError(err)
/.Append(errs...)
- add errors..AsError()
- convert to single error or nil..OK()
- check if no errors.
The PanicError
type wraps panic values with stack traces:
NewPanicError()
/NewPanicErrorf()
- create panic errors.NewPanicWrap()
/NewPanicWrapf()
- wrap existing errors as panics.Panic()
/Panicf()
/PanicWrap()
/PanicWrapf()
- panic withPanicError
.
Panic recovery utilities:
Recovered
interface - marks errors from recovered panics.AsRecovered(v)
- convertrecover()
result to error.Catcher
type - safely call functions that might panic.Catch(fn)
- execute function, returning error if panic occurs.
defer func() {
if err := core.AsRecovered(recover()); err != nil {
// handle panic as error
}
}()
Convenience functions for common error-handling patterns:
Must[T](value T, err error) T
- returns value or panics withPanicError
if err is not nil. Follows the common Go pattern of Must* functions for cases where errors should never occur.Maybe[T](value T, err error) T
- always returns the value, ignoring any error. When an error occurs, the returned value may be the zero value of type T depending on the function that produced it.MustOK[T](value T, ok bool) T
- returns value or panics withPanicError
if ok is false. Useful for operations that should always succeed, such as map access or type assertions that are guaranteed to be valid.MaybeOK[T](value T, ok bool) T
- always returns the value, ignoring the ok flag. When ok is false, the returned value is typically the zero value of type T from the failed operation.MustT[T](value any) T
- equivalent toMustOK(As[any, T](value))
. Returns converted value or panics withPanicError
if type conversion fails.MaybeT[T](value any) T
- equivalent toMaybeOK(As[any, T](value))
. Returns converted value on success, or the zero value of type T if conversion fails.
All these utilities work together with the As[any, T]
function, which returns
(value, ok)
for type conversion, allowing custom handling of conversion
failures.
// Must - panic on error (use in tests, config loading, etc.)
config := Must(loadConfig("config.json")) // panics if loadConfig fails
conn := Must(net.Dial("tcp", "localhost:8080")) // panics if dial fails
// Maybe - ignore errors, proceed with values (may be zero values)
content := Maybe(os.ReadFile("optional.txt")) // []byte{} if file missing
count := Maybe(strconv.Atoi(userInput)) // 0 if parsing fails
// MustOK - panic on failure (use when operation should always succeed)
value := MustOK(MapValue(m, "key", 0)) // panics if key doesn't exist
str := MustOK(As[any, string](v)) // panics if v is not a string
// MaybeOK - ignore ok flag, proceed with values (zero values on failure)
value := MaybeOK(MapValue(m, "key", 0)) // 0 if key doesn't exist
str := MaybeOK(As[any, string](v)) // "" if type assertion fails
// MustT - equivalent to MustOK(As[any, T](value))
str := MustT[string](value) // panics if value is not a string
num := MustT[int](value) // panics if value is not an int
// MaybeT - equivalent to MaybeOK(As[any, T](value))
str := MaybeT[string](value) // "" (zero value) if value is not a string
num := MaybeT[int](value) // 0 (zero value) if value is not an int
// As - the building block for type conversion with custom handling
if str, ok := As[any, string](value); ok {
// use converted string
} else {
str = "custom default" // your own fallback logic
}
For indicating impossible code paths:
NewUnreachableError()
- create unreachable error.NewUnreachableErrorf(format, args...)
- create formatted unreachable error.
These create PanicError
instances with stack traces.
Special error types for network-style temporary and timeout conditions:
TemporaryError
type - implementsTemporary() bool
andIsTemporary() bool
interfaces for marking recoverable errors.NewTemporaryError(err)
- wrap error as temporary condition.NewTimeoutError(err)
- wrap error as timeout condition with both temporary and timeout properties.IsTemporary(err)
- recursively test if error chain contains temporary condition viaTemporary()
orIsTemporary()
methods.CheckIsTemporary(err)
- test single error for temporary condition without unwrapping chain, returns (is, known) tuple.IsTimeout(err)
- recursively test if error chain contains timeout condition viaTimeout()
orIsTimeout()
methods.CheckIsTimeout(err)
- test single error for timeout condition without unwrapping chain, returns (is, known) tuple.
IsError[T](err)
/IsErrorFn[T](err, fn)
/IsErrorFn2[T](err, fn)
- type-safe error testing with generic constraints and custom checker functions.CoalesceError(errs...)
- return first non-nil error from argument list.
Stack tracing utilities for debugging, error reporting, and call context:
Frame
- represents a single function call frame with source location.Stack
- slice of frames representing a complete call stack.MaxDepth
- maximum stack capture depth (32 frames).CallStacker
interface - types that can provide their call stack.
Here()
- capture the current stack frame where called. Returns nil if capture fails. Useful for immediate calling context.StackFrame(skip)
- capture a specific frame in the call stack, skipping the specified number of levels. Returns nil if insufficient frames.
StackTrace(skip)
- capture complete call stack starting from skip level. Returns empty Stack on failure. Maximum depth limited by MaxDepth.
Frame.Name()
- full qualified function name including package path (e.g., "darvaza.org/core.TestFunction").Frame.FuncName()
- function name only without package qualification (e.g., "TestFunction").Frame.PkgName()
- package path portion only (e.g., "darvaza.org/core").Frame.SplitName()
- split full name into (package, function) components. Handles generic functions by ignoring "[...]" suffixes.
Frame.File()
- full path to source file containing the function.Frame.Line()
- line number within source file (0 if unavailable).Frame.FileLine()
- formatted "file:line" string for display.
-
Frame.Format(fmt.State, rune)
- implements fmt.Formatter interface with support for multiple format verbs:%s
- source file basename.%d
- line number.%n
- function name (short form).%v
- equivalent to%s:%d
.%+s
- function name + full file path (newline separated).%+n
- full qualified function name.%+v
- equivalent to%+s:%d
.
-
Stack.Format(fmt.State, rune)
- format entire stack with same verbs as Frame plus '#' flag support:%#s
,%#n
,%#v
- each frame on new line.%#+s
,%#+n
,%#+v
- numbered frames with [index/total] prefix.
// Capture current location
frame := Here()
if frame != nil {
fmt.Printf("Called from %s at %s", frame.FuncName(), frame.FileLine())
}
// Capture complete stack for error reporting
stack := StackTrace(1) // skip current function
fmt.Printf("Stack trace:%+v", stack)
// Numbered stack output
fmt.Printf("Debug stack:%#+v", stack)
This package provides comprehensive public testing utilities for both internal library tests and external library users.
T
interface - abstracts testing functionality for both*testing.T
and mock implementations.MockT
- thread-safe mock testing.T implementation with error/log collection, helper tracking, state inspection (HasErrors()
,HasLogs()
,LastError()
,LastLog()
), reset capabilities, and full Fatal/FailNow support with panic recovery via theRun()
method.
S[T](values...)
- concise slice creation:S(1, 2, 3)
instead of[]int{1, 2, 3}
.S[T]()
- empty slice creation:S[string]()
instead of[]string{}
.
All assertions return boolean results, log successful cases, and work with
both *testing.T
and MockT
:
AssertEqual[T](t, expected, actual, msg...)
- generic value comparison.AssertNotEqual[T](t, expected, actual, msg...)
- generic inequality comparison.AssertSliceEqual[T](t, expected, actual, msg...)
- slice comparison usingreflect.DeepEqual
.AssertTrue(t, condition, msg...)
/AssertFalse(t, condition, msg...)
- boolean assertions.AssertNil(t, value, msg...)
/AssertNotNil(t, value, msg...)
- nil checking.AssertContains(t, text, substring, msg...)
- string containment.
AssertError(t, err, msg...)
/AssertNoError(t, err, msg...)
- error presence/absence.AssertErrorIs(t, err, target, msg...)
- error chain checking witherrors.Is
.AssertTypeIs[T](t, value, msg...)
- type assertion with casting, returns (value, ok).AssertPanic(t, fn, expectedPanic, msg...)
/AssertNoPanic(t, fn, msg...)
- panic testing.
All AssertMustFoo()
functions call the corresponding AssertFoo()
function
and automatically call t.FailNow()
if the assertion fails, terminating test
execution immediately. These follow Go testing conventions where "Fatal"
methods terminate execution, similar to t.Error()
vs t.Fatal()
.
Key Differences:
AssertFoo()
- liket.Error()
, logs failure and allows test to continue.AssertMustFoo()
- liket.Fatal()
, logs failure and terminates test execution.
Fatal Assertion Functions:
AssertMustEqual[T](t, expected, actual, msg...)
- terminate on inequality.AssertMustNotEqual[T](t, expected, actual, msg...)
- terminate on equality.AssertMustSliceEqual[T](t, expected, actual, msg...)
- terminate on slice inequality.AssertMustTrue(t, condition, msg...)
/AssertMustFalse(t, condition, msg...)
- terminate on boolean mismatch.AssertMustNil(t, value, msg...)
/AssertMustNotNil(t, value, msg...)
- terminate on nil check failure.AssertMustContains(t, text, substring, msg...)
- terminate if substring not found.AssertMustError(t, err, msg...)
/AssertMustNoError(t, err, msg...)
- terminate on error expectation mismatch.AssertMustErrorIs(t, err, target, msg...)
- terminate on error chain mismatch.AssertMustTypeIs[T](t, value, msg...) T
- terminate on type assertion failure, returns cast value.AssertMustPanic(t, fn, expectedPanic, msg...)
/AssertMustNoPanic(t, fn, msg...)
- terminate on panic expectation mismatch.
Usage Examples:
// Error pattern - logs failure, test continues
AssertEqual(t, expected, actual, "value check")
// execution continues even if assertion fails
// Fatal pattern - logs failure, test terminates
AssertMustEqual(t, expected, actual, "critical value check")
// execution stops here if assertion fails
// Traditional early abort pattern (equivalent to AssertMust*)
if !AssertEqual(t, expected, actual, "critical value") {
t.FailNow()
}
TestCase
interface - standardised interface for table-driven tests withName()
andTest(t)
methods.RunTestCases[T TestCase](t, cases)
- table-driven test runner for TestCase implementations.RunConcurrentTest(t, numWorkers, workerFn)
- concurrent testing with goroutines.RunBenchmark(b, setupFn, execFn)
- benchmark testing with setup/execution phases.
For detailed testing patterns and guidelines:
- TESTING.md - General testing patterns for all darvaza.org projects
- TESTING_core.md - Core-specific testing patterns and self-testing approaches
Enhanced wait group with error handling:
WaitGroup
- wait group that collects errors..OnError(fn)
- set error handler..Go(fn)
/.GoCatch(fn)
- run functions ingoroutines
..Wait()
- wait for completion..Err()
- get first error.
Context-aware error group with cancellation:
ErrGroup
- context-based error group..SetDefaults()
- configure with defaults..OnError(fn)
- set error handler..Cancel()
/.Context()
- cancellation control..Go(fn)
/.GoCatch(fn)
- run functions with context..Wait()
- wait and return first error..IsCancelled()
/.Cancelled()
- check cancellation state.
SpinLockDeprecated in favour of darvaza.org/x/sync/spinlock
Requirements: Go 1.23 or later
For detailed development setup, build commands, and AI agent guidance:
- AGENT.md - Development guidelines, build system, and testing patterns.
make all # Full build cycle (get deps, generate, tidy, build)
make test # Run tests
make tidy # Format and tidy (run before committing)