Skip to content

Conversation

@amery
Copy link
Contributor

@amery amery commented Sep 23, 2025

User description

Testing Assertions and Documentation Improvements

Summary

This pull request enhances the testing infrastructure for darvaza.org/core by adding new assertion functions, improving error assertion architecture, and correcting API documentation inaccuracies.

Key Changes

🧪 Testing Framework Enhancements

  • New assertions: Added AssertNotContain and AssertMustNotContain for negative string containment testing
  • Independent error assertions: Refactored AssertError and AssertNoError to provide domain-specific messaging rather than delegating to generic nil-checking
  • Improved test coverage: Package coverage increased to 97.4%

📚 Documentation Improvements

  • API accuracy fixes: Corrected 25+ function signatures in README.md to match actual implementation
    • Fixed AddrPort(v) signature (was incorrectly documented as AddrPort(addr, port))
    • Fixed Zero[T](_ *T) signature (was incorrectly documented as Zero[T]())
    • Updated all assertion functions to use name... parameter (was incorrectly documented as msg...)
    • Corrected IsError family signatures by removing incorrect generic parameters
  • Standardised naming: Renamed AGENT.md to AGENTS.md following project conventions
  • Updated testing documentation: Added examples and updated architecture hierarchy in TESTING.md and TESTING_core.md

Technical Impact

Statistics

  • Total changes: 175 additions, 66 deletions across 6 files
  • Files modified: testing.go, testing_test.go, README.md, TESTING.md, TESTING_core.md
  • Files renamed: AGENT.mdAGENTS.md

Architecture Improvements

  • Error assertion independence: AssertError and AssertNoError are now independent base functions with error-specific messages:
    • AssertError produces: "expected error, got nil" (failure) or "error: %v" (success)
    • AssertNoError produces: "unexpected error: %v" (failure) or "no error" (success)
  • Consistent assertion patterns: New AssertNotContain follows established patterns with clear messages:
    • Success: "does not contain %q"
    • Failure: "expected %q not to contain %q"

Implementation Details

Commit Breakdown

  1. refactor(testing): Make error assertions independent with domain-specific messaging

    • Converted AssertError/AssertNoError from derived to independent base functions
    • Updated TESTING_core.md hierarchy documentation
  2. feat(testing): Add AssertNotContain and AssertMustNotContain assertions

    • Implemented negative string containment assertions
    • Added comprehensive unit tests achieving 100% coverage
    • Updated all relevant documentation
  3. docs: Fix API documentation inaccuracies

    • Corrected function signatures throughout README.md
    • Ensures documentation matches actual implementation
  4. docs: Rename AGENT.md to AGENTS.md and update references

    • Standardised filename following project conventions
    • Updated all cross-references

Quality Assurance

  • All existing tests pass without modification
  • Comprehensive test coverage maintained (97.4% overall package coverage)
  • Strict linting requirements satisfied (cognitive complexity ≤7, cyclomatic complexity ≤10)
  • API documentation now 100% accurate with actual implementation
  • All changes follow established project conventions and coding standards

Backwards Compatibility

This change maintains complete backwards compatibility. All modifications are either:

  • Additions (new AssertNotContain/AssertMustNotContain functions)
  • Internal refactoring (error assertion implementation details)
  • Documentation corrections (no code behaviour changes)

PR Type

Enhancement, Tests, Documentation


Description

  • Add AssertNotContain and AssertMustNotContain assertion functions

  • Refactor error assertions to be independent with domain-specific messaging

  • Fix 25+ API documentation inaccuracies in README.md

  • Rename AGENT.md to AGENTS.md and update all references

  • Improve test coverage to 97.4% with comprehensive unit tests


Diagram Walkthrough

flowchart LR
  A["Testing Infrastructure"] --> B["New Assertions"]
  A --> C["Error Assertion Refactor"]
  A --> D["Documentation Fixes"]
  B --> B1["AssertNotContain"]
  B --> B2["AssertMustNotContain"]
  C --> C1["Independent AssertError"]
  C --> C2["Independent AssertNoError"]
  D --> D1["API Signature Corrections"]
  D --> D2["File Rename: AGENT.md → AGENTS.md"]
Loading

File Walkthrough

Relevant files
Enhancement
testing.go
Add negative string assertions and refactor error assertions

testing.go

  • Added AssertNotContain function to verify substring absence with empty
    substring validation
  • Added AssertMustNotContain fatal variant that calls FailNow() on
    assertion failure
  • Refactored AssertError from delegating to AssertNotNil to independent
    implementation with error-specific messaging
  • Refactored AssertNoError from delegating to AssertNil to independent
    implementation with error-specific messaging
  • Both error assertions now produce domain-appropriate success/failure
    messages
+51/-2   
Tests
testing_test.go
Add tests for new assertions and refactor test helpers     

testing_test.go

  • Added comprehensive unit tests for AssertNotContain covering success,
    failure, and empty substring cases
  • Added integration tests for AssertMustNotContain with MockT for both
    success and failure scenarios
  • Introduced helper function assertErrorContains to reduce test code
    duplication
  • Updated existing tests to use new helper function for cleaner error
    message assertions
  • Added testAssertMustNotContain to the TestAssertMustFunctions test
    suite
+72/-17 
Documentation
AGENTS.md
Rename AGENT.md to AGENTS.md with reference updates           

AGENTS.md

  • Renamed file header from AGENT.md to AGENTS.md
  • Updated internal reference from AGENT.md to AGENTS.md in documentation
    link guidance
  • Updated workflow documentation reference from AGENT.md to AGENTS.md
+3/-3     
README.md
Fix 25+ API documentation inaccuracies and parameter naming

README.md

  • Fixed AddrPort function signature from AddrPort(addr, port) to
    AddrPort(v) with corrected description
  • Fixed Zero[T] function signature from Zero[T]() to Zero[T](_ *T) with
    updated description
  • Corrected IsError family signatures by removing incorrect generic
    parameters
  • Updated all assertion function parameter names from msg... to name...
    throughout documentation
  • Added AssertNotContain to basic assertions list
  • Added AssertMustNotContain to fatal assertions list
  • Updated reference from AGENT.md to AGENTS.md in documentation links
  • Fixed minor grammar issues in function descriptions
+41/-36 
TESTING.md
Add AssertNotContain example to documentation                       

TESTING.md

  • Added AssertNotContain example to advanced assertions code snippet
  • Demonstrates usage of new negative string containment assertion
+1/-0     
TESTING_core.md
Update assertion hierarchy documentation for refactored functions

TESTING_core.md

  • Added AssertNotContain to independent base functions hierarchy
  • Moved AssertError and AssertNoError from derived functions to
    independent base functions
  • Updated architecture diagram to reflect new function hierarchy
  • Updated explanation of assertion hierarchy to emphasize independent
    base functions with domain-specific validation
  • Clarified that error assertions are now independent rather than
    delegating to nil checks
+7/-8     

Summary by CodeRabbit

  • New Features

    • Added AssertNotContain assertion for validating content exclusion in tests.
    • Enhanced error testing utilities to support multiple errors and custom checkers.
  • Refactor

    • Updated assertion function signatures to use a name parameter instead of variadic messages.
    • Modified AddrPort and zero-value utility function signatures for improved usability.
  • Documentation

    • Updated development documentation references.
    • Refreshed testing guidance with new assertion examples.

@codecov
Copy link

codecov bot commented Sep 23, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@deepsource-io
Copy link

deepsource-io bot commented Sep 23, 2025

Here's the code health analysis summary for commits 2e6c49d..cf593fd. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Shell LogoShell✅ SuccessView Check ↗
DeepSource Go LogoGo✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@amery amery requested a review from karasz September 23, 2025 01:55
@amery amery self-assigned this Sep 23, 2025
@qodo-code-review
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Naming Clarity

Several factory functions have repetitive names like makeIsZeroInterfaceInterfaceTestCases, makeIsZeroEdgeCasesEdgeCasesTestCases, and makeIsZeroPointersPointersTestCases, which may be unintentional duplications and hurt readability. Consider simplifying to consistent, succinct names.

t.Run("basic", func(t *testing.T) {
	RunTestCases(t, makeIsZeroBasicTestCases())
})
t.Run("interface", func(t *testing.T) {
	RunTestCases(t, makeIsZeroInterfaceInterfaceTestCases())
})
t.Run("edge cases", func(t *testing.T) {
	RunTestCases(t, makeIsZeroEdgeCasesEdgeCasesTestCases())
})
t.Run("complex struct", func(t *testing.T) {
	RunTestCases(t, makeIsZeroComplexStructComplexStructTestCases())
})
t.Run("pointers", func(t *testing.T) {
	RunTestCases(t, makeIsZeroPointersPointersTestCases())
})
t.Run("nested structs", func(t *testing.T) {
	RunTestCases(t, makeIsZeroNestedStructsNestedStructsTestCases())
})
Helper Consistency

Transition from testX to runTestX helpers is mostly consistent, but ensure all references updated. Verify no leftover calls expect previous function names and that all helpers call t.Helper() as added.

	t.Run("slice", func(t *testing.T) { RunTestCases(t, zeroSliceTestCases) })
	t.Run("map", func(t *testing.T) { RunTestCases(t, zeroMapTestCases) })
	t.Run("pointer", func(t *testing.T) { RunTestCases(t, zeroPointerTestCases) })
	t.Run("struct", runTestZeroStruct)
	t.Run("interface", func(t *testing.T) { RunTestCases(t, zeroInterfaceTestCases) })
	t.Run("channel", func(t *testing.T) { RunTestCases(t, zeroChannelTestCases) })
	t.Run("func", func(t *testing.T) { RunTestCases(t, zeroFuncTestCases) })
	t.Run("time", func(t *testing.T) { RunTestCases(t, zeroTimeTestCases) })
}

