wa-lang.org/wazero@v1.0.2/internal/testing/require/require.go (about) 1 // Package require includes test assertions that fail the test immediately. This is like to testify, but without a 2 // dependency. 3 // 4 // Note: Assertions here are internal and are free to be customized to only support valid WebAssembly types, or to 5 // reduce code in tests that only require certain types. 6 package require 7 8 import ( 9 "bytes" 10 "errors" 11 "fmt" 12 "path" 13 "reflect" 14 "runtime" 15 "strings" 16 "unicode" 17 "unicode/utf8" 18 ) 19 20 // TestingT is an interface wrapper of functions used in TestingT 21 type TestingT interface { 22 Fatal(args ...interface{}) 23 } 24 25 type EqualTo interface { 26 EqualTo(that interface{}) bool 27 } 28 29 // TODO: implement, test and document each function without using testify 30 31 // Contains fails if `s` does not contain `substr` using strings.Contains. 32 // 33 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 34 func Contains(t TestingT, s, substr string, formatWithArgs ...interface{}) { 35 if !strings.Contains(s, substr) { 36 fail(t, fmt.Sprintf("expected %q to contain %q", s, substr), "", formatWithArgs...) 37 } 38 } 39 40 // Equal fails if the actual value is not equal to the expected. 41 // 42 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 43 func Equal(t TestingT, expected, actual interface{}, formatWithArgs ...interface{}) { 44 if equal(expected, actual) { 45 return 46 } 47 _, expectString := expected.(string) 48 if actual == nil { 49 if expectString { 50 fail(t, fmt.Sprintf("expected %q, but was nil", expected), "", formatWithArgs...) 51 } else { 52 fail(t, fmt.Sprintf("expected %#v, but was nil", expected), "", formatWithArgs...) 53 } 54 return 55 } 56 57 // Include the type name if the actual wasn't the same 58 et, at := reflect.ValueOf(expected).Type(), reflect.ValueOf(actual).Type() 59 if et != at { 60 if expectString { 61 fail(t, fmt.Sprintf("expected %q, but was %s(%v)", expected, at, actual), "", formatWithArgs...) 62 } else { 63 fail(t, fmt.Sprintf("expected %s(%v), but was %s(%v)", et, expected, at, actual), "", formatWithArgs...) 64 } 65 return 66 } 67 68 // Inline the comparison if the types are likely small: 69 if expectString { 70 // Don't use %q as it escapes newlines! 71 fail(t, fmt.Sprintf("expected \"%s\", but was \"%s\"", expected, actual), "", formatWithArgs...) 72 return 73 } else if et.Kind() < reflect.Array { 74 fail(t, fmt.Sprintf("expected %v, but was %v", expected, actual), "", formatWithArgs...) 75 return 76 } else if et.Kind() == reflect.Func { 77 // compare funcs by string pointer 78 expected := fmt.Sprintf("%v", expected) 79 actual := fmt.Sprintf("%v", actual) 80 if expected != actual { 81 fail(t, fmt.Sprintf("expected %s, but was %s", expected, actual), "", formatWithArgs...) 82 } 83 return 84 } else if eq, ok := actual.(EqualTo); ok { 85 if !eq.EqualTo(expected) { 86 fail(t, fmt.Sprintf("expected %v, but was %v", expected, actual), "", formatWithArgs...) 87 } 88 } 89 90 // If we have the same type, and it isn't a string, but the expected and actual values on a different line. 91 // This allows easier comparison without using a diff library. 92 fail(t, "unexpected value", fmt.Sprintf("expected:\n\t%#v\nwas:\n\t%#v\n", expected, actual), formatWithArgs...) 93 } 94 95 // equal speculatively tries to cast the inputs as byte arrays and falls back to reflection. 96 func equal(expected, actual interface{}) bool { 97 if b1, ok := expected.([]byte); !ok { 98 return reflect.DeepEqual(expected, actual) 99 } else if b2, ok := actual.([]byte); ok { 100 return bytes.Equal(b1, b2) 101 } 102 return false 103 } 104 105 // EqualError fails if the err is nil or its `Error()` value is not equal to the expected. 106 // 107 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 108 func EqualError(t TestingT, err error, expected string, formatWithArgs ...interface{}) { 109 if err == nil { 110 fail(t, "expected an error, but was nil", "", formatWithArgs...) 111 return 112 } 113 actual := err.Error() 114 if actual != expected { 115 fail(t, fmt.Sprintf("expected error \"%s\", but was \"%s\"", expected, actual), "", formatWithArgs...) 116 } 117 } 118 119 // Error fails if the err is nil. 120 // 121 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 122 func Error(t TestingT, err error, formatWithArgs ...interface{}) { 123 if err == nil { 124 fail(t, "expected an error, but was nil", "", formatWithArgs...) 125 } 126 } 127 128 // ErrorIs fails if the err is nil or errors.Is fails against the expected. 129 // 130 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 131 func ErrorIs(t TestingT, err, target error, formatWithArgs ...interface{}) { 132 if err == nil { 133 fail(t, "expected an error, but was nil", "", formatWithArgs...) 134 return 135 } 136 if !errors.Is(err, target) { 137 fail(t, fmt.Sprintf("expected errors.Is(%v, %v), but it wasn't", err, target), "", formatWithArgs...) 138 } 139 } 140 141 // False fails if the actual value was true. 142 // 143 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 144 func False(t TestingT, actual bool, formatWithArgs ...interface{}) { 145 if actual { 146 fail(t, "expected false, but was true", "", formatWithArgs...) 147 } 148 } 149 150 // Nil fails if the object is not nil. 151 // 152 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 153 func Nil(t TestingT, object interface{}, formatWithArgs ...interface{}) { 154 if !isNil(object) { 155 fail(t, fmt.Sprintf("expected nil, but was %v", object), "", formatWithArgs...) 156 } 157 } 158 159 // NoError fails if the err is not nil. 160 // 161 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 162 func NoError(t TestingT, err error, formatWithArgs ...interface{}) { 163 if err != nil { 164 fail(t, fmt.Sprintf("expected no error, but was %v", err), "", formatWithArgs...) 165 } 166 } 167 168 // NotEqual fails if the actual value is equal to the expected. 169 // 170 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 171 func NotEqual(t TestingT, expected, actual interface{}, formatWithArgs ...interface{}) { 172 if !equal(expected, actual) { 173 return 174 } 175 _, expectString := expected.(string) 176 if expectString { 177 fail(t, fmt.Sprintf("expected to not equal %q", actual), "", formatWithArgs...) 178 return 179 } 180 fail(t, fmt.Sprintf("expected to not equal %#v", actual), "", formatWithArgs...) 181 } 182 183 // NotNil fails if the object is nil. 184 // 185 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 186 func NotNil(t TestingT, object interface{}, formatWithArgs ...interface{}) { 187 if isNil(object) { 188 fail(t, "expected to not be nil", "", formatWithArgs...) 189 } 190 } 191 192 // isNil is less efficient for the sake of less code vs tracking all the nil types in Go. 193 func isNil(object interface{}) (isNil bool) { 194 if object == nil { 195 return true 196 } 197 198 v := reflect.ValueOf(object) 199 200 defer func() { 201 if recovered := recover(); recovered != nil { 202 // ignore problems using isNil on a type that can't be nil 203 isNil = false 204 } 205 }() 206 207 isNil = v.IsNil() 208 return 209 } 210 211 // NotSame fails if the inputs point to the same object. 212 // 213 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 214 func NotSame(t TestingT, expected, actual interface{}, formatWithArgs ...interface{}) { 215 if equalsPointer(expected, actual) { 216 fail(t, fmt.Sprintf("expected %v to point to a different object", actual), "", formatWithArgs...) 217 return 218 } 219 } 220 221 // CapturePanic returns an error recovered from a panic. If the panic was not an error, this converts it to one. 222 func CapturePanic(panics func()) (err error) { 223 defer func() { 224 if recovered := recover(); recovered != nil { 225 if e, ok := recovered.(error); ok { 226 err = e 227 } else { 228 err = fmt.Errorf("%v", recovered) 229 } 230 } 231 }() 232 panics() 233 return 234 } 235 236 // Same fails if the inputs don't point to the same object. 237 // 238 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 239 func Same(t TestingT, expected, actual interface{}, formatWithArgs ...interface{}) { 240 if !equalsPointer(expected, actual) { 241 fail(t, fmt.Sprintf("expected %v to point to the same object as %v", actual, expected), "", formatWithArgs...) 242 return 243 } 244 } 245 246 func equalsPointer(expected, actual interface{}) bool { 247 expectedV := reflect.ValueOf(expected) 248 if expectedV.Kind() != reflect.Ptr { 249 panic("BUG: expected was not a pointer") 250 } 251 actualV := reflect.ValueOf(actual) 252 if actualV.Kind() != reflect.Ptr { 253 panic("BUG: actual was not a pointer") 254 } 255 256 if t1, t2 := reflect.TypeOf(expectedV), reflect.TypeOf(actualV); t1 != t2 { 257 return false 258 } else { 259 return expected == actual 260 } 261 } 262 263 // True fails if the actual value wasn't. 264 // 265 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 266 func True(t TestingT, actual bool, formatWithArgs ...interface{}) { 267 if !actual { 268 fail(t, "expected true, but was false", "", formatWithArgs...) 269 } 270 } 271 272 // Zero fails if the actual value wasn't. 273 // 274 // - formatWithArgs are optional. When the first is a string that contains '%', it is treated like fmt.Sprintf. 275 // 276 // Note: This isn't precise to numeric types, but we don't care as being more precise is more code and tests. 277 func Zero(t TestingT, i interface{}, formatWithArgs ...interface{}) { 278 if i == nil { 279 fail(t, "expected zero, but was nil", "", formatWithArgs...) 280 } 281 zero := reflect.Zero(reflect.TypeOf(i)) 282 if i != zero.Interface() { 283 fail(t, fmt.Sprintf("expected zero, but was %v", i), "", formatWithArgs...) 284 } 285 } 286 287 // fail tries to treat the formatWithArgs as fmt.Sprintf parameters or joins on space. 288 func fail(t TestingT, m1, m2 string, formatWithArgs ...interface{}) { 289 var failure string 290 if len(formatWithArgs) > 0 { 291 if s, ok := formatWithArgs[0].(string); ok && strings.Contains(s, "%") { 292 failure = fmt.Sprintf(m1+": "+s, formatWithArgs[1:]...) 293 } else { 294 var builder strings.Builder 295 builder.WriteString(fmt.Sprintf("%s: %v", m1, formatWithArgs[0])) 296 for _, v := range formatWithArgs[1:] { 297 builder.WriteByte(' ') 298 builder.WriteString(fmt.Sprintf("%v", v)) 299 } 300 failure = builder.String() 301 } 302 } else { 303 failure = m1 304 } 305 if m2 != "" { 306 failure = failure + "\n" + m2 307 } 308 309 // Don't write the failStack in our own package! 310 if fs := failStack(); len(fs) > 0 { 311 t.Fatal(failure + "\n" + strings.Join(fs, "\n")) 312 } else { 313 t.Fatal(failure) 314 } 315 } 316 317 // failStack returns the stack leading to the failure, without test infrastructure. 318 // 319 // Note: This is similar to assert.CallerInfo in testify 320 // Note: This is untested because it is a lot of work to do that. The rationale to punt is this is a test-only internal 321 // type which returns optional info. Someone can add tests, but they'd need to do that as an integration test in a 322 // different package with something stable line-number-wise. 323 func failStack() (fs []string) { 324 for i := 0; ; i++ { 325 pc, file, line, ok := runtime.Caller(i) 326 if !ok { 327 break // don't loop forever on a bug 328 } 329 330 f := runtime.FuncForPC(pc) 331 if f == nil { 332 break // don't loop forever on a bug 333 } 334 name := f.Name() 335 336 if name == "testing.tRunner" { 337 break // Don't add the runner from src/testing/testing.go 338 } 339 340 // Ensure we don't add functions in the require package to the failure stack. 341 dir := path.Dir(file) 342 if path.Base(dir) != "require" { 343 fs = append(fs, fmt.Sprintf("%s:%d", file, line)) 344 } 345 346 // Stop the stack when we get to a test. Strip off any leading package name first! 347 if dot := strings.Index(name, "."); dot > 0 { 348 if isTest(name[dot+1:]) { 349 return 350 } 351 } 352 } 353 return 354 } 355 356 var testPrefixes = []string{"Test", "Benchmark", "Example"} 357 358 // isTest is similar to load.isTest in Go's src/cmd/go/internal/load/test.go 359 func isTest(name string) bool { 360 for _, prefix := range testPrefixes { 361 if !strings.HasPrefix(name, prefix) { 362 return false 363 } 364 if len(name) == len(prefix) { // "Test" is ok 365 return true 366 } 367 if r, _ := utf8.DecodeRuneInString(name[len(prefix):]); !unicode.IsLower(r) { 368 return true 369 } 370 } 371 return false 372 }