github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/error-stack_test.go (about)

     1  /*
     2  © 2018–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package errorglue
     7  
     8  import (
     9  	"errors"
    10  	"reflect"
    11  	"slices"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/haraldrudell/parl/pruntime"
    16  )
    17  
    18  func TestErrorStack(t *testing.T) {
    19  	//t.Errorf("logging on")
    20  	// stack to examine
    21  	var stackExp = pruntime.NewStack(0)
    22  	// at-string preceding code locations “ at ”
    23  	var atString = "\x20at\x20"
    24  	// encapsulated error “message”
    25  	var error0 = errors.New("message")
    26  	// an invalid ChainString format code
    27  	var badFormat = func() (badFormat CSFormat) {
    28  		var i = -1
    29  		badFormat = CSFormat(i)
    30  		return
    31  	}()
    32  	// expected [ShortSuffix] output
    33  	var shortSuffixExp = strings.TrimPrefix(stackExp.Frames()[0].Loc().Short(), atString)
    34  	// ordered list of formats
    35  	var formats = []CSFormat{
    36  		DefaultFormat, ShortFormat, LongFormat, ShortSuffix, LongSuffix,
    37  		badFormat,
    38  	}
    39  	// map from format to expected value
    40  	var formatExp map[CSFormat]string
    41  
    42  	var ok bool
    43  	var err error
    44  	var stackAct pruntime.Stack
    45  	var chainStringAct, chainStringExp string
    46  	var _ *errorStack
    47  
    48  	// ChainString() StackTrace()
    49  	//	- delegated: Format() Unwrap() Error()
    50  	var eStackAct *errorStack
    51  
    52  	err = NewErrorStack(error0, stackExp)
    53  
    54  	// NewErrorStack() should return runtime type errorStack
    55  	if eStackAct, ok = err.(*errorStack); !ok {
    56  		t.Fatalf("FAIL NewErrorStack not errorStack")
    57  	}
    58  
    59  	// StackTrace() should return the slice
    60  	stackAct = eStackAct.StackTrace()
    61  	if !slices.Equal(stackAct.Frames(), stackExp.Frames()) {
    62  		t.Errorf("StackTrace bad\n%v exp\n%v", stackAct, stackExp)
    63  	}
    64  
    65  	// ChainString() should return correct string
    66  	formatExp = map[CSFormat]string{
    67  		DefaultFormat: eStackAct.Error(),
    68  		ShortFormat:   eStackAct.Error() + atString + stackExp.Frames()[0].Loc().Short(),
    69  		LongFormat:    eStackAct.Error() + " [" + reflect.TypeOf(eStackAct).String() + "]" + "\n" + stackExp.String(),
    70  		ShortSuffix:   shortSuffixExp,
    71  		LongSuffix:    stackExp.String(),
    72  		badFormat:     "",
    73  	}
    74  	for _, csFormat := range formats {
    75  		if chainStringExp, ok = formatExp[csFormat]; !ok {
    76  			t.Errorf("CORRUPT no formatMap entry for format %s", csFormat)
    77  		}
    78  
    79  		// DefaultFormat: message
    80  		// ShortFormat: message at errorglue.TestErrorStack()-error-stack_test.go:21
    81  		// LongFormat: message [*errorglue.errorStack]ID: 34 IsMain: false status: running
    82  		//     github.com/haraldrudell/parl/perrors/errorglue.TestErrorStack(0x14000120820)
    83  		//       error-stack_test.go:21
    84  		//     testing.tRunner(0x14000120820, 0x102b3a100)
    85  		//       testing.go:1595
    86  		//     cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1
    87  		// ShortSuffix: errorglue.TestErrorStack()-error-stack_test.go:21
    88  		// LongSuffix: ID: 34 IsMain: false status: running
    89  		// 	github.com/haraldrudell/parl/perrors/errorglue.TestErrorStack(0x14000120820)
    90  		// 	error-stack_test.go:21
    91  		// testing.tRunner(0x14000120820, 0x102b3a100)
    92  		// 	testing.go:1595
    93  		// cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1
    94  		// ?255:
    95  		t.Logf("%s: %s", csFormat, chainStringExp)
    96  
    97  		chainStringAct = eStackAct.ChainString(csFormat)
    98  		if chainStringAct != chainStringExp {
    99  			t.Errorf("FAIL ChainString %s:\n%q exp\n%q",
   100  				csFormat,
   101  				chainStringAct, chainStringExp,
   102  			)
   103  		}
   104  	}
   105  }
   106  
   107  // errorStack.ChainString(ShortSuffix), and [perrors.Short], should return
   108  // panic location and not error creation location
   109  func TestErrorStackPanicLine(t *testing.T) {
   110  	var atString = "\x20at\x20"
   111  
   112  	var suffixExp string
   113  	// errContainingPanicAct is created in recovery from a panic() invocation
   114  	var errContainingPanicAct error
   115  	var suffixAct string
   116  	// stackAct is taken on the same line as a errContainingPanic panic() invocation
   117  	var stackAct pruntime.Stack
   118  	var noErrorBase error
   119  
   120  	// ChainString() StackTrace()
   121  	//	- delegated: Format() Unwrap() Error()
   122  	var eStack *errorStack
   123  
   124  	// get actuals
   125  	stackAct, errContainingPanicAct = getErrorStackPanic(noErrorBase)
   126  	if errContainingPanicAct == nil {
   127  		panic(errors.New("errorRecovered == nil"))
   128  	} else if stackAct == nil {
   129  		panic(errors.New("stackSlice == nil"))
   130  	}
   131  	// errContainingPanicAct runtime type should be errorStack
   132  	eStack = errContainingPanicAct.(*errorStack)
   133  
   134  	// stackSlice: "errorglue.getErrorStackPanic()-error-stack_test.go:202"
   135  	t.Logf("stackSlice: %s", stackAct.Frames()[0].Loc().Short())
   136  
   137  	// ShortSuffix should match
   138  	suffixExp = strings.TrimPrefix(stackAct.Frames()[0].Loc().Short(), atString)
   139  	suffixAct = eStack.ChainString(ShortSuffix)
   140  	if suffixAct != suffixExp {
   141  		t.Errorf("FAIL ChainString panic:\n%q exp\n%q", suffixAct, suffixExp)
   142  	}
   143  }
   144  
   145  // ShortFormat and ShortSuffix should panic location for non-stack recover-value
   146  func TestErrorStackPanicWithStack(t *testing.T) {
   147  	//t.Errorf("logging on")
   148  	var suffixExp string
   149  	var atString = "\x20at\x20"
   150  	var error0 = NewErrorStack(errors.New("message"), pruntime.NewStack(0))
   151  
   152  	var errorRecovered error
   153  	var suffixAct string
   154  	var slice pruntime.Stack
   155  	var stacks []pruntime.Stack
   156  
   157  	// ChainString() StackTrace()
   158  	var eStack *errorStack
   159  
   160  	slice, errorRecovered = getErrorStackPanic(error0)
   161  	if errorRecovered == nil {
   162  		panic(errors.New("errorRecovered == nil"))
   163  	} else if slice == nil {
   164  		panic(errors.New("stackSlice == nil"))
   165  	}
   166  
   167  	// errorRecovered should have two stacks
   168  	//	- oldest first
   169  	stacks = GetStacks(errorRecovered)
   170  	if len(stacks) != 2 {
   171  		panic(errors.New("stacks not 2"))
   172  	}
   173  	eStack = errorRecovered.(*errorStack)
   174  
   175  	// oldest comes from error0
   176  	// newest comes from getErrorStackPanic
   177  	// slice is the panic location that should be found
   178  	//	- extracted from newest
   179  	//	- because oldest does not have a panic
   180  
   181  	// oldest: " at errorglue.TestErrorStackPanicWithStack()-error-stack_test.go:117"
   182  	t.Logf("errorRecovered oldest: %q", stacks[0].Frames()[0].Loc().Short())
   183  	// newest: " at errorglue.getErrorStackPanic.func1()-error-stack_test.go:156"
   184  	t.Logf("errorRecovered newest: %q", stacks[1].Frames()[0].Loc().Short())
   185  	// stackSlice: " at errorglue.getErrorStackPanic()-error-stack_test.go:166"
   186  	t.Logf("slice: %q", slice.Frames()[0].Loc().Short())
   187  
   188  	// get expected value from verifying slice
   189  	suffixExp = strings.TrimPrefix(slice.Frames()[0].Loc().Short(), atString)
   190  
   191  	suffixAct = eStack.ChainString(ShortSuffix)
   192  	if suffixAct != suffixExp {
   193  		t.Errorf("ChainString panic:\n%q exp\n%q", suffixAct, suffixExp)
   194  	}
   195  }
   196  
   197  // getErrorStackPanic returns a stack from the same line as a panic in err
   198  //   - error0 is an optional error used as panic argument
   199  func getErrorStackPanic(error0 error) (stack pruntime.Stack, err error) {
   200  	defer getErrorStackPanicRecover(&err)
   201  
   202  	if error0 == nil {
   203  		error0 = errors.New("recover")
   204  	}
   205  	// NewStack and panic on same line
   206  	for stack = pruntime.NewStack(0); ; panic(error0) {
   207  	}
   208  }
   209  
   210  // getErrorStackPanicRecover is deeferred recover function for getErrorStackPanic
   211  func getErrorStackPanicRecover(errp *error) {
   212  	var stack = pruntime.NewStack(0)
   213  	var e = recover().(error)
   214  	*errp = NewErrorStack(e, stack)
   215  }