mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-09 08:55:55 -04:00
Merge pull request #137268 from pohly/ktesting-tcontext-type
ktesting: remove type alias
This commit is contained in:
commit
4e894632b8
7 changed files with 220 additions and 247 deletions
|
|
@ -284,7 +284,7 @@ type driverResourcesMutatorFunc func(map[string]resourceslice.DriverResources)
|
|||
//
|
||||
// Call this outside of ginkgo.It, then use the instance inside ginkgo.It.
|
||||
func NewDriver(f *framework.Framework, nodes *Nodes, driverResourcesGenerator driverResourcesGenFunc, driverResourcesMutators ...driverResourcesMutatorFunc) *Driver {
|
||||
d := NewDriverInstance(nil)
|
||||
d := NewDriverInstance(ktesting.TContext{} /* no namespace yet, will be set later */)
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
tCtx := f.TContext(context.Background())
|
||||
|
|
@ -300,6 +300,7 @@ func NewDriver(f *framework.Framework, nodes *Nodes, driverResourcesGenerator dr
|
|||
|
||||
// NewDriverInstance is a variant of NewDriver where the driver is inactive and must
|
||||
// be started explicitly with Run. May be used inside ginkgo.It or a Go unit test.
|
||||
// The context is used to determine the test's and thus the driver's namespace.
|
||||
func NewDriverInstance(tCtx ktesting.TContext) *Driver {
|
||||
d := &Driver{
|
||||
fail: map[MethodInstance]bool{},
|
||||
|
|
@ -314,9 +315,7 @@ func NewDriverInstance(tCtx ktesting.TContext) *Driver {
|
|||
WithRealNodes: true,
|
||||
ExpectResourceSliceRemoval: true,
|
||||
}
|
||||
if tCtx != nil {
|
||||
d.initName(tCtx)
|
||||
}
|
||||
d.initName(tCtx)
|
||||
return d
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ func (f FailureError) Is(target error) bool {
|
|||
// }
|
||||
var ErrFailure error = FailureError{}
|
||||
|
||||
func gomegaAssertion(tc *TC, fatal bool, actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
testingT := gtypes.GomegaTestingT(tc)
|
||||
func gomegaAssertion(tCtx TContext, fatal bool, actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
testingT := gtypes.GomegaTestingT(tCtx)
|
||||
if !fatal {
|
||||
testingT = assertTestingT{tc}
|
||||
testingT = assertTestingT{tCtx}
|
||||
}
|
||||
return gomega.NewWithT(testingT).Expect(actual, extra...)
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ func gomegaAssertion(tc *TC, fatal bool, actual interface{}, extra ...interface{
|
|||
// reporting failures) using TContext.Errorf, i.e. testing continues after a
|
||||
// failed assertion. The Helper method gets passed through.
|
||||
type assertTestingT struct {
|
||||
*TC
|
||||
TContext
|
||||
}
|
||||
|
||||
var _ gtypes.GomegaTestingT = assertTestingT{}
|
||||
|
|
@ -104,34 +104,34 @@ func (a assertTestingT) Fatalf(format string, args ...any) {
|
|||
//
|
||||
// tCtx.ExpectNoError(somehelper.CreateSomething(tCtx, ...), "creating the first foobar")
|
||||
// tCtx.ExpectNoError(somehelper.CreateSomething(tCtx, ...), "creating the second foobar")
|
||||
func (tc *TC) ExpectNoError(err error, explain ...interface{}) {
|
||||
tc.Helper()
|
||||
tc.noError(tc.Fatalf, err, explain...)
|
||||
func (tCtx TContext) ExpectNoError(err error, explain ...interface{}) {
|
||||
tCtx.Helper()
|
||||
tCtx.noError(tCtx.Fatalf, err, explain...)
|
||||
}
|
||||
|
||||
// AssertNoError is a variant of ExpectNoError which reports an unexpected
|
||||
// error without aborting the test. It returns true if there was no error.
|
||||
func (tc *TC) AssertNoError(err error, explain ...interface{}) bool {
|
||||
tc.Helper()
|
||||
return tc.noError(tc.Errorf, err, explain...)
|
||||
func (tCtx TContext) AssertNoError(err error, explain ...interface{}) bool {
|
||||
tCtx.Helper()
|
||||
return tCtx.noError(tCtx.Errorf, err, explain...)
|
||||
}
|
||||
|
||||
func (tc *TC) noError(failf func(format string, args ...any), err error, explain ...interface{}) bool {
|
||||
func (tCtx TContext) noError(failf func(format string, args ...any), err error, explain ...interface{}) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
tc.Helper()
|
||||
tCtx.Helper()
|
||||
description := buildDescription(explain...)
|
||||
|
||||
if errors.Is(err, ErrFailure) {
|
||||
var failure FailureError
|
||||
if tc.capture == nil && errors.As(err, &failure) {
|
||||
if tCtx.capture == nil && errors.As(err, &failure) {
|
||||
if backtrace := failure.Backtrace(); backtrace != "" {
|
||||
if description != "" {
|
||||
tc.Log(description)
|
||||
tCtx.Log(description)
|
||||
}
|
||||
tc.Logf("Failed at:\n%s", backtrace)
|
||||
tCtx.Logf("Failed at:\n%s", backtrace)
|
||||
}
|
||||
}
|
||||
if description != "" {
|
||||
|
|
@ -145,8 +145,8 @@ func (tc *TC) noError(failf func(format string, args ...any), err error, explain
|
|||
if description == "" {
|
||||
description = "Unexpected error"
|
||||
}
|
||||
if tc.capture == nil {
|
||||
tc.Logf("%s:\n%s", description, format.Object(err, 0))
|
||||
if tCtx.capture == nil {
|
||||
tCtx.Logf("%s:\n%s", description, format.Object(err, 0))
|
||||
}
|
||||
failf("%s: %v", description, err.Error())
|
||||
return false
|
||||
|
|
@ -233,39 +233,39 @@ func buildDescription(explain ...interface{}) string {
|
|||
// return value
|
||||
// }
|
||||
// tCtx.Eventually(cb).Should(gomega.Equal(42), "should be the answer to everything")
|
||||
func (tc *TC) Eventually(arg any) gomega.AsyncAssertion {
|
||||
tc.Helper()
|
||||
return tc.newAsyncAssertion(gomega.NewWithT(tc).Eventually, arg)
|
||||
func (tCtx TContext) Eventually(arg any) gomega.AsyncAssertion {
|
||||
tCtx.Helper()
|
||||
return tCtx.newAsyncAssertion(gomega.NewWithT(tCtx).Eventually, arg)
|
||||
}
|
||||
|
||||
// AssertEventually is a variant of Eventually which merely records a failure
|
||||
// without stopping the test.
|
||||
func (tc *TC) AssertEventually(arg any) gomega.AsyncAssertion {
|
||||
tc.Helper()
|
||||
return tc.newAsyncAssertion(gomega.NewWithT(assertTestingT{tc}).Eventually, arg)
|
||||
func (tCtx TContext) AssertEventually(arg any) gomega.AsyncAssertion {
|
||||
tCtx.Helper()
|
||||
return tCtx.newAsyncAssertion(gomega.NewWithT(assertTestingT{tCtx}).Eventually, arg)
|
||||
}
|
||||
|
||||
// Consistently wraps [gomega.Consistently] the same way as [Eventually] wraps
|
||||
// [gomega.Eventually].
|
||||
func (tc *TC) Consistently(arg any) gomega.AsyncAssertion {
|
||||
tc.Helper()
|
||||
return tc.newAsyncAssertion(gomega.NewWithT(tc).Consistently, arg)
|
||||
func (tCtx TContext) Consistently(arg any) gomega.AsyncAssertion {
|
||||
tCtx.Helper()
|
||||
return tCtx.newAsyncAssertion(gomega.NewWithT(tCtx).Consistently, arg)
|
||||
}
|
||||
|
||||
// AssertConsistently is a variant of Consistently which merely records a failure
|
||||
// without stopping the test.
|
||||
func (tc *TC) AssertConsistently(arg any) gomega.AsyncAssertion {
|
||||
tc.Helper()
|
||||
return tc.newAsyncAssertion(gomega.NewWithT(assertTestingT{tc}).Consistently, arg)
|
||||
func (tCtx TContext) AssertConsistently(arg any) gomega.AsyncAssertion {
|
||||
tCtx.Helper()
|
||||
return tCtx.newAsyncAssertion(gomega.NewWithT(assertTestingT{tCtx}).Consistently, arg)
|
||||
}
|
||||
|
||||
func (tc *TC) newAsyncAssertion(eventuallyOrConsistently func(actualOrCtx any, args ...any) gomega.AsyncAssertion, arg any) gomega.AsyncAssertion {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) newAsyncAssertion(eventuallyOrConsistently func(actualOrCtx any, args ...any) gomega.AsyncAssertion, arg any) gomega.AsyncAssertion {
|
||||
tCtx.Helper()
|
||||
// switch arg := arg.(type) {
|
||||
// case func(tCtx TContext):
|
||||
// // Tricky to handle via reflect, so let's cover this directly...
|
||||
// return eventuallyOrConsistently(tc, func(g gomega.Gomega, ctx context.Context) (err error) {
|
||||
// tCtx := WithContext(tc, ctx)
|
||||
// return eventuallyOrConsistently(tCtx, func(g gomega.Gomega, ctx context.Context) (err error) {
|
||||
// tCtx := WithContext(tCtx, ctx)
|
||||
// tCtx, finalize := WithError(tCtx, &err)
|
||||
// defer finalize()
|
||||
// arg(tCtx)
|
||||
|
|
@ -274,12 +274,12 @@ func (tc *TC) newAsyncAssertion(eventuallyOrConsistently func(actualOrCtx any, a
|
|||
v := reflect.ValueOf(arg)
|
||||
if v.Kind() != reflect.Func {
|
||||
// Gomega must deal with it.
|
||||
return eventuallyOrConsistently(tc, arg)
|
||||
return eventuallyOrConsistently(tCtx, arg)
|
||||
}
|
||||
t := v.Type()
|
||||
if t.NumIn() == 0 || t.In(0) != tContextType {
|
||||
// Not a function we can wrap.
|
||||
return eventuallyOrConsistently(tc, arg)
|
||||
return eventuallyOrConsistently(tCtx, arg)
|
||||
}
|
||||
// Build a wrapper function with context instead of TContext as first parameter.
|
||||
// The wrapper then builds that TContext when called and invokes the actual function.
|
||||
|
|
@ -302,7 +302,7 @@ func (tc *TC) newAsyncAssertion(eventuallyOrConsistently func(actualOrCtx any, a
|
|||
wrapperType := reflect.FuncOf(in, out, t.IsVariadic())
|
||||
wrapper := reflect.MakeFunc(wrapperType, func(args []reflect.Value) (results []reflect.Value) {
|
||||
var err error
|
||||
tCtx, finalize := tc.WithContext(args[0].Interface().(context.Context)).
|
||||
tCtx, finalize := tCtx.WithContext(args[0].Interface().(context.Context)).
|
||||
WithCancel().
|
||||
WithError(&err)
|
||||
args[0] = reflect.ValueOf(tCtx)
|
||||
|
|
@ -339,7 +339,7 @@ func (tc *TC) newAsyncAssertion(eventuallyOrConsistently func(actualOrCtx any, a
|
|||
defer finalize() // Must be called directly, otherwise it cannot recover a panic.
|
||||
return v.Call(args)
|
||||
})
|
||||
return eventuallyOrConsistently(tc, wrapper.Interface())
|
||||
return eventuallyOrConsistently(tCtx, wrapper.Interface())
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -29,27 +29,25 @@ import (
|
|||
|
||||
// WithRESTConfig initializes all client-go clients with new clients
|
||||
// created for the config. The current test name gets included in the UserAgent.
|
||||
func (tc *TC) WithRESTConfig(cfg *rest.Config) TContext {
|
||||
func (tCtx TContext) WithRESTConfig(cfg *rest.Config) TContext {
|
||||
cfg = rest.CopyConfig(cfg)
|
||||
cfg.UserAgent = fmt.Sprintf("%s -- %s", rest.DefaultKubernetesUserAgent(), tc.Name())
|
||||
cfg.UserAgent = fmt.Sprintf("%s -- %s", rest.DefaultKubernetesUserAgent(), tCtx.Name())
|
||||
|
||||
tc = tc.clone()
|
||||
tc.restConfig = cfg
|
||||
tc.client = clientset.NewForConfigOrDie(cfg)
|
||||
tc.dynamic = dynamic.NewForConfigOrDie(cfg)
|
||||
tc.apiextensions = apiextensions.NewForConfigOrDie(cfg)
|
||||
cachedDiscovery := memory.NewMemCacheClient(tc.client.Discovery())
|
||||
tc.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscovery)
|
||||
return tc
|
||||
tCtx.restConfig = cfg
|
||||
tCtx.client = clientset.NewForConfigOrDie(cfg)
|
||||
tCtx.dynamic = dynamic.NewForConfigOrDie(cfg)
|
||||
tCtx.apiextensions = apiextensions.NewForConfigOrDie(cfg)
|
||||
cachedDiscovery := memory.NewMemCacheClient(tCtx.client.Discovery())
|
||||
tCtx.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscovery)
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// WithClients uses an existing config and clients.
|
||||
func (tc *TC) WithClients(cfg *rest.Config, mapper *restmapper.DeferredDiscoveryRESTMapper, client clientset.Interface, dynamic dynamic.Interface, apiextensions apiextensions.Interface) TContext {
|
||||
tc = tc.clone()
|
||||
tc.restConfig = cfg
|
||||
tc.restMapper = mapper
|
||||
tc.client = client
|
||||
tc.dynamic = dynamic
|
||||
tc.apiextensions = apiextensions
|
||||
return tc
|
||||
func (tCtx TContext) WithClients(cfg *rest.Config, mapper *restmapper.DeferredDiscoveryRESTMapper, client clientset.Interface, dynamic dynamic.Interface, apiextensions apiextensions.Interface) TContext {
|
||||
tCtx.restConfig = cfg
|
||||
tCtx.restMapper = mapper
|
||||
tCtx.client = client
|
||||
tCtx.dynamic = dynamic
|
||||
tCtx.apiextensions = apiextensions
|
||||
return tCtx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,10 @@ import (
|
|||
// Test failures are not propagated to the parent context.
|
||||
// WithRESTConfig initializes all client-go clients with new clients
|
||||
// created for the config. The current test name gets included in the UserAgent.
|
||||
func (tc *TC) WithError(err *error) (TContext, func()) {
|
||||
tc = tc.clone()
|
||||
tc.capture = &capture{}
|
||||
func (tCtx TContext) WithError(err *error) (TContext, func()) {
|
||||
tCtx.capture = &capture{}
|
||||
|
||||
return tc, func() {
|
||||
return tCtx, func() {
|
||||
// Recover has to be called in the deferred function. When called inside
|
||||
// a function called by a deferred function (like finalize below), it
|
||||
// returns nil.
|
||||
|
|
@ -54,16 +53,16 @@ func (tc *TC) WithError(err *error) (TContext, func()) {
|
|||
}
|
||||
}
|
||||
|
||||
tc.finalize(err)
|
||||
tCtx.finalize(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *TC) finalize(err *error) {
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
func (tCtx TContext) finalize(err *error) {
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
errs := tc.capture.errors
|
||||
if tc.capture.failed && len(errs) == 0 {
|
||||
errs := tCtx.capture.errors
|
||||
if tCtx.capture.failed && len(errs) == 0 {
|
||||
errs = []error{errFailedWithNoExplanation}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
|
|
@ -85,9 +84,9 @@ func (e failures) GomegaString() string {
|
|||
// buildHeader handles:
|
||||
// - "ERROR:<non-empty prefix><optional header><suffix>" -> use both prefix and suffix when we have a header, otherwise just the suffix
|
||||
// - "<empty prefix><optional header><suffix>" -> use suffix only if we have a header
|
||||
func (tc *TC) buildHeader(prefix, suffix string) string {
|
||||
if tc.perTestHeader != nil {
|
||||
return prefix + tc.perTestHeader() + suffix
|
||||
func (tCtx TContext) buildHeader(prefix, suffix string) string {
|
||||
if tCtx.perTestHeader != nil {
|
||||
return prefix + tCtx.perTestHeader() + suffix
|
||||
}
|
||||
if prefix != "" {
|
||||
return suffix
|
||||
|
|
@ -104,45 +103,45 @@ func indent(msg string, all bool) string {
|
|||
return header + strings.ReplaceAll(msg, "\n", "\n\t")
|
||||
}
|
||||
|
||||
func (tc *TC) Skip(args ...any) {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Skip(args ...any) {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintln.
|
||||
msg := strings.TrimSpace(fmt.Sprintln(args...))
|
||||
tc.TB().Skip("SKIP:", tc.buildHeader(" ", " ")+tc.steps+indent(msg, false))
|
||||
tCtx.TB().Skip("SKIP:", tCtx.buildHeader(" ", " ")+tCtx.steps+indent(msg, false))
|
||||
}
|
||||
|
||||
func (tc *TC) Skipf(format string, args ...any) {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Skipf(format string, args ...any) {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintf.
|
||||
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
tc.TB().Skip("SKIP:", tc.buildHeader(" ", " ")+tc.steps+indent(msg, false))
|
||||
tCtx.TB().Skip("SKIP:", tCtx.buildHeader(" ", " ")+tCtx.steps+indent(msg, false))
|
||||
}
|
||||
|
||||
func (tc *TC) Log(args ...any) {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Log(args ...any) {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintln.
|
||||
msg := strings.TrimSpace(fmt.Sprintln(args...))
|
||||
tc.TB().Log(tc.buildHeader("", " ") + tc.steps + indent(msg, false))
|
||||
tCtx.TB().Log(tCtx.buildHeader("", " ") + tCtx.steps + indent(msg, false))
|
||||
}
|
||||
|
||||
func (tc *TC) Logf(format string, args ...any) {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Logf(format string, args ...any) {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintf.
|
||||
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
tc.TB().Log(tc.buildHeader("", " ") + tc.steps + indent(msg, false))
|
||||
tCtx.TB().Log(tCtx.buildHeader("", " ") + tCtx.steps + indent(msg, false))
|
||||
}
|
||||
|
||||
func (tc *TC) Error(args ...any) {
|
||||
if tc.capture == nil {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Error(args ...any) {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.Helper()
|
||||
msg := strings.TrimSpace(fmt.Sprintln(args...))
|
||||
// ERROR *before* header to make it stand out as failure.
|
||||
tc.TB().Error("ERROR:" + tc.buildHeader(" ", "\n") + indent(tc.steps+msg, true))
|
||||
tCtx.TB().Error("ERROR:" + tCtx.buildHeader(" ", "\n") + indent(tCtx.steps+msg, true))
|
||||
return
|
||||
}
|
||||
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
// Gomega adds a leading newline in https://github.com/onsi/gomega/blob/f804ac6ada8d36164ecae0513295de8affce1245/internal/gomega.go#L37
|
||||
// Let's strip that at start and end because ktesting will make errors
|
||||
|
|
@ -150,89 +149,89 @@ func (tc *TC) Error(args ...any) {
|
|||
// line breaks. Besides, Sprintln (required for `go vet printf`) also
|
||||
// adds a trailing newline that we don't want.
|
||||
msg := strings.TrimSpace(fmt.Sprintln(args...))
|
||||
tc.capture.errors = append(tc.capture.errors, errors.New(tc.steps+msg))
|
||||
tc.capture.failed = true
|
||||
tCtx.capture.errors = append(tCtx.capture.errors, errors.New(tCtx.steps+msg))
|
||||
tCtx.capture.failed = true
|
||||
}
|
||||
|
||||
func (tc *TC) Errorf(format string, args ...any) {
|
||||
if tc.capture == nil {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Errorf(format string, args ...any) {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintln.
|
||||
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
// ERROR *before* header to make it stand out as failure.
|
||||
tc.TB().Error("ERROR:" + tc.buildHeader(" ", "\n") + indent(tc.steps+msg, true))
|
||||
tCtx.TB().Error("ERROR:" + tCtx.buildHeader(" ", "\n") + indent(tCtx.steps+msg, true))
|
||||
return
|
||||
}
|
||||
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
tc.capture.errors = append(tc.capture.errors, errors.New(tc.steps+msg))
|
||||
tc.capture.failed = true
|
||||
tCtx.capture.errors = append(tCtx.capture.errors, errors.New(tCtx.steps+msg))
|
||||
tCtx.capture.failed = true
|
||||
}
|
||||
|
||||
func (tc *TC) Fail() {
|
||||
if tc.capture == nil {
|
||||
tc.TB().Fail()
|
||||
func (tCtx TContext) Fail() {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.TB().Fail()
|
||||
return
|
||||
}
|
||||
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
tc.capture.failed = true
|
||||
tCtx.capture.failed = true
|
||||
}
|
||||
|
||||
func (tc *TC) FailNow() {
|
||||
if tc.capture == nil {
|
||||
tc.TB().FailNow()
|
||||
func (tCtx TContext) FailNow() {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.TB().FailNow()
|
||||
return
|
||||
}
|
||||
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
tc.capture.failed = true
|
||||
tCtx.capture.failed = true
|
||||
panic(failed)
|
||||
}
|
||||
|
||||
func (tc *TC) Failed() bool {
|
||||
if tc.capture == nil {
|
||||
return tc.TB().Failed()
|
||||
func (tCtx TContext) Failed() bool {
|
||||
if tCtx.capture == nil {
|
||||
return tCtx.TB().Failed()
|
||||
}
|
||||
|
||||
tc.capture.mutex.Lock()
|
||||
defer tc.capture.mutex.Unlock()
|
||||
tCtx.capture.mutex.Lock()
|
||||
defer tCtx.capture.mutex.Unlock()
|
||||
|
||||
return tc.capture.failed
|
||||
return tCtx.capture.failed
|
||||
}
|
||||
|
||||
func (tc *TC) Fatal(args ...any) {
|
||||
if tc.capture == nil {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Fatal(args ...any) {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintln.
|
||||
msg := strings.TrimSpace(fmt.Sprintln(args...))
|
||||
// FATAL ERROR *before* header to make it stand out as failure.
|
||||
tc.TB().Fatal("FATAL ERROR:" + tc.buildHeader(" ", "\n") + indent(tc.steps+msg, true))
|
||||
tCtx.TB().Fatal("FATAL ERROR:" + tCtx.buildHeader(" ", "\n") + indent(tCtx.steps+msg, true))
|
||||
}
|
||||
|
||||
tc.Error(args...)
|
||||
tc.FailNow()
|
||||
tCtx.Error(args...)
|
||||
tCtx.FailNow()
|
||||
}
|
||||
|
||||
func (tc *TC) Fatalf(format string, args ...any) {
|
||||
if tc.capture == nil {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) Fatalf(format string, args ...any) {
|
||||
if tCtx.capture == nil {
|
||||
tCtx.Helper()
|
||||
// Enable `go vet printf` by directly calling fmt.Sprintf.
|
||||
msg := strings.TrimSpace(fmt.Sprintf(format, args...))
|
||||
// FATAL ERROR *before* header to make it stand out as failure.
|
||||
tc.TB().Fatal("FATAL ERROR:" + tc.buildHeader(" ", "\n") + indent(tc.steps+msg, true))
|
||||
tCtx.TB().Fatal("FATAL ERROR:" + tCtx.buildHeader(" ", "\n") + indent(tCtx.steps+msg, true))
|
||||
return
|
||||
}
|
||||
|
||||
tc.Errorf(format, args...)
|
||||
tc.FailNow()
|
||||
tCtx.Errorf(format, args...)
|
||||
tCtx.FailNow()
|
||||
}
|
||||
|
||||
// fatalWithError is the internal type that should never get propagated up. The
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ package ktesting
|
|||
// The string should describe the operation that is about to happen ("starting
|
||||
// the controller", "list items") or what is being operated on ("HTTP server").
|
||||
// Multiple different prefixes get concatenated with a colon.
|
||||
func (tc *TC) WithStep(step string) *TC {
|
||||
tc = tc.clone()
|
||||
tc.steps += step + ": "
|
||||
return tc
|
||||
func (tCtx TContext) WithStep(step string) TContext {
|
||||
tCtx.steps += step + ": "
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// Step is useful when the context with the step information is
|
||||
|
|
@ -45,22 +44,22 @@ func (tc *TC) WithStep(step string) *TC {
|
|||
// Inside the callback, the tCtx variable is the one where the step
|
||||
// has been added. This avoids the need to introduce multiple different
|
||||
// context variables and risk of using the wrong one.
|
||||
func (tc *TC) Step(step string, cb func(tCtx TContext)) {
|
||||
tc.Helper()
|
||||
cb(tc.WithStep(step))
|
||||
func (tCtx TContext) Step(step string, cb func(tCtx TContext)) {
|
||||
tCtx.Helper()
|
||||
cb(tCtx.WithStep(step))
|
||||
}
|
||||
|
||||
// Value intercepts a search for the special "GINKGO_SPEC_CONTEXT" and
|
||||
// wraps the underlying reporter so that the steps are visible in the report.
|
||||
func (tc *TC) Value(key any) any {
|
||||
if tc.steps != "" {
|
||||
func (tCtx TContext) Value(key any) any {
|
||||
if tCtx.steps != "" {
|
||||
if s, ok := key.(string); ok && s == ginkgoSpecContextKey {
|
||||
if reporter, ok := tc.Context.Value(key).(ginkgoReporter); ok {
|
||||
return ginkgoReporter(&stepReporter{reporter: reporter, steps: tc.steps})
|
||||
if reporter, ok := tCtx.Context.Value(key).(ginkgoReporter); ok {
|
||||
return ginkgoReporter(&stepReporter{reporter: reporter, steps: tCtx.steps})
|
||||
}
|
||||
}
|
||||
}
|
||||
return tc.Context.Value(key)
|
||||
return tCtx.Context.Value(key)
|
||||
}
|
||||
|
||||
type stepReporter struct {
|
||||
|
|
|
|||
|
|
@ -216,11 +216,10 @@ type InitOption = initoption.InitOption
|
|||
// Functional options are part of the API, but currently
|
||||
// there are none which have an effect.
|
||||
func InitCtx(ctx context.Context, tb TB, _ ...InitOption) TContext {
|
||||
tc := TC{
|
||||
return TContext{
|
||||
Context: ctx,
|
||||
testingTB: testingTB{TB: tb},
|
||||
}
|
||||
return &tc
|
||||
}
|
||||
|
||||
// withTB constructs a new TContext with a different TB instance.
|
||||
|
|
@ -239,23 +238,21 @@ func InitCtx(ctx context.Context, tb TB, _ ...InitOption) TContext {
|
|||
// })
|
||||
//
|
||||
// withTB sets up cancellation for the sub-test and uses per-test output.
|
||||
func (tc *TC) withTB(tb TB) TContext {
|
||||
tc = tc.clone()
|
||||
tc.testingTB.TB = tb
|
||||
if tc.perTestHeader != nil {
|
||||
func (tCtx TContext) withTB(tb TB) TContext {
|
||||
tCtx.testingTB.TB = tb
|
||||
if tCtx.perTestHeader != nil {
|
||||
logger := newLogger(tb, false /* don't buffer logs in sub-test */)
|
||||
tc.Context = klog.NewContext(tc.Context, logger)
|
||||
tCtx.Context = klog.NewContext(tCtx.Context, logger)
|
||||
}
|
||||
tc = tc.WithCancel()
|
||||
return tc
|
||||
return tCtx.WithCancel()
|
||||
}
|
||||
|
||||
// run implements the different Run and SyncTest methods. It's not an exported
|
||||
// method because tCtx.Run is more discoverable (same usage as
|
||||
// with normal Go).
|
||||
func run(tc *TC, name string, syncTest bool, cb func(tc *TC)) bool {
|
||||
tc.Helper()
|
||||
switch tb := tc.TB().(type) {
|
||||
func run(tCtx TContext, name string, syncTest bool, cb func(tCtx TContext)) bool {
|
||||
tCtx.Helper()
|
||||
switch tb := tCtx.TB().(type) {
|
||||
case *testing.T:
|
||||
if syncTest {
|
||||
f := func(t *testing.T) {
|
||||
|
|
@ -265,10 +262,9 @@ func run(tc *TC, name string, syncTest bool, cb func(tc *TC)) bool {
|
|||
//
|
||||
// Sync tests shouldn't need the overall suite timeout,
|
||||
// so this seems okay.
|
||||
tc = tc.clone()
|
||||
tc.isSyncTest = true
|
||||
tc = tc.WithoutCancel().withTB(t)
|
||||
cb(tc)
|
||||
tCtx.isSyncTest = true
|
||||
tCtx = tCtx.WithoutCancel().withTB(t)
|
||||
cb(tCtx)
|
||||
}
|
||||
if name != "" {
|
||||
return tb.Run(name, func(t *testing.T) { synctest.Test(t, f) })
|
||||
|
|
@ -277,12 +273,12 @@ func run(tc *TC, name string, syncTest bool, cb func(tc *TC)) bool {
|
|||
return true
|
||||
}
|
||||
return tb.Run(name, func(t *testing.T) {
|
||||
cb(tc.withTB(t))
|
||||
cb(tCtx.withTB(t))
|
||||
})
|
||||
case *testing.B:
|
||||
if !syncTest {
|
||||
return tb.Run(name, func(b *testing.B) {
|
||||
cb(tc.withTB(b))
|
||||
cb(tCtx.withTB(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -291,7 +287,7 @@ func run(tc *TC, name string, syncTest bool, cb func(tc *TC)) bool {
|
|||
if syncTest {
|
||||
what = "SyncTest"
|
||||
}
|
||||
tc.Fatalf("%s not implemented, underlying %T does not support it", what, tc.TB())
|
||||
tCtx.Fatalf("%s not implemented, underlying %T does not support it", what, tCtx.TB())
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -306,28 +302,23 @@ func run(tc *TC, name string, syncTest bool, cb func(tc *TC)) bool {
|
|||
//
|
||||
// This is important because the Context in the callback could have
|
||||
// a different deadline than in the parent TContext.
|
||||
func (tc *TC) WithContext(ctx context.Context) TContext {
|
||||
tc = tc.clone()
|
||||
logger := tc.Logger()
|
||||
tc.Context = ctx
|
||||
func (tCtx TContext) WithContext(ctx context.Context) TContext {
|
||||
logger := tCtx.Logger()
|
||||
tCtx.Context = ctx
|
||||
if _, err := logr.FromContext(ctx); err != nil {
|
||||
// Keep using the logger from the parent context.
|
||||
tc = tc.WithLogger(logger)
|
||||
tCtx = tCtx.WithLogger(logger)
|
||||
}
|
||||
return tc
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// WithValue wraps [context.WithValue] such that the result is again a TContext.
|
||||
func (tc *TC) WithValue(key, val any) TContext {
|
||||
ctx := context.WithValue(tc, key, val)
|
||||
return tc.WithContext(ctx)
|
||||
func (tCtx TContext) WithValue(key, val any) TContext {
|
||||
ctx := context.WithValue(tCtx, key, val)
|
||||
return tCtx.WithContext(ctx)
|
||||
}
|
||||
|
||||
// TContext is the recommended type for storing a [TC] instance.
|
||||
// The type alias is necessary because TContext used to be an interface.
|
||||
type TContext = *TC
|
||||
|
||||
// TC implements [context.Context], [testing.TB] and some additional
|
||||
// TContext implements [context.Context], [testing.TB] and some additional
|
||||
// methods. [TContext] is the public pointer type for referencing a TC.
|
||||
// Variables are usually called tCtx. To ensure that test code does not
|
||||
// use `t` directly unintentionally, it is recommended to use two functions:
|
||||
|
|
@ -355,7 +346,7 @@ type TContext = *TC
|
|||
// Progress reporting is more informative when doing polling with
|
||||
// [gomega.Eventually] and [gomega.Consistently]. Without that, it
|
||||
// can only report which tests are active.
|
||||
type TC struct {
|
||||
type TContext struct {
|
||||
// Context makes the methods of the underlying context
|
||||
// available. It must not be modified.
|
||||
context.Context
|
||||
|
|
@ -402,12 +393,6 @@ type capture struct {
|
|||
failed bool
|
||||
}
|
||||
|
||||
// tc makes a shallow copy.
|
||||
func (tc *TC) clone() *TC {
|
||||
clone := *tc
|
||||
return &clone
|
||||
}
|
||||
|
||||
// testingTB is needed to avoid a name conflict
|
||||
// between field and method in tContext.
|
||||
type testingTB struct {
|
||||
|
|
@ -417,8 +402,6 @@ type testingTB struct {
|
|||
TB
|
||||
}
|
||||
|
||||
var _ TContext = &TC{}
|
||||
|
||||
// Parallel signals that this test is to be run in parallel with (and
|
||||
// only with) other parallel tests. In other words, it needs to be
|
||||
// called in each test which is meant to run in parallel.
|
||||
|
|
@ -427,11 +410,11 @@ var _ TContext = &TC{}
|
|||
//
|
||||
// When a unit test is run multiple times due to use of -test.count or -test.cpu,
|
||||
// multiple instances of a single test never run in parallel with each other.
|
||||
func (tc *TC) Parallel() {
|
||||
if tb, ok := tc.TB().(interface{ Parallel() }); ok {
|
||||
func (tCtx TContext) Parallel() {
|
||||
if tb, ok := tCtx.TB().(interface{ Parallel() }); ok {
|
||||
tb.Parallel()
|
||||
} else {
|
||||
tc.Fatalf("Parallel not implemented, underlying %T does not support it", tc.TB())
|
||||
tCtx.Fatalf("Parallel not implemented, underlying %T does not support it", tCtx.TB())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,9 +425,9 @@ func (tc *TC) Parallel() {
|
|||
// The cause, if non-empty, is turned into an error which is equivalent
|
||||
// to context.Canceled. context.Cause will return that error for the
|
||||
// context.
|
||||
func (tc *TC) Cancel(cause string) {
|
||||
if tc.cancel != nil {
|
||||
tc.cancel(cause)
|
||||
func (tCtx TContext) Cancel(cause string) {
|
||||
if tCtx.cancel != nil {
|
||||
tCtx.cancel(cause)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -464,24 +447,24 @@ func (tc *TC) Cancel(cause string) {
|
|||
//
|
||||
// The logger and clients are the same as in the TContext that CleanupCtx
|
||||
// is invoked on.
|
||||
func (tc *TC) CleanupCtx(cb func(TContext)) {
|
||||
tc.Helper()
|
||||
func (tCtx TContext) CleanupCtx(cb func(TContext)) {
|
||||
tCtx.Helper()
|
||||
|
||||
if tb, ok := tc.TB().(ContextTB); ok {
|
||||
if tb, ok := tCtx.TB().(ContextTB); ok {
|
||||
// Use context from base TB (most likely Ginkgo).
|
||||
tb.CleanupCtx(func(ctx context.Context) {
|
||||
tCtx := tc.WithContext(ctx)
|
||||
tCtx := tCtx.WithContext(ctx)
|
||||
cb(tCtx)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tc.Cleanup(func() {
|
||||
tCtx.Cleanup(func() {
|
||||
// Use new context. This is the code path for "go test". The
|
||||
// context then has *no* deadline. In the code path above for
|
||||
// Ginkgo, Ginkgo is more sophisticated and also applies
|
||||
// timeouts to cleanup calls which accept a context.
|
||||
childCtx := tc.WithContext(context.WithoutCancel(tc))
|
||||
childCtx := tCtx.WithContext(context.WithoutCancel(tCtx))
|
||||
cb(childCtx)
|
||||
})
|
||||
}
|
||||
|
|
@ -491,8 +474,8 @@ func (tc *TC) CleanupCtx(cb func(TContext)) {
|
|||
//
|
||||
// Only supported in Go unit tests or benchmarks. It fails the current
|
||||
// test when called elsewhere.
|
||||
func (tc *TC) Run(name string, cb func(tCtx TContext)) bool {
|
||||
return run(tc, name, false, cb)
|
||||
func (tCtx TContext) Run(name string, cb func(tCtx TContext)) bool {
|
||||
return run(tCtx, name, false, cb)
|
||||
}
|
||||
|
||||
// SyncTest uses [synctest.Test] to execute the callback inside a bubble.
|
||||
|
|
@ -500,8 +483,8 @@ func (tc *TC) Run(name string, cb func(tCtx TContext)) bool {
|
|||
// the bubble directly in the current test context.
|
||||
//
|
||||
// Only works in Go unit tests.
|
||||
func (tc *TC) SyncTest(name string, cb func(tCtx TContext)) bool {
|
||||
return run(tc, name, true, cb)
|
||||
func (tCtx TContext) SyncTest(name string, cb func(tCtx TContext)) bool {
|
||||
return run(tCtx, name, true, cb)
|
||||
}
|
||||
|
||||
// IsSyncTest returns true if the context was created by SyncTest.
|
||||
|
|
@ -513,8 +496,8 @@ func (tc *TC) SyncTest(name string, cb func(tCtx TContext)) bool {
|
|||
// Eventually and Consistently both call Wait and then check
|
||||
// the condition.
|
||||
// - Outside, polling or some synchronization mechanism has to be used.
|
||||
func (tc *TC) IsSyncTest() bool {
|
||||
return tc.isSyncTest
|
||||
func (tCtx TContext) IsSyncTest() bool {
|
||||
return tCtx.isSyncTest
|
||||
}
|
||||
|
||||
// Wait calls [synctest.Wait] and thus ensures that all background
|
||||
|
|
@ -522,7 +505,7 @@ func (tc *TC) IsSyncTest() bool {
|
|||
//
|
||||
// Only works inside a bubble started by SyncTest (can be checked with
|
||||
// IsSyncTest), panics elsewhere.
|
||||
func (tc *TC) Wait() {
|
||||
func (tCtx TContext) Wait() {
|
||||
synctest.Wait()
|
||||
}
|
||||
|
||||
|
|
@ -542,7 +525,7 @@ func (tc *TC) Wait() {
|
|||
// ...
|
||||
// }
|
||||
// }
|
||||
func (tc *TC) TB() TB { return tc.testingTB.TB }
|
||||
func (tCtx TContext) TB() TB { return tCtx.testingTB.TB }
|
||||
|
||||
// Logger returns a logger for the current test. This is a shortcut
|
||||
// for calling klog.FromContext.
|
||||
|
|
@ -554,22 +537,22 @@ func (tc *TC) TB() TB { return tc.testingTB.TB }
|
|||
//
|
||||
// To skip intermediate helper functions during stack unwinding,
|
||||
// TB.Helper can be called in those functions.
|
||||
func (tc *TC) Logger() klog.Logger {
|
||||
return klog.FromContext(tc.Context)
|
||||
func (tCtx TContext) Logger() klog.Logger {
|
||||
return klog.FromContext(tCtx.Context)
|
||||
}
|
||||
|
||||
// RESTConfig returns a copy of the config for a rest client with the UserAgent
|
||||
// set to include the current test name or nil if not available. Several typed
|
||||
// clients using this config are available through [Client], [Dynamic],
|
||||
// [APIExtensions].
|
||||
func (tc *TC) RESTConfig() *rest.Config {
|
||||
return rest.CopyConfig(tc.restConfig)
|
||||
func (tCtx TContext) RESTConfig() *rest.Config {
|
||||
return rest.CopyConfig(tCtx.restConfig)
|
||||
}
|
||||
|
||||
func (tc *TC) RESTMapper() *restmapper.DeferredDiscoveryRESTMapper { return tc.restMapper }
|
||||
func (tc *TC) Client() clientset.Interface { return tc.client }
|
||||
func (tc *TC) Dynamic() dynamic.Interface { return tc.dynamic }
|
||||
func (tc *TC) APIExtensions() apiextensions.Interface { return tc.apiextensions }
|
||||
func (tCtx TContext) RESTMapper() *restmapper.DeferredDiscoveryRESTMapper { return tCtx.restMapper }
|
||||
func (tCtx TContext) Client() clientset.Interface { return tCtx.client }
|
||||
func (tCtx TContext) Dynamic() dynamic.Interface { return tCtx.dynamic }
|
||||
func (tCtx TContext) APIExtensions() apiextensions.Interface { return tCtx.apiextensions }
|
||||
|
||||
// Expect wraps [gomega.Expect] such that a failure will be reported via
|
||||
// [TContext.Fatal]. As with [gomega.Expect], additional values
|
||||
|
|
@ -579,27 +562,26 @@ func (tc *TC) APIExtensions() apiextensions.Interface { return tc.a
|
|||
//
|
||||
// myAmazingThing := func(int, error) { ...}
|
||||
// tCtx.Expect(myAmazingThing()).Should(gomega.Equal(1))
|
||||
func (tc *TC) Expect(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tc, true, actual, extra...)
|
||||
func (tCtx TContext) Expect(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tCtx, true, actual, extra...)
|
||||
}
|
||||
|
||||
// Require is an alias for Expect.
|
||||
func (tc *TC) Require(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tc, true, actual, extra...)
|
||||
func (tCtx TContext) Require(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tCtx, true, actual, extra...)
|
||||
}
|
||||
|
||||
// Assert also wraps [gomega.Expect], but in contrast to Expect = Require,
|
||||
// it reports a failure through [TContext.Error]. This makes it possible
|
||||
// to test several different assertions.
|
||||
func (tc *TC) Assert(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tc, false, actual, extra...)
|
||||
func (tCtx TContext) Assert(actual interface{}, extra ...interface{}) gomega.Assertion {
|
||||
return gomegaAssertion(tCtx, false, actual, extra...)
|
||||
}
|
||||
|
||||
// WithNamespace creates a new context with a Kubernetes namespace name for retrieval through [Namespace].
|
||||
func (tc *TC) WithNamespace(namespace string) TContext {
|
||||
tc = tc.clone()
|
||||
tc.namespace = namespace
|
||||
return tc
|
||||
func (tCtx TContext) WithNamespace(namespace string) TContext {
|
||||
tCtx.namespace = namespace
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// Namespace returns the Kubernetes namespace name that was set previously
|
||||
|
|
@ -608,6 +590,6 @@ func (tc *TC) WithNamespace(namespace string) TContext {
|
|||
// This namespace is the one to be used by tests which need to create namespace-scoped
|
||||
// objects. The name is guaranteed to be unique for the test context, so tests running
|
||||
// in parallel need to be set up so that each test has its own namespace.
|
||||
func (tc *TC) Namespace() string {
|
||||
return tc.namespace
|
||||
func (tCtx TContext) Namespace() string {
|
||||
return tCtx.namespace
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,31 +26,29 @@ import (
|
|||
// WithCancel sets up cancellation in a [TContext.Cleanup] callback and
|
||||
// constructs a new TContext where [TContext.Cancel] cancels only the new
|
||||
// context.
|
||||
func (tc *TC) WithCancel() TContext {
|
||||
ctx, cancel := context.WithCancelCause(tc)
|
||||
func (tCtx TContext) WithCancel() TContext {
|
||||
ctx, cancel := context.WithCancelCause(tCtx)
|
||||
|
||||
tc = tc.clone()
|
||||
tc.Context = ctx
|
||||
tc.cancel = func(cause string) {
|
||||
tCtx.Context = ctx
|
||||
tCtx.cancel = func(cause string) {
|
||||
var cancelCause error
|
||||
if cause != "" {
|
||||
cancelCause = canceledError(cause)
|
||||
}
|
||||
cancel(cancelCause)
|
||||
}
|
||||
return tc
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// WithoutCancel causes the returned context to ignore cancellation of its parent.
|
||||
// Calling Cancel will not cancel the parent either.
|
||||
// This matches [context.WithoutCancel].
|
||||
func (tc *TC) WithoutCancel() TContext {
|
||||
ctx := context.WithoutCancel(tc)
|
||||
func (tCtx TContext) WithoutCancel() TContext {
|
||||
ctx := context.WithoutCancel(tCtx)
|
||||
|
||||
tc = tc.clone()
|
||||
tc.Context = ctx
|
||||
tc.cancel = nil
|
||||
return tc
|
||||
tCtx.Context = ctx
|
||||
tCtx.cancel = nil
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// WithTimeout sets up new context with a timeout. Canceling the timeout gets
|
||||
|
|
@ -58,20 +56,18 @@ func (tc *TC) WithoutCancel() TContext {
|
|||
// the new context. The cause is used as reason why the context is canceled
|
||||
// once the timeout is reached. It may be empty, in which case the usual
|
||||
// "context canceled" error is used.
|
||||
func (tc *TC) WithTimeout(timeout time.Duration, timeoutCause string) TContext {
|
||||
ctx, cancel := withTimeout(tc, tc.TB(), timeout, timeoutCause)
|
||||
func (tCtx TContext) WithTimeout(timeout time.Duration, timeoutCause string) TContext {
|
||||
ctx, cancel := withTimeout(tCtx, tCtx.TB(), timeout, timeoutCause)
|
||||
|
||||
tc = tc.clone()
|
||||
tc.Context = ctx
|
||||
tc.cancel = cancel
|
||||
return tc
|
||||
tCtx.Context = ctx
|
||||
tCtx.cancel = cancel
|
||||
return tCtx
|
||||
}
|
||||
|
||||
// WithLogger constructs a new context with a different logger.
|
||||
func (tc *TC) WithLogger(logger klog.Logger) TContext {
|
||||
ctx := klog.NewContext(tc, logger)
|
||||
func (tCtx TContext) WithLogger(logger klog.Logger) TContext {
|
||||
ctx := klog.NewContext(tCtx, logger)
|
||||
|
||||
tc = tc.clone()
|
||||
tc.Context = ctx
|
||||
return tc
|
||||
tCtx.Context = ctx
|
||||
return tCtx
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue