Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENT.md → AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AGENT.md
# AGENTS.md
<!-- cspell:ignore linters -->

This file provides guidance to AI agents when working with code in this
Expand Down Expand Up @@ -376,7 +376,7 @@ When editing Markdown files, ensure compliance with:
When creating or editing documentation files:

1. **File Structure**:
- Always include a link to related documentation (e.g., AGENT.md should
- Always include a link to related documentation (e.g., AGENTS.md should
link to README.md).
- Add prerequisites or setup instructions before diving into commands.
- Include paths to configuration files when mentioning tools (e.g.,
Expand Down Expand Up @@ -406,7 +406,7 @@ When creating or editing documentation files:
- If `make tidy` fails, fix the issues and run it again until it passes
2. Verify all tests pass with `make test`.
3. Ensure no linting violations remain.
4. Update `AGENT.md` to reflect any changes in development workflow or
4. Update `AGENTS.md` to reflect any changes in development workflow or
standards.
5. Update `README.md` to reflect significant changes in functionality or API.

Expand Down
77 changes: 41 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Generic type constraints for use with Go generics:
type and friendly name.
* `ContextKey[T].WithValue(ctx, value)` - safely attach value to context,
comparable to standard `context.WithValue()`.
* `ContextKey[T].Get(ctx)` - extract value bound to this key in context,
* `ContextKey[T].Get(ctx)` - extracts value bound to this key in context,
returns (value, found) with nil receiver safety.

### Timeout Utilities
Expand Down Expand Up @@ -78,7 +78,8 @@ Generic type constraints for use with Go generics:
* `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)` - creates `netip.AddrPort` from components.
* `AddrPort(v)` - extracts `netip.AddrPort` from various network types
(`*net.TCPAddr`, `*net.UDPAddr`, etc.), returns `(AddrPort, bool)`.

### Interface Functions

Expand All @@ -90,9 +91,9 @@ Generic type constraints for use with Go generics:

#### Zero Value Creation

* `Zero[T]()` - returns the zero value for type T using reflection when
needed. Supports all Go types including complex generics, interfaces, and
custom types.
* `Zero[T](_ *T)` - returns the zero value for type T using a typed nil
pointer for type inference. Supports all Go types including complex generics,
interfaces, and custom types.

#### Zero Value Detection

Expand Down Expand Up @@ -384,8 +385,9 @@ Special error types for network-style temporary and timeout conditions:

### Error Testing and Utilities

* `IsError[T](err)` / `IsErrorFn[T](err, fn)` / `IsErrorFn2[T](err, fn)` -
type-safe error testing with generic constraints and custom checker functions.
* `IsError(err, errs...)` / `IsErrorFn(check, errs...)` /
`IsErrorFn2(check, errs...)` - error testing with custom checker functions
and multiple error comparison.
* `CoalesceError(errs...)` - return first non-nil error from argument list.

## Stack Tracing
Expand Down Expand Up @@ -443,7 +445,7 @@ Stack tracing utilities for debugging, error reporting, and call context:
* `%+n` - full qualified function name.
* `%+v` - equivalent to `%+s:%d`.

* `Stack.Format(fmt.State, rune)` - format entire stack with same verbs as
* `Stack.Format(fmt.State, rune)` - formats 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.
Expand Down Expand Up @@ -511,31 +513,32 @@ both `*testing.T` and `MockT`:

#### Basic Assertions

* `AssertEqual[T](t, expected, actual, msg...)` - generic value comparison.
* `AssertNotEqual[T](t, expected, actual, msg...)` - generic inequality
* `AssertEqual[T](t, expected, actual, name...)` - generic value comparison.
* `AssertNotEqual[T](t, expected, actual, name...)` - generic inequality
comparison.
* `AssertSliceEqual[T](t, expected, actual, msg...)` - slice comparison using
* `AssertSliceEqual[T](t, expected, actual, name...)` - slice comparison using
`reflect.DeepEqual`.
* `AssertSame(t, expected, actual, msg...)` - same value/reference comparison
* `AssertSame(t, expected, actual, name...)` - same value/reference comparison
using pointer equality for reference types, value equality for basic types.
* `AssertNotSame(t, expected, actual, msg...)` - different value/reference
* `AssertNotSame(t, expected, actual, name...)` - different value/reference
comparison.
* `AssertTrue(t, condition, msg...)` / `AssertFalse(t, condition, msg...)` -
* `AssertTrue(t, condition, name...)` / `AssertFalse(t, condition, name...)` -
boolean assertions.
* `AssertNil(t, value, msg...)` / `AssertNotNil(t, value, msg...)` - nil
* `AssertNil(t, value, name...)` / `AssertNotNil(t, value, name...)` - nil
checking.
* `AssertContains(t, text, substring, msg...)` - string containment.
* `AssertContains(t, text, substring, name...)` - string containment.
* `AssertNotContain(t, text, substring, name...)` - string exclusion.

#### Error and Type Assertions

* `AssertError(t, err, msg...)` / `AssertNoError(t, err, msg...)` - error
* `AssertError(t, err, name...)` / `AssertNoError(t, err, name...)` - error
presence/absence.
* `AssertErrorIs(t, err, target, msg...)` - error chain checking with
* `AssertErrorIs(t, err, target, name...)` - error chain checking with
`errors.Is`.
* `AssertTypeIs[T](t, value, msg...)` - type assertion with casting, returns
* `AssertTypeIs[T](t, value, name...)` - type assertion with casting, returns
(value, ok).
* `AssertPanic(t, fn, expectedPanic, msg...)` / `AssertNoPanic(t, fn, msg...)` -
panic testing with type-aware matching.
* `AssertPanic(t, fn, expectedPanic, name...)` /
`AssertNoPanic(t, fn, name...)` - panic testing with type-aware matching.

#### Fatal Assertions

Expand All @@ -552,26 +555,28 @@ methods terminate execution, similar to `t.Error()` vs `t.Fatal()`.

**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
* `AssertMustEqual[T](t, expected, actual, name...)` - terminate on inequality.
* `AssertMustNotEqual[T](t, expected, actual, name...)` - terminate on equality.
* `AssertMustSliceEqual[T](t, expected, actual, name...)` - terminate on slice
inequality.
* `AssertMustTrue(t, condition, msg...)` /
`AssertMustFalse(t, condition, msg...)` - terminate on boolean mismatch.
* `AssertMustNil(t, value, msg...)` / `AssertMustNotNil(t, value, msg...)` -
* `AssertMustTrue(t, condition, name...)` /
`AssertMustFalse(t, condition, name...)` - terminate on boolean mismatch.
* `AssertMustNil(t, value, name...)` / `AssertMustNotNil(t, value, name...)` -
terminate on nil check failure.
* `AssertMustContains(t, text, substring, msg...)` - terminate if substring not
* `AssertMustContains(t, text, substring, name...)` - terminate if substring not
found.
* `AssertMustError(t, err, msg...)` / `AssertMustNoError(t, err, msg...)` -
* `AssertMustNotContain(t, text, substring, name...)` - terminate if substring
found.
* `AssertMustError(t, err, name...)` / `AssertMustNoError(t, err, name...)` -
terminate on error expectation mismatch.
* `AssertMustErrorIs(t, err, target, msg...)` - terminate on error chain
* `AssertMustErrorIs(t, err, target, name...)` - terminate on error chain
mismatch.
* `AssertMustTypeIs[T](t, value, msg...) T` - terminate on type assertion
* `AssertMustTypeIs[T](t, value, name...) T` - terminate on type assertion
failure, returns cast value.
* `AssertMustPanic(t, fn, expectedPanic, msg...)` /
`AssertMustNoPanic(t, fn, msg...)` - terminate on panic expectation mismatch.
* `AssertMustSame(t, expected, actual, msg...)` /
`AssertMustNotSame(t, expected, actual, msg...)` - terminate on same-ness
* `AssertMustPanic(t, fn, expectedPanic, name...)` /
`AssertMustNoPanic(t, fn, name...)` - terminate on panic expectation mismatch.
* `AssertMustSame(t, expected, actual, name...)` /
`AssertMustNotSame(t, expected, actual, name...)` - terminate on same-ness
mismatch.

**Usage Examples:**
Expand Down Expand Up @@ -646,7 +651,7 @@ Context-aware error group with cancellation:

For detailed development setup, build commands, and AI agent guidance:

* [AGENT.md](./AGENT.md) - Development guidelines, build system, and testing
* [AGENTS.md](./AGENTS.md) - Development guidelines, build system, and testing
patterns.

### Quick Start
Expand Down
1 change: 1 addition & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ core.AssertErrorIs(t, err, target, "error chain")
```go
result, ok := core.AssertTypeIs[MyType](t, value, "type cast")
core.AssertContains(t, text, substring, "text content")
core.AssertNotContain(t, text, substring, "text exclusion")
core.AssertPanic(t, func() { panic("test") }, "panic")
core.AssertNoPanic(t, func() { /* safe code */ }, "no panic")
```
Expand Down
15 changes: 7 additions & 8 deletions TESTING_core.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ Independent Base Functions:
├── AssertNotEqual[T] (standalone implementation)
├── AssertSliceEqual[T] (uses reflect.DeepEqual)
├── AssertContains (uses strings.Contains)
├── AssertNotContain (uses strings.Contains)
├── AssertNil (uses IsNil utility)
├── AssertNotNil (uses IsNil utility)
├── AssertError (standalone implementation)
├── AssertNoError (standalone implementation)
├── AssertErrorIs (uses errors.Is)
├── AssertTypeIs[T] (uses type assertion)
├── AssertPanic (uses recover mechanism)
└── AssertNoPanic (uses recover mechanism)

Derived Functions (depend on base functions):
├── AssertTrue → calls AssertEqual(t, true, value, ...)
├── AssertFalse → calls AssertEqual(t, false, value, ...)
├── AssertError → calls AssertNotNil(t, err, ...)
└── AssertNoError → calls AssertNil(t, err, ...)
└── AssertFalse → calls AssertEqual(t, false, value, ...)
```

### Testing Implications
Expand Down Expand Up @@ -85,11 +86,9 @@ The hierarchy exists for consistency and code reuse:
1. **AssertTrue/AssertFalse → AssertEqual**: Ensures consistent formatting
and logging behaviour for boolean assertions.

2. **AssertError/AssertNoError → AssertNil/AssertNotNil**: Treats errors as
special cases of nil checking with appropriate naming.

3. **Independent Base Functions**: Provide fundamental comparison logic
without dependencies on other assertion functions.
2. **Independent Base Functions**: Provide fundamental comparison logic
without dependencies on other assertion functions. Each base function
is responsible for its own domain-specific validation and messaging.

This design allows for:

Expand Down
53 changes: 51 additions & 2 deletions testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,29 @@ func AssertContains(t T, s, substr, name string, args ...any) bool {
return ok
}

// AssertNotContain fails the test if the string contains the substring.
// The name parameter can include printf-style formatting.
// Returns true if the assertion passed, false otherwise.
//
// Example usage:
//
// AssertNotContain(t, "hello world", "goodbye", "substring absence check")
// AssertNotContain(t, output, "error", "command output for %s", cmd)
func AssertNotContain(t T, s, substr, name string, args ...any) bool {
t.Helper()
if substr == "" {
doError(t, name, args, "substring cannot be empty for AssertNotContain")
return false
}
ok := !strings.Contains(s, substr)
if !ok {
doError(t, name, args, "expected %q not to contain %q", s, substr)
} else {
doLog(t, name, args, "does not contain %q", substr)
}
return ok
}

// AssertError fails the test if error is nil.
// The name parameter can include printf-style formatting.
// Returns true if the assertion passed, false otherwise.
Expand All @@ -355,7 +378,13 @@ func AssertContains(t T, s, substr, name string, args ...any) bool {
// AssertError(t, err, "operation %s", "save")
func AssertError(t T, err error, name string, args ...any) bool {
t.Helper()
return AssertNotNil(t, err, name, args...)
ok := err != nil
if !ok {
doError(t, name, args, "expected error, got nil")
} else {
doLog(t, name, args, "error: %v", err)
}
return ok
}

// AssertNoError fails the test if error is not nil.
Expand All @@ -368,7 +397,13 @@ func AssertError(t T, err error, name string, args ...any) bool {
// AssertNoError(t, err, "loading %s", filename)
func AssertNoError(t T, err error, name string, args ...any) bool {
t.Helper()
return AssertNil(t, err, name, args...)
ok := err == nil
if !ok {
doError(t, name, args, "unexpected error: %v", err)
} else {
doLog(t, name, args, "no error")
}
return ok
}

// AssertPanic runs a function expecting it to panic and optionally validates the panic value.
Expand Down Expand Up @@ -829,6 +864,20 @@ func AssertMustContains(t T, s, substr, name string, args ...any) {
}
}

// AssertMustNotContain calls AssertNotContain and t.FailNow() if the assertion fails.
// This is a convenience function for tests that should terminate on assertion failure.
//
// Example usage:
//
// AssertMustNotContain(t, "hello world", "goodbye", "substring absence check")
// AssertMustNotContain(t, output, "error", "no errors in %s", cmd)
func AssertMustNotContain(t T, s, substr, name string, args ...any) {
t.Helper()
if !AssertNotContain(t, s, substr, name, args...) {
t.FailNow()
}
}

// AssertMustError calls AssertError and t.FailNow() if the assertion fails.
// This is a convenience function for tests that should terminate on assertion failure.
//
Expand Down
Loading