func runTestZeroStruct(t *testing.T) {
	t.Helper()

	type testStruct struct {
Documentation Link

The doc now references AGENTS.md instead of AGENT.md. Verify all cross-references across the repo (including README and other docs) were updated to avoid broken links.

# AGENTS.md
<!-- cspell:ignore linters -->

This file provides guidance to AI agents when working with code in this

@amery amery added documentation Improvements or additions to documentation enhancement New feature or request testing Testing infrastructure, test utilities, and test-related changes labels Sep 23, 2025
@qodo-code-review
Copy link

qodo-code-review bot commented Sep 23, 2025

PR Code Suggestions ✨

Latest suggestions up to b79e6ec

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Fix empty-substring handling

Modify AssertNotContain to correctly fail when the substring is empty, aligning
its behavior with strings.Contains semantics, instead of returning a generic
error.

testing.go [356-369]

 // 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)
+// Note: An empty substr is considered always contained by any string per
+// strings.Contains semantics, so this assertion must fail in that case.
 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)
+	ok := substr != "" && !strings.Contains(s, substr)
 	if !ok {
-		doError(t, name, args, "expected %q not to contain %q", s, substr)
+		if substr == "" {
+			doError(t, name, args, "expected %q not to contain empty substring", s)
+		} else {
+			doError(t, name, args, "expected %q not to contain %q", s, substr)
+		}
 	} else {
 		doLog(t, name, args, "does not contain %q", substr)
 	}
 	return ok
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies and fixes inconsistent behavior for an empty substring in the new AssertNotContain function, making it more robust and aligned with Go's strings.Contains semantics.

Low
Remove brittle index assumptions

Refactor TestRunTestCases to iterate over the test cases and check for their
execution by name, rather than relying on brittle hardcoded indices.

testing_test.go [496-509]

 func TestRunTestCases(t *testing.T) {
 	cases := makeRunTestCasesTestCases()
 	RunTestCases(t, cases)
 
-	// Extract test cases for verification
-	tc1 := AssertMustTypeIs[*mockTestCase](t, cases[0], "test case 1")
-	tc2 := AssertMustTypeIs[*mockTestCase](t, cases[1], "test case 2")
-	tc3 := AssertMustTypeIs[*mockTestCase](t, cases[2], "test case 3")
-
-	// Verify all test cases were called
-	AssertTrue(t, tc1.called, "test case 1 called")
-	AssertTrue(t, tc2.called, "test case 2 called")
-	AssertTrue(t, tc3.called, "test case 3 called")
+	seen := map[string]bool{}
+	for i, c := range cases {
+		tc := AssertMustTypeIs[*mockTestCase](t, c, fmt.Sprintf("test case %d", i+1))
+		AssertTrue(t, tc.called, "test case called")
+		seen[tc.name] = true
+	}
+	AssertTrue(t, seen["test1"], "contains test1")
+	AssertTrue(t, seen["test2"], "contains test2")
+	AssertTrue(t, seen["test3"], "contains test3")
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that the test relies on a fixed order and count of test cases, which is brittle, and proposes a more robust implementation by iterating over the cases.

Low
Avoid misleading variable naming

In TESTING.md, rename the variable in the "forbidden" example from
makeHTTPClientTestCases to httpClientTestCases to avoid confusion with the
recommended factory function naming convention.

TESTING.md [686-692]

 // ❌ FORBIDDEN - Variable declaration for complex lists
-var makeHTTPClientTestCases = []httpClientTestCase{
+var httpClientTestCases = []httpClientTestCase{
     newHTTPClientTestCase("GET request", "GET", "/api/users", 200, nil),
     newHTTPClientTestCase("POST request", "POST", "/api/users", 201,
         userPayload),
     // ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that the variable name in a "forbidden" example is confusingly similar to the recommended function factory pattern, and the proposed change improves the clarity of the documentation.

Low
Fix verb tense inconsistency

In the README.md file, change the verb "extracts" to "extract" in the
description of ContextKey[T].Get(ctx) to maintain consistent verb tense with
other list items.

README.md [37-40]

 * `ContextKey[T].WithValue(ctx, value)` - safely attach value to context,
   comparable to standard `context.WithValue()`.
-* `ContextKey[T].Get(ctx)` - extracts value bound to this key in context,
+* `ContextKey[T].Get(ctx)` - extract value bound to this key in context,
   returns (value, found) with nil receiver safety.
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: This is a minor but valid documentation improvement that enhances readability and consistency by correcting the verb tense in a list.

Low
Possible issue
Fix brittle frame name expectation

Make the makeFrameSplitNameTestCases test more robust by dynamically deriving
the expected package and function names from the captured frame instead of using
hardcoded string literals.

stack_test.go [206-210]

 func makeFrameSplitNameTestCases() []TestCase {
+	frame := Here()
+	pkg, fn := frame.SplitName()
 	return S(
-		newFrameSplitNameTestCase("current function", Here(), "darvaza.org/core", "makeFrameSplitNameTestCases"),
+		newFrameSplitNameTestCase("current function", frame, pkg, fn),
 	)
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that hardcoding the function name makes the test brittle and proposes a more robust approach by dynamically getting the expected values from the captured frame.

Low
Restore concrete factory return types

Revert the factory function's return type from the TestCase interface to the
concrete panicErrorMethodsTestCase struct type. This preserves static type
information for callers and is a more flexible design.

panicerror_test.go [89-95]

-func newPanicErrorMethodsTestCase(name string, payload any, expected string) TestCase {
+func newPanicErrorMethodsTestCase(name string, payload any, expected string) panicErrorMethodsTestCase {
 	return panicErrorMethodsTestCase{
 		name:     name,
 		payload:  payload,
 		expected: expected,
 	}
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that changing factory return types to an interface is a significant design choice with trade-offs. While the PR's approach is valid, returning the concrete type is a common and arguably more flexible pattern in Go, as it preserves type information for callers, which is a valid point for improving maintainability.

Low
Use concrete factory return types

Revert the factory function's return type from the TestCase interface to the
concrete waitGroupGoTestCase struct type. This preserves static type information
for callers and is a more flexible design.

waitgroup_test.go [26-33]

-func newWaitGroupGoTestCase(name string, fn func() error, expectError bool, errorMsg string) TestCase {
+func newWaitGroupGoTestCase(name string, fn func() error, expectError bool, errorMsg string) waitGroupGoTestCase {
 	return waitGroupGoTestCase{
 		name:        name,
 		fn:          fn,
 		expectError: expectError,
 		errorMsg:    errorMsg,
 	}
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that changing factory return types to an interface is a significant design choice with trade-offs. While the PR's approach is valid, returning the concrete type is a common and arguably more flexible pattern in Go, as it preserves type information for callers, which is a valid point for improving maintainability.

Low
General
Remove hardcoded package path

Make the makeFramePkgNameTestCases test more robust by using the result of
frame.PkgName() as the expected value instead of a hardcoded string.

stack_test.go [292-300]

 func makeFramePkgNameTestCases() []TestCase {
+	frame := Here()
 	return S(
-		newFramePkgNameTestCase("current function", Here(), "darvaza.org/core"),
+		newFramePkgNameTestCase("current function", frame, frame.PkgName()),
 		newFramePkgNameTestCase("empty frame", &Frame{name: ""}, ""),
 		newFramePkgNameTestCase("no package frame", &Frame{name: "func"}, ""),
 		newFramePkgNameTestCase("dot separator", &Frame{name: "pkg.func"}, "pkg"),
 		newFramePkgNameTestCase("slash separator", &Frame{name: "pkg/module.func"}, "pkg/module"),
 	)
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that hardcoding the package name makes the test brittle and proposes a more robust approach by using the captured frame's PkgName() as the expectation.

Low
  • More

Previous suggestions

Suggestions up to commit 47f2cf6
CategorySuggestion                                                                                                                                    Impact
General
Avoid using fatal test assertions

Replace t.Fatalf with AssertMustTypeIs to avoid halting the entire test run on a
type assertion failure, allowing for more comprehensive test reports.

panic_test.go [523-539]

 func (tc mustPanicTestCase) Test(t *testing.T) {
 	t.Helper()
 
 	_, err := runTestMust("value", tc.err)
 	AssertError(t, err, "Must panic")
 
 	// Verify the panic contains our original error
-	panicErr, ok := err.(*PanicError)
-	if !ok {
-		t.Fatalf("expected *PanicError, got %T", err)
-	}
+	panicErr := AssertMustTypeIs[*PanicError](t, err, "must be *PanicError")
 	AssertErrorIs(t, panicErr.error, tc.err, "panic error should wrap original error")
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that using t.Fatalf is not ideal and proposes using AssertMustTypeIs, which is consistent with the testing style in the PR and improves test suite robustness.

Medium
Validate the content of panic errors

*Enhance the mustOKPanicTestCase test to validate the content of the PanicError
message, not just its type, ensuring the panic occurs for the expected reason.

panic_test.go [740-756]

 func (tc mustOKPanicTestCase) Test(t *testing.T) {
 	t.Helper()
 
 	_, err := runTestMustOK(tc.value, tc.ok)
 	AssertError(t, err, "MustOK panic")
 
 	// Verify it's a proper PanicError
-	_, ok := err.(*PanicError)
-	if !ok {
-		t.Fatalf("expected *PanicError, got %T", err)
-	}
+	panicErr := AssertMustTypeIs[*PanicError](t, err, "must be *PanicError")
+	AssertEqual(t, "assertion failed: ok is false", panicErr.Error(), "panic error message")
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion improves the test by adding a check for the specific panic error message, making the test more thorough and ensuring the function panics for the correct reason.

Medium
Use exact match for error message

Replace strings.Contains with AssertEqual to perform an exact match on the error
message for a more robust test.

testing_test.go [162-170]

 // Test failed assertion (substring present)
 result = AssertNotContain(mock, "hello world", "world", "contains test")
 AssertFalse(t, result, "AssertNotContain result when contains")
 AssertTrue(t, mock.HasErrors(), "has errors on failure")
 AssertFalse(t, mock.HasLogs(), "no logs on failure")
 
 lastErr, ok := mock.LastError()
 AssertTrue(t, ok, "LastError ok on failure")
-AssertTrue(t, strings.Contains(lastErr, "expected \"hello world\" not to contain \"world\""), "error message")
+AssertEqual(t, "contains test: expected \"hello world\" not to contain \"world\"", lastErr, "error message")
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that using AssertEqual for error message validation is more robust than strings.Contains, making the test less brittle and more precise.

Low
Possible issue
Prevent bug with empty substring

Add a check to AssertNotContain to explicitly fail with a clearer error message
when the provided substring is empty, as this assertion is a logical
contradiction.

testing.go [356-365]

 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
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a logical edge case where AssertNotContain will always fail for an empty substring and proposes a more explicit error, improving the utility's robustness and developer feedback.

Low

@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

The pull request refactors the testing assertion API surface and updates documentation references. It renames documentation files (AGENT.md to AGENTS.md), modifies assertion function signatures to use a name parameter instead of msg..., introduces new assertion functions (AssertNotContain/AssertMustNotContain), restructures error utilities to remove generic constraints, and adjusts the Zero value utility signature.

Changes

Cohort / File(s) Summary
Documentation file references
AGENTS.md, README.md
Updated all references from AGENT.md to AGENTS.md. Minor grammar fixes to context key descriptions. Documentation of API surface changes (signature updates for assertion functions, Zero utility, and error testing utilities).
Testing assertion APIs
TESTING.md, TESTING_core.md
Added AssertNotContain to both documentation sections. Repositioned AssertError and AssertNoError from derived to independent base functions. Updated architecture rationale to reflect independent validation/messaging responsibilities.
Testing implementation
testing.go
Added AssertNotContain(t T, s, substr, name string, args ...any) bool with inline validation. Converted AssertError and AssertNoError to standalone inline implementations. Added convenience function AssertMustNotContain(t T, s, substr, name string, args ...any).
Testing test coverage
testing_test.go
Introduced TestAssertNotContain with success/failure/empty-substring scenarios. Added internal helper assertErrorContains for centralised error message validation. Expanded TestAssertMustFunctions to include AssertMustNotContain. Migrated existing error checks to use the new helper.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas requiring attention:
    • All assertion function signature changes (msg... → name...) across both public API documentation and implementation to ensure consistency
    • New AssertNotContain and AssertMustNotContain implementations for correct substring validation logic
    • Repositioning of AssertError and AssertNoError from derived to base functions to verify no functional regression
    • Test coverage for the new assertErrorContains helper and its usage across multiple test functions

Possibly related PRs

Suggested labels

tests, Review effort 4/5

Suggested reviewers

  • karasz

Poem

🐰 With paws on the keyboard, I hop with delight,
Assertions grow stronger, now shining so bright,
No longer contained—we add the reverse,
AssertNotContain—oh, a blessing, not curse!
The tests run more truly, the APIs take flight,
Through names and through messages, testing's done right! 🧪✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Testing Infrastructure Enhancement and Documentation Overhaul" directly aligns with the substantive changes in the changeset. The PR introduces new assertion functions (AssertNotContain, AssertMustNotContain) and refactors existing assertions, addressing the testing infrastructure component. Simultaneously, it corrects API documentation across multiple files (README.md with 25+ signature updates, TESTING.md, TESTING_core.md) and renames the AGENT.md file to AGENTS.md, fulfilling the documentation overhaul aspect. The title is specific and clear rather than generic, avoiding vague terms like "misc updates", and a teammate reviewing the commit history would readily understand that this PR encompasses both testing improvements and documentation work.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr-amery-testing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b79e6ec and cf593fd.

📒 Files selected for processing (6)
  • AGENTS.md (3 hunks)
  • README.md (8 hunks)
  • TESTING.md (1 hunks)
  • TESTING_core.md (2 hunks)
  • testing.go (4 hunks)
  • testing_test.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • TESTING_core.md
  • testing.go
  • TESTING.md
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (AGENT.md)

**/*.go: Adhere to revive limits: max function length 40 lines, max 3 results, max 5 arguments, cognitive complexity ≤7, cyclomatic complexity ≤10
Keep struct fields aligned to minimize padding (use fieldalignment -fix)

Files:

  • testing_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENT.md)

**/*_test.go: Prefer table-driven tests
Use the public testing utilities from testing.go in tests
Test assertions should log successful checks for better debugging

Files:

  • testing_test.go
**/*.{md,go}

📄 CodeRabbit inference engine (AGENT.md)

Pass CSpell checks using the project dictionary (British English) for docs and code comments

Files:

  • testing_test.go
  • AGENTS.md
  • README.md
**/*.md

📄 CodeRabbit inference engine (AGENT.md)

**/*.md: Comply with markdownlint rules (80-character line limit, strict formatting, selective HTML allowlist)
Run LanguageTool with British English; fix missing articles, punctuation, and hyphenation issues
End all bullet list items with periods
Capitalize proper nouns correctly (e.g., JavaScript, TypeScript, Markdown)
Hyphenate compound modifiers when used before nouns (e.g., capture-specific)

Files:

  • AGENTS.md
  • README.md
🧠 Learnings (4)
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to **/*_test.go : Test assertions should log successful checks for better debugging

Applied to files:

  • testing_test.go
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: Applies to AGENT.md : Update `AGENT.md` to reflect any changes in development workflow or standards.

Applied to files:

  • AGENTS.md
  • README.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Keep AGENT.md and README.md updated when workflows or APIs change

Applied to files:

  • AGENTS.md
📚 Learning: 2025-08-05T22:27:53.946Z
Learnt from: amery
PR: darvaza-proxy/core#135
File: compounderror_test.go:201-201
Timestamp: 2025-08-05T22:27:53.946Z
Learning: In Go test functions using assertion helpers like AssertSame, AssertEqual, etc., early returns after assertion calls are only necessary when there is subsequent code that should be skipped if the assertion fails. At the end of a function or code block where no further operations follow, the early return pattern is redundant since the function will naturally return anyway.

Applied to files:

  • README.md
🧬 Code graph analysis (1)
testing_test.go (1)
testing.go (5)
  • T (22-33)
  • MockT (45-51)
  • AssertNotContain (356-369)
  • AssertEqual (278-287)
  • AssertMustNotContain (874-879)
🪛 LanguageTool
README.md

[grammar] ~536-~536: This sentence seems to be incomplete. Insert a noun before ‘Is’ to make the sentence complete.
Context: ..., name...)- error chain checking with errors.Is. * AssertTypeIs[T](t, value, name...)...

(MISSING_SUBJECT)


[locale-violation] ~539-~539: Lower-case ‘ok’ is American English. For British English use “OK”.
Context: ...sertion with casting, returns (value, ok). * `AssertPanic(t, fn, expectedPanic, ...

(OK)

🔇 Additional comments (12)
testing_test.go (5)

68-68: LGTM: Centralised error validation.

The refactoring to use assertErrorContains eliminates code duplication and provides a consistent pattern for validating error messages throughout the tests.

Also applies to: 93-93, 137-138, 763-763, 807-807


140-173: LGTM: Comprehensive test coverage.

The test covers all essential scenarios for AssertNotContain: success case (substring absent), failure case (substring present), and the edge case of an empty substring. The implementation follows established patterns and is well-structured.


1023-1023: LGTM: Proper test registration.

The addition of testAssertMustNotContain to the test suite follows the established pattern for Must variant tests.


1133-1154: LGTM: Consistent Must variant test.

The test for AssertMustNotContain follows the established pattern for Must variant assertions, correctly verifying both the success case (execution continues) and the failure case (execution aborts via FailNow).


1448-1454: LGTM: Well-designed helper function.

The assertErrorContains helper effectively centralises error message validation. It correctly calls t.Helper() and uses assertions that log successful checks as per project guidelines.

AGENTS.md (1)

1-1: LGTM: Documentation references updated.

All references correctly updated from AGENT.md to AGENTS.md, consistent with the file rename.

Also applies to: 379-379, 409-409

README.md (6)

39-40: LGTM: Improved documentation clarity.

The addition of "with nil receiver safety" provides useful information about the method's behaviour.


81-82: LGTM: Accurate signature documentation.

The updated signature correctly documents that AddrPort takes a single parameter v and returns (AddrPort, bool), with clear indication of supported network types.


94-96: LGTM: Clear signature documentation.

The updated signature and description effectively explain the new pattern of using a typed nil pointer for type inference. The documentation covers the broad type support comprehensively.


388-390: LGTM: Comprehensive error testing documentation.

The updated signatures accurately document the expanded IsError family with multi-error comparison and custom checker function support.


516-530: LGTM: Comprehensive assertion API documentation.

The documentation correctly reflects the signature changes across all assertion functions, migrating from variadic msg... to a name parameter. The addition of AssertNotContain and AssertMustNotContain is properly documented.

Note: The static analysis hint about line 536 (incomplete sentence with errors.Is) appears to be a false positive—the backtick formatting is correct for documenting function references in Markdown. The hint about "ok" vs "OK" at line 539 is a minor locale preference that doesn't affect clarity.

Also applies to: 534-542, 558-580


654-655: LGTM: Documentation reference updated.

The reference correctly updated to AGENTS.md, consistent with the file rename.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
testing_test.go (1)

46-69: Avoid circular testing of base assertions in testing_test.go.

Per TESTING_core.md, when testing base assertions (e.g., AssertEqual), use the standard testing package, not other assert helpers like AssertTrue/False which depend on AssertEqual.

Apply this pattern locally; example for this test:

- result := AssertEqual(mock, 42, 42, "equal test")
- AssertTrue(t, result, "AssertEqual result when equal")
- AssertFalse(t, mock.HasErrors(), "no errors on success")
- AssertTrue(t, mock.HasLogs(), "has logs on success")
+ result := AssertEqual(mock, 42, 42, "equal test")
+ if !result {
+   t.Error("AssertEqual should return true")
+ }
+ if mock.HasErrors() {
+   t.Error("no errors on success")
+ }
+ if !mock.HasLogs() {
+   t.Error("success should be logged")
+ }

Please apply similarly to other base‑assert tests in this file.

generics_test.go (1)

1-477: Replace remaining t.Errorf/t.Fatalf uses with Assert helpers*

Standardise on the repo's public testing utilities (AssertEqual/AssertSame, etc.) and remove redundant early returns after assertions.

Occurrences to fix: generics_test.go — lines 32, 99, 103, 219, 280, 328, 332, 376, 423, 428, 433.

stack_test.go (1)

90-95: Fix: invalid range over ints (compilation error).

You cannot range over an int. Switch to conventional for-loops.

Apply:

-	for i := range MaxTestDepth {
-		for j := range MaxTestSpace {
+	for i := 0; i < MaxTestDepth; i++ {
+		for j := 0; j < MaxTestSpace; j++ {
 			testDeepStackTrace(t, i, j)
 		}
 	}
lists_test.go (1)

383-392: Fix the “nil function” test to actually pass nil.

Currently a non‑nil no‑op is passed, so this does not exercise the nil‑fn early‑return path.

Apply:

 func runTestListForEachNilFunction(t *testing.T, name string, iterFn func(*list.List, func(int) bool)) {
 	t.Helper()
 	l := list.New()
 	l.PushBack(1)
 	l.PushBack(2)
 
-	var result []int
-	iterFn(l, func(int) bool { return false })
-	AssertEqual(t, 0, len(result), name+" nil function")
+	var result []int
+	iterFn(l, nil)
+	AssertEqual(t, 0, len(result), name+" nil function")
 }
🧹 Nitpick comments (46)
testing.go (2)

787-800: Capture panics from worker goroutines to avoid crashing the process.

A panic inside a worker will currently take down the test process. Convert panics to errors on errCh.

Apply:

 func runWorkers(numWorkers int, worker func(int) error, errCh chan error) {
   var wg sync.WaitGroup
-  for i := range numWorkers {
+  for i := 0; i < numWorkers; i++ {
     wg.Add(1)
     go func(id int) {
-      defer wg.Done()
-      if err := worker(id); err != nil {
-        errCh <- err
-      }
+      defer wg.Done()
+      defer func() {
+        if r := recover(); r != nil {
+          if e, ok := r.(error); ok {
+            errCh <- e
+          } else {
+            errCh <- fmt.Errorf("panic: %v", r)
+          }
+        }
+      }()
+      if err := worker(id); err != nil {
+        errCh <- err
+      }
     }(i)
   }
   wg.Wait()
   close(errCh)
 }

714-724: British English in comments.

Project guidelines require British English in code comments. Please standardise wording.

-// This standardizes concurrent testing patterns.
+// This standardises concurrent testing patterns.
-// This standardizes benchmark patterns with proper timer management.
+// This standardises benchmark patterns with proper timer management.

Also applies to: 732-742

TESTING_core.md (1)

376-393: Incorrect example: RunBenchmark doesn’t return an error.

The sample assigns err := RunBenchmark(...) but the function returns no value.

-func TestRunBenchmark(t *testing.T) {
-  called := false
-  err := RunBenchmark(&testing.B{},
+func TestRunBenchmark(t *testing.T) {
+  called := false
+  RunBenchmark(&testing.B{},
     func() interface{} {
       return "test data"
     },
     func(data interface{}) {
       called = true
       AssertEqual(t, "test data", data.(string), "benchmark data")
     },
   )
-
-  AssertNoError(t, err, "benchmark should run")
   AssertTrue(t, called, "benchmark function should be called")
 }
errgroup_test.go (2)

431-457: Reduce time‑based flakiness in Done test.

The fixed sleeps (5 ms / 50 ms) are tight for CI. Prefer synchronisation over timers or widen margins.

- case <-time.After(5 * time.Millisecond):
+ case <-time.After(50 * time.Millisecond):

Optionally gate readiness with a barrier channel or a small WaitGroup instead of sleeping.


373-401: Similar flakiness risks with small timeouts.

1 ms budgets can intermittently fail under load. Consider 10–20 ms minima or synchronisation primitives.

Also applies to: 402-429

testing_test.go (1)

512-530: Creating testing.B directly is brittle and ties to Go 1.22 loop syntax.

Constructing &testing.B{N: 10} outside the benchmark runner is unsupported and RunBenchmark currently uses for range b.N (Go 1.22 only). Either:

  • abstract a tiny B interface with just ResetTimer() and N access and provide a mock, or
  • drop this unit test and rely on real benchmarks.

Confirm toolchain (see earlier script) and whether CI enforces 1.22+.

TESTING.md (4)

393-393: Fix typo: "AssertNotContain" should be "AssertNotContains".

The function name should be AssertNotContains to maintain consistency with the existing AssertContains function and follow proper grammatical structure.

-core.AssertNotContain(t, text, substring, "text exclusion")
+core.AssertNotContains(t, text, substring, "text exclusion")

1153-1158: Fix command formatting for markdownlint compliance.

The command should be on a single line to avoid markdownlint issues, or use proper line continuation syntax.

-<!-- markdownlint-disable MD013 -->
 ```bash
-go run golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest \\
-  -fix sample_file.go
+go run golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest -fix sample_file.go

-


---

`1454-1455`: **Consolidate return type in factory function example.**

The example shows `[]TestCase` but earlier documentation states factory functions should return the concrete type for better type safety.



```diff
-func makeMyFunctionTestCases() []TestCase {
-    return []TestCase{
+func makeMyFunctionTestCases() []myTestCase {
+    return []myTestCase{

1470-1472: Fix inconsistency in return type documentation.

The example shows returning []TestCase but this contradicts the earlier guidance about returning concrete types.

-func makeValidationTestCases(fieldName string) []TestCase {
-    return []TestCase{
+func makeValidationTestCases(fieldName string) []validationTestCase {
+    return []validationTestCase{
waitgroup_test.go (2)

100-100: Consider simplifying the factory function signature.

The function signature could be more readable by placing the return type on the same line when it fits.

-func newWaitGroupGoCatchTestCase(name string, fn func() error, catch func(error) error,
-	expectError bool, errorMsg string) TestCase {
+func newWaitGroupGoCatchTestCase(name string, fn func() error, catch func(error) error, expectError bool, errorMsg string) TestCase {

181-181: Inconsistent line breaking in function signature.

For consistency with other factory functions in the file, consider keeping parameters on the same line when they fit.

-func newWaitGroupOnErrorTestCase(name string, workers []func() error,
-	onErrorHandler func(error) error, expectError bool, errorMsg string) TestCase {
+func newWaitGroupOnErrorTestCase(name string, workers []func() error, onErrorHandler func(error) error, expectError bool, errorMsg string) TestCase {
addrs_test.go (1)

53-53: Remove unnecessary revive directive.

The flag-parameter directive appears unnecessary as the boolean parameter is essential for the function's purpose.

-//revive:disable-next-line:flag-parameter
 func newParseAddrTestCaseStr(name, input, wantAddr string, wantErr bool) TestCase {
addrport_test.go (2)

95-95: Consider adding the //revive:disable-next-line:flag-parameter directive.

For consistency with other test files in the codebase that have boolean flag parameters in factory functions.

+//revive:disable-next-line:flag-parameter
 func newAddrPortTestCase(name string, input any, want netip.AddrPort, wantOK bool) TestCase {

158-159: Function signature formatting inconsistency.

Consider keeping the parameters on the same line for consistency with other factory functions.

-func newTypeSpecificAddrPortTestCase(name string, input any, want netip.AddrPort,
-	wantOK bool) TestCase {
+func newTypeSpecificAddrPortTestCase(name string, input any, want netip.AddrPort, wantOK bool) TestCase {
slices_test.go (3)

192-192: Inconsistent function signature formatting.

For consistency with other factory functions, consider keeping parameters on the same line when they fit.

-func newSliceMinusFnTestCase(name string, a, b, expected []string,
-	equal func(string, string) bool) TestCase {
+func newSliceMinusFnTestCase(name string, a, b, expected []string, equal func(string, string) bool) TestCase {

329-330: Inconsistent function signature formatting in generic factory.

For consistency, consider keeping the signature on fewer lines.

-func newSliceMapTestCase[T1, T2 any](name string, input []T1, fn func([]T2, T1) []T2,
-	expected []T2) sliceMapTestCase[T1, T2] {
+func newSliceMapTestCase[T1, T2 any](name string, input []T1, fn func([]T2, T1) []T2, expected []T2) sliceMapTestCase[T1, T2] {

444-445: Inconsistent function signature formatting in generic factory.

Consider formatting consistently with other factory functions.

-func newSliceReversedFnTestCase[T any](name string, input []T, fn func([]T, T) (T, bool),
-	expected []T) sliceReversedFnTestCase[T] {
+func newSliceReversedFnTestCase[T any](name string, input []T, fn func([]T, T) (T, bool), expected []T) sliceReversedFnTestCase[T] {
context_test.go (4)

266-275: “wrong type” case currently also tests key identity; bind the same key to isolate type‑mismatch.

Using two distinct NewContextKeyint instances makes the failure due to key mismatch rather than value type. Reuse the same key in both context.WithValue and Get.

Apply this diff:

 func makeContextKeyGetTestCases() []TestCase {
   var nilCtx context.Context
+  k := NewContextKey[int]("test")
   return S(
     newContextKeyGetTestCase(context.Background(), "nil receiver", nil, 0, false),
-    newContextKeyGetTestCase(nilCtx, "nil context", NewContextKey[int]("test"), 0, false),
-    newContextKeyGetTestCase(
-      context.WithValue(context.Background(), NewContextKey[int]("test"), "string_value"),
-      "wrong type", NewContextKey[int]("test"), 0, false),
+    newContextKeyGetTestCase(nilCtx, "nil context", k, 0, false),
+    newContextKeyGetTestCase(
+      context.WithValue(context.Background(), k, "string_value"),
+      "wrong type", k, 0, false),
   )
 }

103-114: Reduce timing flakiness in timeout test.

Tight sleeps can be flaky in slow CI. Widen the margins.

- ctx, cancel := WithTimeout(context.Background(), 10*time.Millisecond)
+ ctx, cancel := WithTimeout(context.Background(), 25*time.Millisecond)
 ...
- case <-time.After(100 * time.Millisecond):
+ case <-time.After(250 * time.Millisecond):

116-133: Ensure cleanup on early failures.

Defer cancel to avoid leaked timers if the test fails before explicit cancel.

 func runTestWithTimeoutCancellation(t *testing.T) {
   ctx, cancel := WithTimeout(context.Background(), time.Hour)
+  defer cancel()

215-218: Assert cause robustly.

Use errors.Is to assert the propagated cause; it’s clearer and resilient to wrapping.

-    if cause := context.Cause(ctx); cause != testErr {
-      AssertEqual(t, testErr, cause, "cause")
-    }
+    AssertTrue(t, errors.Is(context.Cause(ctx), testErr), "cause")
compounderror_test.go (2)

304-323: Also assert order and identity of unwrapped errors.

Strengthen the “unwrappable” path by checking that items are appended in order and by identity.

   expectedLen := 3 // original 1 + unwrapped 2
   if !AssertEqual(t, expectedLen, len(ce.Errs), "error count") {
     return
   }
+  if !AssertSame(t, mock.errs[0], ce.Errs[1], "first unwrapped error") {
+    return
+  }
+  if !AssertSame(t, mock.errs[1], ce.Errs[2], "second unwrapped error") {
+    return
+  }

351-360: Formatted note assertion: simplify and avoid dual-path contains.

You can assert the exact formatted string when args are present, and avoid OR checks that can hide regressions.

-    if tc.expectNote {
-      if tc.note != "" {
-        expectedNote := tc.note
-        if len(tc.args) > 0 {
-          expectedNote = "wrapped note: 42" // for the formatted case
-        }
-        foundExpected := strings.Contains(errorStr, expectedNote)
-        foundOriginal := strings.Contains(errorStr, tc.note)
-        if !AssertTrue(t, foundExpected || foundOriginal, "note in error string") {
-          return
-        }
-      }
-    }
+    if tc.expectNote && tc.note != "" {
+      if len(tc.args) > 0 {
+        AssertContains(t, errorStr, "wrapped note: 42", "formatted note")
+      } else {
+        AssertContains(t, errorStr, tc.note, "note")
+      }
+    }

Also applies to: 368-402

panicerror_test.go (8)

120-135: Prefer Assert helpers over Fatalf for consistency and pass‑logging.*

   if tc.expectUnwrap {
-    if unwrapped == nil {
-      t.Fatalf("expected unwrapped error, got nil")
-    }
-    if unwrapped.Error() != tc.expectedError {
-      t.Fatalf("expected unwrapped error '%s', got '%s'", tc.expectedError, unwrapped.Error())
-    }
-  } else if unwrapped != nil {
-    t.Fatalf("expected nil unwrapped, got %v", unwrapped)
+    if !AssertNotNil(t, unwrapped, "unwrapped error") {
+      return
+    }
+    AssertEqual(t, tc.expectedError, unwrapped.Error(), "unwrapped error message")
+  } else {
+    AssertNil(t, unwrapped, "unwrapped error")
   }

167-182: Use Assert for message and payload type checks.*

   errorStr := pe.Error()
   expectedError := fmt.Sprintf("panic: %s", tc.expected)
-  if errorStr != expectedError {
-    t.Fatalf("expected error '%s', got '%s'", expectedError, errorStr)
-  }
+  AssertEqual(t, expectedError, errorStr, "error message")
 ...
-  if _, ok := pe.Recovered().(error); !ok {
-    t.Fatalf("expected error payload, got %T", pe.Recovered())
-  }
+  if _, ok := pe.Recovered().(error); !AssertTrue(t, ok, "error payload") {
+    return
+  }

198-214: Wrap tests: assert with helpers for readability.

   unwrapped := pe.Unwrap()
-  if unwrapped == nil {
-    t.Fatalf("expected unwrapped error, got nil")
-  }
+  AssertNotNil(t, unwrapped, "unwrapped error")
 ...
-  if !strings.Contains(errorStr, note) {
-    t.Fatalf("expected error to contain note '%s', got '%s'", note, errorStr)
-  }
-  if !strings.Contains(errorStr, originalErr.Error()) {
-    t.Fatalf("expected error to contain original error '%s', got '%s'", originalErr.Error(), errorStr)
-  }
+  AssertContains(t, errorStr, note, "note in error")
+  AssertContains(t, errorStr, originalErr.Error(), "original error in message")

221-237: Wrapf tests: use AssertContains.

-  if unwrapped == nil {
-    t.Fatalf("expected unwrapped error, got nil")
-  }
+  AssertNotNil(t, unwrapped, "unwrapped error")
 ...
-  if !strings.Contains(errorStr, "wrapped note: 42") {
-    t.Fatalf("expected error to contain formatted note, got '%s'", errorStr)
-  }
-  if !strings.Contains(errorStr, originalErr.Error()) {
-    t.Fatalf("expected error to contain original error '%s', got '%s'", originalErr.Error(), errorStr)
-  }
+  AssertContains(t, errorStr, "wrapped note: 42", "formatted note")
+  AssertContains(t, errorStr, originalErr.Error(), "original error in message")

397-427: NewUnreachableError: convert to Assert and early returns.*

   result := NewUnreachableError(0, tc.err, tc.note)
-
-  if result == nil {
-    t.Fatalf("expected non-nil error, got nil")
-  }
+  if !AssertNotNil(t, result, "non-nil error") { return }
 
   // Test that it's a PanicError
   pe, ok := result.(*PanicError)
-  if !ok {
-    t.Fatalf("expected PanicError, got %T", result)
-  }
+  if !AssertTrue(t, ok, "PanicError type") { return }
 
   // Test that ErrUnreachable is somewhere in the chain
-  if !errors.Is(result, ErrUnreachable) {
-    t.Fatalf("expected ErrUnreachable in error chain")
-  }
+  AssertTrue(t, errors.Is(result, ErrUnreachable), "ErrUnreachable in chain")
 
   // Test error message
   errorStr := result.Error()
-  if errorStr == "" {
-    t.Fatalf("expected non-empty error message")
-  }
+  AssertTrue(t, errorStr != "", "non-empty error message")
 
   // Test stack trace
   stack := pe.CallStack()
-  if len(stack) == 0 {
-    t.Fatalf("expected non-empty stack trace")
-  }
+  AssertTrue(t, len(stack) > 0, "non-empty stack trace")

444-476: NewUnreachableErrorf: ditto.

-  if result == nil {
-    t.Fatalf("expected non-nil error, got nil")
-  }
+  if !AssertNotNil(t, result, "non-nil error") { return }
 ...
-  if !errors.Is(result, ErrUnreachable) {
-    t.Fatalf("expected ErrUnreachable in error chain")
-  }
+  AssertTrue(t, errors.Is(result, ErrUnreachable), "ErrUnreachable in chain")
 ...
-  if !errors.Is(result, err) {
-    t.Fatalf("expected original error in error chain")
-  }
+  AssertTrue(t, errors.Is(result, err), "original error in chain")
 ...
-  if !strings.Contains(errorStr, "formatted note: 42") {
-    t.Fatalf("expected formatted message in error, got '%s'", errorStr)
-  }
+  AssertContains(t, errorStr, "formatted note: 42", "formatted message")
 ...
-  if len(stack) == 0 {
-    t.Fatalf("expected non-empty stack trace")
-  }
+  AssertTrue(t, len(stack) > 0, "non-empty stack trace")

376-381: Remove unused field from newUnreachableErrorTestCase.

expectType is never read; drop it to keep the type lean.

 type newUnreachableErrorTestCase struct {
   name       string
   err        error
   note       string
-  expectType string
 }
 ...
 func newNewUnreachableErrorTestCase(name string, err error, note string) TestCase {
   return newUnreachableErrorTestCase{
     name:       name,
     err:        err,
     note:       note,
-    expectType: "PanicError",
   }
 }

Also applies to: 430-436


1-522: Standardise tests — replace direct t.Fatalf calls with Assert helpers*

Found 26 direct t.Fatalf occurrences (no t.Errorf) in panicerror_test.go; convert them to the appropriate Assert* helpers (AssertEqual / AssertNotNil / AssertTrue / AssertNil) for consistent, positive test logging.

Locations: panicerror_test.go — lines 78, 127, 130, 133, 175, 180, 203, 209, 212, 226, 232, 235, 287, 307, 355, 402, 408, 413, 419, 425, 447, 453, 458, 463, 469, 475.

generics_test.go (7)

27-34: Use AssertEqual for consistency and pass‑logs.

   got := Coalesce(tc.inputs...)
-  if got != tc.expected {
-    t.Errorf("Coalesce(%v) = %v, want %v", tc.inputs, got, tc.expected)
-  }
+  if !AssertEqual(t, tc.expected, got, "Coalesce(%v)", tc.inputs) {
+    return
+  }

95-104: Pointer case: assert nilness and value via Assert.*

   if tc.expected == nil && got == nil {
     return
   }
   if tc.expected == nil || got == nil {
-    t.Errorf("Coalesce(%v) = %v, want %v", tc.inputs, got, tc.expected)
+    AssertEqual(t, tc.expected == nil, got == nil, "Coalesce(%v) nilness", tc.inputs)
     return
   }
-  if *got != *tc.expected {
-    t.Errorf("Coalesce(%v) = %v, want %v", tc.inputs, *got, *tc.expected)
-  }
+  AssertEqual(t, *tc.expected, *got, "Coalesce(%v)", tc.inputs)

214-221: IIf int: switch to AssertEqual.

   got := IIf(tc.cond, tc.yes, tc.no)
-  if got != tc.expected {
-    t.Errorf("IIf(%v, %v, %v) = %v, want %v", tc.cond, tc.yes, tc.no, got, tc.expected)
-  }
+  if !AssertEqual(t, tc.expected, got, "IIf(%v, %v, %v)", tc.cond, tc.yes, tc.no) {
+    return
+  }

275-282: IIf string: use AssertEqual.

   got := IIf(tc.cond, tc.yes, tc.no)
-  if got != tc.expected {
-    t.Errorf("IIf(%v, %v, %v) = %v, want %v", tc.cond, tc.yes, tc.no, got, tc.expected)
-  }
+  AssertEqual(t, tc.expected, got, "IIf(%v, %q, %q)", tc.cond, tc.yes, tc.no)

323-333: IIf pointer: assert nilness and value with Assert.*

   if tc.expected == nil && got == nil {
     return
   }
   if tc.expected == nil || got == nil {
-    t.Errorf("IIf(%v, %v, %v) = %v, want %v", tc.cond, tc.yes, tc.no, got, tc.expected)
+    AssertEqual(t, tc.expected == nil, got == nil, "IIf(%v, %v, %v) nilness", tc.cond, tc.yes, tc.no)
     return
   }
-  if *got != *tc.expected {
-    t.Errorf("IIf(%v, %v, %v) = %v, want %v", tc.cond, tc.yes, tc.no, *got, *tc.expected)
-  }
+  AssertEqual(t, *tc.expected, *got, "IIf(%v, %v, %v)", tc.cond, tc.yes, tc.no)

371-377: IIf struct: use AssertEqual.

   got := IIf(tc.cond, tc.yes, tc.no)
-  if got != tc.expected {
-    t.Errorf("IIf(%v, %+v, %+v) = %+v, want %+v", tc.cond, tc.yes, tc.no, got, tc.expected)
-  }
+  AssertEqual(t, tc.expected, got, "IIf(%v, %+v, %+v)", tc.cond, tc.yes, tc.no)

412-435: Evaluation test: replace direct errors with Assert.*

-  if !calledYes || !calledNo {
-    t.Errorf("Functions not evaluated before IIf call: yes=%v, no=%v", calledYes, calledNo)
-  }
+  AssertTrue(t, calledYes && calledNo, "both branches evaluated")
 ...
-  if result != 42 {
-    t.Errorf("IIf(true, 42, 100) = %v, want 42", result)
-  }
+  AssertEqual(t, 42, result, "IIf(true, 42, 100)")
 ...
-  if result != 100 {
-    t.Errorf("IIf(false, 42, 100) = %v, want 100", result)
-  }
+  AssertEqual(t, 100, result, "IIf(false, 42, 100)")
spinlock_test.go (2)

95-126: Avoid sleeps in contended case; synchronise with channels.

Time‑based sleeps can flake. Signal the second worker only after the first acquires the lock.

   if tc.name == "contended spinlock" {
-    // Test concurrent access
-    acquired := make(chan bool, 2)
-
-    err := RunConcurrentTest(t, 2, func(id int) error {
-      if id == 0 {
-        // First goroutine acquires lock
-        sl.Lock()
-        acquired <- true
-        time.Sleep(10 * time.Millisecond)
-        sl.Unlock()
-      } else {
-        // Second goroutine waits for lock
-        time.Sleep(5 * time.Millisecond)
-        sl.Lock()
-        acquired <- true
-        sl.Unlock()
-      }
-      return nil
-    })
+    acquired := make(chan bool, 2)
+    firstLocked := make(chan struct{})
+    err := RunConcurrentTest(t, 2, func(id int) error {
+      if id == 0 {
+        sl.Lock()
+        close(firstLocked)
+        acquired <- true
+        sl.Unlock()
+      } else {
+        <-firstLocked
+        sl.Lock()
+        acquired <- true
+        sl.Unlock()
+      }
+      return nil
+    })

263-281: Benchmark remainder handling.

iterations := b.N / numWorkers drops the remainder. Consider distributing the remainder to one worker to account for all iterations. Non‑blocking.

-  iterations := b.N / numWorkers
+  iterations := b.N / numWorkers
+  remainder := b.N % numWorkers
 ...
-  wg.Wait()
+  wg.Wait()
+  // handle remainder in the current goroutine
+  for j := 0; j < remainder; j++ {
+    sl.Lock(); _ = runtime.NumGoroutine(); sl.Unlock()
+  }
errors_test.go (1)

93-102: Unify constructor return type to TestCase for consistency.

Other constructors return TestCase; align newIsErrorFn2TestCase to reduce friction and keep API uniform.

Apply:

-func newIsErrorFn2TestCase(name string, checkFn func(error) (bool, bool),
-	expectedIs, expectedKnown bool, errs ...error) isErrorFn2TestCase {
+func newIsErrorFn2TestCase(name string, checkFn func(error) (bool, bool),
+	expectedIs, expectedKnown bool, errs ...error) TestCase {
 	return isErrorFn2TestCase{
 		name:          name,
 		checkFn:       checkFn,
 		errs:          errs,
 		expectedIs:    expectedIs,
 		expectedKnown: expectedKnown,
 	}
 }
maps_test.go (1)

268-277: Correctness: do not derive ‘found’ from expected==def.

If a key exists with a value equal to def, expected may equal def but found must be true. Compute found from map membership.

Apply:

 func newMapValueTestCase(name string, m map[string]int, key string,
 	def int, expected int) TestCase {
+	found := false
+	if m != nil {
+		_, found = m[key]
+	}
 	return mapValueTestCase{
 		name:     name,
 		m:        m,
 		key:      key,
 		def:      def,
 		expected: expected,
-		found:    expected != def, // derive found from whether expected == def
+		found:    found, // true when key is present, regardless of value
 	}
 }

Also add a case where key exists with value equal to def to prevent regressions.

AGENTS.md (2)

10-14: Docs style: end list items with periods (project rule).

Add terminal periods to satisfy docs guidelines and internal linters.

Apply:

-- [README.md](README.md) - Package overview and API reference
-- [TESTING.md](TESTING.md) - Testing patterns and guidelines for all
-  darvaza.org projects
-- [TESTING_core.md](TESTING_core.md) - Core-specific testing patterns
+- [README.md](README.md) - Package overview and API reference.
+- [TESTING.md](TESTING.md) - Testing patterns and guidelines for all
+  darvaza.org projects.
+- [TESTING_core.md](TESTING_core.md) - Core-specific testing patterns.

198-201: Avoid brittle line numbers in docs.

Referencing “Makefile (line 10)” and “.tmp/gen.mk:39” will drift; prefer descriptive text.

Apply:

-Before: ... Makefile (line 10) ... used ... `.tmp/gen.mk:39`.
+Before: ... The Makefile defines it with an empty default and it is used by the generated rules in `.tmp/gen.mk`.

Also applies to: 245-249

README.md (1)

22-30: Docs style: terminate bullets with periods (repo rule).

Update this and similar lists to end each item with a period for markdownlint/CSpell/LanguageTool compliance.

Apply:

-* `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.
+* `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.

Consider running “make tidy” to let markdownlint/LanguageTool flag the rest across the file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fae8f6b and b79e6ec.

📒 Files selected for processing (23)
  • AGENTS.md (3 hunks)
  • README.md (8 hunks)
  • TESTING.md (19 hunks)
  • TESTING_core.md (2 hunks)
  • addrport_test.go (8 hunks)
  • addrs_test.go (17 hunks)
  • as_test.go (12 hunks)
  • compounderror_test.go (16 hunks)
  • context_test.go (12 hunks)
  • errgroup_test.go (18 hunks)
  • errors_test.go (21 hunks)
  • generics_test.go (11 hunks)
  • lists_test.go (20 hunks)
  • maps_test.go (25 hunks)
  • panic_test.go (36 hunks)
  • panicerror_test.go (12 hunks)
  • slices_test.go (18 hunks)
  • spinlock_test.go (6 hunks)
  • stack_test.go (18 hunks)
  • testing.go (4 hunks)
  • testing_test.go (41 hunks)
  • waitgroup_test.go (11 hunks)
  • zero_test.go (43 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.md

📄 CodeRabbit inference engine (AGENT.md)

**/*.md: Comply with markdownlint rules (80-character line limit, strict formatting, selective HTML allowlist)
Run LanguageTool with British English; fix missing articles, punctuation, and hyphenation issues
End all bullet list items with periods
Capitalize proper nouns correctly (e.g., JavaScript, TypeScript, Markdown)
Hyphenate compound modifiers when used before nouns (e.g., capture-specific)

Files:

  • AGENTS.md
  • README.md
  • TESTING_core.md
  • TESTING.md
**/*.{md,go}

📄 CodeRabbit inference engine (AGENT.md)

Pass CSpell checks using the project dictionary (British English) for docs and code comments

Files:

  • AGENTS.md
  • testing_test.go
  • lists_test.go
  • testing.go
  • addrs_test.go
  • addrport_test.go
  • as_test.go
  • README.md
  • waitgroup_test.go
  • panicerror_test.go
  • context_test.go
  • slices_test.go
  • compounderror_test.go
  • maps_test.go
  • spinlock_test.go
  • zero_test.go
  • errors_test.go
  • panic_test.go
  • stack_test.go
  • generics_test.go
  • TESTING_core.md
  • errgroup_test.go
  • TESTING.md
**/*.go

📄 CodeRabbit inference engine (AGENT.md)

**/*.go: Adhere to revive limits: max function length 40 lines, max 3 results, max 5 arguments, cognitive complexity ≤7, cyclomatic complexity ≤10
Keep struct fields aligned to minimize padding (use fieldalignment -fix)

Files:

  • testing_test.go
  • lists_test.go
  • testing.go
  • addrs_test.go
  • addrport_test.go
  • as_test.go
  • waitgroup_test.go
  • panicerror_test.go
  • context_test.go
  • slices_test.go
  • compounderror_test.go
  • maps_test.go
  • spinlock_test.go
  • zero_test.go
  • errors_test.go
  • panic_test.go
  • stack_test.go
  • generics_test.go
  • errgroup_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENT.md)

**/*_test.go: Prefer table-driven tests
Use the public testing utilities from testing.go in tests
Test assertions should log successful checks for better debugging

Files:

  • testing_test.go
  • lists_test.go
  • addrs_test.go
  • addrport_test.go
  • as_test.go
  • waitgroup_test.go
  • panicerror_test.go
  • context_test.go
  • slices_test.go
  • compounderror_test.go
  • maps_test.go
  • spinlock_test.go
  • zero_test.go
  • errors_test.go
  • panic_test.go
  • stack_test.go
  • generics_test.go
  • errgroup_test.go
🧠 Learnings (19)
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: Applies to AGENT.md : Update `AGENT.md` to reflect any changes in development workflow or standards.

Applied to files:

  • AGENTS.md
  • README.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Keep AGENT.md and README.md updated when workflows or APIs change

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: Applies to **/*.md : Include examples for complex concepts or common pitfalls in documentation.

Applied to files:

  • AGENTS.md
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: ALWAYS run `make tidy` first before committing: fix all issues (Go formatting, whitespace, CSpell, markdownlint, shellcheck).

Applied to files:

  • AGENTS.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Run make tidy before committing and ensure all linters pass

Applied to files:

  • AGENTS.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to **/*_test.go : Prefer table-driven tests

Applied to files:

  • testing_test.go
  • lists_test.go
  • as_test.go
  • waitgroup_test.go
  • panicerror_test.go
  • context_test.go
  • slices_test.go
  • compounderror_test.go
  • maps_test.go
  • zero_test.go
  • errors_test.go
  • panic_test.go
  • stack_test.go
  • generics_test.go
  • errgroup_test.go
  • TESTING.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to **/*_test.go : Use the public testing utilities from testing.go in tests

Applied to files:

  • testing_test.go
  • panic_test.go
  • TESTING.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to **/*_test.go : Test assertions should log successful checks for better debugging

Applied to files:

  • testing_test.go
  • testing.go
  • README.md
  • panic_test.go
  • TESTING_core.md
  • TESTING.md
📚 Learning: 2025-08-05T22:27:53.946Z
Learnt from: amery
PR: darvaza-proxy/core#135
File: compounderror_test.go:201-201
Timestamp: 2025-08-05T22:27:53.946Z
Learning: In Go test functions using assertion helpers like AssertSame, AssertEqual, etc., early returns after assertion calls are only necessary when there is subsequent code that should be skipped if the assertion fails. At the end of a function or code block where no further operations follow, the early return pattern is redundant since the function will naturally return anyway.

Applied to files:

  • testing_test.go
  • README.md
  • TESTING_core.md
📚 Learning: 2024-11-16T07:43:16.106Z
Learnt from: amery
PR: darvaza-proxy/core#86
File: list.go:83-89
Timestamp: 2024-11-16T07:43:16.106Z
Learning: In `list.go`, the `List[T]` methods `Len` and `ForEach` handle a nil receiver gracefully.

Applied to files:

  • lists_test.go
📚 Learning: 2024-11-15T04:50:14.792Z
Learnt from: amery
PR: darvaza-proxy/core#84
File: list.go:160-171
Timestamp: 2024-11-15T04:50:14.792Z
Learning: In the Go `core` package's `list.go` file, the method `unsafeForEachElement` in `List[T]` does not need to check if `l` is `nil` because all public methods that call it already perform this check.

Applied to files:

  • lists_test.go
📚 Learning: 2025-09-07T19:20:04.539Z
Learnt from: amery
PR: darvaza-proxy/core#143
File: testing.go:407-437
Timestamp: 2025-09-07T19:20:04.539Z
Learning: PanicError already implements both Recovered() method (which returns the original payload) and Unwrap() method (which returns the payload as error if it implements error interface), providing complete support for error chain traversal and errors.Is compatibility.

Applied to files:

  • panicerror_test.go
📚 Learning: 2025-04-17T20:14:08.093Z
Learnt from: amery
PR: darvaza-proxy/core#105
File: sync.go:11-12
Timestamp: 2025-04-17T20:14:08.093Z
Learning: In the darvaza.org/core module, the SpinLock type is being deprecated in favor of darvaza.org/x/sync/spinlock. The implementation must remain in place (not an alias) to avoid circular dependencies, as the new package likely imports from core.

Applied to files:

  • spinlock_test.go
📚 Learning: 2025-08-09T22:44:20.744Z
Learnt from: amery
PR: darvaza-proxy/core#135
File: zero.go:300-314
Timestamp: 2025-08-09T22:44:20.744Z
Learning: In Go's `IsSame` function implementation, structs and arrays intentionally return `false` because they are composite value types that are always copied by value, making it impossible to determine if they're "the same instance" versus just equal values. This maintains the distinction between sameness (reference equality) and equality (value equality).

Applied to files:

  • zero_test.go
📚 Learning: 2025-08-13T00:58:23.367Z
Learnt from: amery
PR: darvaza-proxy/core#135
File: zero.go:178-192
Timestamp: 2025-08-13T00:58:23.367Z
Learning: amery questions whether all invalid reflect.Values should be considered equivalent in IsSame() comparisons, preferring stricter sameness semantics where invalid values are not considered "the same thing" since they represent absence rather than actual comparable values.

Applied to files:

  • zero_test.go
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: Applies to **/*.go : Max function results: 3.

Applied to files:

  • TESTING.md
📚 Learning: 2025-07-18T16:39:19.390Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-07-18T16:39:19.390Z
Learning: Applies to **/*.go : Max function length: 40 lines.

Applied to files:

  • TESTING.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to **/*.go : Keep struct fields aligned to minimize padding (use fieldalignment -fix)

Applied to files:

  • TESTING.md
📚 Learning: 2025-08-20T03:45:42.506Z
Learnt from: CR
PR: darvaza-proxy/core#0
File: AGENT.md:0-0
Timestamp: 2025-08-20T03:45:42.506Z
Learning: Applies to .golangci.yml : Use golangci-lint v2 configuration (version: "2") with fieldalignment enabled

Applied to files:

  • TESTING.md
🧬 Code graph analysis (18)
testing_test.go (1)
testing.go (8)
  • T (22-33)
  • MockT (45-51)
  • AssertNotContain (356-369)
  • AssertEqual (278-287)
  • TestCase (249-252)
  • RunTestCases (260-267)
  • AssertMustTypeIs (991-998)
  • AssertMustNotContain (874-879)
lists_test.go (2)
testing.go (6)
  • T (22-33)
  • S (240-245)
  • TestCase (249-252)
  • RunTestCases (260-267)
  • AssertFalse (570-573)
  • AssertEqual (278-287)
lists.go (6)
  • ListForEach (29-40)
  • ListForEachElement (43-55)
  • ListForEachBackward (65-76)
  • ListForEachBackwardElement (79-91)
  • ListContainsFn (16-26)
  • ListCopyFn (106-125)
addrs_test.go (1)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • RunTestCases (260-267)
  • T (22-33)
addrport_test.go (2)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
addrport.go (1)
  • AddrPort (20-49)
as_test.go (1)
testing.go (4)
  • TestCase (249-252)
  • T (22-33)
  • RunTestCases (260-267)
  • S (240-245)
waitgroup_test.go (2)
testing.go (6)
  • TestCase (249-252)
  • S (240-245)
  • RunTestCases (260-267)
  • T (22-33)
  • AssertNoError (398-407)
  • AssertNil (622-631)
sync.go (1)
  • WaitGroup (42-47)
panicerror_test.go (3)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • RunTestCases (260-267)
  • T (22-33)
panicerror.go (4)
  • Panic (90-92)
  • Panicf (95-97)
  • PanicWrap (100-102)
  • PanicWrapf (106-108)
errors.go (1)
  • ErrUnreachable (25-25)
context_test.go (1)
testing.go (3)
  • TestCase (249-252)
  • T (22-33)
  • RunTestCases (260-267)
slices_test.go (2)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
constraints.go (1)
  • Ordered (39-41)
compounderror_test.go (1)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
maps_test.go (1)
testing.go (5)
  • TestCase (249-252)
  • S (240-245)
  • RunTestCases (260-267)
  • T (22-33)
  • AssertSliceEqual (318-327)
spinlock_test.go (2)
sync.go (1)
  • SpinLock (13-13)
testing.go (6)
  • TestCase (249-252)
  • S (240-245)
  • RunTestCases (260-267)
  • T (22-33)
  • AssertEqual (278-287)
  • AssertPanic (433-440)
zero_test.go (2)
testing.go (7)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
  • TestCase (249-252)
  • AssertNil (622-631)
  • AssertSliceEqual (318-327)
  • AssertEqual (278-287)
zero.go (2)
  • Zero (23-26)
  • IsZero (67-85)
errors_test.go (1)
testing.go (3)
  • TestCase (249-252)
  • T (22-33)
  • RunTestCases (260-267)
panic_test.go (2)
testing.go (6)
  • TestCase (249-252)
  • RunTestCases (260-267)
  • T (22-33)
  • AssertNoError (398-407)
  • AssertEqual (278-287)
  • S (240-245)
panicerror.go (1)
  • NewPanicError (45-53)
stack_test.go (2)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
stack.go (3)
  • Here (296-305)
  • Frame (35-46)
  • Stack (242-242)
generics_test.go (1)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
errgroup_test.go (2)
sync.go (1)
  • ErrGroup (128-135)
testing.go (4)
  • TestCase (249-252)
  • S (240-245)
  • T (22-33)
  • RunTestCases (260-267)
🔇 Additional comments (34)
testing.go (4)

348-369: New negative‑contain assertion looks good.

Clear behaviour, explicit empty‑substring check, and consistent logging. No issues.


381-388: Inline Error/NoError base implementations are sound.

Return values and messages are consistent with the new “independent base functions” model.

Also applies to: 399-406


867-879: Must‑variant wrapper is consistent.

AssertMustNotContain correctly composes AssertNotContain and aborts via FailNow. Looks good.


787-800: No change required — repo targets Go 1.23.0

go.mod declares 'go 1.23.0' and Go 1.22 added integer-range loops, so for i := range numWorkers / for range b.N are supported; keep the loops as-is.

TESTING_core.md (3)

23-36: Hierarchy update reads well and matches code.

Listing AssertNotContain, and promoting AssertError/AssertNoError to base is consistent with testing.go.


89-92: Rationale aligns with the refactor.

Clear separation of base vs derived assertions; no issues.


120-133: Fix Markdown style: end list items with full stops and use British English spellings.

  • Several bullet lists in TESTING_core.md omit full stops; convert American spellings (e.g. standardize → standardise, initialize → initialise) to British English.
  • File: TESTING_core.md — lines 120–133. Also check lines 135–156, 349–357, 397–401, 412–417.
  • Run LanguageTool and markdownlint locally or attach CI linter output to confirm compliance.
errgroup_test.go (3)

114-144: Confirm expected outcome when shutdown returns an error.

Case “worker with shutdown error” expects cancellation but treats any non‑context.Canceled error as a failure. If shutdownFunc errors propagate from Wait(), this test will be flaky/incorrect.

Please confirm ErrGroup.Wait semantics for shutdown errors. If they can surface, set expectError=true or relax checkError to accept either context.Canceled or the shutdown error.


283-301: Top‑level orchestrator LGTM.

Clean grouping via RunTestCases plus focused subtests.


219-241: Nil run function panic test is correct.

Panic expectation and recovery are handled properly.

testing_test.go (3)

72-95: Same circular‑test pattern repeated.

The tests for AssertNotEqual, AssertSliceEqual, AssertContains, AssertNotContain, AssertError/AssertNoError also use Assert* helpers to validate base assertions.

Also applies to: 96-114, 116-139, 140-173, 175-207


699-738: Concurrency test is solid.

Good use of RunConcurrentTest and deterministic expectations per modulo case.


1457-1463: Helper for error substring checks LGTM.

Concise and improves test readability.

as_test.go (2)

32-39: Constructors returning TestCase interface are a good abstraction.

This aligns with the new unified test harness and keeps call‑sites decoupled.

Also applies to: 115-123, 149-155, 184-191, 250-257, 286-293


312-321: Factory + RunTestCases pattern is clean and consistent.

Tables are clear, cover edge cases (nil func, nil slices), and follow the new runner.

Also applies to: 327-343, 349-359, 365-382, 388-403, 409-437

waitgroup_test.go (2)

36-47: Well-structured test case factory with clear scenarios.

The factory function effectively covers the essential test scenarios: success, error, panic handling, and nil function cases. Good use of the S() helper for slice creation.


191-210: Comprehensive test coverage for OnError handler scenarios.

Excellent test cases covering successful workers, error dismissal, and error transformation scenarios.

addrs_test.go (3)

29-42: Excellent test coverage for ParseAddr function.

The test cases comprehensively cover IPv4, IPv6, edge cases (unspecified addresses), invalid inputs, and zone handling. Good use of both concrete and string-based factory functions.


162-178: Well-designed test cases for AddrFromNetIP.

Good coverage of various net.Addr types including edge cases like nil inputs and unsupported types (TCPAddr, UDPAddr).


400-403: Clean subtest organisation for GetInterfacesNames.

Good separation of basic test cases and exclusion verification logic using subtests.

addrport_test.go (2)

21-63: Comprehensive test coverage for AddrPort function.

Excellent variety of test cases covering direct types, TCP/UDP addresses, interface implementations, and invalid inputs. The factory function properly returns TestCase interface as required.


120-130: Well-structured test organisation with clear subtests.

Good separation of concerns with dedicated subtests for basic functionality, type-specific handling, interface chaining, mock connections, and IPv4 handling.

slices_test.go (1)

51-61: Clean factory implementation for SliceReverse test cases.

Good use of the S() helper and clear test case progression from empty to multiple elements.

context_test.go (1)

136-138: Nice consolidation to RunTestCases + factories.

Consistent with the PR’s testing scaffolding: table‑driven, helpers via S(), and subtests. LGTM.

Also applies to: 225-227, 278-279

compounderror_test.go (1)

404-427: Unified runner and subtests look good.

The suite is neatly partitioned; tests use Assert* helpers and log passes. LGTM.

panicerror_test.go (1)

24-31: Adopts factory + RunTestCases pattern consistently.

Good alignment with the new TestCase scaffolding and subtests.

Also applies to: 245-260, 481-486, 489-490, 498-499, 512-519

spinlock_test.go (1)

58-60: Good use of TestCase factories and Assert helpers.*

The SpinLock tests follow the project’s testing utilities closely and read clearly. LGTM.

Also applies to: 135-136, 182-184, 205-209

errors_test.go (1)

69-71: LGTM: migration to RunTestCases and factory builders.

Factories and TestCase usage look good and match testing.go.

Also applies to: 127-129, 283-285, 457-460, 503-506, 549-551

stack_test.go (1)

350-400: LGTM: path‑handling tests for PkgFile are comprehensive.

zero_test.go (1)

223-242: LGTM: TestCase factories + RunTestCases adoption.

The refactor standardises test construction and execution cleanly.

maps_test.go (1)

108-119: LGTM: SortedKeys/Values and factories use the shared utilities correctly.

Also applies to: 157-159, 208-210, 281-283, 321-323

panic_test.go (1)

493-500: LGTM: Must/MustOK test harness split into success/panic suites.

Good reuse of runTestMust* helpers and TestCase factories.

lists_test.go (1)

14-19: LGTM: iteration helpers cover forward/backward, element/value, nil and early‑return cases.

Also applies to: 37-47, 69-77, 79-87, 89-97, 99-108

README.md (1)

81-83: LGTM: README reflects API shifts (AddrPort, Zero, assertions, stack formatting) coherently.

Also applies to: 94-97, 388-391, 516-531, 558-581, 654-656

@amery amery marked this pull request as draft September 23, 2025 17:14
amery added 4 commits October 27, 2025 13:33
…ific messaging

Convert AssertError and AssertNoError from derived functions that delegate to
nil assertions into independent base functions with error-specific messaging.

Changes:
- AssertError now directly checks err != nil and produces "expected error, got nil".
- AssertNoError now directly checks err == nil and produces "unexpected error: %v".
- Both functions log domain-appropriate success messages.
- Update TESTING_core.md to reflect the new architecture hierarchy.
- Maintain 100% test coverage and all existing tests pass.

This improves clarity when debugging test failures by providing error-oriented
messages rather than generic nil-checking messages.

Signed-off-by: Alejandro Mery <amery@apptly.co>
Add negative variants of string containment assertions to complement the
existing AssertContains functionality.

Implementation:
- Add AssertNotContain to verify substring absence.
- Add AssertMustNotContain for fatal variant that calls FailNow.
- Both functions follow established patterns with appropriate error messages.
- Use "NotContain" (not "NotContains") for grammatical consistency.

Testing:
- Add comprehensive unit test for AssertNotContain.
- Add integration test for AssertMustNotContain with MockT.
- Achieve 100% test coverage for both new functions.
- Overall package coverage improved to 97.4%.

Documentation:
- Update TESTING_core.md function hierarchy.
- Add examples to TESTING.md Advanced Assertions.
- Update README.md with new assertions in both basic and fatal lists.
- All documentation maintains consistent formatting and passes linting.

The implementation provides clear error messages:
- Success: "does not contain %q".
- Failure: "expected %q not to contain %q".

Signed-off-by: Alejandro Mery <amery@apptly.co>
This commit addresses multiple documentation issues identified during API
verification, ensuring all function signatures and descriptions accurately
reflect the actual implementation.

Key fixes:
- Fix AddrPort function signature from AddrPort(addr, port) to AddrPort(v)
  and correct description to reflect extraction rather than creation.
- Correct Zero function signature from Zero[T]() to Zero[T](_ *T) to show
  the required dummy parameter for type inference.
- Update all assertion function signatures to use 'name...' instead of
  'msg...' to accurately reflect actual parameter names (affects 25+
  functions including AssertEqual, AssertTrue, AssertMust* variants).
- Fix IsError function signatures by removing incorrect generic type
  parameters [T] and updating parameter lists to match implementation.

The documentation now provides 100% accurate function signatures that
match the actual Go implementation, improving developer experience and
reducing confusion when using the API.

Signed-off-by: Alejandro Mery <amery@apptly.co>
Rename AGENT.md to AGENTS.md as AGENTS.md is now the standard.
Update all internal references within the renamed file and
cross-references in README.md to maintain documentation integrity.

Signed-off-by: Alejandro Mery <amery@apptly.co>
@amery
Copy link
Contributor Author

amery commented Oct 27, 2025

Branch Update

Rebased to simplify PR scope:

Removed:

  • Massive test consolidation (18 files, 1,702 deletions reverted)
  • Comprehensive TESTING.md overhaul (394 lines → minimal)
  • Factory pattern conversions and test* → runTest* renames

Retained:

  • New AssertNotContain/AssertMustNotContain assertions
  • Error assertion refactoring (domain-specific messaging)
  • API documentation fixes (25+ signatures corrected)
  • AGENT.md → AGENTS.md rename

Added:

Result: Focused PR with 6 files changed (175 additions, 66 deletions) instead of 23 files (1,849 additions, 1,252 deletions).

@amery amery marked this pull request as ready for review October 27, 2025 18:04
@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Assertion semantics change

Description: Rejecting empty substring in AssertNotContain may surprise callers given Go’s
strings.Contains semantics; if callers previously passed empty substrings assuming a
no-op, this behavioral change could cause unexpected test failures—review intent and
document clearly.
testing.go [358-361]

Referred Code
if substr == "" {
	doError(t, name, args, "substring cannot be empty for AssertNotContain")
	return false
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No audit scope: The new code adds testing assertions and documentation without handling critical system
actions, so audit logging requirements are not applicable to this change set.

Referred Code
func AssertContains(t T, s, substr, name string, args ...any) bool {
	t.Helper()
	ok := strings.Contains(s, substr)
	if !ok {
		doError(t, name, args, "expected %q to contain %q", s, substr)
	} else {
		doLog(t, name, args, "contains %q", substr)
	}
	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()


 ... (clipped 522 lines)
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Error detail logs: The assertion helpers log error values via formatting (e.g., "error: %v" and
"unexpected error: %v"), which is fine for tests but could be sensitive if these
utilities were ever used outside test contexts; confirm they are test-only.

Referred Code
// Returns true if the assertion passed, false otherwise.
//
// Example usage:
//
//	AssertError(t, err, "parse error")
//	AssertError(t, err, "operation %s", "save")
func AssertError(t T, err error, name string, args ...any) bool {
	t.Helper()
	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.
// The name parameter can include printf-style formatting.
// Returns true if the assertion passed, false otherwise.
//


 ... (clipped 14 lines)
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Log content risk: Logging in assertions echoes input strings and errors which in production code could
include sensitive data; since these are testing utilities, validate they are not used in
production logging paths.

Referred Code
func AssertContains(t T, s, substr, name string, args ...any) bool {
	t.Helper()
	ok := strings.Contains(s, substr)
	if !ok {
		doError(t, name, args, "expected %q to contain %q", s, substr)
	} else {
		doLog(t, name, args, "contains %q", substr)
	}
	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()


 ... (clipped 522 lines)
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Improve test failure message clarity

Improve the assertErrorContains test helper to provide more descriptive failure
messages, making it easier to debug test failures.

testing_test.go [1449-1454]

 func assertErrorContains(t *testing.T, mock *MockT, expected, desc string) {
 	t.Helper()
 	lastErr, ok := mock.LastError()
-	AssertTrue(t, ok, "LastError ok for "+desc)
-	AssertTrue(t, strings.Contains(lastErr, expected), desc)
+	AssertTrue(t, ok, "expected an error for "+desc+", but got none")
+	if ok {
+		AssertTrue(t, strings.Contains(lastErr, expected),
+			"error message for %s should contain %q, but was %q", desc, expected, lastErr)
+	}
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the failure messages in the new assertErrorContains helper can be confusing and provides an improved version with much clearer, more actionable error messages for easier debugging.

Medium
Simplify success log for readability

In AssertError, change the success log message from printing the full error to a
more concise message like "received expected error" to reduce log verbosity.

testing.go [379-388]

 func AssertError(t T, err error, name string, args ...any) bool {
 	t.Helper()
 	ok := err != nil
 	if !ok {
 		doError(t, name, args, "expected error, got nil")
 	} else {
-		doLog(t, name, args, "error: %v", err)
+		doLog(t, name, args, "received expected error")
 	}
 	return ok
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that logging the full error on success can create verbose test logs, and proposes a more concise message, which is a valid point for improving log readability.

Low
  • More

@amery amery merged commit 9e3b9f0 into main Oct 27, 2025
22 checks passed
@amery amery deleted the pr-amery-testing branch October 27, 2025 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request Review effort 3/5 testing Testing infrastructure, test utilities, and test-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants