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  }