// Package fctx provides utilities for working with f2 test contexts. // // This package is implemented based on the idea that nil references and panic() // should not happen in test code being exercised by the framework. It includes // helpers for acessing context values in type-safe ways without extra boilerplate // per access. package fctx import ( "context" "errors" "reflect" "testing" "time" ) func init() { // TODO: allow providing random seed } // ErrNotFound occurs when the expected value does not exist in the context. var ErrNotFound = errors.New("not found in context") // Context is the f2.Framework test context. It carries test configuration // and state throughout the various stages of test execution. Individual tests // and framework functions can pass state in a loosely coupled way by using // WithValue, like a standard context.Context. type Context struct { RunID string context.Context } // InnerContext returns the wrapped [context.Context] func (c Context) InnerContext() context.Context { return c.Context } // WithInnerContext updates the wrapped [context.Context] func (c Context) WithInnerContext(ctx context.Context) Context { c.Context = ctx return c } // Assert is a helper method for creating an assert instance in a test. // func (c Context) Assert(t *testing.T) *assert.Assertions { // return assert.New(t) // } // Require is a helper method for creating an require instance in a test. // Require is an assertion instance that immediately ends the test if an // assertion fails. // func (c Context) Require(t *testing.T) *require.Assertions { // return require.New(t) // } // WithCancel is equivalent to context.WithCancel while preserving the generic // Context implementation. It should be used when mutating f2.Context. func WithCancel(c Context) (Context, context.CancelFunc) { ctx, cancel := context.WithCancel(c.InnerContext()) return c.WithInnerContext(ctx), cancel } // WithValue is equivalent to context.WithValue while preserving the generic // Context implementation. It should be used when mutating f2.Context. func WithValue(parent Context, k, v any) Context { return parent.WithInnerContext(context.WithValue(parent.InnerContext(), k, v)) } // WithDeadline is equivalent to context.WithDeadline while preserving the generic // Context implementation. It should be used when mutating f2.Context. func WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) { ctx, cancel := context.WithDeadline(parent.InnerContext(), d) return parent.WithInnerContext(ctx), cancel } // WithTimeout is equivalent to context.WithTimeout while preserving the generic // Context implementation. It should be used when mutating f2.Context. func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) { ctx, cancel := context.WithTimeout(parent.InnerContext(), timeout) return parent.WithInnerContext(ctx), cancel } // ValueInto is a type-safe helper for storing a value in a context by its type. // Only one instance of each type can be stored. Typically used for storing // framework extensions in context, since there should only be one of those per // instance of the framework. func ValueInto[V any](parent Context, v *V) Context { return WithValue(parent, reflect.TypeOf((*V)(nil)), v) } // ValueFrom allows retrieving a type-safe value that was stored using ValueInto[T] // Only one instance of each type can be stored. Typically used for storing // framework extensions in context, since there should only be one of those per // instance of the framework. func ValueFrom[V any](ctx context.Context) *V { v, _ := ctx.Value(reflect.TypeOf((*V)(nil))).(*V) return v } // ValueFromT is a testing variant of ValueFrom that will automatically fail the // test if the value being retrieved doesn't exist in ctx. func ValueFromT[V any](ctx context.Context, t *testing.T) *V { v := ValueFrom[V](ctx) if v == nil { t.Fatalf("%v: %T", ErrNotFound, v) } return v }