// Package testutil implements helper functions for more convenient testing // and may be imported in '_test.go' files only. // Usage of some of these functions would have to be replaced by the ones // provided by the official testing toolkit packages, should they decide // to include helper functions of similar logic and kind. package testutil import ( "bufio" "bytes" "context" "fmt" "net/http" "reflect" "sync" "testing" "github.com/go-chi/chi/v5" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" ) // IgnoreBleveWorkers ignores the analysis workers bleve starts when it is // loaded and never stops, for goleak.VerifyTestMain in every package that // links the search index. func IgnoreBleveWorkers() goleak.Option { return goleak.IgnoreTopFunction("github.com/blevesearch/bleve_index_api.AnalysisWorker") } // AddChiCtx adds the provided URL parameter to the context's chi route // context, creating one when the context doesn't carry it yet. func AddChiCtx(ctx context.Context, key, value string) context.Context { rctx, ok := ctx.Value(chi.RouteCtxKey).(*chi.Context) if !ok { rctx = chi.NewRouteContext() ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx) } rctx.URLParams.Add(key, value) return ctx } // AssertEqualError uses testify's assert package to check if errors // are equal or, if assert.AnError is expected, whether an error exists // or not. func AssertEqualError(t *testing.T, exp, err error) { t.Helper() if exp != nil { if exp == assert.AnError { //nolint:err113,errorlint // direct check is needed assert.Error(t, err) return } assert.Equal(t, exp, err) return } assert.NoError(t, err) } // RequireEqualError uses testify's require package to check if errors // are equal or, if assert.AnError is expected, whether an error exists // or not. func RequireEqualError(t *testing.T, exp, err error) { t.Helper() if exp != nil { if exp == assert.AnError { //nolint:err113,errorlint // direct check is needed require.Error(t, err) return } require.Equal(t, exp, err) return } require.NoError(t, err) } // FilterEqual asserts that two objects are equal. // All ignored types found on any of the provided objects will not be compared. func FilterEqual(v1, v2 any, ignoreTypes ...any) error { diff := cmp.Diff(v1, v2, cmpopts.IgnoreTypes(ignoreTypes...), cmp.Exporter(func(reflect.Type) bool { return true })) if diff != "" { return fmt.Errorf("not equal:\n%s", diff) } return nil } // AssertFilterEqual asserts that two objects are equal. // All ignored types found on any of the provided objects will not be compared. func AssertFilterEqual(t *testing.T, v1, v2 any, ignoreTypes ...any) { t.Helper() if err := FilterEqual(v1, v2, ignoreTypes...); err != nil { t.Error(err) } } // MockHTTP creates an HTTP client backed by an isolated httpmock // transport, so tests never activate httpmock globally. func MockHTTP() (*http.Client, *httpmock.MockTransport) { mt := httpmock.NewMockTransport() return &http.Client{Transport: mt}, mt } // NewBuffer creates a fresh instance of concurrent writer and its buffer. func NewBuffer() (*Writer, *bytes.Buffer) { b := &bytes.Buffer{} out := bufio.NewWriter(b) return &Writer{out: out}, b } // Writer wraps the underlying bufio.Writer to allow concurrently safe // access. type Writer struct { out *bufio.Writer mu sync.Mutex } // Write writes the contents of p into the buffer. func (w *Writer) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock() return w.out.Write(p) } // Flush writes any buffered data to the underlying io.Writer. func (w *Writer) Flush() error { w.mu.Lock() defer w.mu.Unlock() return w.out.Flush() }