A lightweight test helper package
0

Configure Feed

Select the types of activity you want to include in your feed.

Implement a few polishes and add `NotNearlyEqual` (#104)

authored by

Tom Fleet and committed by
GitHub
(Apr 19, 2026, 7:36 AM +0100) 4d09b516 f99220d1

+354 -98
+28 -17
config.go
··· 5 5 "fmt" 6 6 "math" 7 7 "strings" 8 + "unicode/utf8" 8 9 ) 9 10 10 11 const ( ··· 37 38 // String implements [fmt.Stringer] for failure, allowing it to print itself in the test log. 38 39 func (f failure[T]) String() string { 39 40 s := &strings.Builder{} 40 - s.WriteByte('\n') 41 - 42 - s.WriteString(f.cfg.title) 43 - s.WriteByte('\n') 44 - s.WriteString(strings.Repeat("-", len(f.cfg.title))) 45 - s.WriteString("\n\n") 41 + f.cfg.writeHeader(s) 46 42 47 43 fmt.Fprintf(s, "Got:\t%+v\n", f.got) 48 44 fmt.Fprintf(s, "Wanted:\t%+v\n", f.want) 49 45 50 - if f.cfg.context != "" { 51 - fmt.Fprintf(s, "\n(%s)\n", f.cfg.context) 52 - } 53 - 54 - if f.cfg.reason != "" { 55 - fmt.Fprintf(s, "\nBecause: %s\n", f.cfg.reason) 56 - } 46 + f.cfg.writeFooter(s) 57 47 58 48 return s.String() 49 + } 50 + 51 + // writeHeader writes the title block (leading blank line, title, underline, blank line) 52 + // to s. The underline is sized by rune count so multi-byte titles align correctly. 53 + func (c config) writeHeader(s *strings.Builder) { 54 + s.WriteByte('\n') 55 + s.WriteString(c.title) 56 + s.WriteByte('\n') 57 + s.WriteString(strings.Repeat("-", utf8.RuneCountInString(c.title))) 58 + s.WriteString("\n\n") 59 + } 60 + 61 + // writeFooter writes any optional context and reason lines to s. 62 + func (c config) writeFooter(s *strings.Builder) { 63 + if c.context != "" { 64 + fmt.Fprintf(s, "\n(%s)\n", c.context) 65 + } 66 + 67 + if c.reason != "" { 68 + fmt.Fprintf(s, "\nBecause: %s\n", c.reason) 69 + } 59 70 } 60 71 61 72 // Option is a configuration option for a test. ··· 79 90 // two floating point numbers before they are considered equal. This setting is only 80 91 // used in [NearlyEqual] and [NotNearlyEqual]. 81 92 // 82 - // Setting threshold to ±math.Inf is an error and will fail the test. 93 + // Setting threshold to ±[math.Inf] is an error and will fail the test. 83 94 // 84 95 // The default is 1e-8, a sensible default for most cases. 85 96 func FloatEqualityThreshold(threshold float64) Option { ··· 137 148 // test.Ok(t, err, test.Context("something complicated failed")) 138 149 func Context(format string, args ...any) Option { 139 150 f := func(cfg *config) error { 140 - if format == "" { 151 + context := strings.TrimSpace(fmt.Sprintf(format, args...)) 152 + if context == "" { 141 153 return errors.New("cannot set context to an empty string") 142 154 } 143 155 144 - context := fmt.Sprintf(format, args...) 145 - cfg.context = strings.TrimSpace(context) 156 + cfg.context = context 146 157 147 158 return nil 148 159 }
+136 -69
test.go
··· 11 11 "io" 12 12 "math" 13 13 "os" 14 - "sync" 14 + "strings" 15 15 "testing" 16 16 17 17 "go.followtheprocess.codes/diff" 18 18 "go.followtheprocess.codes/diff/render" 19 19 "go.followtheprocess.codes/hue" 20 20 ) 21 + 22 + // errAny is a sentinel rendered in failure output when the caller expected 23 + // an error but got nil — it reads more naturally than the previous 24 + // placeholder errors.New("error") (which printed "Wanted: error"). 25 + var errAny = errors.New("<any error>") 21 26 22 27 // ColorEnabled sets whether the output from this package is colourised. 23 28 // ··· 125 130 } 126 131 } 127 132 128 - // NotEqualFunc is like [Equal] but accepts a custom comparator function, useful 133 + // NotEqualFunc is like [NotEqual] but accepts a custom comparator function, useful 129 134 // when the items to be compared do not implement the comparable generic constraint. 130 135 // 131 136 // The signature of the comparator is such that standard library functions such as ··· 133 138 // 134 139 // The comparator should return true if the two items should be considered equal. 135 140 // 136 - // test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Fails 137 - // test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes 141 + // test.NotEqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Fails 142 + // test.NotEqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes 138 143 func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { 139 144 tb.Helper() 140 145 ··· 181 186 } 182 187 } 183 188 184 - diff := math.Abs(float64(got - want)) 185 - if diff > cfg.floatEqualityThreshold { 189 + delta := math.Abs(float64(got - want)) 190 + if delta > cfg.floatEqualityThreshold { 186 191 cfg.reason = fmt.Sprintf( 187 192 "Difference %v - %v = %v exceeds maximum tolerance of %v", 188 193 got, 189 194 want, 190 - diff, 195 + delta, 196 + cfg.floatEqualityThreshold, 197 + ) 198 + fail := failure[T]{ 199 + got: got, 200 + want: want, 201 + cfg: cfg, 202 + } 203 + tb.Fatal(fail.String()) 204 + } 205 + } 206 + 207 + // NotNearlyEqual is the opposite of [NearlyEqual]. It fails when got and want 208 + // are within the float equality threshold of each other. 209 + // 210 + // The threshold defaults to 1e-8 and can be configured with the 211 + // [FloatEqualityThreshold] option. 212 + // 213 + // test.NotNearlyEqual(t, 3.0000001, 3.0) // Passes, different enough 214 + // test.NotNearlyEqual(t, 3.0000000001, 3.0) // Fails, too close to be considered different 215 + func NotNearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) { 216 + tb.Helper() 217 + 218 + cfg := defaultConfig() 219 + cfg.title = "NearlyEqual" 220 + 221 + for _, option := range options { 222 + if err := option.apply(&cfg); err != nil { 223 + tb.Fatalf("NotNearlyEqual: could not apply options: %v", err) 224 + 225 + return 226 + } 227 + } 228 + 229 + delta := math.Abs(float64(got - want)) 230 + if delta <= cfg.floatEqualityThreshold { 231 + cfg.reason = fmt.Sprintf( 232 + "Difference %v - %v = %v is within tolerance of %v", 233 + got, 234 + want, 235 + delta, 191 236 cfg.floatEqualityThreshold, 192 237 ) 193 238 fail := failure[T]{ ··· 248 293 if err == nil { 249 294 fail := failure[error]{ 250 295 got: nil, 251 - want: errors.New("error"), 296 + want: errAny, 252 297 cfg: cfg, 253 298 } 254 299 tb.Fatal(fail.String()) ··· 287 332 288 333 if want { 289 334 reason = fmt.Sprintf("Wanted an error but got %v", err) 290 - wanted = errors.New("error") 335 + wanted = errAny 291 336 } else { 292 337 reason = fmt.Sprintf("Got an unexpected error: %v", err) 293 338 wanted = nil ··· 364 409 // 365 410 // If either got or want do not end in a newline, one is added to avoid a 366 411 // "No newline at end of file" warning in the diff which is visually distracting. 367 - func Diff(tb testing.TB, got, want string) { 412 + func Diff(tb testing.TB, got, want string, options ...Option) { 368 413 tb.Helper() 369 - DiffBytes(tb, []byte(got), []byte(want)) 414 + DiffBytes(tb, []byte(got), []byte(want), options...) 370 415 } 371 416 372 417 // DiffBytes fails if the two []byte got and want are not equal and provides a rich ··· 374 419 // 375 420 // If either got or want do not end in a newline, one is added to avoid a 376 421 // "No newline at end of file" warning in the diff which is visually distracting. 377 - func DiffBytes(tb testing.TB, got, want []byte) { 422 + func DiffBytes(tb testing.TB, got, want []byte, options ...Option) { 378 423 tb.Helper() 424 + 425 + cfg := defaultConfig() 426 + cfg.title = "Diff" 427 + 428 + for _, option := range options { 429 + if err := option.apply(&cfg); err != nil { 430 + tb.Fatalf("DiffBytes: could not apply options: %v", err) 431 + 432 + return 433 + } 434 + } 379 435 380 436 got = fixNL(got) 381 437 want = fixNL(want) ··· 383 439 d := diff.New("want", want, "got", got) 384 440 385 441 if !d.Equal() { 386 - tb.Fatalf("\nDiff\n----\n%s\n", render.Render(d)) 442 + s := &strings.Builder{} 443 + cfg.writeHeader(s) 444 + s.Write(render.Render(d)) 445 + cfg.writeFooter(s) 446 + tb.Fatal(s.String()) 387 447 } 388 448 } 389 449 ··· 392 452 // 393 453 // If either got or want do not end in a newline, one is added to avoid 394 454 // a "No newline at end of file" warning in the diff which is visually distracting. 395 - func DiffReader(tb testing.TB, got, want io.Reader) { 455 + func DiffReader(tb testing.TB, got, want io.Reader, options ...Option) { 396 456 tb.Helper() 397 457 398 458 gotData, err := io.ReadAll(got) 399 459 if err != nil { 400 - tb.Fatalf("DiffReader: could not read from got: %v\n", err) 460 + tb.Fatalf("DiffReader: could not read from got: %v", err) 401 461 } 402 462 403 463 wantData, err := io.ReadAll(want) 404 464 if err != nil { 405 - tb.Fatalf("DiffReader: could not read from want: %v\n", err) 465 + tb.Fatalf("DiffReader: could not read from want: %v", err) 406 466 } 407 467 408 - DiffBytes(tb, gotData, wantData) 468 + DiffBytes(tb, gotData, wantData, options...) 409 469 } 410 470 411 471 // CaptureOutput captures and returns data printed to [os.Stdout] and [os.Stderr] by the provided function fn, allowing ··· 414 474 // If the provided function returns a non nil error, the test is failed with the error logged as the reason. 415 475 // 416 476 // If any error occurs capturing stdout or stderr, the test will also be failed with a descriptive log. 477 + // 478 + // CaptureOutput replaces the process-wide [os.Stdout] and [os.Stderr] for the duration of the call, 479 + // so it is NOT safe to use from tests marked with [testing.T.Parallel]. 417 480 // 418 481 // fn := func() error { 419 482 // fmt.Println("hello stdout") ··· 426 489 func CaptureOutput(tb testing.TB, fn func() error) (stdout, stderr string) { 427 490 tb.Helper() 428 491 429 - // Take copies of the original streams 430 492 oldStdout := os.Stdout 431 493 oldStderr := os.Stderr 432 - 433 - defer func() { 434 - // Restore everything back to normal 435 - os.Stdout = oldStdout 436 - os.Stderr = oldStderr 437 - }() 438 494 439 495 stdoutReader, stdoutWriter, err := os.Pipe() 440 496 if err != nil { 441 497 tb.Fatalf("CaptureOutput: could not construct an os.Pipe(): %v", err) 498 + 499 + return "", "" 442 500 } 443 501 444 502 stderrReader, stderrWriter, err := os.Pipe() 445 503 if err != nil { 504 + stdoutReader.Close() 505 + stdoutWriter.Close() 446 506 tb.Fatalf("CaptureOutput: could not construct an os.Pipe(): %v", err) 507 + 508 + return "", "" 447 509 } 448 510 449 - // Set stdout and stderr streams to the pipe writers 450 511 os.Stdout = stdoutWriter 451 512 os.Stderr = stderrWriter 452 513 453 - stdoutCapture := make(chan string) 454 - stderrCapture := make(chan string) 514 + // Buffered so the copy goroutines can always deliver their result, even if 515 + // the main goroutine exits early via Fatalf / runtime.Goexit / panic. 516 + stdoutCapture := make(chan string, 1) 517 + stderrCapture := make(chan string, 1) 455 518 456 - var wg sync.WaitGroup 519 + // Goroutines use Errorf rather than Fatalf — the testing contract says 520 + // FailNow/Fatal* must only be called from the main test goroutine. 521 + go copyInto(tb, "stdout", stdoutReader, stdoutCapture) 522 + go copyInto(tb, "stderr", stderrReader, stderrCapture) 457 523 458 - // Copy in goroutines to avoid blocking 459 - wg.Go(func() { 460 - defer func() { 461 - close(stdoutCapture) 462 - }() 463 - 464 - buf := &bytes.Buffer{} 465 - if _, err := io.Copy(buf, stdoutReader); err != nil { 466 - tb.Fatalf("CaptureOutput: failed to copy from stdout reader: %v", err) 524 + // Ensure the real streams are restored and the pipe writers are closed on 525 + // every exit path (including panic / Goexit). Closing the writers lets the 526 + // copy goroutines see EOF and deliver their buffers to the channels. 527 + writersClosed := false 528 + closeWriters := func() { 529 + if writersClosed { 530 + return 467 531 } 468 532 469 - stdoutCapture <- buf.String() 470 - }) 533 + writersClosed = true 471 534 472 - wg.Go(func() { 473 - defer func() { 474 - close(stderrCapture) 475 - }() 476 - 477 - buf := &bytes.Buffer{} 478 - if _, err := io.Copy(buf, stderrReader); err != nil { 479 - tb.Fatalf("CaptureOutput: failed to copy from stderr reader: %v", err) 480 - } 481 - 482 - stderrCapture <- buf.String() 483 - }) 484 - 485 - // Call the test function that produces the output 486 - if err := fn(); err != nil { 487 - tb.Fatalf("CaptureOutput: user function returned an error: %v", err) 535 + stdoutWriter.Close() 536 + stderrWriter.Close() 488 537 } 489 538 490 - // Close the writers 491 - stdoutCloseErr := stdoutWriter.Close() 492 - if stdoutCloseErr != nil { 493 - tb.Fatalf("CaptureOutput: could not close stdout pipe: %v", stdoutCloseErr) 539 + defer func() { 540 + closeWriters() 541 + 542 + os.Stdout = oldStdout 543 + os.Stderr = oldStderr 544 + }() 545 + 546 + if fnErr := fn(); fnErr != nil { 547 + tb.Fatalf("CaptureOutput: user function returned an error: %v", fnErr) 548 + 549 + return "", "" 494 550 } 495 551 496 - stderrCloseErr := stderrWriter.Close() 497 - if stderrCloseErr != nil { 498 - tb.Fatalf("CaptureOutput: could not close stderr pipe: %v", stderrCloseErr) 552 + // Happy path: close writers now so we can receive the captured data before 553 + // the defer runs (the defer's close is then a no-op). 554 + closeWriters() 555 + 556 + return <-stdoutCapture, <-stderrCapture 557 + } 558 + 559 + // copyInto reads from r into a buffer and sends the result on out. Any copy 560 + // error is reported against tb via Errorf — Fatal* is unsafe from non-main 561 + // goroutines. The send uses a buffered channel so it never blocks. 562 + func copyInto(tb testing.TB, name string, r io.Reader, out chan<- string) { 563 + tb.Helper() 564 + 565 + buf := &bytes.Buffer{} 566 + 567 + defer func() { 568 + out <- buf.String() 569 + }() 570 + 571 + if _, err := io.Copy(buf, r); err != nil { 572 + tb.Errorf("CaptureOutput: failed to copy from %s reader: %v", name, err) 499 573 } 500 - 501 - capturedStdout := <-stdoutCapture 502 - capturedStderr := <-stderrCapture 503 - 504 - wg.Wait() 505 - 506 - return capturedStdout, capturedStderr 507 574 } 508 575 509 576 // If data is empty or ends in \n, fixNL returns data.
+82
test_test.go
··· 6 6 "flag" 7 7 "fmt" 8 8 "io" 9 + "math" 9 10 "os" 10 11 "slices" 11 12 "testing" ··· 241 242 wantFail: true, 242 243 }, 243 244 { 245 + name: "NotNearlyEqual/pass", 246 + fn: func(tb testing.TB) { 247 + test.NotNearlyEqual(tb, 3.0000001, 3.0) 248 + }, 249 + wantFail: false, 250 + }, 251 + { 252 + name: "NotNearlyEqual/fail", 253 + fn: func(tb testing.TB) { 254 + test.NotNearlyEqual(tb, 3.0000000001, 3.0) 255 + }, 256 + wantFail: true, 257 + }, 258 + { 259 + name: "NotNearlyEqual/fail custom tolerance", 260 + fn: func(tb testing.TB) { 261 + test.NotNearlyEqual(tb, 3.05, 3.0, test.FloatEqualityThreshold(0.1)) 262 + }, 263 + wantFail: true, 264 + }, 265 + { 266 + name: "NotNearlyEqual/fail with context", 267 + fn: func(tb testing.TB) { 268 + test.NotNearlyEqual(tb, 3.0000000001, 3.0, test.Context("Numbers don't work that way")) 269 + }, 270 + wantFail: true, 271 + }, 272 + { 244 273 name: "Ok/pass", 245 274 fn: func(tb testing.TB) { 246 275 test.Ok(tb, nil) ··· 468 497 want := []byte("Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n") 469 498 470 499 test.DiffReader(tb, bytes.NewReader(got), bytes.NewReader(want)) 500 + }, 501 + wantFail: true, 502 + }, 503 + { 504 + name: "Diff/fail with title", 505 + fn: func(tb testing.TB) { 506 + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" 507 + want := "Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n" 508 + test.Diff(tb, got, want, test.Title("File drift")) 509 + }, 510 + wantFail: true, 511 + }, 512 + { 513 + name: "Diff/fail with context", 514 + fn: func(tb testing.TB) { 515 + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" 516 + want := "Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n" 517 + test.Diff(tb, got, want, test.Context("config file drifted from checked-in copy")) 518 + }, 519 + wantFail: true, 520 + }, 521 + { 522 + name: "Option errors/Title empty", 523 + fn: func(tb testing.TB) { 524 + test.Equal(tb, 1, 1, test.Title("")) 525 + }, 526 + wantFail: true, 527 + }, 528 + { 529 + name: "Option errors/Context empty", 530 + fn: func(tb testing.TB) { 531 + test.Equal(tb, 1, 1, test.Context("")) 532 + }, 533 + wantFail: true, 534 + }, 535 + { 536 + name: "Option errors/Context format resolves empty", 537 + fn: func(tb testing.TB) { 538 + test.Equal(tb, 1, 1, test.Context("%s", "")) 539 + }, 540 + wantFail: true, 541 + }, 542 + { 543 + name: "Option errors/FloatEqualityThreshold positive infinity", 544 + fn: func(tb testing.TB) { 545 + test.NearlyEqual(tb, 1.0, 1.0, test.FloatEqualityThreshold(math.Inf(1))) 546 + }, 547 + wantFail: true, 548 + }, 549 + { 550 + name: "Option errors/FloatEqualityThreshold negative infinity", 551 + fn: func(tb testing.TB) { 552 + test.NearlyEqual(tb, 1.0, 1.0, test.FloatEqualityThreshold(math.Inf(-1))) 471 553 }, 472 554 wantFail: true, 473 555 },
+2 -2
testdata/snapshots/TestTest/Diff/fail.snap
··· 1 1 source: test_test.go 2 2 expression: buf.String() 3 3 --- 4 - |+ 4 + | 5 5 6 6 Diff 7 7 ---- 8 + 8 9 diff want got 9 10 --- want 10 11 +++ got ··· 15 16 - this line is different 16 17 + lines as well wow 17 18 some more stuff 18 -
+2 -2
testdata/snapshots/TestTest/Diff/fail_no_trailing_newline.snap
··· 1 1 source: test_test.go 2 2 expression: buf.String() 3 3 --- 4 - |+ 4 + | 5 5 6 6 Diff 7 7 ---- 8 + 8 9 diff want got 9 10 --- want 10 11 +++ got ··· 15 16 - this line is different 16 17 + lines as well wow 17 18 some more stuff 18 -
+20
testdata/snapshots/TestTest/Diff/fail_with_context.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + | 5 + 6 + Diff 7 + ---- 8 + 9 + diff want got 10 + --- want 11 + +++ got 12 + @@ -1,4 +1,4 @@ 13 + Some 14 + - different stuff here in this file 15 + + stuff here in this file 16 + - this line is different 17 + + lines as well wow 18 + some more stuff 19 + 20 + (config file drifted from checked-in copy)
+18
testdata/snapshots/TestTest/Diff/fail_with_title.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + | 5 + 6 + File drift 7 + ---------- 8 + 9 + diff want got 10 + --- want 11 + +++ got 12 + @@ -1,4 +1,4 @@ 13 + Some 14 + - different stuff here in this file 15 + + stuff here in this file 16 + - this line is different 17 + + lines as well wow 18 + some more stuff
+2 -2
testdata/snapshots/TestTest/DiffBytes/fail.snap
··· 1 1 source: test_test.go 2 2 expression: buf.String() 3 3 --- 4 - |+ 4 + | 5 5 6 6 Diff 7 7 ---- 8 + 8 9 diff want got 9 10 --- want 10 11 +++ got ··· 15 16 - this line is different 16 17 + lines as well wow 17 18 some more stuff 18 -
+2 -2
testdata/snapshots/TestTest/DiffReader/fail.snap
··· 1 1 source: test_test.go 2 2 expression: buf.String() 3 3 --- 4 - |+ 4 + | 5 5 6 6 Diff 7 7 ---- 8 + 8 9 diff want got 9 10 --- want 10 11 +++ got ··· 15 16 - this line is different 16 17 + lines as well wow 17 18 some more stuff 18 -
+1 -1
testdata/snapshots/TestTest/Err/fail.snap
··· 7 7 ------- 8 8 9 9 Got: <nil> 10 - Wanted: error 10 + Wanted: <any error>
+1 -1
testdata/snapshots/TestTest/Err/fail_with_context.snap
··· 7 7 ------- 8 8 9 9 Got: <nil> 10 - Wanted: error 10 + Wanted: <any error> 11 11 12 12 (Frobnicated the baz when it should have failed)
+1 -1
testdata/snapshots/TestTest/Err/fail_with_title.snap
··· 7 7 ------------------- 8 8 9 9 Got: <nil> 10 - Wanted: error 10 + Wanted: <any error>
+12
testdata/snapshots/TestTest/NotNearlyEqual/fail.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + | 5 + 6 + NearlyEqual 7 + ----------- 8 + 9 + Got: 3.0000000001 10 + Wanted: 3 11 + 12 + Because: Difference 3.0000000001 - 3 = 1.000000082740371e-10 is within tolerance of 1e-08
+12
testdata/snapshots/TestTest/NotNearlyEqual/fail_custom_tolerance.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + | 5 + 6 + NearlyEqual 7 + ----------- 8 + 9 + Got: 3.05 10 + Wanted: 3 11 + 12 + Because: Difference 3.05 - 3 = 0.04999999999999982 is within tolerance of 0.1
+14
testdata/snapshots/TestTest/NotNearlyEqual/fail_with_context.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + | 5 + 6 + NearlyEqual 7 + ----------- 8 + 9 + Got: 3.0000000001 10 + Wanted: 3 11 + 12 + (Numbers don't work that way) 13 + 14 + Because: Difference 3.0000000001 - 3 = 1.000000082740371e-10 is within tolerance of 1e-08
+4
testdata/snapshots/TestTest/Option_errors/Context_empty.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + 'Equal: could not apply options: cannot set context to an empty string'
+4
testdata/snapshots/TestTest/Option_errors/Context_format_resolves_empty.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + 'Equal: could not apply options: cannot set context to an empty string'
+4
testdata/snapshots/TestTest/Option_errors/FloatEqualityThreshold_negative_infinity.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + 'NearlyEqual: could not apply options: cannot set floating point equality threshold to ±infinity'
+4
testdata/snapshots/TestTest/Option_errors/FloatEqualityThreshold_positive_infinity.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + 'NearlyEqual: could not apply options: cannot set floating point equality threshold to ±infinity'
+4
testdata/snapshots/TestTest/Option_errors/Title_empty.snap
··· 1 + source: test_test.go 2 + expression: buf.String() 3 + --- 4 + 'Equal: could not apply options: cannot set title to an empty string'
+1 -1
testdata/snapshots/TestTest/WantErr/fail_nil.snap
··· 7 7 ------- 8 8 9 9 Got: <nil> 10 - Wanted: error 10 + Wanted: <any error> 11 11 12 12 Because: Wanted an error but got <nil>