Skip to content

Commit 2fec807

Browse files
authored
Merge pull request #122
Enhance test utilities with comprehensive helper functions
2 parents f6adf21 + 9a9f0cc commit 2fec807

17 files changed

+690
-503
lines changed

AGENT.md

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,67 @@ Always run `make tidy` before committing to ensure proper formatting.
165165
- Helper functions like `S[T]()` create test slices.
166166
- Comprehensive coverage for generic functions is expected.
167167

168+
#### Test Helper Functions
169+
170+
The project uses a comprehensive set of test helper functions defined in
171+
`testutils_test.go` to reduce boilerplate and improve test consistency:
172+
173+
**Slice Creation:**
174+
175+
- `S[T](values...)` - Creates test slices concisely: `S(1, 2, 3)` instead of
176+
`[]int{1, 2, 3}`
177+
- `S[T]()` - Creates empty slices: `S[string]()` instead of `[]string{}`
178+
179+
**Assertion Helpers:**
180+
181+
- `AssertEqual[T](t, expected, actual, msg...)` - Generic value comparison with
182+
better error messages
183+
- `AssertSliceEqual[T](t, expected, actual, msg...)` - Slice comparison using
184+
`reflect.DeepEqual`
185+
- `AssertError(t, err, expectError, msg...)` - Standardized error expectation
186+
checking
187+
- `AssertBool(t, actual, expected, msg...)` - Boolean assertions with context
188+
- `AssertPanic(t, fn, expectedPanic, msg...)` - Simplified panic testing
189+
- `AssertNoPanic(t, fn, msg...)` - Ensure functions don't panic
190+
191+
**Advanced Helpers:**
192+
193+
- `RunConcurrentTest(t, numWorkers, workerFn)` - Concurrent testing with
194+
goroutines
195+
- `RunBenchmark(b, setupFn, execFn)` - Benchmark testing with setup/execution
196+
phases
197+
- `RunTestCases(t, []TestCase)` - Table-driven test runner (requires
198+
`TestCase` interface)
199+
200+
**Usage Examples:**
201+
202+
```go
203+
// Before: Manual assertions
204+
if !reflect.DeepEqual(got, expected) {
205+
t.Errorf("Expected %v, got %v", expected, got)
206+
}
207+
208+
// After: Helper function
209+
AssertSliceEqual(t, expected, got, "operation failed")
210+
211+
// Before: Manual error checking
212+
if expectError && err == nil {
213+
t.Error("Expected error but got nil")
214+
} else if !expectError && err != nil {
215+
t.Errorf("Expected no error but got: %v", err)
216+
}
217+
218+
// After: Helper function
219+
AssertError(t, err, expectError, "operation error expectation")
220+
```
221+
222+
These helpers provide:
223+
224+
- Consistent error messages across all tests
225+
- Reduced boilerplate code
226+
- Better test maintainability
227+
- Clearer test intent
228+
168229
## Important Notes
169230

170231
- Go 1.23 is the minimum required version.
@@ -286,18 +347,18 @@ go run "$FA@latest" -fix ./...
286347

287348
This tool will:
288349

289-
- Analyze all struct definitions in the project
290-
- Reorder fields to minimize memory padding
291-
- Automatically update source files with optimized field ordering
350+
- Analyze all struct definitions in the project.
351+
- Reorder fields to minimize memory padding.
352+
- Automatically update source files with optimized field ordering.
292353

293354
#### Field Alignment Notes
294355

295356
- Always run `make tidy` after field alignment fixes to ensure all linting
296-
passes
297-
- Field alignment changes may require updating struct literal initializations
298-
- The tool is safe to run repeatedly - it only makes changes when beneficial
299-
- Memory savings can be significant for frequently allocated structs
300-
- Run field alignment manually as needed for struct optimization
357+
passes.
358+
- Field alignment changes may require updating struct literal initializations.
359+
- The tool is safe to run repeatedly - it only makes changes when beneficial.
360+
- Memory savings can be significant for frequently allocated structs.
361+
- Run field alignment manually as needed for struct optimization.
301362

302363
### golangci-lint Configuration
303364

@@ -343,17 +404,18 @@ linters-settings:
343404
344405
Common IDE diagnostics and their meanings:
345406
346-
1. **Missing property "version"**: IDE schema expects `version: "2"`
347-
2. **Property linters-settings is not allowed**: IDE expects v2 structure
407+
1. **Missing property "version"**: IDE schema expects `version: "2"`.
408+
2. **Property linters-settings is not allowed**: IDE expects v2 structure.
348409
3. **cSpell warnings**: Linter names are technical terms not in spell-check
349-
dictionary
410+
dictionary.
350411

351412
#### Resolution Status
352413

353-
- **Current priority**: Low - configuration works functionally
354-
- **IDE warnings**: Cannot be resolved without upgrading golangci-lint to v2
355-
- **System constraint**: Project uses v1.64.8 to avoid v2 configuration issues
356-
- **Workaround**: IDE warnings can be ignored as they don't affect functionality
414+
- **Current priority**: Low - configuration works functionally.
415+
- **IDE warnings**: Cannot be resolved without upgrading golangci-lint to v2.
416+
- **System constraint**: Project uses v1.64.8 to avoid v2 configuration issues.
417+
- **Workaround**: IDE warnings can be ignored as they don't affect
418+
functionality.
357419

358420
#### Field Alignment Integration
359421

addrport_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,13 +225,13 @@ var typeSpecificAddrPortTestCases = []typeSpecificAddrPortTestCase{
225225
},
226226
{
227227
name: "TCPAddr with invalid IP",
228-
input: &net.TCPAddr{IP: []byte{1, 2, 3}, Port: 80}, // Invalid IP length
228+
input: &net.TCPAddr{IP: S[byte](1, 2, 3), Port: 80}, // Invalid IP length
229229
want: netip.AddrPort{},
230230
wantOk: false,
231231
},
232232
{
233233
name: "UDPAddr with invalid IP",
234-
input: &net.UDPAddr{IP: []byte{}, Port: 80}, // Empty IP
234+
input: &net.UDPAddr{IP: S[byte](), Port: 80}, // Empty IP
235235
want: netip.AddrPort{},
236236
wantOk: false,
237237
},

addrs_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99

