blob: b10466ad820b0a0b250b53565f07146cd32dbae6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package test
import (
"errors"
"fmt"
"runtime"
"sync"
"testing"
)
var _ Failer = &testing.T{}
// Failer is an interface to be provided to test functions of the form XXXOrFail. This is a
// substitute for testing.TB, which cannot be implemented outside of the testing
// package.
type Failer interface {
Fail()
FailNow()
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
Cleanup(func())
}
// errorWrapper is a Failer that can be used to just extract an `error`. This allows mixing
// functions that take in a Failer and those that take an error.
// The function must be called within a goroutine, or calls to Fatal will try to terminate the outer
// test context, which will cause the test to panic. The Wrap function handles this automatically
type errorWrapper struct {
mu sync.RWMutex
failed error
cleanup func()
}
// Wrap executes a function with a fake Failer, and returns an error if the test failed. This allows
// calling functions that take a Failer and using them with functions that expect an error, or
// allowing calling functions that would cause a test to immediately fail to instead return an error.
// Wrap handles Cleanup() and short-circuiting of Fatal() just like the real testing.T.
func Wrap(f func(t Failer)) error {
done := make(chan struct{})
w := &errorWrapper{}
go func() {
defer close(done)
f(w)
}()
<-done
return w.ToErrorCleanup()
}
// ToErrorCleanup returns any errors encountered and executes any cleanup actions
func (e *errorWrapper) ToErrorCleanup() error {
e.mu.RLock()
defer e.mu.RUnlock()
if e.cleanup != nil {
e.cleanup()
}
return e.failed
}
func (e *errorWrapper) Fail() {
e.Fatal("fail called")
}
func (e *errorWrapper) FailNow() {
e.Fatal("fail now called")
}
func (e *errorWrapper) Fatal(args ...interface{}) {
e.mu.Lock()
defer e.mu.Unlock()
if e.failed == nil {
e.failed = errors.New(fmt.Sprint(args...))
}
runtime.Goexit()
}
func (e *errorWrapper) Fatalf(format string, args ...interface{}) {
e.Fatal(fmt.Sprintf(format, args...))
}
func (e *errorWrapper) Helper() {
}
func (e *errorWrapper) Cleanup(f func()) {
e.mu.Lock()
defer e.mu.Unlock()
oldCleanup := e.cleanup
e.cleanup = func() {
if oldCleanup != nil {
defer func() {
oldCleanup()
}()
}
f()
}
}
var _ Failer = &errorWrapper{}