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 }