...

Source file src/github.com/go-stack/stack/stack_test.go

Documentation: github.com/go-stack/stack

     1  package stack_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path"
     7  	"path/filepath"
     8  	"reflect"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/go-stack/stack"
    14  )
    15  
    16  func TestCaller(t *testing.T) {
    17  	t.Parallel()
    18  
    19  	c := stack.Caller(0)
    20  	_, file, line, ok := runtime.Caller(0)
    21  	line--
    22  	if !ok {
    23  		t.Fatal("runtime.Caller(0) failed")
    24  	}
    25  
    26  	if got, want := c.Frame().File, file; got != want {
    27  		t.Errorf("got file == %v, want file == %v", got, want)
    28  	}
    29  
    30  	if got, want := c.Frame().Line, line; got != want {
    31  		t.Errorf("got line == %v, want line == %v", got, want)
    32  	}
    33  }
    34  
    35  func f3(f1 func() stack.Call) stack.Call {
    36  	return f2(f1)
    37  }
    38  
    39  func f2(f1 func() stack.Call) stack.Call {
    40  	return f1()
    41  }
    42  
    43  func TestCallerMidstackInlined(t *testing.T) {
    44  	t.Parallel()
    45  
    46  	_, _, line, ok := runtime.Caller(0)
    47  	line -= 10 // adjust to return f1() line inside f2()
    48  	if !ok {
    49  		t.Fatal("runtime.Caller(0) failed")
    50  	}
    51  
    52  	c := f3(func() stack.Call {
    53  		return stack.Caller(2)
    54  	})
    55  
    56  	if got, want := c.Frame().Line, line; got != want {
    57  		t.Errorf("got line == %v, want line == %v", got, want)
    58  	}
    59  	if got, want := c.Frame().Function, "github.com/go-stack/stack_test.f3"; got != want {
    60  		t.Errorf("got func name == %v, want func name == %v", got, want)
    61  	}
    62  }
    63  
    64  func TestCallerPanic(t *testing.T) {
    65  	t.Parallel()
    66  
    67  	var (
    68  		line int
    69  		ok   bool
    70  	)
    71  
    72  	defer func() {
    73  		if recover() != nil {
    74  			var pcs [32]uintptr
    75  			n := runtime.Callers(1, pcs[:])
    76  			frames := runtime.CallersFrames(pcs[:n])
    77  			// count frames to runtime.sigpanic
    78  			panicIdx := 0
    79  			for {
    80  				f, more := frames.Next()
    81  				if f.Function == "runtime.sigpanic" {
    82  					break
    83  				}
    84  				panicIdx++
    85  				if !more {
    86  					t.Fatal("no runtime.sigpanic entry on the stack")
    87  				}
    88  			}
    89  			c := stack.Caller(panicIdx)
    90  			if got, want := c.Frame().Function, "runtime.sigpanic"; got != want {
    91  				t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want)
    92  			}
    93  			c1 := stack.Caller(panicIdx + 1)
    94  			if got, want := c1.Frame().Function, "github.com/go-stack/stack_test.TestCallerPanic"; got != want {
    95  				t.Errorf("TestCallerPanic frame: got name == %v, want name == %v", got, want)
    96  			}
    97  			if got, want := c1.Frame().Line, line; got != want {
    98  				t.Errorf("TestCallerPanic frame: got line == %v, want line == %v", got, want)
    99  			}
   100  		}
   101  	}()
   102  
   103  	_, _, line, ok = runtime.Caller(0)
   104  	line += 7 // adjust to match line of panic below
   105  	if !ok {
   106  		t.Fatal("runtime.Caller(0) failed")
   107  	}
   108  	// Initiate a sigpanic.
   109  	var x *uintptr
   110  	_ = *x
   111  }
   112  
   113  type tholder struct {
   114  	trace func() stack.CallStack
   115  }
   116  
   117  func (th *tholder) traceLabyrinth() stack.CallStack {
   118  	for {
   119  		return th.trace()
   120  	}
   121  }
   122  
   123  func TestTrace(t *testing.T) {
   124  	t.Parallel()
   125  
   126  	_, _, line, ok := runtime.Caller(0)
   127  	if !ok {
   128  		t.Fatal("runtime.Caller(0) failed")
   129  	}
   130  
   131  	fh := tholder{
   132  		trace: func() stack.CallStack {
   133  			cs := stack.Trace()
   134  			return cs
   135  		},
   136  	}
   137  
   138  	cs := fh.traceLabyrinth()
   139  
   140  	lines := []int{line + 7, line - 7, line + 12}
   141  
   142  	for i, line := range lines {
   143  		if got, want := cs[i].Frame().Line, line; got != want {
   144  			t.Errorf("got line[%d] == %v, want line[%d] == %v", i, got, i, want)
   145  		}
   146  	}
   147  }
   148  
   149  // Test stack handling originating from a sigpanic.
   150  func TestTracePanic(t *testing.T) {
   151  	t.Parallel()
   152  
   153  	var (
   154  		line int
   155  		ok   bool
   156  	)
   157  
   158  	defer func() {
   159  		if recover() != nil {
   160  			trace := stack.Trace()
   161  
   162  			// find runtime.sigpanic
   163  			panicIdx := -1
   164  			for i, c := range trace {
   165  				if c.Frame().Function == "runtime.sigpanic" {
   166  					panicIdx = i
   167  					break
   168  				}
   169  			}
   170  			if panicIdx == -1 {
   171  				t.Fatal("no runtime.sigpanic entry on the stack")
   172  			}
   173  			if got, want := trace[panicIdx].Frame().Function, "runtime.sigpanic"; got != want {
   174  				t.Errorf("sigpanic frame: got name == %v, want name == %v", got, want)
   175  			}
   176  			if got, want := trace[panicIdx+1].Frame().Function, "github.com/go-stack/stack_test.TestTracePanic"; got != want {
   177  				t.Errorf("TestTracePanic frame: got name == %v, want name == %v", got, want)
   178  			}
   179  			if got, want := trace[panicIdx+1].Frame().Line, line; got != want {
   180  				t.Errorf("TestTracePanic frame: got line == %v, want line == %v", got, want)
   181  			}
   182  		}
   183  	}()
   184  
   185  	_, _, line, ok = runtime.Caller(0)
   186  	line += 7 // adjust to match line of panic below
   187  	if !ok {
   188  		t.Fatal("runtime.Caller(0) failed")
   189  	}
   190  	// Initiate a sigpanic.
   191  	var x *uintptr
   192  	_ = *x
   193  }
   194  
   195  const importPath = "github.com/go-stack/stack"
   196  
   197  type testType struct{}
   198  
   199  func (tt testType) testMethod() (c stack.Call, file string, line int, ok bool) {
   200  	c = stack.Caller(0)
   201  	_, file, line, ok = runtime.Caller(0)
   202  	line--
   203  	return
   204  }
   205  
   206  func TestCallFormat(t *testing.T) {
   207  	t.Parallel()
   208  
   209  	c := stack.Caller(0)
   210  	_, file, line, ok := runtime.Caller(0)
   211  	line--
   212  	if !ok {
   213  		t.Fatal("runtime.Caller(0) failed")
   214  	}
   215  	relFile := path.Join(importPath, filepath.Base(file))
   216  
   217  	c2, file2, line2, ok2 := testType{}.testMethod()
   218  	if !ok2 {
   219  		t.Fatal("runtime.Caller(0) failed")
   220  	}
   221  	relFile2 := path.Join(importPath, filepath.Base(file2))
   222  
   223  	data := []struct {
   224  		c    stack.Call
   225  		desc string
   226  		fmt  string
   227  		out  string
   228  	}{
   229  		{stack.Call{}, "error", "%s", "%!s(NOFUNC)"},
   230  
   231  		{c, "func", "%s", path.Base(file)},
   232  		{c, "func", "%+s", relFile},
   233  		{c, "func", "%#s", file},
   234  		{c, "func", "%d", fmt.Sprint(line)},
   235  		{c, "func", "%n", "TestCallFormat"},
   236  		{c, "func", "%+n", "github.com/go-stack/stack_test.TestCallFormat"},
   237  		{c, "func", "%k", "stack_test"},
   238  		{c, "func", "%+k", "github.com/go-stack/stack_test"},
   239  		{c, "func", "%v", fmt.Sprint(path.Base(file), ":", line)},
   240  		{c, "func", "%+v", fmt.Sprint(relFile, ":", line)},
   241  		{c, "func", "%#v", fmt.Sprint(file, ":", line)},
   242  
   243  		{c2, "meth", "%s", path.Base(file2)},
   244  		{c2, "meth", "%+s", relFile2},
   245  		{c2, "meth", "%#s", file2},
   246  		{c2, "meth", "%d", fmt.Sprint(line2)},
   247  		{c2, "meth", "%n", "testType.testMethod"},
   248  		{c2, "meth", "%+n", "github.com/go-stack/stack_test.testType.testMethod"},
   249  		{c2, "meth", "%k", "stack_test"},
   250  		{c2, "meth", "%+k", "github.com/go-stack/stack_test"},
   251  		{c2, "meth", "%v", fmt.Sprint(path.Base(file2), ":", line2)},
   252  		{c2, "meth", "%+v", fmt.Sprint(relFile2, ":", line2)},
   253  		{c2, "meth", "%#v", fmt.Sprint(file2, ":", line2)},
   254  	}
   255  
   256  	for _, d := range data {
   257  		got := fmt.Sprintf(d.fmt, d.c)
   258  		if got != d.out {
   259  			t.Errorf("fmt.Sprintf(%q, Call(%s)) = %s, want %s", d.fmt, d.desc, got, d.out)
   260  		}
   261  	}
   262  }
   263  
   264  func TestCallString(t *testing.T) {
   265  	t.Parallel()
   266  
   267  	c := stack.Caller(0)
   268  	_, file, line, ok := runtime.Caller(0)
   269  	line--
   270  	if !ok {
   271  		t.Fatal("runtime.Caller(0) failed")
   272  	}
   273  
   274  	c2, file2, line2, ok2 := testType{}.testMethod()
   275  	if !ok2 {
   276  		t.Fatal("runtime.Caller(0) failed")
   277  	}
   278  
   279  	data := []struct {
   280  		c    stack.Call
   281  		desc string
   282  		out  string
   283  	}{
   284  		{stack.Call{}, "error", "%!v(NOFUNC)"},
   285  		{c, "func", fmt.Sprint(path.Base(file), ":", line)},
   286  		{c2, "meth", fmt.Sprint(path.Base(file2), ":", line2)},
   287  	}
   288  
   289  	for _, d := range data {
   290  		got := d.c.String()
   291  		if got != d.out {
   292  			t.Errorf("got %s, want %s", got, d.out)
   293  		}
   294  	}
   295  }
   296  
   297  func TestCallMarshalText(t *testing.T) {
   298  	t.Parallel()
   299  
   300  	c := stack.Caller(0)
   301  	_, file, line, ok := runtime.Caller(0)
   302  	line--
   303  	if !ok {
   304  		t.Fatal("runtime.Caller(0) failed")
   305  	}
   306  
   307  	c2, file2, line2, ok2 := testType{}.testMethod()
   308  	if !ok2 {
   309  		t.Fatal("runtime.Caller(0) failed")
   310  	}
   311  
   312  	data := []struct {
   313  		c    stack.Call
   314  		desc string
   315  		out  []byte
   316  		err  error
   317  	}{
   318  		{stack.Call{}, "error", nil, stack.ErrNoFunc},
   319  		{c, "func", []byte(fmt.Sprint(path.Base(file), ":", line)), nil},
   320  		{c2, "meth", []byte(fmt.Sprint(path.Base(file2), ":", line2)), nil},
   321  	}
   322  
   323  	for _, d := range data {
   324  		text, err := d.c.MarshalText()
   325  		if got, want := err, d.err; got != want {
   326  			t.Errorf("%s: got err %v, want err %v", d.desc, got, want)
   327  		}
   328  		if got, want := text, d.out; !reflect.DeepEqual(got, want) {
   329  			t.Errorf("%s: got %s, want %s", d.desc, got, want)
   330  		}
   331  	}
   332  }
   333  
   334  func TestCallStackString(t *testing.T) {
   335  	cs, line0 := getTrace(t)
   336  	_, file, line1, ok := runtime.Caller(0)
   337  	line1--
   338  	if !ok {
   339  		t.Fatal("runtime.Caller(0) failed")
   340  	}
   341  	file = path.Base(file)
   342  	if got, want := cs.String(), fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1); got != want {
   343  		t.Errorf("\n got %v\nwant %v", got, want)
   344  	}
   345  }
   346  
   347  func TestCallStackMarshalText(t *testing.T) {
   348  	cs, line0 := getTrace(t)
   349  	_, file, line1, ok := runtime.Caller(0)
   350  	line1--
   351  	if !ok {
   352  		t.Fatal("runtime.Caller(0) failed")
   353  	}
   354  	file = path.Base(file)
   355  	text, _ := cs.MarshalText()
   356  	if got, want := text, []byte(fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1)); !reflect.DeepEqual(got, want) {
   357  		t.Errorf("\n got %v\nwant %v", got, want)
   358  	}
   359  }
   360  
   361  func getTrace(t *testing.T) (stack.CallStack, int) {
   362  	cs := stack.Trace().TrimRuntime()
   363  	_, _, line, ok := runtime.Caller(0)
   364  	line--
   365  	if !ok {
   366  		t.Fatal("runtime.Caller(0) failed")
   367  	}
   368  	return cs, line
   369  }
   370  
   371  func TestTrimAbove(t *testing.T) {
   372  	trace := trimAbove()
   373  	if got, want := len(trace), 2; got != want {
   374  		t.Fatalf("got len(trace) == %v, want %v, trace: %n", got, want, trace)
   375  	}
   376  	if got, want := fmt.Sprintf("%n", trace[1]), "TestTrimAbove"; got != want {
   377  		t.Errorf("got %q, want %q", got, want)
   378  	}
   379  }
   380  
   381  func trimAbove() stack.CallStack {
   382  	call := stack.Caller(1)
   383  	trace := stack.Trace()
   384  	return trace.TrimAbove(call)
   385  }
   386  
   387  func TestTrimBelow(t *testing.T) {
   388  	trace := trimBelow()
   389  	if got, want := fmt.Sprintf("%n", trace[0]), "TestTrimBelow"; got != want {
   390  		t.Errorf("got %q, want %q", got, want)
   391  	}
   392  }
   393  
   394  func trimBelow() stack.CallStack {
   395  	call := stack.Caller(1)
   396  	trace := stack.Trace()
   397  	return trace.TrimBelow(call)
   398  }
   399  
   400  func TestTrimRuntime(t *testing.T) {
   401  	trace := stack.Trace().TrimRuntime()
   402  	if got, want := len(trace), 1; got != want {
   403  		t.Errorf("got len(trace) == %v, want %v, goroot: %q, trace: %#v", got, want, runtime.GOROOT(), trace)
   404  	}
   405  }
   406  
   407  func BenchmarkCallVFmt(b *testing.B) {
   408  	c := stack.Caller(0)
   409  	b.ResetTimer()
   410  	for i := 0; i < b.N; i++ {
   411  		fmt.Fprint(ioutil.Discard, c)
   412  	}
   413  }
   414  
   415  func BenchmarkCallPlusVFmt(b *testing.B) {
   416  	c := stack.Caller(0)
   417  	b.ResetTimer()
   418  	for i := 0; i < b.N; i++ {
   419  		fmt.Fprintf(ioutil.Discard, "%+v", c)
   420  	}
   421  }
   422  
   423  func BenchmarkCallSharpVFmt(b *testing.B) {
   424  	c := stack.Caller(0)
   425  	b.ResetTimer()
   426  	for i := 0; i < b.N; i++ {
   427  		fmt.Fprintf(ioutil.Discard, "%#v", c)
   428  	}
   429  }
   430  
   431  func BenchmarkCallSFmt(b *testing.B) {
   432  	c := stack.Caller(0)
   433  	b.ResetTimer()
   434  	for i := 0; i < b.N; i++ {
   435  		fmt.Fprintf(ioutil.Discard, "%s", c)
   436  	}
   437  }
   438  
   439  func BenchmarkCallPlusSFmt(b *testing.B) {
   440  	c := stack.Caller(0)
   441  	b.ResetTimer()
   442  	for i := 0; i < b.N; i++ {
   443  		fmt.Fprintf(ioutil.Discard, "%+s", c)
   444  	}
   445  }
   446  
   447  func BenchmarkCallSharpSFmt(b *testing.B) {
   448  	c := stack.Caller(0)
   449  	b.ResetTimer()
   450  	for i := 0; i < b.N; i++ {
   451  		fmt.Fprintf(ioutil.Discard, "%#s", c)
   452  	}
   453  }
   454  
   455  func BenchmarkCallDFmt(b *testing.B) {
   456  	c := stack.Caller(0)
   457  	b.ResetTimer()
   458  	for i := 0; i < b.N; i++ {
   459  		fmt.Fprintf(ioutil.Discard, "%d", c)
   460  	}
   461  }
   462  
   463  func BenchmarkCallNFmt(b *testing.B) {
   464  	c := stack.Caller(0)
   465  	b.ResetTimer()
   466  	for i := 0; i < b.N; i++ {
   467  		fmt.Fprintf(ioutil.Discard, "%n", c)
   468  	}
   469  }
   470  
   471  func BenchmarkCallPlusNFmt(b *testing.B) {
   472  	c := stack.Caller(0)
   473  	b.ResetTimer()
   474  	for i := 0; i < b.N; i++ {
   475  		fmt.Fprintf(ioutil.Discard, "%+n", c)
   476  	}
   477  }
   478  
   479  func BenchmarkCaller(b *testing.B) {
   480  	for i := 0; i < b.N; i++ {
   481  		stack.Caller(0)
   482  	}
   483  }
   484  
   485  func BenchmarkTrace(b *testing.B) {
   486  	for i := 0; i < b.N; i++ {
   487  		stack.Trace()
   488  	}
   489  }
   490  
   491  func deepStack(depth int, b *testing.B) stack.CallStack {
   492  	if depth > 0 {
   493  		return deepStack(depth-1, b)
   494  	}
   495  	b.StartTimer()
   496  	s := stack.Trace()
   497  	return s
   498  }
   499  
   500  func BenchmarkTrace10(b *testing.B) {
   501  	for i := 0; i < b.N; i++ {
   502  		b.StopTimer()
   503  		deepStack(10, b)
   504  	}
   505  }
   506  
   507  func BenchmarkTrace50(b *testing.B) {
   508  	b.StopTimer()
   509  	for i := 0; i < b.N; i++ {
   510  		deepStack(50, b)
   511  	}
   512  }
   513  
   514  func BenchmarkTrace100(b *testing.B) {
   515  	b.StopTimer()
   516  	for i := 0; i < b.N; i++ {
   517  		deepStack(100, b)
   518  	}
   519  }
   520  
   521  ////////////////
   522  // Benchmark functions followed by formatting
   523  ////////////////
   524  
   525  func BenchmarkCallerAndVFmt(b *testing.B) {
   526  	for i := 0; i < b.N; i++ {
   527  		fmt.Fprint(ioutil.Discard, stack.Caller(0))
   528  	}
   529  }
   530  
   531  func BenchmarkTraceAndVFmt(b *testing.B) {
   532  	for i := 0; i < b.N; i++ {
   533  		fmt.Fprint(ioutil.Discard, stack.Trace())
   534  	}
   535  }
   536  
   537  func BenchmarkTrace10AndVFmt(b *testing.B) {
   538  	for i := 0; i < b.N; i++ {
   539  		b.StopTimer()
   540  		fmt.Fprint(ioutil.Discard, deepStack(10, b))
   541  	}
   542  }
   543  
   544  ////////////////
   545  // Baseline against package runtime.
   546  ////////////////
   547  
   548  func BenchmarkRuntimeCaller(b *testing.B) {
   549  	for i := 0; i < b.N; i++ {
   550  		runtime.Caller(0)
   551  	}
   552  }
   553  
   554  func BenchmarkRuntimeCallerAndFmt(b *testing.B) {
   555  	for i := 0; i < b.N; i++ {
   556  		_, file, line, _ := runtime.Caller(0)
   557  		const sep = "/"
   558  		if i := strings.LastIndex(file, sep); i != -1 {
   559  			file = file[i+len(sep):]
   560  		}
   561  		fmt.Fprint(ioutil.Discard, file, ":", line)
   562  	}
   563  }
   564  
   565  func BenchmarkFuncForPC(b *testing.B) {
   566  	pc, _, _, _ := runtime.Caller(0)
   567  	pc--
   568  	b.ResetTimer()
   569  	for i := 0; i < b.N; i++ {
   570  		runtime.FuncForPC(pc)
   571  	}
   572  }
   573  
   574  func BenchmarkFuncFileLine(b *testing.B) {
   575  	pc, _, _, _ := runtime.Caller(0)
   576  	pc--
   577  	fn := runtime.FuncForPC(pc)
   578  	b.ResetTimer()
   579  	for i := 0; i < b.N; i++ {
   580  		fn.FileLine(pc)
   581  	}
   582  }
   583  

View as plain text