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