1 // Package fctx provides utilities for working with f2 test contexts. 2 // 3 // This package is implemented based on the idea that nil references and panic() 4 // should not happen in test code being exercised by the framework. It includes 5 // helpers for acessing context values in type-safe ways without extra boilerplate 6 // per access. 7 package fctx 8 9 import ( 10 "context" 11 "errors" 12 "reflect" 13 "testing" 14 "time" 15 ) 16 17 func init() { 18 // TODO: allow providing random seed 19 } 20 21 // ErrNotFound occurs when the expected value does not exist in the context. 22 var ErrNotFound = errors.New("not found in context") 23 24 // Context is the f2.Framework test context. It carries test configuration 25 // and state throughout the various stages of test execution. Individual tests 26 // and framework functions can pass state in a loosely coupled way by using 27 // WithValue, like a standard context.Context. 28 type Context struct { 29 RunID string 30 31 context.Context 32 } 33 34 // InnerContext returns the wrapped [context.Context] 35 func (c Context) InnerContext() context.Context { 36 return c.Context 37 } 38 39 // WithInnerContext updates the wrapped [context.Context] 40 func (c Context) WithInnerContext(ctx context.Context) Context { 41 c.Context = ctx 42 return c 43 } 44 45 // Assert is a helper method for creating an assert instance in a test. 46 // func (c Context) Assert(t *testing.T) *assert.Assertions { 47 // return assert.New(t) 48 // } 49 50 // Require is a helper method for creating an require instance in a test. 51 // Require is an assertion instance that immediately ends the test if an 52 // assertion fails. 53 // func (c Context) Require(t *testing.T) *require.Assertions { 54 // return require.New(t) 55 // } 56 57 // WithCancel is equivalent to context.WithCancel while preserving the generic 58 // Context implementation. It should be used when mutating f2.Context. 59 func WithCancel(c Context) (Context, context.CancelFunc) { 60 ctx, cancel := context.WithCancel(c.InnerContext()) 61 return c.WithInnerContext(ctx), cancel 62 } 63 64 // WithValue is equivalent to context.WithValue while preserving the generic 65 // Context implementation. It should be used when mutating f2.Context. 66 func WithValue(parent Context, k, v any) Context { 67 return parent.WithInnerContext(context.WithValue(parent.InnerContext(), k, v)) 68 } 69 70 // WithDeadline is equivalent to context.WithDeadline while preserving the generic 71 // Context implementation. It should be used when mutating f2.Context. 72 func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) { 73 ctx, cancel := context.WithDeadline(parent.InnerContext(), d) 74 return parent.WithInnerContext(ctx), cancel 75 } 76 77 // WithTimeout is equivalent to context.WithTimeout while preserving the generic 78 // Context implementation. It should be used when mutating f2.Context. 79 func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) { 80 ctx, cancel := context.WithTimeout(parent.InnerContext(), timeout) 81 return parent.WithInnerContext(ctx), cancel 82 } 83 84 // ValueInto is a type-safe helper for storing a value in a context by its type. 85 // Only one instance of each type can be stored. Typically used for storing 86 // framework extensions in context, since there should only be one of those per 87 // instance of the framework. 88 func ValueInto[V any](parent Context, v *V) Context { 89 return WithValue(parent, reflect.TypeOf((*V)(nil)), v) 90 } 91 92 // ValueFrom allows retrieving a type-safe value that was stored using ValueInto[T] 93 // Only one instance of each type can be stored. Typically used for storing 94 // framework extensions in context, since there should only be one of those per 95 // instance of the framework. 96 func ValueFrom[V any](ctx context.Context) *V { 97 v, _ := ctx.Value(reflect.TypeOf((*V)(nil))).(*V) 98 return v 99 } 100 101 // ValueFromT is a testing variant of ValueFrom that will automatically fail the 102 // test if the value being retrieved doesn't exist in ctx. 103 func ValueFromT[V any](ctx context.Context, t *testing.T) *V { 104 v := ValueFrom[V](ctx) 105 if v == nil { 106 t.Fatalf("%v: %T", ErrNotFound, v) 107 } 108 return v 109 } 110