istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/failer.go (about)

     1  // Copyright Istio Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package test
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"os"
    21  	"runtime"
    22  	"sync"
    23  	"testing"
    24  
    25  	"istio.io/istio/pkg/log"
    26  )
    27  
    28  var (
    29  	_ Failer = &testing.T{}
    30  	_ Failer = &testing.B{}
    31  	_ Failer = &errorWrapper{}
    32  )
    33  
    34  // Failer is an interface to be provided to test functions of the form XXXOrFail. This is a
    35  // substitute for testing.TB, which cannot be implemented outside of the testing
    36  // package.
    37  type Failer interface {
    38  	Fail()
    39  	FailNow()
    40  	Fatal(args ...any)
    41  	Fatalf(format string, args ...any)
    42  	Log(args ...any)
    43  	Logf(format string, args ...any)
    44  	TempDir() string
    45  	Helper()
    46  	Cleanup(func())
    47  	Skip(args ...any)
    48  }
    49  
    50  // Fuzzer abstracts *testing.F
    51  type Fuzzer interface {
    52  	Fuzz(ff any)
    53  	Add(args ...any)
    54  }
    55  
    56  // errorWrapper is a Failer that can be used to just extract an `error`. This allows mixing
    57  // functions that take in a Failer and those that take an error.
    58  // The function must be called within a goroutine, or calls to Fatal will try to terminate the outer
    59  // test context, which will cause the test to panic. The Wrap function handles this automatically
    60  type errorWrapper struct {
    61  	mu      sync.RWMutex
    62  	failed  error
    63  	cleanup func()
    64  }
    65  
    66  // Wrap executes a function with a fake Failer, and returns an error if the test failed. This allows
    67  // calling functions that take a Failer and using them with functions that expect an error, or
    68  // allowing calling functions that would cause a test to immediately fail to instead return an error.
    69  // Wrap handles Cleanup() and short-circuiting of Fatal() just like the real testing.T.
    70  func Wrap(f func(t Failer)) error {
    71  	done := make(chan struct{})
    72  	w := &errorWrapper{}
    73  	go func() {
    74  		defer close(done)
    75  		f(w)
    76  	}()
    77  	<-done
    78  	return w.ToErrorCleanup()
    79  }
    80  
    81  // ToErrorCleanup returns any errors encountered and executes any cleanup actions
    82  func (e *errorWrapper) ToErrorCleanup() error {
    83  	e.mu.RLock()
    84  	defer e.mu.RUnlock()
    85  	if e.cleanup != nil {
    86  		e.cleanup()
    87  	}
    88  	return e.failed
    89  }
    90  
    91  func (e *errorWrapper) Fail() {
    92  	e.Fatal("fail called")
    93  }
    94  
    95  func (e *errorWrapper) FailNow() {
    96  	e.Fatal("fail now called")
    97  }
    98  
    99  func (e *errorWrapper) Fatal(args ...any) {
   100  	e.mu.Lock()
   101  	defer e.mu.Unlock()
   102  	if e.failed == nil {
   103  		e.failed = errors.New(fmt.Sprint(args...))
   104  	}
   105  	runtime.Goexit()
   106  }
   107  
   108  func (e *errorWrapper) Fatalf(format string, args ...any) {
   109  	e.Fatal(fmt.Sprintf(format, args...))
   110  }
   111  
   112  func (e *errorWrapper) Helper() {
   113  }
   114  
   115  func (e *errorWrapper) Skip(args ...any) {
   116  	e.Fatal(args...)
   117  }
   118  
   119  func (e *errorWrapper) Cleanup(f func()) {
   120  	e.mu.Lock()
   121  	defer e.mu.Unlock()
   122  	oldCleanup := e.cleanup
   123  	e.cleanup = func() {
   124  		if oldCleanup != nil {
   125  			defer func() {
   126  				oldCleanup()
   127  			}()
   128  		}
   129  		f()
   130  	}
   131  }
   132  
   133  func (e *errorWrapper) Log(args ...any) {
   134  	log.Info(fmt.Sprint(args...))
   135  }
   136  
   137  func (e *errorWrapper) Logf(format string, args ...any) {
   138  	log.Infof(format, args...)
   139  }
   140  
   141  func (e *errorWrapper) TempDir() string {
   142  	tempDir, err := os.MkdirTemp("", "test")
   143  	if err == nil {
   144  		e.Cleanup(func() {
   145  			os.RemoveAll(tempDir)
   146  		})
   147  	}
   148  	return tempDir
   149  }