github.com/haraldrudell/parl@v0.4.176/perrors/panicdetector/indices_test.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package panicdetector
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"runtime"
    12  	"testing"
    13  
    14  	"github.com/haraldrudell/parl/pruntime"
    15  )
    16  
    17  const (
    18  	// value indicator for indices()
    19  	panicYes = true
    20  	// value indicator for indices()
    21  	panicNo = false
    22  )
    23  
    24  func TestIndices(t *testing.T) {
    25  	var testList = []struct {
    26  		name           string
    27  		stackGenerator func() (stack pruntime.Stack, panicLoc, stackLoc *pruntime.CodeLocation)
    28  		isPanic        bool
    29  	}{
    30  		{"no panic", noPanic, panicNo},
    31  		{"invoke panic()", invokePanic, panicYes},
    32  		{"nil pointer dereference", nilPointerDereference, panicYes},
    33  		{"bad slice", slicePanic, panicYes},
    34  	}
    35  
    36  	var stackAct pruntime.Stack
    37  	var isPanicAct bool
    38  	var stackIndexAct, panicIndexAct int
    39  	var panicLocExp, stackLocExp *pruntime.CodeLocation
    40  	for _, params := range testList {
    41  		stackAct, panicLocExp, stackLocExp = params.stackGenerator()
    42  
    43  		if params.isPanic {
    44  			// INPUTSTACK for test: panic(1) stack:ID: 4 IsMain: false status: running
    45  			//   github.com/haraldrudell/parl/perrors/errorglue.panicOneDefer(0x1, 0x1400006ce08)
    46  			//     indices_test.go:99
    47  			//   panic({0x102dcdba0?, 0x102eb3188?})
    48  			//     panic.go:914
    49  			//   github.com/haraldrudell/parl/perrors/errorglue.panicOne()
    50  			//     indices_test.go:88
    51  			//   github.com/haraldrudell/parl/perrors/errorglue.TestIndices(0x140000036c0)
    52  			//     indices_test.go:43
    53  			//   testing.tRunner(0x140000036c0, 0x102df5de8)
    54  			//     testing.go:1595
    55  			//   cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1
    56  			// INPUTSTACK for test: nil pointer dereference stack:ID: 4 IsMain: false status: running
    57  			// github.com/haraldrudell/parl/perrors/errorglue.panicNilPointerDefer({0x102d95195, 0x40}, 0x14000123e00)
    58  			// 	indices_test.go:134
    59  			// panic({0x102dd5740?, 0x102ebb950?})
    60  			// 	panic.go:914
    61  			// github.com/haraldrudell/parl/perrors/errorglue.panicNilPointer()
    62  			// 	indices_test.go:113
    63  			// github.com/haraldrudell/parl/perrors/errorglue.TestIndices(0x140000036c0)
    64  			// 	indices_test.go:43
    65  			// testing.tRunner(0x140000036c0, 0x102df5de8)
    66  			// 	testing.go:1595
    67  			// cre: testing.(*T).Run-testing.go:1648 in goroutine 1 1
    68  			t.Logf("INPUTSTACK for test: ‘%s’ stack:%s", params.name, stackAct)
    69  		}
    70  
    71  		isPanicAct, stackIndexAct, panicIndexAct = Indices(stackAct)
    72  
    73  		// isPanic should match
    74  		if isPanicAct != params.isPanic {
    75  			t.Errorf("FAIL test ‘%s’ Indices() isPanic %t exp %t",
    76  				params.name,
    77  				isPanicAct, params.isPanic,
    78  			)
    79  			continue
    80  		} else if !isPanicAct {
    81  			continue
    82  		} else if stackLocExp == nil || panicLocExp == nil {
    83  			t.Fatalf("FAIL CORRUPTION test ‘%s’ recover nil %t panic nil %t",
    84  				params.name,
    85  				stackLocExp == nil,
    86  				panicLocExp == nil,
    87  			)
    88  		}
    89  
    90  		// recoveryIndex should match
    91  		if actLine, expLine := stackAct.Frames()[stackIndexAct].Loc().FuncLine(), stackLocExp.FuncLine(); actLine != expLine {
    92  			t.Errorf("FAIL test ‘%s’ Indices() recovery index: %d:\n%s\n%s",
    93  				params.name, stackIndexAct,
    94  				actLine, expLine,
    95  			)
    96  		}
    97  
    98  		// panicIndex should match
    99  		if actLine, expLine := stackAct.Frames()[panicIndexAct].Loc().FuncLine(), panicLocExp.FuncLine(); actLine != expLine {
   100  			t.Errorf("FAIL test ‘%s’ Indices() panic index %d:\n%s\n%s",
   101  				params.name, panicIndexAct,
   102  				actLine, expLine,
   103  			)
   104  		}
   105  	}
   106  }
   107  
   108  // noPanic returns a deferred stack trace generated without a panic
   109  func noPanic() (stack pruntime.Stack, panicLoc, recoverLoc *pruntime.CodeLocation) {
   110  	defer noPanicDefer(&stack)
   111  
   112  	return
   113  }
   114  
   115  // noPanicDefer is defereed function for noPanic
   116  func noPanicDefer(stackp *pruntime.Stack) {
   117  	*stackp = pruntime.NewStack(0)
   118  }
   119  
   120  // invokePanic returns a deferred stack trace generated by panic(1)
   121  func invokePanic() (stack pruntime.Stack, panicLoc, stackLoc *pruntime.CodeLocation) {
   122  	var one = 1
   123  	defer invokePanicDefer(one, &stack, &stackLoc)
   124  
   125  	for panicLoc = pruntime.NewCodeLocation(0); ; panic(one) {
   126  	}
   127  }
   128  
   129  // invokePanicDefer is defer function for panicOne
   130  func invokePanicDefer(one int, stackp *pruntime.Stack, stackLoc **pruntime.CodeLocation) {
   131  	var recoverValue = recover()
   132  	if recoverValue != one {
   133  		panic(fmt.Errorf("bad recover value: %T “%[1]v” exp: %d",
   134  			recoverValue,
   135  			one,
   136  		))
   137  	}
   138  	var s, loc = pruntime.NewStack(0), pruntime.NewCodeLocation(0)
   139  	*stackp = s
   140  	*stackLoc = loc
   141  }
   142  
   143  // panicFunction recovers a panic using [parl.RecoverErr]
   144  //   - panicLine is the exact code line of the panic
   145  //   - err is the error value produced by [parl.RecoverErr]
   146  func nilPointerDereference() (stack pruntime.Stack, panicLoc, stackLoc *pruntime.CodeLocation) {
   147  	// runtime.errorString “runtime error: invalid memory address or nil pointer dereference”
   148  	//	- runtime.errorString implements error
   149  	//	- only methods are Error() and RuntimeError()
   150  	var message = "runtime error: invalid memory address or nil pointer dereference"
   151  	defer nilPointerDereferenceDefer(message, &stack, &stackLoc)
   152  
   153  	// nil pointer dereference panic
   154  	for panicLoc = pruntime.NewCodeLocation(0); ; _ = *(*int)(nil) {
   155  	}
   156  }
   157  
   158  // nilPointerDereferenceDefer is defer function for panicNilPointer
   159  func nilPointerDereferenceDefer(message string, stackp *pruntime.Stack, stackLoc **pruntime.CodeLocation) {
   160  	var recoverValue = recover()
   161  	var isOk bool
   162  	if err, ok := recoverValue.(error); ok {
   163  		var runtimeError runtime.Error
   164  		if errors.As(err, &runtimeError) {
   165  			isOk = err.Error() == message
   166  		}
   167  	}
   168  	if !isOk {
   169  		panic(fmt.Errorf("bad recover value: %T “%[1]v” exp err message: “%s”",
   170  			recoverValue,
   171  			message,
   172  		))
   173  	}
   174  	var s, loc = pruntime.NewStack(0), pruntime.NewCodeLocation(0)
   175  	*stackp = s
   176  	*stackLoc = loc
   177  }
   178  
   179  // panicFunction recovers a panic using [parl.RecoverErr]
   180  //   - panicLine is the exact code line of the panic
   181  //   - err is the error value produced by [parl.RecoverErr]
   182  func slicePanic() (stack pruntime.Stack, panicLoc, stackLoc *pruntime.CodeLocation) {
   183  	// runtime.errorString “runtime error: invalid memory address or nil pointer dereference”
   184  	//	- runtime.errorString implements error
   185  	//	- only methods are Error() and RuntimeError()
   186  	var message = "runtime error: index out of range [0] with length 0"
   187  	defer slicePanicDefer(message, &stack, &stackLoc)
   188  
   189  	// nil pointer dereference panic
   190  	var slice = make([]byte, 0)
   191  	for panicLoc = pruntime.NewCodeLocation(0); ; _ = slice[0] {
   192  	}
   193  }
   194  
   195  // slicePanicDefer is defer function for slicePanic
   196  func slicePanicDefer(message string, stackp *pruntime.Stack, stackLoc **pruntime.CodeLocation) {
   197  	var recoverValue = recover()
   198  	var isOk bool
   199  	if err, ok := recoverValue.(error); ok {
   200  		var runtimeError runtime.Error
   201  		if errors.As(err, &runtimeError) {
   202  			isOk = err.Error() == message
   203  		}
   204  	}
   205  	if !isOk {
   206  		panic(fmt.Errorf("bad recover value: %T “%[1]v” exp err message: “%s”",
   207  			recoverValue,
   208  			message,
   209  		))
   210  	}
   211  	var s, loc = pruntime.NewStack(0), pruntime.NewCodeLocation(0)
   212  	*stackp = s
   213  	*stackLoc = loc
   214  }