k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/utils/ktesting/errorcontext.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ktesting 18 19 import ( 20 "errors" 21 "fmt" 22 "strings" 23 "sync" 24 25 "github.com/onsi/gomega" 26 "k8s.io/klog/v2" 27 ) 28 29 // WithError creates a context where test failures are collected and stored in 30 // the provided error instance when the caller is done. Use it like this: 31 // 32 // func doSomething(tCtx ktesting.TContext) (finalErr error) { 33 // tCtx, finalize := WithError(tCtx, &finalErr) 34 // defer finalize() 35 // ... 36 // tCtx.Fatal("some failure") 37 // 38 // Any error already stored in the variable will get overwritten by finalize if 39 // there were test failures, otherwise the variable is left unchanged. 40 // If there were multiple test errors, then the error will wrap all of 41 // them with errors.Join. 42 // 43 // Test failures are not propagated to the parent context. 44 func WithError(tCtx TContext, err *error) (TContext, func()) { 45 eCtx := &errorContext{ 46 TContext: tCtx, 47 } 48 49 return eCtx, func() { 50 // Recover has to be called in the deferred function. When called inside 51 // a function called by a deferred function (like finalize below), it 52 // returns nil. 53 if e := recover(); e != nil { 54 if _, ok := e.(fatalWithError); !ok { 55 // Not our own panic, pass it on instead of setting the error. 56 panic(e) 57 } 58 } 59 60 eCtx.finalize(err) 61 } 62 } 63 64 type errorContext struct { 65 TContext 66 67 mutex sync.Mutex 68 errors []error 69 failed bool 70 } 71 72 func (eCtx *errorContext) finalize(err *error) { 73 eCtx.mutex.Lock() 74 defer eCtx.mutex.Unlock() 75 76 if !eCtx.failed { 77 return 78 } 79 80 errs := eCtx.errors 81 if len(errs) == 0 { 82 errs = []error{errFailedWithNoExplanation} 83 } 84 *err = errors.Join(errs...) 85 } 86 87 func (eCtx *errorContext) Error(args ...any) { 88 eCtx.mutex.Lock() 89 defer eCtx.mutex.Unlock() 90 91 // Gomega adds a leading newline in https://github.com/onsi/gomega/blob/f804ac6ada8d36164ecae0513295de8affce1245/internal/gomega.go#L37 92 // Let's strip that at start and end because ktesting will make errors 93 // stand out more with the "ERROR" prefix, so there's no need for additional 94 // line breaks. 95 eCtx.errors = append(eCtx.errors, errors.New(strings.TrimSpace(fmt.Sprintln(args...)))) 96 eCtx.failed = true 97 } 98 99 func (eCtx *errorContext) Errorf(format string, args ...any) { 100 eCtx.mutex.Lock() 101 defer eCtx.mutex.Unlock() 102 103 eCtx.errors = append(eCtx.errors, errors.New(strings.TrimSpace(fmt.Sprintf(format, args...)))) 104 eCtx.failed = true 105 } 106 107 func (eCtx *errorContext) Fail() { 108 eCtx.mutex.Lock() 109 defer eCtx.mutex.Unlock() 110 111 eCtx.failed = true 112 } 113 114 func (eCtx *errorContext) FailNow() { 115 eCtx.Helper() 116 eCtx.Fail() 117 panic(failed) 118 } 119 120 func (eCtx *errorContext) Failed() bool { 121 eCtx.mutex.Lock() 122 defer eCtx.mutex.Unlock() 123 124 return eCtx.failed 125 } 126 127 func (eCtx *errorContext) Fatal(args ...any) { 128 eCtx.Error(args...) 129 eCtx.FailNow() 130 } 131 132 func (eCtx *errorContext) Fatalf(format string, args ...any) { 133 eCtx.Errorf(format, args...) 134 eCtx.FailNow() 135 } 136 137 func (eCtx *errorContext) CleanupCtx(cb func(TContext)) { 138 eCtx.Helper() 139 cleanupCtx(eCtx, cb) 140 } 141 142 func (eCtx *errorContext) Expect(actual interface{}, extra ...interface{}) gomega.Assertion { 143 eCtx.Helper() 144 return expect(eCtx, actual, extra...) 145 } 146 147 func (eCtx *errorContext) ExpectNoError(err error, explain ...interface{}) { 148 eCtx.Helper() 149 expectNoError(eCtx, err, explain...) 150 } 151 152 func (eCtx *errorContext) Logger() klog.Logger { 153 return klog.FromContext(eCtx) 154 } 155 156 // fatalWithError is the internal type that should never get propagated up. The 157 // only case where that can happen is when the developer forgot to call 158 // finalize via defer. The string explains that, in case that developers get to 159 // see it. 160 type fatalWithError string 161 162 const failed = fatalWithError("WithError TContext encountered a fatal error, but the finalize function was not called via defer as it should have been.") 163 164 var errFailedWithNoExplanation = errors.New("WithError context was marked as failed without recording an error")