github.com/haraldrudell/parl@v0.4.176/perrors/new-errorf_test.go (about)

     1  /*
     2  © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package perrors
     7  
     8  import (
     9  	"errors"
    10  	"runtime"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/haraldrudell/parl/perrors/errorglue"
    15  	"github.com/haraldrudell/parl/pruntime"
    16  )
    17  
    18  func TestStack(t *testing.T) {
    19  	var errMsg = "error message"
    20  	var err0 = errors.New(errMsg)
    21  	var expectedStackDepth = 1
    22  	type testNo int
    23  	const (
    24  		addStack testNo = iota
    25  		hasAlready
    26  		isNil
    27  		stackn
    28  		LessThan
    29  	)
    30  	var testNames = map[testNo]string{
    31  		addStack:   "addStack",
    32  		hasAlready: "hasAlready",
    33  		isNil:      "isNil",
    34  		stackn:     "Stackn",
    35  	}
    36  	var fileExp, failure = expectedFile()
    37  	if failure != "" {
    38  		t.Fatal(failure)
    39  	}
    40  
    41  	// var actualInt int
    42  	var err, err1, errorNil error
    43  	var stack pruntime.Stack
    44  	var frames []pruntime.Frame
    45  	var codeLocation *pruntime.CodeLocation
    46  
    47  	for i := testNo(0); i < LessThan; i++ {
    48  		var name = testNames[i]
    49  		switch i {
    50  		case addStack:
    51  			// add stack to existing non-stack error
    52  			err1 = invokeStack(err0)
    53  			err = err1
    54  		case hasAlready:
    55  			// do not add stack if error already has it
    56  			err = invokeStack(err1)
    57  			// Stack invoked on an error with stack should return same error
    58  			if err != err1 {
    59  				t.Error("FAIL Stack added stack trace although it was already there")
    60  			}
    61  			continue
    62  		case isNil:
    63  			// Stack invoked with nil should return nil
    64  			err = Stack(errorNil)
    65  			if err != nil {
    66  				t.Error("FAIL Stack nil is not nil")
    67  			}
    68  			continue
    69  		case stackn:
    70  			// Stackn
    71  			err = invokeStackn(err0)
    72  		}
    73  
    74  		// err should not be nil
    75  		if err == nil {
    76  			t.Errorf("FAIL test %s err is nil", name)
    77  		}
    78  
    79  		// err.Error() should match
    80  		if messageAct := err.Error(); messageAct != errMsg {
    81  			t.Errorf("FAIL %s error message wrong: %q expected: %q", name, messageAct, errMsg)
    82  		}
    83  
    84  		stack = errorglue.GetStackTrace(err)
    85  		frames = stack.Frames()
    86  
    87  		// stack: ID: 35 status: ‘running’
    88  		// github.com/haraldrudell/parl/perrors.stackGoroutine({0x104d62f88?, 0x140001022e0?}, 0x14000120820?, 0x140001022f0)
    89  		// 	/opt/sw/parl/perrors/new-errorf_test.go:213
    90  		// Parent-ID: 34 go: github.com/haraldrudell/parl/perrors.invokeStack
    91  		// 	/opt/sw/parl/perrors/new-errorf_test.go:203
    92  		t.Logf("stack: %s", stack)
    93  
    94  		// number of frames should match
    95  		if actualInt := len(frames); actualInt != expectedStackDepth {
    96  			t.Errorf("FAIL %s stack depth: %d expected: %d", name, actualInt, expectedStackDepth)
    97  		}
    98  
    99  		codeLocation = frames[0].Loc()
   100  
   101  		// error116.CodeLocation{
   102  		//	 File:"/opt/sw/privates/parl/error116/new-errorf_test.go",
   103  		// 	 Line:48,
   104  		//	 FuncName:"github.com/haraldrudell/parl/error116.TestStack.func2"
   105  		// }
   106  		t.Logf("codeLocation %s", codeLocation)
   107  
   108  		// filename should match
   109  		if !strings.HasSuffix(codeLocation.File, fileExp) {
   110  			t.Errorf("FAIL %s top stack frame not from this file: %q should end: %q", name, codeLocation.File, fileExp)
   111  		}
   112  	}
   113  }
   114  
   115  func TestNew(t *testing.T) {
   116  	var errPrefix = "StackNew from "
   117  	// var errContains = ""
   118  	var errMsg, noMsg = "error message", ""
   119  	var expectedStackDepth = 1
   120  	var fileExp, failure = expectedFile()
   121  	if failure != "" {
   122  		t.Fatal(failure)
   123  	}
   124  	var names = []string{"setMessage", "defaultMessage"}
   125  
   126  	var err error
   127  	var stack pruntime.Stack
   128  	var frames []pruntime.Frame
   129  	var codeLocation *pruntime.CodeLocation
   130  	var messageAct string
   131  
   132  	for i, message := range []string{errMsg, noMsg} {
   133  		var name = names[i]
   134  
   135  		// invoke [perrors.New]
   136  		err = invokeNew(message)
   137  
   138  		// err should not be nil
   139  		if err == nil {
   140  			t.Errorf("FAIL test %s err is nil", name)
   141  		}
   142  
   143  		messageAct = err.Error()
   144  		if i == 0 {
   145  			// err.Error() should match
   146  			if messageAct != errMsg {
   147  				t.Errorf("FAIL %s error message wrong: %q expected: %q", name, messageAct, errMsg)
   148  			}
   149  			// New add proper default message
   150  		} else if !strings.HasPrefix(messageAct, errPrefix) {
   151  			t.Errorf("FAIL %s error message wrong: %q expected prefix: %q", name, messageAct, errPrefix)
   152  		}
   153  
   154  		stack = errorglue.GetStackTrace(err)
   155  		frames = stack.Frames()
   156  
   157  		// stack: ID: 35 status: ‘running’
   158  		// github.com/haraldrudell/parl/perrors.newGoroutine({0x1045a2f0e?, 0x10450a934?}, 0x140001209c0?, 0x140001022f0)
   159  		// 	/opt/sw/parl/perrors/new-errorf_test.go:241
   160  		// Parent-ID: 34 go: github.com/haraldrudell/parl/perrors.invokeNew
   161  		// 	/opt/sw/parl/perrors/new-errorf_test.go:231
   162  		t.Logf("stack: %s", stack)
   163  
   164  		// number of frames should match
   165  		if actualInt := len(frames); actualInt != expectedStackDepth {
   166  			t.Errorf("FAIL %s stack depth: %d expected: %d", name, actualInt, expectedStackDepth)
   167  		}
   168  
   169  		codeLocation = frames[0].Loc()
   170  
   171  		// codeLocation github.com/haraldrudell/parl/perrors.newGoroutine
   172  		// /opt/sw/parl/perrors/new-errorf_test.go:241
   173  		t.Logf("codeLocation %s", codeLocation)
   174  
   175  		// filename should match
   176  		if !strings.HasSuffix(codeLocation.File, fileExp) {
   177  			t.Errorf("FAIL %s top stack frame not from this file: %q should end: %q", name, codeLocation.File, fileExp)
   178  		}
   179  	}
   180  }
   181  
   182  // expectedFile obtains the file numer for this file
   183  func expectedFile() (file, failure string) {
   184  	// 0 is the caller of Caller
   185  	// pc unitptr to get function name
   186  	// file "/opt/sw/privates/parl/error116/stackslice_test.go"
   187  	// line int
   188  	if pc, file0, line, ok := runtime.Caller(0); !ok {
   189  		failure = "runtime.Caller failed"
   190  	} else {
   191  		_ = pc
   192  		_ = line
   193  		file = file0
   194  	}
   195  	return
   196  }
   197  
   198  // invokeStack invokes [perrors.Stack] in a goroutine
   199  func invokeStack(err error) (errStack error) {
   200  	var ch = make(chan struct{})
   201  	go stackGoroutine(err, ch, &errStack)
   202  	<-ch
   203  
   204  	return
   205  }
   206  
   207  // stackGoroutine is a goroutine invoking [perrors.Stack]
   208  func stackGoroutine(err error, ch chan struct{}, errp *error) {
   209  	defer close(ch)
   210  
   211  	*errp = Stack(err)
   212  }
   213  
   214  // invokeStack invokes [perrors.Stack] in a goroutine
   215  func invokeStackn(err error) (errStack error) {
   216  	var ch = make(chan struct{})
   217  	go stacknGoroutine(err, ch, &errStack)
   218  	<-ch
   219  
   220  	return
   221  }
   222  
   223  // stackGoroutine is a goroutine invoking [perrors.Stack]
   224  func stacknGoroutine(err error, ch chan struct{}, errp *error) {
   225  	defer close(ch)
   226  
   227  	*errp = Stackn(err, 0)
   228  }
   229  
   230  // invokeNew invokes [perrors.New] in a goroutine
   231  func invokeNew(message string) (errStack error) {
   232  	var ch = make(chan struct{})
   233  	go newGoroutine(message, ch, &errStack)
   234  	<-ch
   235  
   236  	return
   237  }
   238  
   239  // stackGoroutine is a goroutine invoking [perrors.Stack]
   240  func newGoroutine(message string, ch chan struct{}, errp *error) {
   241  	defer close(ch)
   242  
   243  	*errp = New(message)
   244  }