ktesting: add Run

This is useful in Go unit tests because it directly replaces the corresponding
testing.T/B.Run.
This commit is contained in:
Patrick Ohly 2025-02-20 14:41:06 +01:00
parent c2ad724e9a
commit f9e7b15c00
5 changed files with 76 additions and 0 deletions

View File

@ -86,6 +86,10 @@ func (cCtx clientContext) ExpectNoError(err error, explain ...interface{}) {
expectNoError(cCtx, err, explain...)
}
func (cCtx clientContext) Run(name string, cb func(tCtx TContext)) bool {
return run(cCtx, name, cb)
}
func (cCtx clientContext) Logger() klog.Logger {
return klog.FromContext(cCtx)
}

View File

@ -149,6 +149,10 @@ func (eCtx *errorContext) ExpectNoError(err error, explain ...interface{}) {
expectNoError(eCtx, err, explain...)
}
func (cCtx *errorContext) Run(name string, cb func(tCtx TContext)) bool {
return run(cCtx, name, cb)
}
func (eCtx *errorContext) Logger() klog.Logger {
return klog.FromContext(eCtx)
}

View File

@ -21,6 +21,7 @@ import (
"flag"
"fmt"
"strings"
"testing"
"time"
"github.com/onsi/gomega"
@ -84,6 +85,13 @@ type TContext interface {
// a single test never run in parallel with each other.
Parallel()
// Run runs f as a subtest of t called name. It blocks until f returns or
// calls t.Parallel to become a parallel test.
//
// Only supported in Go unit tests or benchmarks. It fails the current
// test when called elsewhere.
Run(name string, f func(tCtx TContext)) bool
// Cancel can be invoked to cancel the context before the test is completed.
// Tests which use the context to control goroutines and then wait for
// termination of those goroutines must call Cancel to avoid a deadlock.
@ -174,6 +182,7 @@ type TContext interface {
// - CleanupCtx
// - Expect
// - ExpectNoError
// - Run
// - Logger
//
// Usually these methods would be stand-alone functions with a TContext
@ -337,6 +346,9 @@ func InitCtx(ctx context.Context, tb TB, _ ...InitOption) TContext {
// })
//
// WithTB sets up cancellation for the sub-test.
//
// A simpler API is to use TContext.Run as replacement
// for [testing.T.Run].
func WithTB(parentCtx TContext, tb TB) TContext {
tCtx := InitCtx(parentCtx, tb)
tCtx = WithCancel(tCtx)
@ -350,6 +362,27 @@ func WithTB(parentCtx TContext, tb TB) TContext {
return tCtx
}
// run implements the different Run methods. It's not an exported
// method because tCtx.Run is more discoverable (same usage as
// with normal Go).
func run(tCtx TContext, name string, cb func(tCtx TContext)) bool {
tCtx.Helper()
switch tb := tCtx.TB().(type) {
case interface {
Run(string, func(t *testing.T)) bool
}:
return tb.Run(name, func(t *testing.T) { cb(WithTB(tCtx, t)) })
case interface {
Run(string, func(t *testing.B)) bool
}:
return tb.Run(name, func(b *testing.B) { cb(WithTB(tCtx, b)) })
default:
tCtx.Fatalf("Run not implemented, underlying %T does not support it", tCtx.TB())
}
return false
}
// WithContext constructs a new TContext with a different Context instance.
// This can be used in callbacks which receive a Context, for example
// from Gomega:
@ -439,6 +472,10 @@ func cleanupCtx(tCtx TContext, cb func(TContext)) {
})
}
func (cCtx tContext) Run(name string, cb func(tCtx TContext)) bool {
return run(cCtx, name, cb)
}
func (tCtx tContext) Logger() klog.Logger {
return klog.FromContext(tCtx)
}

View File

@ -123,6 +123,33 @@ func TestWithTB(t *testing.T) {
assert.Equal(t, apiextensions, tCtx.APIExtensions(), "APIExtensions")
tCtx.Cancel("test is complete")
<-tCtx.Done()
})
if err := tCtx.Err(); err != nil {
t.Errorf("parent TContext should not have been cancelled: %v", err)
}
}
func TestRun(t *testing.T) {
tCtx := ktesting.Init(t)
cfg := new(rest.Config)
mapper := new(restmapper.DeferredDiscoveryRESTMapper)
client := clientset.New(nil)
dynamic := dynamic.New(nil)
apiextensions := apiextensions.New(nil)
tCtx = ktesting.WithClients(tCtx, cfg, mapper, client, dynamic, apiextensions)
tCtx.Run("sub", func(tCtx ktesting.TContext) {
assert.Equal(t, cfg, tCtx.RESTConfig(), "RESTConfig")
assert.Equal(t, mapper, tCtx.RESTMapper(), "RESTMapper")
assert.Equal(t, client, tCtx.Client(), "Client")
assert.Equal(t, dynamic, tCtx.Dynamic(), "Dynamic")
assert.Equal(t, apiextensions, tCtx.APIExtensions(), "APIExtensions")
tCtx.Cancel("test is complete")
<-tCtx.Done()
})
if err := tCtx.Err(); err != nil {

View File

@ -102,6 +102,10 @@ func (wCtx withContext) ExpectNoError(err error, explain ...interface{}) {
expectNoError(wCtx, err, explain...)
}
func (cCtx withContext) Run(name string, cb func(tCtx TContext)) bool {
return run(cCtx, name, cb)
}
func (wCtx withContext) Logger() klog.Logger {
return klog.FromContext(wCtx)
}