1010
// Test cases for ParseAddr
1111
type parseAddrTestCase struct {
12-
// netip.Addr struct (24 bytes) - result value
12+
// netip.Addr struct - result value
1313
want netip.Addr
1414

15-
// String fields (16 bytes each) - alphabetically ordered
15+
// String fields - alphabetically ordered
1616
input string
1717
name string
1818

@@ -180,13 +180,13 @@ func TestParseNetIP(t *testing.T) {
180180

181181
// Test cases for AddrFromNetIP
182182
type addrFromNetIPTestCase struct {
183-
// netip.Addr struct (24 bytes) - result value
183+
// netip.Addr struct - result value
184184
want netip.Addr
185185

186-
// Interface field (16 bytes) - input value
186+
// Interface field - input value
187187
input net.Addr
188188

189-
// String field (16 bytes) - test name
189+
// String field - test name
190190
name string
191191

192192
// Boolean field (1 byte) - success flag
@@ -324,7 +324,7 @@ func TestGetStringIPAddresses(t *testing.T) {
324324

325325
func testLoopbackInterface(t *testing.T) {
326326
// Most systems have a loopback interface
327-
loopbackNames := []string{"lo", "lo0", "Loopback Pseudo-Interface 1"}
327+
loopbackNames := S("lo", "lo0", "Loopback Pseudo-Interface 1")
328328

329329
var found bool
330330
for _, name := range loopbackNames {
@@ -430,15 +430,15 @@ var getInterfacesNamesTestCases = []getInterfacesNamesTestCase{
430430
},
431431
{
432432
name: "empty exclusions",
433-
except: []string{},
433+
except: S[string](),
434434
},
435435
{
436436
name: "exclude invalid",
437-
except: []string{"invalid-interface"},
437+
except: S("invalid-interface"),
438438
},
439439
{
440440
name: "exclude multiple",
441-
except: []string{"eth0", "eth1", "lo"},
441+
except: S("eth0", "eth1", "lo"),
442442
},
443443
}
444444

@@ -513,28 +513,28 @@ func TestGetInterfacesNames(t *testing.T) {
513513

514514
// Test internal helper functions
515515
func TestAsStringIPAddresses(t *testing.T) {
516-
addrs := []netip.Addr{
516+
addrs := S[netip.Addr](
517517
netip.MustParseAddr("192.168.1.1"),
518518
netip.MustParseAddr("2001:db8::1"),
519-
{}, // Invalid address
519+
netip.Addr{}, // Invalid address
520520
netip.MustParseAddr("10.0.0.1"),
521-
}
521+
)
522522

523523
result := asStringIPAddresses(addrs...)
524-
expected := []string{"192.168.1.1", "2001:db8::1", "10.0.0.1"}
524+
expected := S("192.168.1.1", "2001:db8::1", "10.0.0.1")
525525

526526
if !reflect.DeepEqual(result, expected) {
527527
t.Errorf("Expected %v, got %v", expected, result)
528528
}
529529
}
530530

531531
func TestAsNetIPAddresses(t *testing.T) {
532-
addrs := []netip.Addr{
532+
addrs := S[netip.Addr](
533533
netip.MustParseAddr("192.168.1.1"),
534534
netip.MustParseAddr("2001:db8::1"),
535-
{}, // Invalid address
535+
netip.Addr{}, // Invalid address
536536
netip.MustParseAddr("::ffff:192.168.1.2"), // IPv4-mapped IPv6
537-
}
537+
)
538538

539539
result := asNetIPAddresses(addrs...)
540540

@@ -582,12 +582,12 @@ func TestAsNetIP(t *testing.T) {
582582
func TestAppendNetIPAsIP(t *testing.T) {
583583
var out []netip.Addr
584584

585-
addrs := []net.Addr{
585+
addrs := S[net.Addr](
586586
&net.IPAddr{IP: net.ParseIP("192.168.1.1")},
587587
&net.IPNet{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(24, 32)},
588588
&net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}, // Should be skipped
589589
&net.IPAddr{IP: nil}, // Should be skipped
590-
}
590+
)
591591

592592
result := appendNetIPAsIP(out, addrs...)
593593

as_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import (
88

99
// asTestCase tests As function
1010
type asTestCase struct {
11-
// Interface fields (16 bytes each) - input/output test data
11+
// Interface fields - input/output test data
1212
input any
1313
want any
1414

15-
// String fields (16 bytes) - test identification
15+
// String fields - test identification
1616
name string
1717

1818
// Boolean fields (1 byte) - expected result flags
@@ -151,10 +151,10 @@ func (e errorWithOK) OK() bool {
151151

152152
// asErrorTestCase tests AsError function
153153
type asErrorTestCase struct {
154-
// Interface fields (16 bytes) - input test data
154+
// Interface fields - input test data
155155
input any
156156

157-
// String fields (16 bytes) - test identification and expected message
157+
// String fields - test identification and expected message
158158
name string
159159
wantMsg string
160160

@@ -292,12 +292,12 @@ func TestSliceAs(t *testing.T) {
292292
{
293293
name: "mixed types to string",
294294
input: S[any]("hello", 42, "world", 3.14, "!"),
295-
want: []string{"hello", "world", "!"},
295+
want: S("hello", "world", "!"),
296296
},
297297
{
298298
name: "all strings",
299299
input: S[any]("a", "b", "c"),
300-
want: []string{"a", "b", "c"},
300+
want: S("a", "b", "c"),
301301
},
302302
{
303303
name: "no strings",
@@ -317,7 +317,7 @@ func TestSliceAs(t *testing.T) {
317317
{
318318
name: "with nil values",
319319
input: S[any]("hello", nil, "world"),
320-
want: []string{"hello", "world"},
320+
want: S("hello", "world"),
321321
},
322322
}
323323

@@ -340,7 +340,7 @@ func TestSliceAsFn(t *testing.T) {
340340
name: "with custom conversion",
341341
fn: prefixString,
342342
input: S[any]("a", 1, "b", 2, "c"),
343-
want: []string{"prefix:a", "prefix:b", "prefix:c"},
343+
want: S("prefix:a", "prefix:b", "prefix:c"),
344344
},
345345
{
346346
name: "with nil function",
@@ -443,7 +443,7 @@ func TestAsErrors(t *testing.T) {
443443
nil,
444444
),
445445
wantLen: 3,
446-
wantMsgs: []string{"error1", "error2", "error3"},
446+
wantMsgs: S("error1", "error2", "error3"),
447447
},
448448
{
449449
name: "all errors",
@@ -453,25 +453,25 @@ func TestAsErrors(t *testing.T) {
453453
errors.New("c"),
454454
),
455455
wantLen: 3,
456-
wantMsgs: []string{"a", "b", "c"},
456+
wantMsgs: S("a", "b", "c"),
457457
},
458458
{
459459
name: "no errors",
460460
input: S[any]("a", 1, true, nil),
461461
wantLen: 0,
462-
wantMsgs: nil,
462+
wantMsgs: S[string](),
463463
},
464464
{
465465
name: "empty slice",
466466
input: S[any](),
467467
wantLen: 0,
468-
wantMsgs: nil,
468+
wantMsgs: S[string](),
469469
},
470470
{
471471
name: "nil slice",
472472
input: nil,
473473
wantLen: 0,
474-
wantMsgs: nil,
474+
wantMsgs: S[string](),
475475
},
476476
{
477477
name: "with OK interface",
@@ -481,7 +481,7 @@ func TestAsErrors(t *testing.T) {
481481
errorWithOK{msg: "fail2", ok: false},
482482
),
483483
wantLen: 2,
484-
wantMsgs: []string{"fail", "fail2"},
484+
wantMsgs: S("fail", "fail2"),
485485
},
486486
}
487487

0 commit comments

Comments
 (0)