github.com/pachyderm/pachyderm@v1.13.4/src/client/pkg/require/require.go (about)

     1  package require
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"runtime/debug"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    12  )
    13  
    14  // Matches checks that a string matches a regular-expression.
    15  func Matches(tb testing.TB, expectedMatch string, actual string, msgAndArgs ...interface{}) {
    16  	tb.Helper()
    17  	r, err := regexp.Compile(expectedMatch)
    18  	if err != nil {
    19  		fatal(tb, msgAndArgs, "Match string provided (%v) is invalid", expectedMatch)
    20  	}
    21  	if !r.MatchString(actual) {
    22  		fatal(tb, msgAndArgs, "Actual string (%v) does not match pattern (%v)", actual, expectedMatch)
    23  	}
    24  }
    25  
    26  // OneOfMatches checks whether one element of a slice matches a regular-expression.
    27  func OneOfMatches(tb testing.TB, expectedMatch string, actuals []string, msgAndArgs ...interface{}) {
    28  	tb.Helper()
    29  	r, err := regexp.Compile(expectedMatch)
    30  	if err != nil {
    31  		fatal(tb, msgAndArgs, "Match string provided (%v) is invalid", expectedMatch)
    32  	}
    33  	for _, actual := range actuals {
    34  		if r.MatchString(actual) {
    35  			return
    36  		}
    37  	}
    38  	fatal(tb, msgAndArgs, "None of actual strings (%v) match pattern (%v)", actuals, expectedMatch)
    39  
    40  }
    41  
    42  // Equal checks the equality of two values
    43  func Equal(tb testing.TB, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
    44  	tb.Helper()
    45  	if err := EqualOrErr(expected, actual); err != nil {
    46  		fatal(tb, msgAndArgs, err.Error())
    47  	}
    48  }
    49  
    50  // EqualOrErr checks equality of two values and returns an error if they're not equal
    51  func EqualOrErr(expected interface{}, actual interface{}) error {
    52  	eV, aV := reflect.ValueOf(expected), reflect.ValueOf(actual)
    53  	if eV.Type() != aV.Type() {
    54  		return errors.Errorf("Not equal: %T(%#v) (expected)\n"+
    55  			"        != %T(%#v) (actual)", expected, expected, actual, actual)
    56  	}
    57  	if !reflect.DeepEqual(expected, actual) {
    58  		return errors.Errorf(
    59  			"Not equal: %#v (expected)\n"+
    60  				"        != %#v (actual)", expected, actual)
    61  	}
    62  	return nil
    63  }
    64  
    65  // NotEqual checks inequality of two values.
    66  func NotEqual(tb testing.TB, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
    67  	tb.Helper()
    68  	if reflect.DeepEqual(expected, actual) {
    69  		fatal(
    70  			tb,
    71  			msgAndArgs,
    72  			"Equal: %#v (expected)\n"+
    73  				"    == %#v (actual)", expected, actual)
    74  	}
    75  }
    76  
    77  // ElementsEqualOrErr returns nil if the elements of the slice "expecteds" are
    78  // exactly the elements of the slice "actuals", ignoring order (i.e.
    79  // setwise-equal), and an error otherwise.
    80  //
    81  // Unlike other require.* functions, this returns an error, so that if the
    82  // caller is polling e.g. ListCommit or ListAdmins, they can wrap
    83  // ElementsEqualOrErr in a retry loop.
    84  //
    85  // Also, like ElementsEqual, treat 'nil' and the empty slice as equivalent (for
    86  // convenience)
    87  func ElementsEqualOrErr(expecteds interface{}, actuals interface{}) error {
    88  	es := reflect.ValueOf(expecteds)
    89  	as := reflect.ValueOf(actuals)
    90  
    91  	// If either slice is empty, check that both are empty
    92  	esIsEmpty := expecteds == nil || es.IsNil() || (es.Kind() == reflect.Slice && es.Len() == 0)
    93  	asIsEmpty := actuals == nil || as.IsNil() || (as.Kind() == reflect.Slice && as.Len() == 0)
    94  	if esIsEmpty && asIsEmpty {
    95  		return nil
    96  	} else if esIsEmpty {
    97  		return errors.Errorf("expected 0 elements, but got %d: %v", as.Len(), actuals)
    98  	} else if asIsEmpty {
    99  		return errors.Errorf("expected %d elements, but got 0\n  expected: %v", es.Len(), expecteds)
   100  	}
   101  
   102  	// Both slices are nonempty--compare elements
   103  	if es.Kind() != reflect.Slice {
   104  		return errors.Errorf("\"expecteds\" must be a slice, but was %s", es.Type().String())
   105  	}
   106  	if as.Kind() != reflect.Slice {
   107  		return errors.Errorf("\"actuals\" must be a slice, but was %s", as.Type().String())
   108  	}
   109  
   110  	// Make sure expecteds and actuals are slices of the same type, modulo
   111  	// pointers (*T ~= T in this function)
   112  	esArePtrs := es.Type().Elem().Kind() == reflect.Ptr
   113  	asArePtrs := as.Type().Elem().Kind() == reflect.Ptr
   114  	esElemType, asElemType := es.Type().Elem(), as.Type().Elem()
   115  	if esArePtrs {
   116  		esElemType = es.Type().Elem().Elem()
   117  	}
   118  	if asArePtrs {
   119  		asElemType = as.Type().Elem().Elem()
   120  	}
   121  	if esElemType != asElemType {
   122  		return errors.Errorf("expected []%s but got []%s", es.Type().Elem(), as.Type().Elem())
   123  	}
   124  
   125  	if es.Len() != as.Len() {
   126  		// slight abuse of error: contains newlines so final output prints well
   127  		return errors.Errorf("expected %d elements, but got %d\n  expected: %v\n  actual: %v",
   128  			es.Len(), as.Len(), expecteds, actuals)
   129  	}
   130  
   131  	// Count up elements of expecteds
   132  	intType := reflect.TypeOf(int64(0))
   133  	expectedCt := reflect.MakeMap(reflect.MapOf(esElemType, intType))
   134  	for i := 0; i < es.Len(); i++ {
   135  		v := es.Index(i)
   136  		if esArePtrs {
   137  			v = v.Elem()
   138  		}
   139  		if !expectedCt.MapIndex(v).IsValid() {
   140  			expectedCt.SetMapIndex(v, reflect.ValueOf(int64(1)))
   141  		} else {
   142  			newCt := expectedCt.MapIndex(v).Int() + 1
   143  			expectedCt.SetMapIndex(v, reflect.ValueOf(newCt))
   144  		}
   145  	}
   146  
   147  	// Count up elements of actuals
   148  	actualCt := reflect.MakeMap(reflect.MapOf(asElemType, intType))
   149  	for i := 0; i < as.Len(); i++ {
   150  		v := as.Index(i)
   151  		if asArePtrs {
   152  			v = v.Elem()
   153  		}
   154  		if !actualCt.MapIndex(v).IsValid() {
   155  			actualCt.SetMapIndex(v, reflect.ValueOf(int64(1)))
   156  		} else {
   157  			newCt := actualCt.MapIndex(v).Int() + 1
   158  			actualCt.SetMapIndex(v, reflect.ValueOf(newCt))
   159  		}
   160  	}
   161  	for _, key := range expectedCt.MapKeys() {
   162  		ec := expectedCt.MapIndex(key)
   163  		ac := actualCt.MapIndex(key)
   164  		if !ec.IsValid() || !ac.IsValid() || ec.Int() != ac.Int() {
   165  			ecInt, acInt := int64(0), int64(0)
   166  			if ec.IsValid() {
   167  				ecInt = ec.Int()
   168  			}
   169  			if ac.IsValid() {
   170  				acInt = ac.Int()
   171  			}
   172  			// slight abuse of error: contains newlines so final output prints well
   173  			return errors.Errorf("expected %d copies of %v, but got %d copies\n  expected: %v\n  actual: %v", ecInt, key, acInt, expecteds, actuals)
   174  		}
   175  	}
   176  	return nil
   177  }
   178  
   179  // ImagesEqual is similar to 'ElementsEqualUnderFn', but it applies 'f' to both
   180  // 'expecteds' and 'actuals'. This is useful for doing before/after
   181  // comparisons. This can also compare 'T' and '*T' , but 'f' should expect
   182  // interfaces wrapping the underlying types of 'expecteds' (e.g. if 'expecteds'
   183  // has type []*T and 'actuals' has type []T, then 'f' should cast its argument
   184  // to '*T')
   185  //
   186  // Like ElementsEqual, treat 'nil' and the empty slice as equivalent (for
   187  // convenience)
   188  func ImagesEqual(tb testing.TB, expecteds interface{}, actuals interface{}, f func(interface{}) interface{}, msgAndArgs ...interface{}) {
   189  	tb.Helper()
   190  	as := reflect.ValueOf(actuals)
   191  	es := reflect.ValueOf(expecteds)
   192  
   193  	// Check if 'actuals' is empty; if so, just pass nil (no need to transform)
   194  	if actuals != nil && !as.IsNil() && as.Kind() != reflect.Slice {
   195  		fatal(tb, msgAndArgs, fmt.Sprintf("\"actuals\" must be a slice, but was %s", as.Type().String()))
   196  	} else if actuals == nil || as.IsNil() || as.Len() == 0 {
   197  		// Just pass 'nil' for 'actuals'
   198  		if err := ElementsEqualOrErr(expecteds, nil); err != nil {
   199  			fatal(tb, msgAndArgs, err.Error())
   200  		}
   201  		return
   202  	}
   203  
   204  	// Check if 'expecteds' is empty: if so, return an error (since 'actuals' is
   205  	// not empty)
   206  	if expecteds != nil && !es.IsNil() && es.Kind() != reflect.Slice {
   207  		fatal(tb, msgAndArgs, fmt.Sprintf("\"expecteds\" must be a slice, but was %s", as.Type().String()))
   208  	} else if expecteds == nil || es.IsNil() || es.Len() == 0 {
   209  		fatal(tb, msgAndArgs, fmt.Sprintf("expected 0 distinct elements, but got %d\n elements (before function is applied): %v", as.Len(), actuals))
   210  	}
   211  
   212  	// Make sure expecteds and actuals are slices of the same type, modulo
   213  	// pointers (*T ~= T in this function). This is better than some kind of
   214  	// opaque reflection error from calling 'f' on mismatched types.
   215  	esArePtrs := es.Type().Elem().Kind() == reflect.Ptr
   216  	asArePtrs := as.Type().Elem().Kind() == reflect.Ptr
   217  	esUnderlyingType, asUnderlyingType := es.Type().Elem(), as.Type().Elem()
   218  	if esArePtrs {
   219  		esUnderlyingType = es.Type().Elem().Elem()
   220  	}
   221  	if asArePtrs {
   222  		asUnderlyingType = as.Type().Elem().Elem()
   223  	}
   224  	if esUnderlyingType != asUnderlyingType {
   225  		fatal(tb, msgAndArgs, "expected []%s but got []%s", esUnderlyingType, asUnderlyingType)
   226  	}
   227  
   228  	if es.Len() != as.Len() {
   229  		// slight abuse of error: contains newlines so final output prints well
   230  		fatal(tb, msgAndArgs, "expected %d elements, but got %d\n  expected: %v\n  actual: %v",
   231  			es.Len(), as.Len(), expecteds, actuals)
   232  	}
   233  
   234  	// apply 'f' to both 'es' and 'as'. Make 'es[i]' have the same underlying
   235  	// type as 'as[i]' (may need to take address or dereference elements) so 'f'
   236  	// can apply to both.
   237  	newExpecteds, newActuals := make([]interface{}, 0, as.Len()), make([]interface{}, 0, as.Len())
   238  	for i := 0; i < es.Len(); i++ {
   239  		switch {
   240  		case asArePtrs && !esArePtrs:
   241  			newExpecteds = append(newExpecteds, f(es.Index(i).Addr().Interface()))
   242  		case !asArePtrs && esArePtrs:
   243  			newExpecteds = append(newExpecteds, f(es.Index(i).Elem().Interface()))
   244  		default:
   245  			newExpecteds = append(newExpecteds, f(es.Index(i).Interface()))
   246  		}
   247  	}
   248  	for i := 0; i < as.Len(); i++ {
   249  		newActuals = append(newActuals, f(as.Index(i).Interface()))
   250  	}
   251  	if err := ElementsEqualOrErr(newExpecteds, newActuals); err != nil {
   252  		fatal(tb, msgAndArgs, err.Error())
   253  	}
   254  }
   255  
   256  // ElementsEqualUnderFn checks that the elements of the slice 'expecteds' are
   257  // the same as the elements of the slice 'actuals' under 'f', ignoring order
   258  // (i.e.  'expecteds' and 'map(f, actuals)' are setwise-equal, but respecting
   259  // duplicates). This is useful for cases where ElementsEqual doesn't quite work,
   260  // e.g. because the type in 'expecteds'/'actuals' contains a pointer, or
   261  // 'actuals' contains superfluous data which you wish to discard.
   262  //
   263  // Like ElementsEqual, treat 'nil' and the empty slice as equivalent (for
   264  // convenience)
   265  func ElementsEqualUnderFn(tb testing.TB, expecteds interface{}, actuals interface{}, f func(interface{}) interface{}, msgAndArgs ...interface{}) {
   266  	tb.Helper()
   267  	as := reflect.ValueOf(actuals)
   268  	es := reflect.ValueOf(expecteds)
   269  
   270  	// Check if 'actuals' is empty; if so, just pass nil (no need to transform)
   271  	if actuals != nil && !as.IsNil() && as.Kind() != reflect.Slice {
   272  		fatal(tb, msgAndArgs, fmt.Sprintf("\"actuals\" must be a slice, but was %s", as.Type().String()))
   273  	} else if actuals == nil || as.IsNil() || as.Len() == 0 {
   274  		// Just pass 'nil' for 'actuals'
   275  		if err := ElementsEqualOrErr(expecteds, nil); err != nil {
   276  			fatal(tb, msgAndArgs, err.Error())
   277  		}
   278  		return
   279  	}
   280  
   281  	// Check if 'expecteds' is empty: if so, return an error (since 'actuals' is
   282  	// not empty)
   283  	if expecteds != nil && !es.IsNil() && es.Kind() != reflect.Slice {
   284  		fatal(tb, msgAndArgs, fmt.Sprintf("\"expecteds\" must be a slice, but was %s", as.Type().String()))
   285  	} else if expecteds == nil || es.IsNil() || es.Len() == 0 {
   286  		fatal(tb, msgAndArgs, fmt.Sprintf("expected 0 distinct elements, but got %d\n elements (before function is applied): %v", as.Len(), actuals))
   287  	}
   288  
   289  	// Neither 'expecteds' nor 'actuals' is empty--apply 'f' to 'actuals'
   290  	newActuals := reflect.MakeSlice(reflect.SliceOf(es.Type().Elem()), as.Len(), as.Len())
   291  	for i := 0; i < as.Len(); i++ {
   292  		newActuals.Index(i).Set(reflect.ValueOf(f(as.Index(i).Interface())))
   293  	}
   294  	if err := ElementsEqualOrErr(expecteds, newActuals.Interface()); err != nil {
   295  		fatal(tb, msgAndArgs, err.Error())
   296  	}
   297  }
   298  
   299  // ElementsEqual checks that the elements of the slice "expecteds" are
   300  // exactly the elements of the slice "actuals", ignoring order (i.e.
   301  // setwise-equal, but respecting duplicates).
   302  //
   303  // Note that if the elements of 'expecteds' and 'actuals' are pointers,
   304  // ElementsEqual will unwrap the pointers before comparing them, so that the
   305  // output of e.g. ListCommit(), which returns []*pfs.Commit can easily be
   306  // verfied.
   307  //
   308  // Also, treat 'nil' and the empty slice as equivalent, so that callers can
   309  // pass 'nil' for 'expecteds'.
   310  func ElementsEqual(tb testing.TB, expecteds interface{}, actuals interface{}, msgAndArgs ...interface{}) {
   311  	tb.Helper()
   312  	if err := ElementsEqualOrErr(expecteds, actuals); err != nil {
   313  		fatal(tb, msgAndArgs, err.Error())
   314  	}
   315  }
   316  
   317  // oneOfEquals is a helper function for EqualOneOf, OneOfEquals and NoneEquals, that simply
   318  // returns a bool indicating whether 'elem' is in 'slice'. 'sliceName' is used for errors
   319  func oneOfEquals(sliceName string, slice interface{}, elem interface{}) (bool, error) {
   320  	e := reflect.ValueOf(elem)
   321  	sl := reflect.ValueOf(slice)
   322  	if slice == nil || sl.IsNil() {
   323  		sl = reflect.MakeSlice(reflect.SliceOf(e.Type()), 0, 0)
   324  	}
   325  	if sl.Kind() != reflect.Slice {
   326  		return false, errors.Errorf("\"%s\" must a be a slice, but instead was %s", sliceName, sl.Type().String())
   327  	}
   328  	if e.Type() != sl.Type().Elem() {
   329  		return false, nil
   330  	}
   331  	arePtrs := e.Kind() == reflect.Ptr
   332  	for i := 0; i < sl.Len(); i++ {
   333  		if !arePtrs && reflect.DeepEqual(e.Interface(), sl.Index(i).Interface()) {
   334  			return true, nil
   335  		} else if arePtrs && reflect.DeepEqual(e.Elem().Interface(), sl.Index(i).Elem().Interface()) {
   336  			return true, nil
   337  		}
   338  	}
   339  	return false, nil
   340  }
   341  
   342  // EqualOneOf checks if a value is equal to one of the elements of a slice. Note
   343  // that if expecteds and actual are a slice of pointers and a pointer
   344  // respectively, then the pointers are unwrapped before comparison (so this
   345  // functions works for e.g. *pfs.Commit and []*pfs.Commit)
   346  func EqualOneOf(tb testing.TB, expecteds interface{}, actual interface{}, msgAndArgs ...interface{}) {
   347  	tb.Helper()
   348  	equal, err := oneOfEquals("expecteds", expecteds, actual)
   349  	if err != nil {
   350  		fatal(tb, msgAndArgs, err.Error())
   351  	}
   352  	if !equal {
   353  		fatal(
   354  			tb,
   355  			msgAndArgs,
   356  			"None of : %#v (expecteds)\n"+
   357  				"              == %#v (actual)", expecteds, actual)
   358  	}
   359  }
   360  
   361  // OneOfEquals checks whether one element of a slice equals a value. Like
   362  // EqualsOneOf, OneOfEquals unwraps pointers
   363  func OneOfEquals(tb testing.TB, expected interface{}, actuals interface{}, msgAndArgs ...interface{}) {
   364  	tb.Helper()
   365  	equal, err := oneOfEquals("actuals", actuals, expected)
   366  	if err != nil {
   367  		fatal(tb, msgAndArgs, err.Error())
   368  	}
   369  	if !equal {
   370  		fatal(tb, msgAndArgs,
   371  			"Not equal : %#v (expected)\n"+
   372  				" one of  != %#v (actuals)", expected, actuals)
   373  	}
   374  }
   375  
   376  // NoneEquals checks one element of a slice equals a value. Like
   377  // EqualsOneOf, NoneEquals unwraps pointers.
   378  func NoneEquals(tb testing.TB, expected interface{}, actuals interface{}, msgAndArgs ...interface{}) {
   379  	tb.Helper()
   380  	equal, err := oneOfEquals("actuals", actuals, expected)
   381  	if err != nil {
   382  		fatal(tb, msgAndArgs, err.Error())
   383  	}
   384  	if equal {
   385  		fatal(tb, msgAndArgs,
   386  			"Equal : %#v (expected)\n == one of %#v (actuals)", expected, actuals)
   387  	}
   388  }
   389  
   390  // NoError checks for no error.
   391  func NoError(tb testing.TB, err error, msgAndArgs ...interface{}) {
   392  	tb.Helper()
   393  	if err != nil {
   394  		fatal(tb, msgAndArgs, "No error is expected but got %v", err)
   395  	}
   396  }
   397  
   398  // NoErrorWithinT checks that 'f' finishes within time 't' and does not emit an
   399  // error
   400  func NoErrorWithinT(tb testing.TB, t time.Duration, f func() error, msgAndArgs ...interface{}) {
   401  	tb.Helper()
   402  	errCh := make(chan error)
   403  	go func() {
   404  		// This goro will leak if the timeout is exceeded, but it's okay because the
   405  		// test is failing anyway
   406  		errCh <- f()
   407  	}()
   408  	select {
   409  	case err := <-errCh:
   410  		if err != nil {
   411  			fatal(tb, msgAndArgs, "No error is expected but got %v", err)
   412  		}
   413  	case <-time.After(t):
   414  		fatal(tb, msgAndArgs, "operation did not finish within %s", t.String())
   415  	}
   416  }
   417  
   418  // NoErrorWithinTRetry checks that 'f' finishes within time 't' and does not
   419  // emit an error. Unlike NoErrorWithinT if f does error, it will retry it.
   420  func NoErrorWithinTRetry(tb testing.TB, t time.Duration, f func() error, msgAndArgs ...interface{}) {
   421  	tb.Helper()
   422  	doneCh := make(chan struct{})
   423  	timeout := false
   424  	var err error
   425  	go func() {
   426  		for !timeout {
   427  			if err = f(); err == nil {
   428  				close(doneCh)
   429  				break
   430  			}
   431  		}
   432  	}()
   433  	select {
   434  	case <-doneCh:
   435  	case <-time.After(t):
   436  		timeout = true
   437  		fatal(tb, msgAndArgs, "operation did not finish within %s - last error: %v", t.String(), err)
   438  	}
   439  }
   440  
   441  // YesError checks for an error.
   442  func YesError(tb testing.TB, err error, msgAndArgs ...interface{}) {
   443  	tb.Helper()
   444  	if err == nil {
   445  		fatal(tb, msgAndArgs, "Error is expected but got %v", err)
   446  	}
   447  }
   448  
   449  // NotNil checks a value is non-nil.
   450  func NotNil(tb testing.TB, object interface{}, msgAndArgs ...interface{}) {
   451  	tb.Helper()
   452  	success := true
   453  
   454  	if object == nil {
   455  		success = false
   456  	} else {
   457  		value := reflect.ValueOf(object)
   458  		kind := value.Kind()
   459  		if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
   460  			success = false
   461  		}
   462  	}
   463  
   464  	if !success {
   465  		fatal(tb, msgAndArgs, "Expected value not to be nil.")
   466  	}
   467  }
   468  
   469  // Nil checks a value is nil.
   470  func Nil(tb testing.TB, object interface{}, msgAndArgs ...interface{}) {
   471  	tb.Helper()
   472  	if object == nil {
   473  		return
   474  	}
   475  	value := reflect.ValueOf(object)
   476  	kind := value.Kind()
   477  	if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
   478  		return
   479  	}
   480  
   481  	fatal(tb, msgAndArgs, "Expected value to be nil, but was %v", object)
   482  }
   483  
   484  // True checks a value is true.
   485  func True(tb testing.TB, value bool, msgAndArgs ...interface{}) {
   486  	tb.Helper()
   487  	if !value {
   488  		fatal(tb, msgAndArgs, "Should be true.")
   489  	}
   490  }
   491  
   492  // False checks a value is false.
   493  func False(tb testing.TB, value bool, msgAndArgs ...interface{}) {
   494  	tb.Helper()
   495  	if value {
   496  		fatal(tb, msgAndArgs, "Should be false.")
   497  	}
   498  }
   499  
   500  // YesPanic checks that the callback panics.
   501  func YesPanic(tb testing.TB, cb func(), msgAndArgs ...interface{}) {
   502  	defer func() {
   503  		r := recover()
   504  		if r == nil {
   505  			fatal(tb, msgAndArgs, "Should have panicked.")
   506  		}
   507  	}()
   508  
   509  	cb()
   510  }
   511  
   512  func logMessage(tb testing.TB, msgAndArgs []interface{}) {
   513  	tb.Helper()
   514  	if len(msgAndArgs) == 1 {
   515  		tb.Logf(msgAndArgs[0].(string))
   516  	}
   517  	if len(msgAndArgs) > 1 {
   518  		tb.Logf(msgAndArgs[0].(string), msgAndArgs[1:]...)
   519  	}
   520  }
   521  
   522  func fatal(tb testing.TB, userMsgAndArgs []interface{}, msgFmt string, msgArgs ...interface{}) {
   523  	tb.Helper()
   524  	logMessage(tb, userMsgAndArgs)
   525  	tb.Logf(msgFmt, msgArgs...)
   526  	if len(msgArgs) > 0 {
   527  		err, ok := msgArgs[0].(error)
   528  		if ok {
   529  			errors.ForEachStackFrame(err, func(frame errors.Frame) {
   530  				tb.Logf("%+v\n", frame)
   531  			})
   532  		}
   533  	}
   534  	tb.Fatalf("current stack:\n%s", string(debug.Stack()))
   535  }