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