@@ -2,6 +2,7 @@ package core
22
33import (
44 "errors"
5+ "fmt"
56 "testing"
67)
78
@@ -441,3 +442,221 @@ func TestCatchWithPanicRecovery(t *testing.T) {
441442 t .Run (tc .name , tc .test )
442443 }
443444}
445+
446+ // testMust is a helper to test Must function by catching panics.
447+ // It wraps Must calls in panic recovery to allow testing both success
448+ // and panic scenarios. Returns the value and any recovered panic as an error.
449+ func testMust [T any ](v0 T , e0 error ) (v1 T , e1 error ) {
450+ defer func () {
451+ if e2 := AsRecovered (recover ()); e2 != nil {
452+ e1 = e2
453+ }
454+ }()
455+
456+ v1 = Must (v0 , e0 )
457+ return v1 , nil
458+ }
459+
460+ // mustSuccessTestCase tests Must function success scenarios where no panic should occur.
461+ type mustSuccessTestCase struct {
462+ // Large fields first - interfaces (8 bytes on 64-bit)
463+ value any
464+ err error
465+
466+ // Small fields last - strings (16 bytes on 64-bit)
467+ name string
468+ }
469+
470+ // newMustSuccessTestCase creates a new mustSuccessTestCase with the given parameters.
471+ // For success cases, err is always nil.
472+ func newMustSuccessTestCase (name string , value any ) mustSuccessTestCase {
473+ return mustSuccessTestCase {
474+ value : value ,
475+ err : nil ,
476+ name : name ,
477+ }
478+ }
479+
480+ // test validates that Must returns the value unchanged when err is nil.
481+ func (tc mustSuccessTestCase ) test (t * testing.T ) {
482+ t .Helper ()
483+
484+ tc .testMustWithValue (t )
485+ }
486+
487+ // testMustT is a generic test helper for Must function with comparable types.
488+ // It handles the common pattern of testing Must with a value and verifying
489+ // the result matches expectations.
490+ func testMustT [V comparable ](t * testing.T , tc mustSuccessTestCase , value V ) {
491+ t .Helper ()
492+
493+ got , err := testMust (value , tc .err )
494+ AssertNoError (t , err , "Must success" )
495+ AssertEqual (t , value , got , "Must value" )
496+ }
497+
498+ // testMustSlice is a specialized test helper for Must function with slice types.
499+ func testMustSlice [V any ](t * testing.T , tc mustSuccessTestCase , value []V ) {
500+ t .Helper ()
501+
502+ got , err := testMust (value , tc .err )
503+ AssertNoError (t , err , "Must success" )
504+ AssertSliceEqual (t , value , got , "Must slice" )
505+ }
506+
507+ // testMustWithValue dispatches to the appropriate test helper.
508+ func (tc mustSuccessTestCase ) testMustWithValue (t * testing.T ) {
509+ t .Helper ()
510+
511+ // Test with different types using type switches
512+ switch v := tc .value .(type ) {
513+ case string :
514+ testMustT (t , tc , v )
515+ case int :
516+ testMustT (t , tc , v )
517+ case bool :
518+ testMustT (t , tc , v )
519+ case []int :
520+ testMustSlice (t , tc , v )
521+ case * int :
522+ testMustT (t , tc , v )
523+ case struct { Name string }:
524+ testMustT (t , tc , v )
525+ default :
526+ t .Fatalf ("unsupported test value type: %T" , tc .value )
527+ }
528+ }
529+
530+ func TestMustSuccess (t * testing.T ) {
531+ testCases := []mustSuccessTestCase {
532+ newMustSuccessTestCase ("string success" , "hello" ),
533+ newMustSuccessTestCase ("int success" , 42 ),
534+ newMustSuccessTestCase ("bool success" , true ),
535+ newMustSuccessTestCase ("slice success" , S (1 , 2 , 3 )),
536+ newMustSuccessTestCase ("nil pointer success" , (* int )(nil )),
537+ newMustSuccessTestCase ("struct success" , struct { Name string }{"test" }),
538+ }
539+
540+ for _ , tc := range testCases {
541+ t .Run (tc .name , tc .test )
542+ }
543+ }
544+
545+ // mustPanicTestCase tests Must function panic scenarios where Must should panic.
546+ type mustPanicTestCase struct {
547+ // Large fields first - error interface (8 bytes)
548+ err error
549+
550+ // Small fields last - string (16 bytes)
551+ name string
552+ }
553+
554+ // test validates that Must panics with proper PanicError when err is not nil.
555+ func (tc mustPanicTestCase ) test (t * testing.T ) {
556+ t .Helper ()
557+
558+ _ , err := testMust ("value" , tc .err )
559+ AssertError (t , err , "Must panic" )
560+
561+ // Verify the panic contains our original error
562+ AssertTrue (t , errors .Is (err , tc .err ), "panic wraps original" )
563+
564+ // Verify it's a proper PanicError
565+ panicErr , ok := AssertTypeIs [* PanicError ](t , err , "panic type" )
566+ if ok {
567+ // Verify stack trace exists
568+ stack := panicErr .CallStack ()
569+ AssertTrue (t , len (stack ) > 0 , "has stack trace" )
570+ }
571+ }
572+
573+ func TestMustPanic (t * testing.T ) {
574+ testCases := []mustPanicTestCase {
575+ {
576+ name : "simple error" ,
577+ err : errors .New ("test error" ),
578+ },
579+ {
580+ name : "formatted error" ,
581+ err : fmt .Errorf ("formatted error: %d" , 42 ),
582+ },
583+ {
584+ name : "wrapped error" ,
585+ err : fmt .Errorf ("wrapped: %w" , errors .New ("inner" )),
586+ },
587+ }
588+
589+ for _ , tc := range testCases {
590+ t .Run (tc .name , tc .test )
591+ }
592+ }
593+
594+ type maybeTestCase struct {
595+ // Large fields first - interfaces (8 bytes)
596+ value any
597+ err error
598+
599+ // Small fields last - string (16 bytes)
600+ name string
601+ }
602+
603+ func (tc maybeTestCase ) test (t * testing.T ) {
604+ t .Helper ()
605+
606+ // Test with different types using type switches
607+ switch v := tc .value .(type ) {
608+ case string :
609+ got := Maybe (v , tc .err )
610+ AssertEqual (t , v , got , "Maybe string" )
611+ case int :
612+ got := Maybe (v , tc .err )
613+ AssertEqual (t , v , got , "Maybe int" )
614+ case * int :
615+ got := Maybe (v , tc .err )
616+ AssertEqual (t , v , got , "Maybe pointer" )
617+ case struct { Name string }:
618+ got := Maybe (v , tc .err )
619+ AssertEqual (t , v , got , "Maybe struct" )
620+ default :
621+ t .Fatalf ("unsupported test value type: %T" , tc .value )
622+ }
623+ }
624+
625+ func TestMaybe (t * testing.T ) {
626+ testCases := []maybeTestCase {
627+ {
628+ name : "string with nil error" ,
629+ value : "hello" ,
630+ err : nil ,
631+ },
632+ {
633+ name : "string with error" ,
634+ value : "world" ,
635+ err : errors .New ("ignored error" ),
636+ },
637+ {
638+ name : "int with nil error" ,
639+ value : 42 ,
640+ err : nil ,
641+ },
642+ {
643+ name : "int with error" ,
644+ value : 0 ,
645+ err : errors .New ("another ignored error" ),
646+ },
647+ {
648+ name : "nil pointer with error" ,
649+ value : (* int )(nil ),
650+ err : errors .New ("pointer error" ),
651+ },
652+ {
653+ name : "struct with error" ,
654+ value : struct { Name string }{"test" },
655+ err : fmt .Errorf ("formatted: %d" , 123 ),
656+ },
657+ }
658+
659+ for _ , tc := range testCases {
660+ t .Run (tc .name , tc .test )
661+ }
662+ }
0 commit comments