github.com/mitranim/gg@v0.1.17/gtest/gtest.go (about) 1 /* 2 Missing feature of the standard library: terse, expressive test assertions. 3 */ 4 package gtest 5 6 import ( 7 e "errors" 8 "fmt" 9 "strings" 10 "testing" 11 12 "github.com/mitranim/gg" 13 ) 14 15 /* 16 Used internally by assertion utils. Error wrapper whose default stringing 17 uses "%+v" formatting on the inner error, causing it to be ALWAYS formatted 18 with a stack trace, which is useful when panics are not caught. 19 */ 20 type Err struct{ gg.Err } 21 22 /* 23 Implement `error` by using full formatting on the inner error: multiline with a 24 stack trace. 25 */ 26 func (self Err) Error() string { return self.String() } 27 28 /* 29 Implement `fmt.Stringer` by using full formatting on the inner error: multiline 30 with a stack trace. 31 */ 32 func (self Err) String() string { 33 var buf gg.Buf 34 buf = self.Err.AppendStack(buf) 35 buf.AppendString(gg.Newline) 36 return buf.String() 37 } 38 39 /* 40 Shortcut for generating a test error (of type `Err` provided by this package) 41 with the given message, skipping the given amount of stack frames. 42 */ 43 func ErrAt(skip int, msg ...any) Err { 44 return Err{gg.Err{}.Msgv(msg...).TracedAt(skip + 1)} 45 } 46 47 /* 48 Shortcut for generating an error where the given messages are combined as 49 lines. 50 */ 51 func ErrLines(msg ...any) Err { 52 // Suboptimal but not anyone's bottleneck. 53 return ErrAt(1, gg.JoinLinesOpt(gg.Map(msg, gg.String[any])...)) 54 } 55 56 /* 57 Must be deferred. Usage: 58 59 func TestSomething(t *testing.T) { 60 // Catches panics and uses `t.Fatalf`. 61 defer gtest.Catch(t) 62 63 // Test assertion. Panics and gets caught above. 64 gtest.Eq(10, 20) 65 } 66 */ 67 func Catch(t testing.TB) { 68 t.Helper() 69 val := gg.AnyErrTracedAt(recover(), 1) 70 if val != nil { 71 t.Fatalf(`%+v`, val) 72 } 73 } 74 75 /* 76 Asserts that the input is `true`, or fails the test, printing the optional 77 additional messages and the stack trace. 78 */ 79 func True(val bool, opt ...any) { 80 if !val { 81 panic(ErrAt(1, msgOpt(opt, `expected true, got false`))) 82 } 83 } 84 85 /* 86 Asserts that the input is `false`, or fails the test, printing the optional 87 additional messages and the stack trace. 88 */ 89 func False(val bool, opt ...any) { 90 if val { 91 panic(ErrAt(1, msgOpt(opt, `expected false, got true`))) 92 } 93 } 94 95 /* 96 Asserts that the inputs are byte-for-byte identical, via `gg.Is`. Otherwise 97 fails the test, printing the optional additional messages and the stack trace. 98 Intended for interface values, maps, chans, funcs. For slices, use `SliceIs`. 99 */ 100 func Is[A any](act, exp A, opt ...any) { 101 if gg.Is(act, exp) { 102 return 103 } 104 105 if gg.Equal(act, exp) { 106 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 107 `inputs are equal but not identical`, 108 MsgEqInner(act, exp), 109 )))) 110 } 111 112 panic(ErrAt(1, msgOpt(opt, MsgEq(act, exp)))) 113 } 114 115 /* 116 Asserts that the inputs are NOT byte-for-byte identical, via `gg.Is`. Otherwise 117 fails the test, printing the optional additional messages and the stack trace. 118 Intended for interface values, maps, chans, funcs. For slices, use `NotSliceIs`. 119 */ 120 func NotIs[A any](act, exp A, opt ...any) { 121 if gg.Is(act, exp) { 122 panic(ErrAt(1, msgOpt(opt, MsgNotEq(act)))) 123 } 124 } 125 126 /* 127 Asserts that the inputs are equal via `==`, or fails the test, printing the 128 optional additional messages and the stack trace. 129 */ 130 func Eq[A comparable](act, exp A, opt ...any) { 131 if act != exp { 132 panic(ErrAt(1, msgOpt(opt, MsgEq(act, exp)))) 133 } 134 } 135 136 /* 137 Asserts that the inputs are equal via `==`, or fails the test, printing the 138 optional additional messages and the stack trace. Doesn't statically require 139 the inputs to be comparable, but may panic if they aren't. 140 */ 141 func AnyEq[A any](act, exp A, opt ...any) { 142 if any(act) != any(exp) { 143 panic(ErrAt(1, msgOpt(opt, MsgEq(act, exp)))) 144 } 145 } 146 147 /* 148 Asserts that the inputs are equal via `gg.TextEq`, or fails the test, printing 149 the optional additional messages and the stack trace. 150 */ 151 func TextEq[A gg.Text](act, exp A, opt ...any) { 152 if !gg.TextEq(act, exp) { 153 panic(ErrAt(1, msgOpt(opt, MsgEq(act, exp)))) 154 } 155 } 156 157 /* 158 Asserts that the inputs are not equal via `!=`, or fails the test, printing the 159 optional additional messages and the stack trace. 160 */ 161 func NotEq[A comparable](act, nom A, opt ...any) { 162 if act == nom { 163 panic(ErrAt(1, msgOpt(opt, MsgNotEq(act)))) 164 } 165 } 166 167 // Internal shortcut for generating parts of an error message. 168 func MsgEq(act, exp any) string { 169 return gg.JoinLinesOpt(`unexpected difference`, MsgEqInner(act, exp)) 170 } 171 172 // Used internally when generating error messages about failed equality. 173 func MsgEqInner(act, exp any) string { 174 if isSimple(act) && isSimple(exp) { 175 return gg.JoinLinesOpt( 176 Msg(`actual:`, gg.StringAny(act)), 177 Msg(`expected:`, gg.StringAny(exp)), 178 ) 179 } 180 181 return gg.JoinLinesOpt( 182 MsgEqDetailed(act, exp), 183 MsgEqSimple(act, exp), 184 ) 185 } 186 187 // Internal shortcut for generating parts of an error message. 188 func MsgEqDetailed(act, exp any) string { 189 return gg.JoinLinesOpt( 190 Msg(`actual detailed:`, goStringIndent(act)), 191 Msg(`expected detailed:`, goStringIndent(exp)), 192 ) 193 } 194 195 // Internal shortcut for generating parts of an error message. 196 func MsgEqSimple(act, exp any) string { 197 return gg.JoinLinesOpt( 198 Msg(`actual simple:`, gg.StringAny(act)), 199 Msg(`expected simple:`, gg.StringAny(exp)), 200 ) 201 } 202 203 // Internal shortcut for generating parts of an error message. 204 func MsgNotEq[A any](act A) string { 205 return gg.JoinLinesOpt(`unexpected equality`, msgSingle(act)) 206 } 207 208 // Internal shortcut for generating parts of an error message. 209 func MsgOpt(msg, det string) string { 210 if det == `` { 211 return `` 212 } 213 return Msg(msg, det) 214 } 215 216 // Internal shortcut for generating parts of an error message. 217 func Msg(msg, det string) string { return gg.JoinLinesOpt(msg, reindent(det)) } 218 219 /* 220 Asserts that the inputs are not equal via `gg.TextEq`, or fails the test, 221 printing the optional additional messages and the stack trace. 222 */ 223 func NotTextEq[A gg.Text](act, nom A, opt ...any) { 224 if gg.TextEq(act, nom) { 225 panic(ErrAt(1, msgOpt(opt, MsgNotEq(act)))) 226 } 227 } 228 229 /* 230 Asserts that the inputs are deeply equal, or fails the test, printing the 231 optional additional messages and the stack trace. 232 */ 233 func Equal[A any](act, exp A, opt ...any) { 234 if !gg.Equal(act, exp) { 235 panic(ErrAt(1, msgOpt(opt, MsgEq(act, exp)))) 236 } 237 } 238 239 /* 240 Asserts that the input slices have the same set of elements, or fails the test, 241 printing the optional additional messages and the stack trace. 242 */ 243 func EqualSet[A ~[]B, B comparable](act, exp A, opt ...any) { 244 missingAct := gg.Exclude(exp, act...) 245 var msgMissingAct string 246 247 missingExp := gg.Exclude(act, exp...) 248 var msgMissingExp string 249 250 if len(missingAct) > 0 { 251 msgMissingAct = Msg(`missing from actual:`, goStringIndent(missingAct)) 252 } 253 254 if len(missingExp) > 0 { 255 msgMissingExp = Msg(`missing from expected:`, goStringIndent(missingExp)) 256 } 257 258 if !gg.Equal(gg.SetFrom(act), gg.SetFrom(exp)) { 259 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 260 `unexpected difference in element sets`, 261 Msg(`actual:`, goStringIndent(act)), 262 Msg(`expected:`, goStringIndent(exp)), 263 msgMissingAct, 264 msgMissingExp, 265 )))) 266 } 267 } 268 269 /* 270 Asserts that the inputs are not deeply equal, or fails the test, printing the 271 optional additional messages and the stack trace. 272 */ 273 func NotEqual[A any](act, nom A, opt ...any) { 274 if gg.Equal(act, nom) { 275 panic(ErrAt(1, msgOpt(opt, MsgNotEq(act)))) 276 } 277 } 278 279 /* 280 Asserts that the given slice headers (not their elements) are equal via 281 `gg.SliceIs`. This means they have the same data pointer, length, capacity. 282 Does NOT compare individual elements, unlike `Equal`. Otherwise fails the test, 283 printing the optional additional messages and the stack trace. 284 */ 285 func SliceIs[A ~[]B, B any](act, exp A, opt ...any) { 286 if !gg.SliceIs(act, exp) { 287 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 288 `expected given slice headers to be identical, but they were distinct`, 289 Msg(`actual header:`, goStringIndent(gg.SliceHeaderOf(act))), 290 Msg(`expected header:`, goStringIndent(gg.SliceHeaderOf(exp))), 291 )))) 292 } 293 } 294 295 /* 296 Asserts that the given slice headers (not their elements) are distinct. This 297 means at least one of the following fields is different: data pointer, length, 298 capacity. Does NOT compare individual elements, unlike `NotEqual`. Otherwise 299 fails the test, printing the optional additional messages and the stack trace. 300 */ 301 func NotSliceIs[A ~[]B, B any](act, nom A, opt ...any) { 302 if gg.SliceIs(act, nom) { 303 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 304 `expected given slice headers to be distinct, but they were identical`, 305 Msg(`actual header:`, goStringIndent(gg.SliceHeaderOf(act))), 306 Msg(`nominal header:`, goStringIndent(gg.SliceHeaderOf(nom))), 307 )))) 308 } 309 } 310 311 /* 312 Asserts that the input is zero via `gg.IsZero`, or fails the test, printing the 313 optional additional messages and the stack trace. 314 */ 315 func Zero[A any](val A, opt ...any) { 316 if !gg.IsZero(val) { 317 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 318 `unexpected non-zero value`, 319 msgSingle(val), 320 )))) 321 } 322 } 323 324 /* 325 Asserts that the input is zero via `gg.IsZero`, or fails the test, printing the 326 optional additional messages and the stack trace. 327 */ 328 func NotZero[A any](val A, opt ...any) { 329 if gg.IsZero(val) { 330 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 331 `unexpected zero value`, 332 msgSingle(val), 333 )))) 334 } 335 } 336 337 /* 338 Asserts that the given function panics AND that the resulting error satisfies 339 the given error-testing function. Otherwise fails the test, printing the 340 optional additional messages and the stack trace. 341 */ 342 func Panic(test func(error) bool, fun func(), opt ...any) { 343 err := gg.Catch(fun) 344 345 if err == nil { 346 panic(ErrAt(1, msgOpt(opt, msgPanicNoneWithTest(fun, test)))) 347 } 348 349 if !test(err) { 350 panic(ErrAt(1, msgOpt(opt, msgErrMismatch(fun, test, err)))) 351 } 352 } 353 354 /* 355 Asserts that the given function panics with an error whose message contains the 356 given substring, or fails the test, printing the optional additional messages 357 and the stack trace. 358 */ 359 func PanicStr(exp string, fun func(), opt ...any) { 360 if exp == `` { 361 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 362 `refusing to test for panic without a non-empty expected error message`, 363 msgFun(fun), 364 )))) 365 } 366 367 err := gg.Catch(fun) 368 if err == nil { 369 panic(ErrAt(1, msgOpt(opt, msgPanicNoneWithStr(fun, exp)))) 370 } 371 372 msg := err.Error() 373 if !strings.Contains(msg, exp) { 374 panic(ErrAt(1, msgOpt(opt, msgErrMsgMismatch(fun, exp, msg)))) 375 } 376 } 377 378 /* 379 Asserts that the given function panics and the panic result matches the given 380 error via `errors.Is`, or fails the test, printing the optional additional 381 messages and the stack trace. 382 */ 383 func PanicErrIs(exp error, fun func(), opt ...any) { 384 if exp == nil { 385 panic(ErrAt(1, msgOpt(opt, `expected error must be non-nil`))) 386 } 387 388 err := gg.Catch(fun) 389 if err == nil { 390 panic(ErrAt(1, msgOpt(opt, msgPanicNoneWithErr(fun, exp)))) 391 } 392 393 if !e.Is(err, exp) { 394 panic(ErrAt(1, msgOpt(opt, msgErrIsMismatch(err, exp)))) 395 } 396 } 397 398 /* 399 Asserts that the given function panics, or fails the test, printing the optional 400 additional messages and the stack trace. 401 */ 402 func PanicAny(fun func(), opt ...any) { 403 err := gg.Catch(fun) 404 405 if err == nil { 406 panic(ErrAt(1, msgOpt(opt, msgPanicNoneWithTest(fun, nil)))) 407 } 408 } 409 410 /* 411 Asserts that the given function doesn't panic, or fails the test, printing the 412 error's trace if possible, the optional additional messages, and the stack 413 trace. 414 */ 415 func NotPanic(fun func(), opt ...any) { 416 err := gg.Catch(fun) 417 if err != nil { 418 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 419 `unexpected panic`, 420 msgFunErr(fun, err), 421 )))) 422 } 423 } 424 425 /* 426 Asserts that the given error is non-nil AND satisfies the given error-testing 427 function. Otherwise fails the test, printing the optional additional messages 428 and the stack trace. 429 */ 430 func Error(test func(error) bool, err error, opt ...any) { 431 if err == nil { 432 panic(ErrAt(1, msgOpt(opt, msgErrorNone(test)))) 433 } 434 435 if !test(err) { 436 panic(ErrAt(1, msgOpt(opt, msgErrMismatch(nil, test, err)))) 437 } 438 } 439 440 /* 441 Asserts that the given error is non-nil and its message contains the given 442 substring, or fails the test, printing the optional additional messages and the 443 stack trace. 444 */ 445 func ErrorStr(exp string, err error, opt ...any) { 446 if err == nil { 447 panic(ErrAt(1, msgOpt(opt, msgErrorNone(nil)))) 448 } 449 450 msg := err.Error() 451 452 if !strings.Contains(msg, exp) { 453 panic(ErrAt(1, msgOpt(opt, msgErrMsgMismatch(nil, exp, msg)))) 454 } 455 } 456 457 /* 458 Asserts that the given error is non-nil and matches the expected error via 459 `errors.Is`, or fails the test, printing the optional additional messages and 460 the stack trace. 461 */ 462 func ErrorIs(exp, err error, opt ...any) { 463 if exp == nil { 464 panic(ErrAt(1, msgOpt(opt, `expected error must be non-nil`))) 465 } 466 467 if !e.Is(err, exp) { 468 panic(ErrAt(1, msgOpt(opt, msgErrIsMismatch(err, exp)))) 469 } 470 } 471 472 /* 473 Asserts that the given error is non-nil, or fails the test, printing the 474 optional additional messages and the stack trace. 475 */ 476 func ErrorAny(err error, opt ...any) { 477 if err == nil { 478 panic(ErrAt(1, msgOpt(opt, msgErrorNone(nil)))) 479 } 480 } 481 482 /* 483 Asserts that the given error is nil, or fails the test, printing the error's 484 trace if possible, the optional additional messages, and the stack trace. 485 */ 486 func NoError(err error, opt ...any) { 487 if err != nil { 488 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 489 `unexpected error`, 490 msgErr(err), 491 )))) 492 } 493 } 494 495 // Shortcut for error testing. 496 type ErrMsgTest string 497 498 // Tests that the given error has the given message. 499 func (self ErrMsgTest) Is(err error) bool { 500 return err != nil && strings.Contains(err.Error(), string(self)) 501 } 502 503 /* 504 Asserts that the given slice contains the given value, or fails the test, 505 printing the optional additional messages and the stack trace. 506 */ 507 func Has[A ~[]B, B comparable](src A, val B, opt ...any) { 508 if !gg.Has(src, val) { 509 panic(ErrAt(1, msgOpt(opt, msgSliceElemMissing(src, val)))) 510 } 511 } 512 513 /* 514 Asserts that the given slice does not contain the given value, or fails the 515 test, printing the optional additional messages and the stack trace. 516 */ 517 func NotHas[A ~[]B, B comparable](src A, val B, opt ...any) { 518 if gg.Has(src, val) { 519 panic(ErrAt(1, msgOpt(opt, msgSliceElemUnexpected(src, val)))) 520 } 521 } 522 523 /* 524 Asserts that the given slice contains the given value, or fails the test, 525 printing the optional additional messages and the stack trace. Uses `gg.Equal` 526 to compare values. For values that implement `comparable`, use `Has` which is 527 simpler and faster. 528 */ 529 func HasEqual[A ~[]B, B any](src A, val B, opt ...any) { 530 if !gg.HasEqual(src, val) { 531 panic(ErrAt(1, msgOpt(opt, msgSliceElemMissing(src, val)))) 532 } 533 } 534 535 /* 536 Asserts that the given slice does not contain the given value, or fails the 537 test, printing the optional additional messages and the stack trace. Uses 538 `gg.Equal` to compare values. For values that implement `comparable`, use 539 `HasNot` which is simpler and faster. 540 */ 541 func NotHasEqual[A ~[]B, B any](src A, val B, opt ...any) { 542 if gg.HasEqual(src, val) { 543 panic(ErrAt(1, msgOpt(opt, msgSliceElemUnexpected(src, val)))) 544 } 545 } 546 547 /* 548 Asserts that the first slice contains all elements from the second slice. In 549 other words, asserts that the first slice is a strict superset of the second. 550 Otherwise fails the test, printing the optional additional messages and the 551 stack trace. 552 */ 553 func HasEvery[A ~[]B, B comparable](src, exp A, opt ...any) { 554 missing := gg.Exclude(exp, src...) 555 556 if len(missing) > 0 { 557 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 558 `expected outer slice to contain all elements from inner slice`, 559 // TODO avoid detailed view when it's unnecessary. 560 Msg(`outer detailed:`, goStringIndent(src)), 561 Msg(`inner detailed:`, goStringIndent(exp)), 562 Msg(`missing detailed:`, goStringIndent(missing)), 563 Msg(`outer simple:`, gg.StringAny(src)), 564 Msg(`inner simple:`, gg.StringAny(exp)), 565 Msg(`missing simple:`, gg.StringAny(missing)), 566 )))) 567 } 568 } 569 570 /* 571 Asserts that the first slice contains some elements from the second slice. In 572 other words, asserts that the element sets have an intersection. Otherwise 573 fails the test, printing the optional additional messages and the stack trace. 574 */ 575 func HasSome[A ~[]B, B comparable](src, exp A, opt ...any) { 576 if !gg.HasSome(src, exp) { 577 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 578 `unexpected lack of shared elements in two slices`, 579 Msg(`left detailed:`, goStringIndent(src)), 580 Msg(`right detailed:`, goStringIndent(exp)), 581 Msg(`left simple:`, gg.StringAny(src)), 582 Msg(`right simple:`, gg.StringAny(exp)), 583 )))) 584 } 585 } 586 587 /* 588 Asserts that the first slice does not contain any from the second slice. In 589 other words, asserts that the element sets are disjoint. Otherwise fails the 590 test, printing the optional additional messages and the stack trace. 591 */ 592 func HasNone[A ~[]B, B comparable](src, exp A, opt ...any) { 593 inter := gg.Intersect(src, exp) 594 595 if len(inter) > 0 { 596 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 597 `expected left slice to contain no elements from right slice`, 598 Msg(`left detailed:`, goStringIndent(src)), 599 Msg(`right detailed:`, goStringIndent(exp)), 600 Msg(`intersection detailed:`, goStringIndent(inter)), 601 Msg(`left simple:`, gg.StringAny(src)), 602 Msg(`right simple:`, gg.StringAny(exp)), 603 Msg(`intersection simple:`, gg.StringAny(inter)), 604 )))) 605 } 606 } 607 608 /* 609 Asserts that every element of the given slice satisfies the given predicate 610 function, or fails the test, printing the optional additional messages and the 611 stack trace. 612 */ 613 func Every[A ~[]B, B any](src A, fun func(B) bool, opt ...any) { 614 for ind, val := range src { 615 if fun == nil || !fun(val) { 616 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 617 gg.Str( 618 `expected every element to satisfy predicate `, gg.FuncName(fun), 619 `; element at index `, ind, ` did not satisfy`, 620 ), 621 Msg(`slice detailed:`, goStringIndent(src)), 622 Msg(`element detailed:`, goStringIndent(val)), 623 Msg(`slice simple:`, gg.StringAny(src)), 624 Msg(`element simple:`, gg.StringAny(val)), 625 )))) 626 } 627 } 628 } 629 630 /* 631 Asserts that at least one element of the given slice satisfies the given 632 predicate function, or fails the test, printing the optional additional 633 messages and the stack trace. 634 */ 635 func Some[A ~[]B, B any](src A, fun func(B) bool, opt ...any) { 636 if gg.Some(src, fun) { 637 return 638 } 639 640 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 641 gg.Str( 642 `expected at least one element to satisfy predicate `, gg.FuncName(fun), 643 `; found no such elements`, 644 ), 645 Msg(`slice detailed:`, goStringIndent(src)), 646 Msg(`slice simple:`, gg.StringAny(src)), 647 )))) 648 } 649 650 /* 651 Asserts that no elements of the given slice satisfy the given predicate 652 function, or fails the test, printing the optional additional messages and the 653 stack trace. 654 */ 655 func None[A ~[]B, B any](src A, fun func(B) bool, opt ...any) { 656 for ind, val := range src { 657 if fun == nil || fun(val) { 658 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 659 gg.Str( 660 `expected every element to fail predicate `, gg.FuncName(fun), 661 `; element at index `, ind, ` did not fail`, 662 ), 663 Msg(`slice detailed:`, goStringIndent(src)), 664 Msg(`element detailed:`, goStringIndent(val)), 665 Msg(`slice simple:`, gg.StringAny(src)), 666 Msg(`element simple:`, gg.StringAny(val)), 667 )))) 668 } 669 } 670 } 671 672 /* 673 Asserts that the given slice contains no duplicates, or fails the test, printing 674 the optional additional messages and the stack trace. 675 */ 676 func Uniq[A ~[]B, B comparable](src A, opt ...any) { 677 dup, ind0, ind1, ok := foundDup(src) 678 if ok { 679 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 680 fmt.Sprintf(`unexpected duplicate at indexes %v and %v`, ind0, ind1), 681 msgSingle(dup), 682 )))) 683 } 684 } 685 686 func foundDup[A comparable](src []A) (_ A, _ int, _ int, _ bool) { 687 size := len(src) 688 if !(size > 0) { 689 return 690 } 691 692 found := make(map[A]int, size) 693 for ind1, val := range src { 694 ind0, ok := found[val] 695 if ok { 696 return val, ind0, ind1, true 697 } 698 found[val] = ind1 699 } 700 return 701 } 702 703 /* 704 Asserts that the given chunk of text contains the given substring, or fails the 705 test, printing the optional additional messages and the stack trace. 706 */ 707 func TextHas[A, B gg.Text](src A, exp B, opt ...any) { 708 if !strings.Contains(gg.ToString(src), gg.ToString(exp)) { 709 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 710 `text does not contain substring`, 711 Msg(`full text:`, goStringIndent(gg.ToString(src))), 712 Msg(`substring:`, goStringIndent(gg.ToString(exp))), 713 )))) 714 } 715 } 716 717 /* 718 Asserts that the given chunk of text does not contain the given substring, or 719 fails the test, printing the optional additional messages and the stack trace. 720 */ 721 func NotTextHas[A, B gg.Text](src A, exp B, opt ...any) { 722 if strings.Contains(gg.ToString(src), gg.ToString(exp)) { 723 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 724 `text contains unexpected substring`, 725 Msg(`full text:`, goStringIndent(gg.ToString(src))), 726 Msg(`substring:`, goStringIndent(gg.ToString(exp))), 727 )))) 728 } 729 } 730 731 /* 732 Asserts that the given slice is empty, or fails the test, printing the optional 733 additional messages and the stack trace. 734 */ 735 func Empty[A ~[]B, B any](src A, opt ...any) { 736 if len(src) != 0 { 737 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 738 `unexpected non-empty slice`, 739 Msg(`detailed:`, goStringIndent(src)), 740 Msg(`simple:`, gg.StringAny(src)), 741 )))) 742 } 743 } 744 745 /* 746 Asserts that the given slice is not empty, or fails the test, printing the 747 optional additional messages and the stack trace. 748 */ 749 func NotEmpty[A ~[]B, B any](src A, opt ...any) { 750 if len(src) <= 0 { 751 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt(`unexpected empty slice`, msgSingle(src))))) 752 } 753 } 754 755 /* 756 Asserts that the given slice is not empty, or fails the test, printing the 757 optional additional messages and the stack trace. 758 */ 759 func MapNotEmpty[Src ~map[Key]Val, Key comparable, Val any](src Src, opt ...any) { 760 if len(src) <= 0 { 761 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt(`unexpected empty map`, msgSingle(src))))) 762 } 763 } 764 765 /* 766 Asserts that the given slice has exactly the given length, or fails the test, 767 printing the optional additional messages and the stack trace. 768 */ 769 func Len[A ~[]B, B any](src A, exp int, opt ...any) { 770 if len(src) != exp { 771 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 772 fmt.Sprintf(`got slice length %v, expected %v`, len(src), exp), 773 msgSingle(src), 774 )))) 775 } 776 } 777 778 /* 779 Asserts that the given slice has exactly the given capacity, or fails the test, 780 printing the optional additional messages and the stack trace. 781 */ 782 func Cap[A ~[]B, B any](src A, exp int, opt ...any) { 783 if cap(src) != exp { 784 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 785 fmt.Sprintf(`got slice capacity %v, expected %v`, cap(src), exp), 786 msgSingle(src), 787 )))) 788 } 789 } 790 791 /* 792 Asserts that the given text has exactly the given length, or fails the test, 793 printing the optional additional messages and the stack trace. 794 */ 795 func TextLen[A gg.Text](src A, exp int, opt ...any) { 796 if len(src) != exp { 797 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 798 fmt.Sprintf(`got text length %v, expected %v`, len(src), exp), 799 msgSingle(src), 800 )))) 801 } 802 } 803 804 /* 805 Asserts that `.String` of the input matches the expected string, or fails the 806 test, printing the optional additional messages and the stack trace. 807 */ 808 func Str[A any](src A, exp string, opt ...any) { 809 Eq(gg.String(src), exp, opt...) 810 } 811 812 /* 813 Asserts `one < two`, or fails the test, printing the optional additional 814 messages and the stack trace. For non-primitives that implement `gg.Lesser`, 815 see `Less`. Also see `LessEqPrim`. 816 */ 817 func LessPrim[A gg.LesserPrim](one, two A, opt ...any) { 818 if !(one < two) { 819 panic(ErrAt(1, msgOpt(opt, msgLess(one, two)))) 820 } 821 } 822 823 /* 824 Asserts `one < two`, or fails the test, printing the optional additional 825 messages and the stack trace. For primitives, see `LessPrim`. 826 */ 827 func Less[A gg.Lesser[A]](one, two A, opt ...any) { 828 if !one.Less(two) { 829 panic(ErrAt(1, msgOpt(opt, msgLess(one, two)))) 830 } 831 } 832 833 /* 834 Asserts `one <= two`, or fails the test, printing the optional additional 835 messages and the stack trace. For non-primitives that implement `gg.Lesser`, 836 see `LessEq`. Also see `LessPrim`. 837 */ 838 func LessEqPrim[A gg.LesserPrim](one, two A, opt ...any) { 839 if !(one <= two) { 840 panic(ErrAt(1, msgOpt(opt, msgLessEq(one, two)))) 841 } 842 } 843 844 /* 845 Asserts `one <= two`, or fails the test, printing the optional additional 846 messages and the stack trace. For primitives, see `LessEqPrim`. Also see 847 `Less`. 848 */ 849 func LessEq[A interface { 850 gg.Lesser[A] 851 comparable 852 }](one, two A, opt ...any) { 853 if !(one == two || one.Less(two)) { 854 panic(ErrAt(1, msgOpt(opt, msgLessEq(one, two)))) 855 } 856 } 857 858 /* 859 Asserts that the given number is > 0, or fails the test, printing the optional 860 additional messages and the stack trace. 861 */ 862 func Pos[A gg.Signed](src A, opt ...any) { 863 if !gg.IsPos(src) { 864 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 865 `expected > 0, got value out of range`, 866 msgSingle(src), 867 )))) 868 } 869 } 870 871 /* 872 Asserts that the given number is < 0, or fails the test, printing the optional 873 additional messages and the stack trace. 874 */ 875 func Neg[A gg.Signed](src A, opt ...any) { 876 if !gg.IsNeg(src) { 877 panic(ErrAt(1, msgOpt(opt, gg.JoinLinesOpt( 878 `expected < 0, got value out of range`, 879 msgSingle(src), 880 )))) 881 } 882 }