Skip to content

Commit f0ac879

Browse files
author
Alejandro Mery
committed
feat(core): add same-ness testing functions and modernise test patterns
Adds same-ness testing utilities that distinguish between value equality and reference equality for different Go types, alongside comprehensive refactoring of existing test suites to use modern assertion patterns. For value types (numbers, strings, booleans), same-ness means equal values. For reference types (slices, maps, pointers, channels, functions), same-ness means pointer equality to the same underlying data structure. New functions: - IsSame() base function with comprehensive type handling. - AssertSame() and AssertNotSame() testing assertions. - asReflectValue() helper to avoid duplicate reflect.ValueOf() calls. - isReflectValueSame() helper following established naming patterns. Key features: - Proper nil handling: typed nils of same type are considered same. - Interface support with recursive comparison of contained values. - Complexity-compliant implementation using helper functions. - Interface comparison inlined directly into isSamePointer() for efficiency. Test modernisation: - Refactored as_test.go to use AssertEqual for length checks. - Modernised compounderror_test.go with AssertSame, AssertEqual, AssertNotNil. - Updated errgroup_test.go to use AssertSame for error instance checks. - Improved lists_test.go with AssertNotSame for independence verification. - Applied fatal assertion pattern consistently for critical preconditions. Documentation updates: - Added same-ness function documentation to README.md. - Extended TESTING.md with assertion usage examples. - Updated function documentation to match final implementation. Includes comprehensive test suite with 263 lines of test code covering all scenarios: value types, reference types, nil handling, different types, interface wrapping, and reflect.Value handling. Coverage increased to 97.0% with full linting compliance. Signed-off-by: Alejandro Mery <amery@apply.co>
1 parent b3ec005 commit f0ac879

File tree

10 files changed

+656
-91
lines changed

10 files changed

+656
-91
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ Key distinctions from `IsZero`:
124124
* **Structs**: `IsNil(struct{}{})` returns `false` (structs cannot be nil),
125125
`IsZero(struct{}{})` returns `true` (zero struct is uninitialized).
126126

127+
#### Same Value Detection
128+
129+
* `IsSame(a, b)` - reports whether two values are the same. For reference
130+
types (slices, maps, pointers), compares by pointer equality. For value
131+
types (numbers, strings, booleans), compares by equal values. Two nils
132+
of the same type are considered the same.
133+
127134
#### Other Utilities
128135

129136
* `Coalesce[T](values...)` returns the first non-zero value.
@@ -466,6 +473,10 @@ both `*testing.T` and `MockT`:
466473
comparison.
467474
* `AssertSliceEqual[T](t, expected, actual, msg...)` - slice comparison using
468475
`reflect.DeepEqual`.
476+
* `AssertSame(t, expected, actual, msg...)` - same value/reference comparison
477+
using pointer equality for reference types, value equality for basic types.
478+
* `AssertNotSame(t, expected, actual, msg...)` - different value/reference
479+
comparison.
469480
* `AssertTrue(t, condition, msg...)` / `AssertFalse(t, condition, msg...)` -
470481
boolean assertions.
471482
* `AssertNil(t, value, msg...)` / `AssertNotNil(t, value, msg...)` - nil

TESTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ for log messages, not a complete message:
8989
core.AssertEqual(t, expected, actual, "value")
9090
core.AssertNotEqual(t, expected, actual, "value")
9191
core.AssertSliceEqual(t, expectedSlice, actualSlice, "slice")
92+
core.AssertSame(t, expected, actual, "same reference/value")
93+
core.AssertNotSame(t, expected, actual, "different reference/value")
9294
core.AssertTrue(t, condition, "condition")
9395
core.AssertFalse(t, condition, "negation")
9496
core.AssertNil(t, value, "nil check")

