blob: 40cebbe4d1b5ff8b26b34eca74f38bc51afdefd5 [file] [log] [blame]
// Copyright Istio Authors.
//
// Licensed 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"
"os"
"runtime"
"sync"
"testing"
)
import (
"istio.io/pkg/log"
)
var (
_ Failer = &testing.T{}
_ Failer = &testing.B{}
_ Failer = &errorWrapper{}
)
// 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{})
Log(args ...interface{})
Logf(format string, args ...interface{})
TempDir() string
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()
}
}
func (e *errorWrapper) Log(args ...interface{}) {
log.Info(args...)
}
func (e *errorWrapper) Logf(format string, args ...interface{}) {
ag := []interface{}{format}
ag = append(ag, args...)
log.Infof(ag...)
}
func (e *errorWrapper) TempDir() string {
tempDir, err := os.MkdirTemp("", "test")
if err == nil {
e.Cleanup(func() {
os.RemoveAll(tempDir)
})
}
return tempDir
}