as_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ func (tc sliceAsTestCase) Test(t *testing.T) {
162162
t.Helper()
163163

164164
got := SliceAs[any, string](tc.input)
165-
if len(got) != len(tc.want) {
166-
t.Fatalf("SliceAs() len = %v, want %v", len(got), len(tc.want))
165+
if !AssertEqual(t, len(tc.want), len(got), "slice length") {
166+
return
167167
}
168168
for i, v := range got {
169169
if v != tc.want[i] {
@@ -198,8 +198,8 @@ func (tc sliceAsFnTestCase) Test(t *testing.T) {
198198
t.Helper()
199199

200200
got := SliceAsFn(tc.fn, tc.input)
201-
if len(got) != len(tc.want) {
202-
t.Fatalf("SliceAsFn() len = %v, want %v", len(got), len(tc.want))
201+
if !AssertEqual(t, len(tc.want), len(got), "slice length") {
202+
return
203203
}
204204
for i, v := range got {
205205
if v != tc.want[i] {
@@ -299,8 +299,8 @@ func (tc asErrorsTestCase) Test(t *testing.T) {
299299
t.Helper()
300300

301301
got := AsErrors(tc.input)
302-
if len(got) != tc.wantLen {
303-
t.Fatalf("AsErrors() len = %v, want %v", len(got), tc.wantLen)
302+
if !AssertEqual(t, tc.wantLen, len(got), "slice length") {
303+
return
304304
}
305305
for i, err := range got {
306306
if err.Error() != tc.wantMsgs[i] {

compounderror_test.go

Lines changed: 58 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func (tc compoundErrorErrorTestCase) Test(t *testing.T) {
5656
ce := &CompoundError{Errs: tc.errs}
5757
result := ce.Error()
5858

59-
if result != tc.expected {
60-
t.Fatalf("expected '%s', got '%s'", tc.expected, result)
59+
if !AssertEqual(t, tc.expected, result, "error string") {
60+
return
6161
}
6262
}
6363

@@ -74,13 +74,13 @@ func TestCompoundErrorErrors(t *testing.T) {
7474
ce := &CompoundError{Errs: errs}
7575
result := ce.Errors()
7676

77-
if len(result) != len(errs) {
78-
t.Fatalf("expected %d errors, got %d", len(errs), len(result))
77+
if !AssertEqual(t, len(errs), len(result), "error count") {
78+
return
7979
}
8080

8181
for i, err := range result {
82-
if err != errs[i] {
83-
t.Fatalf("expected error %d to be %v, got %v", i, errs[i], err)
82+
if !AssertSame(t, errs[i], err, "error %d", i) {
83+
return
8484
}
8585
}
8686
}
@@ -94,13 +94,13 @@ func TestCompoundErrorUnwrap(t *testing.T) {
9494
ce := &CompoundError{Errs: errs}
9595
result := ce.Unwrap()
9696

97-
if len(result) != len(errs) {
98-
t.Fatalf("expected %d errors, got %d", len(errs), len(result))
97+
if !AssertEqual(t, len(errs), len(result), "error count") {
98+
return
9999
}
100100

101101
for i, err := range result {
102-
if err != errs[i] {
103-
t.Fatalf("expected error %d to be %v, got %v", i, errs[i], err)
102+
if !AssertSame(t, errs[i], err, "error %d", i) {
103+
return
104104
}
105105
}
106106
}
@@ -191,16 +191,14 @@ func (tc compoundErrorAsErrorTestCase) Test(t *testing.T) {
191191
result := ce.AsError()
192192

193193
if tc.expectNil {
194-
if result != nil {
195-
t.Fatalf("expected nil, got %v", result)
194+
if !AssertNil(t, result, "result") {
195+
return
196196
}
197197
} else {
198-
if result == nil {
199-
t.Fatalf("expected non-nil error, got nil")
200-
}
201-
if result != ce {
202-
t.Fatalf("expected same CompoundError instance, got different")
198+
if !AssertNotNil(t, result, "result") {
199+
return
203200
}
201+
AssertSame(t, ce, result, "CompoundError instance")
204202
}
205203
}
206204

@@ -245,19 +243,19 @@ func (tc compoundErrorAppendErrorTestCase) Test(t *testing.T) {
245243
result := ce.AppendError(tc.toAppend...)
246244

247245
// Test method chaining
248-
if result != ce {
249-
t.Fatalf("expected same CompoundError instance for chaining")
246+
if !AssertSame(t, ce, result, "CompoundError instance") {
247+
return
250248
}
251249

252250
// Test length
253-
if len(ce.Errs) != tc.expectedLen {
254-
t.Fatalf("expected %d errors, got %d", tc.expectedLen, len(ce.Errs))
251+
if !AssertEqual(t, tc.expectedLen, len(ce.Errs), "error count") {
252+
return
255253
}
256254

257255
// Test no nil errors were added
258256
for i, err := range ce.Errs {
259-
if err == nil {
260-
t.Fatalf("unexpected nil error at index %d", i)
257+
if !AssertNotNil(t, err, "error %d", i) {
258+
return
261259
}
262260
}
263261
}
@@ -273,26 +271,26 @@ func TestCompoundErrorAppendErrorWithCompoundError(t *testing.T) {
273271
result := ce1.AppendError(ce2)
274272

275273
// Test method chaining
276-
if result != ce1 {
277-
t.Fatalf("expected same CompoundError instance for chaining")
274+
if !AssertSame(t, ce1, result, "CompoundError instance") {
275+
return
278276
}
279277

280278
// Should unwrap the compound error and append individual errors
281279
expectedLen := 3 // original 1 + unwrapped 2
282-
if len(ce1.Errs) != expectedLen {
283-
t.Fatalf("expected %d errors, got %d", expectedLen, len(ce1.Errs))
280+
if !AssertEqual(t, expectedLen, len(ce1.Errs), "error count") {
281+
return
284282
}
285283

286284
// Check error messages
287285
errorStr := ce1.Error()
288-
if !strings.Contains(errorStr, "first") {
289-
t.Fatalf("expected 'first' in error string, got '%s'", errorStr)
286+
if !AssertContains(t, errorStr, "first", "error string") {
287+
return
290288
}
291-
if !strings.Contains(errorStr, "second") {
292-
t.Fatalf("expected 'second' in error string, got '%s'", errorStr)
289+
if !AssertContains(t, errorStr, "second", "error string") {
290+
return
293291
}
294-
if !strings.Contains(errorStr, "third") {
295-
t.Fatalf("expected 'third' in error string, got '%s'", errorStr)
292+
if !AssertContains(t, errorStr, "third", "error string") {
293+
return
296294
}
297295
}
298296

@@ -317,14 +315,14 @@ func TestCompoundErrorAppendErrorWithUnwrappable(t *testing.T) {
317315
result := ce.AppendError(mock)
318316

319317
// Test method chaining
320-
if result != ce {
321-
t.Fatalf("expected same CompoundError instance for chaining")
318+
if !AssertSame(t, ce, result, "CompoundError instance") {
319+
return
322320
}
323321

324322
// Should unwrap and append individual errors
325323
expectedLen := 3 // original 1 + unwrapped 2
326-
if len(ce.Errs) != expectedLen {
327-
t.Fatalf("expected %d errors, got %d", expectedLen, len(ce.Errs))
324+
if !AssertEqual(t, expectedLen, len(ce.Errs), "error count") {
325+
return
328326
}
329327
}
330328

@@ -375,19 +373,17 @@ func (tc compoundErrorAppendTestCase) Test(t *testing.T) {
375373
result := ce.Append(tc.err, tc.note, tc.args...)
376374

377375
// Test method chaining
378-
if result != ce {
379-
t.Fatalf("expected same CompoundError instance for chaining")
380-
}
376+
AssertSame(t, ce, result, "CompoundError instance")
381377

382378
// Test length
383-
if len(ce.Errs) != tc.expectedLen {
384-
t.Fatalf("expected %d errors, got %d", tc.expectedLen, len(ce.Errs))
379+
if !AssertEqual(t, tc.expectedLen, len(ce.Errs), "error count") {
380+
return
385381
}
386382

387383
if tc.expectedLen > 0 {
388384
lastErr := ce.Errs[len(ce.Errs)-1]
389-
if lastErr == nil {
390-
t.Fatalf("expected non-nil error")
385+
if !AssertNotNil(t, lastErr, "last error") {
386+
return
391387
}
392388

393389
errorStr := lastErr.Error()
@@ -397,8 +393,10 @@ func (tc compoundErrorAppendTestCase) Test(t *testing.T) {
397393
if len(tc.args) > 0 {
398394
expectedNote = "wrapped note: 42" // for the formatted case
399395
}
400-
if !strings.Contains(errorStr, expectedNote) && !strings.Contains(errorStr, tc.note) {
401-
t.Fatalf("expected note in error string, got '%s'", errorStr)
396+
foundExpected := strings.Contains(errorStr, expectedNote)
397+
foundOriginal := strings.Contains(errorStr, tc.note)
398+
if !AssertTrue(t, foundExpected || foundOriginal, "note in error string") {
399+
return
402400
}
403401
}
404402
}
@@ -419,20 +417,20 @@ func TestCompoundErrorAppendChaining(t *testing.T) {
419417
Append(errors.New("fourth"), "wrapped %s", "note")
420418

421419
// Test method chaining returns same instance
422-
if result != ce {
423-
t.Fatalf("expected same CompoundError instance for chaining")
420+
if !AssertSame(t, ce, result, "CompoundError instance") {
421+
return
424422
}
425423

426424
// Test final length
427425
expectedLen := 4
428-
if len(ce.Errs) != expectedLen {
429-
t.Fatalf("expected %d errors, got %d", expectedLen, len(ce.Errs))
426+
if !AssertEqual(t, expectedLen, len(ce.Errs), "error count") {
427+
return
430428
}
431429

432430
// Test that all errors are non-nil
433431
for i, err := range ce.Errs {
434-
if err == nil {
435-
t.Fatalf("unexpected nil error at index %d", i)
432+
if !AssertNotNil(t, err, "error %d", i) {
433+
return
436434
}
437435
}
438436
}
@@ -443,12 +441,12 @@ func TestCompoundErrorNilHandling(t *testing.T) {
443441

444442
_ = ce.AppendError(nil, errors.New("valid"), nil)
445443

446-
if len(ce.Errs) != 1 {
447-
t.Fatalf("expected 1 error, got %d", len(ce.Errs))
444+
if !AssertEqual(t, 1, len(ce.Errs), "error count") {
445+
return
448446
}
449447

450-
if ce.Errs[0].Error() != "valid" {
451-
t.Fatalf("expected 'valid' error, got '%s'", ce.Errs[0].Error())
448+
if !AssertEqual(t, "valid", ce.Errs[0].Error(), "error message") {
449+
return
452450
}
453451
}
454452

@@ -462,13 +460,13 @@ func TestCompoundErrorIsInterface(t *testing.T) {
462460
var _ error = ce
463461

464462
// Test that Error() method works
465-
if ce.Error() == "" {
466-
t.Fatalf("expected non-empty error string")
463+
if !AssertTrue(t, ce.Error() != "", "non-empty error string") {
464+
return
467465
}
468466

469467
// Test that Errors() method works
470468
errs := ce.Errors()
471-
if len(errs) != 1 {
472-
t.Fatalf("expected 1 error, got %d", len(errs))
469+
if !AssertEqual(t, 1, len(errs), "error count") {
470+
return
473471
}
474472
}

errgroup_test.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,7 @@ func testErrGroupSubsequentCancellation(t *testing.T) {
317317
}
318318

319319
// Should keep the first error
320-
if err := eg.Err(); err != cause1 {
321-
t.Errorf("Expected first error %v, got %v", cause1, err)
322-
}
320+
AssertSame(t, cause1, eg.Err(), "error instance")
323321
}
324322

325323
func testErrGroupNilCause(t *testing.T) {
@@ -332,9 +330,7 @@ func testErrGroupNilCause(t *testing.T) {
332330
t.Error("Expected first cancellation to return true")
333331
}
334332

335-
if err := eg.Err(); err != context.Canceled {
336-
t.Errorf("Expected context.Canceled, got %v", err)
337-
}
333+
AssertSame(t, context.Canceled, eg.Err(), "error instance")
338334
}
339335

340336
func TestErrGroupCancel(t *testing.T) {
@@ -357,9 +353,7 @@ func TestErrGroupOnError(t *testing.T) {
357353
// Give time for onError to be called
358354
time.Sleep(1 * time.Millisecond)
359355

360-
if errorReceived != testErr {
361-
t.Errorf("Expected onError to receive %v, got %v", testErr, errorReceived)
362-
}
356+
AssertSame(t, testErr, errorReceived, "error instance")
363357
}
364358

365359
func TestErrGroupContext(t *testing.T) {
@@ -494,9 +488,7 @@ func testErrGroupCatcherErrorWhenNotCancelled(t *testing.T) {
494488
testErr := errors.New("test error")
495489
result := eg.defaultErrGroupCatcher(testErr)
496490

497-
if result != testErr {
498-
t.Errorf("Expected %v, got %v", testErr, result)
499-
}
491+
AssertSame(t, testErr, result, "error instance")
500492
}
501493

502494
func testErrGroupCatcherErrorWhenCancelled(t *testing.T) {

lists_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,7 @@ func (tc listCopyTestCase) Test(t *testing.T) {
219219
}
220220

221221
// Verify they are different lists (not the same pointer)
222-
if orig == copied {
223-
t.Error("ListCopy should return a different list instance")
224-
}
222+
AssertNotSame(t, orig, copied, "list instance")
225223

226224
// Verify independence - modifying one doesn't affect the other
227225
if orig.Len() > 0 {

0 commit comments

Comments
 (0)