A lightweight test helper package
0

Configure Feed

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

Big refresh (#49)

authored by

Tom Fleet and committed by
GitHub
(Jan 6, 2025, 5:41 PM UTC) 41b13e6c 18020902

+1124 -778
+7
.golangci.yml
··· 36 36 - unused 37 37 - whitespace 38 38 39 + issues: 40 + exclude-rules: 41 + - path: test_test.go 42 + linters: 43 + - thelper # The entire package is effectively a t.Helper 44 + - goconst # Lots of repetition in here 45 + 39 46 linters-settings: 40 47 errcheck: 41 48 check-type-assertions: true
+19 -77
README.md
··· 42 42 43 43 test.True(t, true) // Passes 44 44 test.False(t, true) // Fails 45 - 46 - // Get $CWD/testdata easily 47 - test.Data(t) // /Users/you/project/package/testdata 48 - 49 - // Check against contents of a file including line ending normalisation 50 - file := filepath.Join(test.Data(t), "expected.txt") 51 - test.File(t, "hello\n", file) 52 - 53 - // Just like the good old reflect.DeepEqual, but with a nicer format 54 - test.DeepEqual(t, []string{"hello"}, []string{"world"}) // Fails 55 45 } 56 46 ``` 57 47 58 - ### Self Documenting Tests 48 + ### Add Additional Context 59 49 60 - > [!TIP] 61 - > Line comments on the line you call most `test` functions on will be shown in failure messages as additional context 62 - 63 - That means you can have additional context in the failure message, as well as helpful comments explaining the assertion to readers of your code 50 + `test` provides a number of options to decorate your test log with useful context: 64 51 65 52 ```go 66 - func TestSomething(t *testing.T) { 67 - test.Equal(t, "apples", "oranges") // Fruits are not equal 53 + func TestDetail(t *testing.T) { 54 + test.Equal(t, "apples", "oranges", test.Title("Fruit scramble!"), test.Context("Apples are not oranges!")) 68 55 } 69 56 ``` 70 57 71 - Will get you a failure message like: 58 + Will get you an error log in the test that looks like this... 72 59 73 - ```shell 74 - --- FAIL: TestSomething (0.00s) 75 - something_test.go:1: 76 - Not Equal // Fruits are not equal 77 - --------- 60 + ```plaintext 61 + --- FAIL: TestDemo (0.00s) 62 + test_test.go:501: 63 + Fruit scramble! 64 + --------------- 65 + 78 66 Got: apples 79 67 Wanted: oranges 68 + 69 + (Apples are not oranges!) 70 + 71 + FAIL 80 72 ``` 81 73 82 74 ### Non Comparable Types 83 75 84 - `test` uses Go 1.18+ generics under the hood for most of the comparison, which is great, but what if your types don't satisfy `comparable`. We also provide 76 + `test` uses generics under the hood for most of the comparison, which is great, but what if your types don't satisfy `comparable`. We also provide 85 77 `test.EqualFunc` and `test.NotEqualFunc` for those exact situations! 86 78 87 79 These allow you to pass in a custom comparator function for your type, if your comparator function returns true, the types are considered equal. ··· 98 90 99 91 test.EqualFunc(t, a, b, sliceEqual) // Passes 100 92 101 - // Can also use e.g. the new slices package 102 - test.EqualFunc(t, a, b, slices.Equal[string]) // Also passes :) 93 + // Can also use any function here 94 + test.EqualFunc(t, a, b, slices.Equal) // Also passes :) 103 95 104 - test.EqualFunc(t, a, c, slices.Equal[string]) // Fails 96 + test.EqualFunc(t, a, c, slices.Equal) // Fails 105 97 } 106 98 ``` 107 99 108 100 You can also use this same pattern for custom user defined types, structs etc. 109 - 110 - ### Rich Comparison 111 - 112 - Large structs or long slices can often be difficult to compare using `reflect.DeepEqual`, you have to scan for the difference yourself. `test` provides a 113 - `test.Diff` function that produces a rich text diff for you on failure: 114 - 115 - ```go 116 - func TestDiff(t *testing.T) { 117 - // Pretend these are very long, or are large structs 118 - a := []string{"hello", "world"} 119 - b := []string{"hello", "there"} 120 - 121 - test.Diff(t, a, b) 122 - } 123 - ``` 124 - 125 - Will give you: 126 - 127 - ```plain 128 - --- FAIL: TestDiff (0.00s) 129 - main_test.go:14: Mismatch (-want, +got): 130 - []string{ 131 - "hello", 132 - - "there", 133 - + "world", 134 - } 135 - ``` 136 101 137 102 ### Table Driven Tests 138 103 ··· 231 196 ``` 232 197 233 198 Under the hood `CaptureOutput` temporarily captures both streams, copies the data to a buffer and returns the output back to you, before cleaning everything back up again. 234 - 235 - ### Golden Files 236 - 237 - `test` has great support for golden files: 238 - 239 - ```go 240 - func TestFile(t *testing.T) { 241 - got := "some contents\n" 242 - want := filepath.Join(test.Data(t), "golden.txt") 243 - 244 - test.File(t, got, want) 245 - } 246 - ``` 247 - 248 - This will read the file, normalise line endings and then generate an output almost like a git diff: 249 - 250 - ```patch 251 - --- want 252 - +++ got 253 - @@ -1 +1 @@ 254 - -some file contents 255 - +some contents 256 - ``` 257 199 258 200 ### Credits 259 201
+47 -10
Taskfile.yml
··· 9 9 default: 10 10 desc: List all available tasks 11 11 silent: true 12 - cmd: task --list 12 + cmds: 13 + - task --list 13 14 14 15 tidy: 15 16 desc: Tidy dependencies in go.mod and go.sum 16 - cmd: go mod tidy 17 + sources: 18 + - "**/*.go" 19 + - go.mod 20 + - go.sum 21 + cmds: 22 + - go mod tidy 17 23 18 24 fmt: 19 25 desc: Run go fmt on all source files 26 + sources: 27 + - "**/*.go" 20 28 preconditions: 21 29 - sh: command -v golines 22 - msg: golines not installed, see https://github.com/segmentio/golines 30 + msg: golines not installed, run `go install github.com/segmentio/golines@latest` 23 31 cmds: 24 32 - go fmt ./... 25 - - golines . --chain-split-dots --ignore-generated --write-output 33 + - golines . --ignore-generated --write-output --max-len 120 26 34 27 35 test: 28 36 desc: Run the test suite 29 - cmd: go test -race ./... {{ .CLI_ARGS }} 37 + sources: 38 + - "**/*.go" 39 + - testdata/**/*.snap.txt 40 + cmds: 41 + - go test -race ./... {{ .CLI_ARGS }} 42 + env: 43 + NO_COLOR: true 30 44 31 45 bench: 32 46 desc: Run all project benchmarks 33 - cmd: go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }} 47 + sources: 48 + - "**/*.go" 49 + cmds: 50 + - go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }} 34 51 35 52 lint: 36 53 desc: Run the linters and auto-fix if possible 37 - cmd: golangci-lint run --fix 54 + sources: 55 + - "**/*.go" 56 + - .golangci.yml 38 57 deps: 39 58 - fmt 40 59 preconditions: 41 60 - sh: command -v golangci-lint 42 61 msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation 43 62 63 + - sh: command -v betteralign 64 + msg: requires betteralign, run `go install github.com/dkorunic/betteralign/cmd/betteralign@latest` 65 + 66 + - sh: command -v typos 67 + msg: requires typos-cli, run `brew install typos-cli` 68 + cmds: 69 + - betteralign -test_files -apply ./... 70 + - golangci-lint run --fix 71 + - typos 72 + 44 73 doc: 45 74 desc: Render the pkg docs locally 46 - cmd: pkgsite -open 47 75 preconditions: 48 76 - sh: command -v pkgsite 49 - msg: pkgsite not installed, run go install golang.org/x/pkgsite/cmd/pkgsite@latest 77 + msg: pkgsite not installed, run `go install golang.org/x/pkgsite/cmd/pkgsite@latest` 78 + cmds: 79 + - pkgsite -open 50 80 51 81 cov: 52 82 desc: Calculate test coverage and render the html ··· 64 94 65 95 sloc: 66 96 desc: Print lines of code 67 - cmd: fd . -e go | xargs wc -l | sort -nr | head 97 + cmds: 98 + - fd . -e go | xargs wc -l | sort -nr | head 68 99 69 100 clean: 70 101 desc: Remove build artifacts and other clutter 71 102 cmds: 72 103 - go clean ./... 73 104 - rm -rf {{ .COV_DATA }} 105 + 106 + update: 107 + desc: Updates dependencies in go.mod and go.sum 108 + cmds: 109 + - go get -u ./... 110 + - go mod tidy
+142
config.go
··· 1 + package test 2 + 3 + import ( 4 + "errors" 5 + "fmt" 6 + "math" 7 + "strings" 8 + ) 9 + 10 + const ( 11 + defaultFloatEqualityThreshold = 1e-8 12 + ) 13 + 14 + // config holds test-specific configuration including additional context 15 + // and how the caller wants this library to behave. 16 + type config struct { 17 + title string // Title of the test, shown as a header in the failure log 18 + context string // Additional context passed by the caller 19 + reason string // Concise reason why the test has failed, only used sparingly and not in a user option 20 + floatEqualityThreshold float64 // The difference threshold below which two floats are considered equal 21 + } 22 + 23 + // defaultConfig returns a default configuration. 24 + func defaultConfig() config { 25 + return config{ 26 + floatEqualityThreshold: defaultFloatEqualityThreshold, 27 + } 28 + } 29 + 30 + // failure represents a test failure, including any set config. 31 + type failure[T any] struct { 32 + got T // The actual value 33 + want T // Expected value 34 + cfg config // Test config 35 + } 36 + 37 + // String implements [fmt.Stringer] for failure, allowing it to print itself in the test log. 38 + func (f failure[T]) String() string { 39 + 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") 46 + 47 + fmt.Fprintf(s, "Got:\t%+v\n", f.got) 48 + fmt.Fprintf(s, "Wanted:\t%+v\n", f.want) 49 + 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 + } 57 + 58 + return s.String() 59 + } 60 + 61 + // Option is a configuration option for a test. 62 + type Option interface { 63 + // Apply the option to the test config, returning an error if the option 64 + // cannot be applied for whatever reason. 65 + apply(*config) error 66 + } 67 + 68 + // option is a function adapter implementing the Option interface, analogous 69 + // to how http.HandlerFunc implements the Handler interface. 70 + type option func(*config) error 71 + 72 + // apply applies the option, implementing the Option interface for the option 73 + // function adapter. 74 + func (o option) apply(cfg *config) error { 75 + return o(cfg) 76 + } 77 + 78 + // FloatEqualityThreshold is an [Option] to set the maximum difference allowed between 79 + // two floating point numbers before they are considered equal. This setting is only 80 + // used in [NearlyEqual] and [NotNearlyEqual]. 81 + // 82 + // Setting threshold to ±math.Inf is an error and will fail the test. 83 + // 84 + // The default is 1e-8, a sensible default for most cases. 85 + func FloatEqualityThreshold(threshold float64) Option { 86 + f := func(cfg *config) error { 87 + if math.IsInf(threshold, 0) { 88 + return errors.New("cannot set floating point equality threshold to ±infinity") 89 + } 90 + cfg.floatEqualityThreshold = threshold 91 + return nil 92 + } 93 + return option(f) 94 + } 95 + 96 + // Title is an [Option] that sets the title of the test in the test failure log. 97 + // 98 + // The title is shown as an underlined header in the test failure, below which the 99 + // actual and expected values will be shown. 100 + // 101 + // By default this will be named sensibly after the test function being called, for 102 + // example [Equal] has a default title "Not Equal". 103 + // 104 + // Setting title explicitly to the empty string "" is an error and will fail the test. 105 + // 106 + // test.Equal(t, "apples", "oranges", test.Title("Wrong fruits!")) 107 + func Title(title string) Option { 108 + f := func(cfg *config) error { 109 + if title == "" { 110 + return errors.New("cannot set title to an empty string") 111 + } 112 + cfg.title = strings.TrimSpace(title) 113 + return nil 114 + } 115 + return option(f) 116 + } 117 + 118 + // Context is an [Option] that allows the caller to inject useful contextual information 119 + // as to why the test failed. This can be a useful addition to the test failure output log. 120 + // 121 + // The signature of context allows the use of fmt print verbs to format the message in the 122 + // same way one might use [fmt.Sprintf]. 123 + // 124 + // It is not necessary to include a newline character at the end of format. 125 + // 126 + // Setting context explicitly to the empty string "" is an error and will fail the test. 127 + // 128 + // For example: 129 + // 130 + // err := doSomethingComplicated() 131 + // test.Ok(t, err, test.Context("something complicated failed")) 132 + func Context(format string, args ...any) Option { 133 + f := func(cfg *config) error { 134 + if format == "" { 135 + return errors.New("cannot set context to an empty string") 136 + } 137 + context := fmt.Sprintf(format, args...) 138 + cfg.context = strings.TrimSpace(context) 139 + return nil 140 + } 141 + return option(f) 142 + }
+4 -3
go.mod
··· 2 2 3 3 go 1.23 4 4 5 - require github.com/google/go-cmp v0.6.0 6 - 7 - require golang.org/x/tools v0.28.0 5 + require ( 6 + github.com/FollowTheProcess/snapshot v0.1.0 7 + golang.org/x/tools v0.28.0 8 + )
+2 -2
go.sum
··· 1 - github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 - github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 1 + github.com/FollowTheProcess/snapshot v0.1.0 h1:G5tWpBXVre6cTrmN1OWZLiTnqEi9IKLiewtQQYOnQqc= 2 + github.com/FollowTheProcess/snapshot v0.1.0/go.mod h1:sJv2oq83QK5Yj6+JVts8fQIaAJjQfsxN/WtGqX8oKIg= 3 3 golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= 4 4 golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+233 -270
test.go
··· 1 - // Package test provides a lightweight, but useful extension to the std lib's testing package 2 - // with a friendlier and more intuitive API. 1 + // Package test provides a lightweight, but useful extension to the std lib's testing package with 2 + // a friendlier and more intuitive API. 3 + // 4 + // Simple tests become trivial and test provides mechanisms for adding useful context to test failures. 3 5 package test 4 6 5 7 import ( 6 - "bufio" 7 8 "bytes" 8 9 "errors" 9 10 "fmt" 10 11 "io" 11 12 "math" 12 13 "os" 13 - "path/filepath" 14 - "reflect" 15 - "runtime" 16 14 "strings" 17 15 "sync" 18 16 "testing" 19 17 20 18 "github.com/FollowTheProcess/test/internal/colour" 21 19 "github.com/FollowTheProcess/test/internal/diff" 22 - "github.com/google/go-cmp/cmp" 23 20 ) 24 - 25 - // floatEqualityThreshold allows us to do near-equality checks for floats. 26 - const floatEqualityThreshold = 1e-8 27 - 28 - // failure represents a test failure, including context and reason. 29 - type failure[T any] struct { 30 - got T // What we got 31 - want T // Expected value 32 - title string // Title of the failure, used as a header 33 - reason string // Optional reason for additional context 34 - comment string // Optional line comment for context 35 - } 36 - 37 - // String prints a failure. 38 - func (f failure[T]) String() string { 39 - var msg string 40 - if f.comment != "" { 41 - msg = fmt.Sprintf( 42 - "\n%s // %s\n%s\nGot:\t%+v\nWanted:\t%+v\n", 43 - f.title, 44 - f.comment, 45 - strings.Repeat("-", len(f.title)), 46 - f.got, 47 - f.want, 48 - ) 49 - } else { 50 - msg = fmt.Sprintf( 51 - "\n%s\n%s\nGot:\t%+v\nWanted:\t%+v\n", 52 - f.title, 53 - strings.Repeat("-", len(f.title)), 54 - f.got, 55 - f.want, 56 - ) 57 - } 58 - 59 - if f.reason != "" { 60 - // Bolt the reason on the end 61 - msg = fmt.Sprintf("%s\n%s\n", msg, f.reason) 62 - } 63 - 64 - return msg 65 - } 66 21 67 22 // Equal fails if got != want. 68 23 // 69 24 // test.Equal(t, "apples", "apples") // Passes 70 25 // test.Equal(t, "apples", "oranges") // Fails 71 - func Equal[T comparable](tb testing.TB, got, want T) { 26 + func Equal[T comparable](tb testing.TB, got, want T, options ...Option) { 72 27 tb.Helper() 28 + cfg := defaultConfig() 29 + cfg.title = "Not Equal" 30 + 31 + for _, option := range options { 32 + if err := option.apply(&cfg); err != nil { 33 + tb.Fatalf("Equal: could not apply options: %v", err) 34 + return 35 + } 36 + } 37 + 73 38 if got != want { 74 39 fail := failure[T]{ 75 - got: got, 76 - want: want, 77 - title: "Not Equal", 78 - comment: getComment(), 40 + got: got, 41 + want: want, 42 + cfg: cfg, 79 43 } 80 44 tb.Fatal(fail.String()) 81 45 } 82 46 } 83 47 84 - // NearlyEqual is like Equal but for floating point numbers where typically equality often fails. 48 + // NotEqual is the opposite of [Equal], it fails if got == want. 85 49 // 86 - // If the difference between got and want is sufficiently small, they are considered equal. 50 + // test.NotEqual(t, 10, 42) // Passes 51 + // test.NotEqual(t, 42, 42) // Fails 52 + func NotEqual[T comparable](tb testing.TB, got, want T, options ...Option) { 53 + tb.Helper() 54 + cfg := defaultConfig() 55 + cfg.title = "Equal" 56 + 57 + for _, option := range options { 58 + if err := option.apply(&cfg); err != nil { 59 + tb.Fatalf("NotEqual: could not apply options: %v", err) 60 + return 61 + } 62 + } 63 + 64 + if got == want { 65 + fail := failure[T]{ 66 + got: got, 67 + want: want, 68 + cfg: cfg, 69 + } 70 + tb.Fatal(fail.String()) 71 + } 72 + } 73 + 74 + // EqualFunc is like [Equal] but accepts a custom comparator function, useful 75 + // when the items to be compared do not implement the comparable generic constraint. 76 + // 77 + // The signature of the comparator is such that standard library functions such as 78 + // [slices.Equal] or [maps.Equal] can be used. 79 + // 80 + // The comparator should return true if the two items should be considered equal. 81 + // 82 + // test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Passes 83 + // test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Fails 84 + func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { 85 + tb.Helper() 86 + cfg := defaultConfig() 87 + cfg.title = "Not Equal" 88 + 89 + for _, option := range options { 90 + if err := option.apply(&cfg); err != nil { 91 + tb.Fatalf("EqualFunc: could not apply options: %v", err) 92 + return 93 + } 94 + } 95 + 96 + if !equal(got, want) { 97 + cfg.reason = "equal(got, want) returned false" 98 + fail := failure[T]{ 99 + got: got, 100 + want: want, 101 + cfg: cfg, 102 + } 103 + tb.Fatal(fail.String()) 104 + } 105 + } 106 + 107 + // NotEqualFunc is like [Equal] but accepts a custom comparator function, useful 108 + // when the items to be compared do not implement the comparable generic constraint. 109 + // 110 + // The signature of the comparator is such that standard library functions such as 111 + // [slices.Equal] or [maps.Equal] can be used. 112 + // 113 + // The comparator should return true if the two items should be considered equal. 114 + // 115 + // test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Fails 116 + // test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes 117 + func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { 118 + tb.Helper() 119 + cfg := defaultConfig() 120 + cfg.title = "Equal" 121 + 122 + for _, option := range options { 123 + if err := option.apply(&cfg); err != nil { 124 + tb.Fatalf("NotEqualFunc: could not apply options: %v", err) 125 + return 126 + } 127 + } 128 + 129 + if equal(got, want) { 130 + cfg.reason = "equal(got, want) returned true" 131 + fail := failure[T]{ 132 + got: got, 133 + want: want, 134 + cfg: cfg, 135 + } 136 + tb.Fatal(fail.String()) 137 + } 138 + } 139 + 140 + // NearlyEqual is like [Equal] but for floating point numbers where absolute equality often fails. 141 + // 142 + // If the difference between got and want is sufficiently small, they are considered equal. This threshold 143 + // defaults to 1e-8 but can be configured with the [FloatEqualityThreshold] option. 87 144 // 88 145 // test.NearlyEqual(t, 3.0000000001, 3.0) // Passes, close enough to be considered equal 89 146 // test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different 90 - func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T) { 147 + func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) { 91 148 tb.Helper() 149 + cfg := defaultConfig() 150 + cfg.title = "Not NearlyEqual" 151 + 152 + for _, option := range options { 153 + if err := option.apply(&cfg); err != nil { 154 + tb.Fatalf("NearlyEqual: could not apply options: %v", err) 155 + return 156 + } 157 + } 158 + 92 159 diff := math.Abs(float64(got - want)) 93 - if diff >= floatEqualityThreshold { 160 + if diff > cfg.floatEqualityThreshold { 161 + cfg.reason = fmt.Sprintf( 162 + "Difference %v - %v = %v exceeds maximum tolerance of %v", 163 + got, 164 + want, 165 + diff, 166 + cfg.floatEqualityThreshold, 167 + ) 94 168 fail := failure[T]{ 95 - got: got, 96 - want: want, 97 - title: "Not NearlyEqual", 98 - reason: fmt.Sprintf( 99 - "Difference %v exceeds maximum tolerance of %v", 100 - diff, 101 - floatEqualityThreshold, 102 - ), 103 - comment: getComment(), 169 + got: got, 170 + want: want, 171 + cfg: cfg, 104 172 } 105 173 tb.Fatal(fail.String()) 106 - } 107 - } 108 - 109 - // EqualFunc is like Equal but allows the user to pass a custom comparator, useful 110 - // when the items to be compared do not implement the comparable generic constraint 111 - // 112 - // The comparator should return true if the two items should be considered equal. 113 - func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) { 114 - tb.Helper() 115 - if !equal(got, want) { 116 - fail := failure[T]{ 117 - got: got, 118 - want: want, 119 - title: "Not Equal", 120 - reason: "equal(got, want) returned false", 121 - comment: getComment(), 122 - } 123 - tb.Fatal(fail.String()) 124 - } 125 - } 126 - 127 - // NotEqual fails if got == want. 128 - // 129 - // test.NotEqual(t, "apples", "oranges") // Passes 130 - // test.NotEqual(t, "apples", "apples") // Fails 131 - func NotEqual[T comparable](tb testing.TB, got, want T) { 132 - tb.Helper() 133 - if got == want { 134 - if comment := getComment(); comment != "" { 135 - tb.Fatalf( 136 - "\nEqual // %s\n%s\nGot:\t%+v\n\nExpected values to be different\n", 137 - comment, 138 - strings.Repeat("-", len("Equal")), 139 - got, 140 - ) 141 - } else { 142 - tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nExpected values to be different\n", strings.Repeat("-", len("Equal")), got) 143 - } 144 - } 145 - } 146 - 147 - // NotEqualFunc is like NotEqual but allows the user to pass a custom comparator, useful 148 - // when the items to be compared do not implement the comparable generic constraint 149 - // 150 - // The comparator should return true if the two items should be considered equal. 151 - func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) { 152 - tb.Helper() 153 - if equal(got, want) { 154 - if comment := getComment(); comment != "" { 155 - tb.Fatalf( 156 - "\nEqual // %s\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n", 157 - comment, 158 - strings.Repeat("-", len("Equal")), 159 - got, 160 - ) 161 - } else { 162 - tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n", strings.Repeat("-", len("Equal")), got) 163 - } 164 174 } 165 175 } 166 176 ··· 168 178 // 169 179 // err := doSomething() 170 180 // test.Ok(t, err) 171 - func Ok(tb testing.TB, err error) { 181 + func Ok(tb testing.TB, err error, options ...Option) { 172 182 tb.Helper() 183 + cfg := defaultConfig() 184 + cfg.title = "Not Ok" 185 + 186 + for _, option := range options { 187 + if optionErr := option.apply(&cfg); optionErr != nil { 188 + tb.Fatalf("Ok: could not apply options: %v", optionErr) 189 + return 190 + } 191 + } 192 + 173 193 if err != nil { 174 194 fail := failure[error]{ 175 - got: err, 176 - want: nil, 177 - title: "Not Ok", 178 - comment: getComment(), 195 + got: err, 196 + want: nil, 197 + cfg: cfg, 179 198 } 180 199 tb.Fatal(fail.String()) 181 200 } ··· 183 202 184 203 // Err fails if err == nil. 185 204 // 186 - // err := shouldReturnErr() 205 + // err := shouldFail() 187 206 // test.Err(t, err) 188 - func Err(tb testing.TB, err error) { 207 + func Err(tb testing.TB, err error, options ...Option) { 189 208 tb.Helper() 209 + cfg := defaultConfig() 210 + cfg.title = "Not Err" 211 + 212 + for _, option := range options { 213 + if optionErr := option.apply(&cfg); optionErr != nil { 214 + tb.Fatalf("Err: could not apply options: %v", optionErr) 215 + return 216 + } 217 + } 218 + 190 219 if err == nil { 191 220 fail := failure[error]{ 192 - got: nil, 193 - want: errors.New("error"), 194 - title: "Not Err", 195 - comment: getComment(), 221 + got: nil, 222 + want: errors.New("error"), 223 + cfg: cfg, 196 224 } 197 225 tb.Fatal(fail.String()) 198 226 } 199 227 } 200 228 201 - // WantErr fails if you got an error and didn't want it, or if you 202 - // didn't get an error but wanted one. 229 + // WantErr fails if you got an error and didn't want it, or if you didn't 230 + // get an error but wanted one. 203 231 // 204 - // It simplifies checking for errors in table driven tests where on any 205 - // iteration err may or may not be nil. 232 + // It greatly simplifies checking for errors in table driven tests where an error 233 + // may or may not be nil on any given test case. 206 234 // 207 235 // test.WantErr(t, errors.New("uh oh"), true) // Passes, got error when we wanted one 208 236 // test.WantErr(t, errors.New("uh oh"), false) // Fails, got error but didn't want one 209 237 // test.WantErr(t, nil, true) // Fails, wanted an error but didn't get one 210 238 // test.WantErr(t, nil, false) // Passes, didn't want an error and didn't get one 211 - func WantErr(tb testing.TB, err error, want bool) { 239 + func WantErr(tb testing.TB, err error, want bool, options ...Option) { 212 240 tb.Helper() 241 + cfg := defaultConfig() 242 + cfg.title = "WantErr" 243 + 244 + for _, option := range options { 245 + if optionErr := option.apply(&cfg); optionErr != nil { 246 + tb.Fatalf("WantErr: could not apply options: %v", optionErr) 247 + return 248 + } 249 + } 250 + 213 251 if (err != nil) != want { 214 - var reason string 215 - var wanted error 252 + var ( 253 + reason string 254 + wanted error 255 + ) 216 256 if want { 217 257 reason = fmt.Sprintf("Wanted an error but got %v", err) 218 258 wanted = errors.New("error") ··· 220 260 reason = fmt.Sprintf("Got an unexpected error: %v", err) 221 261 wanted = nil 222 262 } 223 - fail := failure[any]{ 224 - got: err, 225 - want: wanted, 226 - title: "WantErr", 227 - reason: reason, 228 - comment: getComment(), 263 + cfg.reason = reason 264 + fail := failure[error]{ 265 + got: err, 266 + want: wanted, 267 + cfg: cfg, 229 268 } 230 269 tb.Fatal(fail.String()) 231 270 } 232 271 } 233 272 234 - // True fails if v is false. 273 + // True fails if got is false. 235 274 // 236 275 // test.True(t, true) // Passes 237 276 // test.True(t, false) // Fails 238 - func True(tb testing.TB, v bool) { 277 + func True(tb testing.TB, got bool, options ...Option) { 239 278 tb.Helper() 240 - if !v { 279 + cfg := defaultConfig() 280 + cfg.title = "Not True" 281 + 282 + for _, option := range options { 283 + if err := option.apply(&cfg); err != nil { 284 + tb.Fatalf("True: could not apply options: %v", err) 285 + return 286 + } 287 + } 288 + 289 + if !got { 241 290 fail := failure[bool]{ 242 - got: v, 243 - want: true, 244 - title: "Not True", 245 - comment: getComment(), 291 + got: got, 292 + want: true, 293 + cfg: cfg, 246 294 } 247 295 tb.Fatal(fail.String()) 248 296 } 249 297 } 250 298 251 - // False fails if v is true. 299 + // False fails if got is true. 252 300 // 253 301 // test.False(t, false) // Passes 254 302 // test.False(t, true) // Fails 255 - func False(tb testing.TB, v bool) { 303 + func False(tb testing.TB, got bool, options ...Option) { 256 304 tb.Helper() 257 - if v { 305 + cfg := defaultConfig() 306 + cfg.title = "Not False" 307 + 308 + for _, option := range options { 309 + if err := option.apply(&cfg); err != nil { 310 + tb.Fatalf("False: could not apply options: %v", err) 311 + return 312 + } 313 + } 314 + 315 + if got { 258 316 fail := failure[bool]{ 259 - got: v, 260 - want: false, 261 - title: "Not False", 262 - comment: getComment(), 317 + got: got, 318 + want: false, 319 + cfg: cfg, 263 320 } 264 321 tb.Fatal(fail.String()) 265 322 } 266 323 } 267 324 268 - // Diff fails if got != want and provides a rich diff. 269 - func Diff(tb testing.TB, got, want any) { 270 - // TODO: Nicer output for diff, don't like the +got -want thing 325 + // Diff fails if the two strings got and want are not equal and provides a rich 326 + // unified diff of the two for easy comparison. 327 + func Diff(tb testing.TB, got, want string) { 271 328 tb.Helper() 272 - if diff := cmp.Diff(want, got); diff != "" { 273 - tb.Fatalf("\nMismatch (-want, +got):\n%s\n", diff) 329 + 330 + if diff := diff.Diff("want", []byte(want), "got", []byte(got)); diff != nil { 331 + tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff))) 274 332 } 275 333 } 276 334 277 - // DeepEqual fails if reflect.DeepEqual(got, want) == false. 278 - func DeepEqual(tb testing.TB, got, want any) { 335 + // DiffBytes fails if the two []byte got and want are not equal and provides a rich 336 + // unified diff of the two for easy comparison. 337 + func DiffBytes(tb testing.TB, got, want []byte) { 279 338 tb.Helper() 280 - if !reflect.DeepEqual(got, want) { 281 - fail := failure[any]{ 282 - got: got, 283 - want: want, 284 - title: "Not Equal", 285 - reason: "reflect.DeepEqual(got, want) returned false", 286 - comment: getComment(), 287 - } 288 - tb.Fatal(fail.String()) 339 + 340 + if diff := diff.Diff("want", want, "got", got); diff != nil { 341 + tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff))) 289 342 } 290 343 } 291 344 292 - // Data returns the filepath to the testdata directory for the current package. 293 - // 294 - // When running tests, Go will change the cwd to the directory of the package under test. This means 295 - // that reference data stored in $CWD/testdata can be easily retrieved in the same way for any package. 296 - // 297 - // The $CWD/testdata directory is a Go idiom, common practice, and is completely ignored by the go tool. 298 - // 299 - // Data makes no guarantee that $CWD/testdata exists, it simply returns it's path. 300 - // 301 - // file := filepath.Join(test.Data(t), "test.txt") 302 - func Data(tb testing.TB) string { 303 - tb.Helper() 304 - cwd, err := os.Getwd() 305 - if err != nil { 306 - tb.Fatalf("could not get $CWD: %v", err) 307 - } 308 - 309 - return filepath.Join(cwd, "testdata") 310 - } 311 - 312 - // File fails if got does not match the contents of the given file. 313 - // 314 - // It takes a string and the path of a file to compare, use [Data] to obtain 315 - // the path to the current packages testdata directory. 316 - // 317 - // If the contents differ, the test will fail with output similar to executing git diff 318 - // on the contents. 319 - // 320 - // Files with differing line endings (e.g windows CR LF \r\n vs unix LF \n) will be normalised to 321 - // \n prior to comparison so this function will behave identically across multiple platforms. 322 - // 323 - // test.File(t, "hello\n", "expected.txt") 324 - func File(tb testing.TB, got, file string) { 325 - tb.Helper() 326 - f, err := filepath.Abs(file) 327 - if err != nil { 328 - tb.Fatalf("could not make %s absolute: %v", file, err) 329 - } 330 - contents, err := os.ReadFile(f) 331 - if err != nil { 332 - tb.Fatalf("could not read %s: %v", f, err) 333 - } 334 - 335 - contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n")) 336 - 337 - if diff := diff.Diff(f, contents, "got", []byte(got)); diff != nil { 338 - tb.Fatalf("\nMismatch\n--------\n%s\n", prettyDiff(string(diff))) 339 - } 340 - } 341 - 342 - // CaptureOutput captures and returns data printed to stdout and stderr by the provided function fn, allowing 345 + // CaptureOutput captures and returns data printed to [os.Stdout] and [os.Stderr] by the provided function fn, allowing 343 346 // you to test functions that write to those streams and do not have an option to pass in an [io.Writer]. 344 347 // 345 348 // If the provided function returns a non nil error, the test is failed with the error logged as the reason. ··· 427 430 wg.Wait() 428 431 429 432 return capturedStdout, capturedStderr 430 - } 431 - 432 - // getComment loads a Go line comment from a line where a test function has been called. 433 - // 434 - // If any error happens or there is no comment, an empty string is returned so as not 435 - // to influence the test with an unrelated error. 436 - func getComment() string { 437 - skip := 2 // Skip 2 frames, one for this function, the other for the calling test function 438 - _, file, line, ok := runtime.Caller(skip) 439 - if !ok { 440 - return "" 441 - } 442 - 443 - f, err := os.Open(file) 444 - if err != nil { 445 - return "" 446 - } 447 - defer f.Close() 448 - 449 - currentLine := 1 // Line numbers in source files start from 1 450 - scanner := bufio.NewScanner(f) 451 - for scanner.Scan() { 452 - // Skip through until we get to the line returned from runtime.Caller 453 - if currentLine != line { 454 - currentLine++ 455 - continue 456 - } 457 - 458 - _, comment, ok := strings.Cut(scanner.Text(), "//") 459 - if !ok { 460 - // There was no comment on this line 461 - return "" 462 - } 463 - 464 - // Now comment will be everything from the "//" until the end of the line 465 - return strings.TrimSpace(comment) 466 - } 467 - 468 - // Didn't find one 469 - return "" 470 433 } 471 434 472 435 // prettyDiff takes a string diff in unified diff format and colourises it for easier viewing.
+300 -382
test_test.go
··· 3 3 import ( 4 4 "bytes" 5 5 "errors" 6 + "flag" 6 7 "fmt" 7 8 "io" 8 9 "os" 9 - "path/filepath" 10 + "slices" 10 11 "testing" 11 12 13 + "github.com/FollowTheProcess/snapshot" 12 14 "github.com/FollowTheProcess/test" 13 - "github.com/google/go-cmp/cmp" 14 15 ) 16 + 17 + var update = flag.Bool("update", false, "Update snapshots") 15 18 16 19 // TB is a fake implementation of [testing.TB] that simply records in internal 17 20 // state whether or not it would have failed and what it would have written. ··· 33 36 fmt.Fprintf(t.out, format, args...) 34 37 } 35 38 36 - func TestPassFail(t *testing.T) { 39 + func TestTest(t *testing.T) { 37 40 tests := []struct { 38 - testFunc func(tb testing.TB) // The test function we're... testing 39 - wantOut string // What we wanted the TB to print 41 + fn func(tb testing.TB) // The test function we're... testing? 40 42 name string // Name of the test case 41 - wantFail bool // Whether we wanted the testFunc to fail it's TB 43 + wantFail bool // Whether it should fail 42 44 }{ 43 45 { 44 - name: "equal string pass", 45 - testFunc: func(tb testing.TB) { 46 - tb.Helper() 47 - test.Equal(tb, "apples", "apples") // These obviously are equal 46 + name: "Equal/pass", 47 + fn: func(tb testing.TB) { 48 + test.Equal(tb, "apples", "apples") 48 49 }, 49 - wantFail: false, // Should pass 50 - wantOut: "", // And write no output 50 + wantFail: false, 51 51 }, 52 52 { 53 - name: "equal string fail", 54 - testFunc: func(tb testing.TB) { 55 - tb.Helper() 53 + name: "Equal/fail", 54 + fn: func(tb testing.TB) { 56 55 test.Equal(tb, "apples", "oranges") 57 56 }, 58 57 wantFail: true, 59 - wantOut: "\nNot Equal\n---------\nGot:\tapples\nWanted:\toranges\n", 60 58 }, 61 59 { 62 - name: "equal string fail with comment", 63 - testFunc: func(tb testing.TB) { 64 - tb.Helper() 65 - test.Equal(tb, "apples", "oranges") // apples are not oranges 60 + name: "Equal/fail with context", 61 + fn: func(tb testing.TB) { 62 + test.Equal(tb, "apples", "oranges", test.Context("Apples are not oranges!")) 66 63 }, 67 64 wantFail: true, 68 - wantOut: "\nNot Equal // apples are not oranges\n---------\nGot:\tapples\nWanted:\toranges\n", 69 65 }, 70 66 { 71 - name: "equal int pass", 72 - testFunc: func(tb testing.TB) { 73 - tb.Helper() 74 - test.Equal(tb, 1, 1) 67 + name: "Equal/fail context format", 68 + fn: func(tb testing.TB) { 69 + test.Equal(tb, "apples", "oranges", test.Context("Apples == Oranges: %v", false)) 70 + }, 71 + wantFail: true, 72 + }, 73 + { 74 + name: "Equal/fail with title", 75 + fn: func(tb testing.TB) { 76 + test.Equal(tb, "apples", "oranges", test.Title("My fruit test")) 77 + }, 78 + wantFail: true, 79 + }, 80 + { 81 + name: "NotEqual/pass", 82 + fn: func(tb testing.TB) { 83 + test.NotEqual(tb, "apples", "oranges") 75 84 }, 76 85 wantFail: false, 77 - wantOut: "", 78 86 }, 79 87 { 80 - name: "equal int fail", 81 - testFunc: func(tb testing.TB) { 82 - tb.Helper() 83 - test.Equal(tb, 1, 42) 84 - }, 85 - wantFail: true, 86 - wantOut: "\nNot Equal\n---------\nGot:\t1\nWanted:\t42\n", 87 - }, 88 - { 89 - name: "nearly equal pass", 90 - testFunc: func(tb testing.TB) { 91 - tb.Helper() 92 - test.NearlyEqual(tb, 3.0000000001, 3.0) 93 - }, 94 - wantFail: false, 95 - wantOut: "", 96 - }, 97 - { 98 - name: "nearly equal fail", 99 - testFunc: func(tb testing.TB) { 100 - tb.Helper() 101 - test.NearlyEqual(tb, 3.0000001, 3.0) 102 - }, 103 - wantFail: true, 104 - wantOut: "\nNot NearlyEqual\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n", 105 - }, 106 - { 107 - name: "nearly equal fail with comment", 108 - testFunc: func(tb testing.TB) { 109 - tb.Helper() 110 - test.NearlyEqual(tb, 3.0000001, 3.0) // Ooof so close 111 - }, 112 - wantFail: true, 113 - wantOut: "\nNot NearlyEqual // Ooof so close\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n", 114 - }, 115 - { 116 - name: "not equal string pass", 117 - testFunc: func(tb testing.TB) { 118 - tb.Helper() 119 - test.NotEqual(tb, "apples", "oranges") // Should pass, these aren't equal 120 - }, 121 - wantFail: false, 122 - wantOut: "", 123 - }, 124 - { 125 - name: "not equal string fail", 126 - testFunc: func(tb testing.TB) { 127 - tb.Helper() 88 + name: "NotEqual/fail", 89 + fn: func(tb testing.TB) { 128 90 test.NotEqual(tb, "apples", "apples") 129 91 }, 130 92 wantFail: true, 131 - wantOut: "\nEqual\n-----\nGot:\tapples\n\nExpected values to be different\n", 132 93 }, 133 94 { 134 - name: "not equal string fail with comment", 135 - testFunc: func(tb testing.TB) { 136 - tb.Helper() 137 - test.NotEqual(tb, "apples", "apples") // different apples 95 + name: "NotEqual/fail with context", 96 + fn: func(tb testing.TB) { 97 + test.NotEqual(tb, 42, 42, test.Context("42 is the meaning of life")) 138 98 }, 139 99 wantFail: true, 140 - wantOut: "\nEqual // different apples\n-----\nGot:\tapples\n\nExpected values to be different\n", 141 100 }, 142 101 { 143 - name: "not equal int pass", 144 - testFunc: func(tb testing.TB) { 145 - tb.Helper() 146 - test.NotEqual(tb, 1, 42) 102 + name: "NotEqual/fail context format", 103 + fn: func(tb testing.TB) { 104 + test.NotEqual(tb, 42, 42, test.Context("42 == meaning of life: %v", true)) 105 + }, 106 + wantFail: true, 107 + }, 108 + { 109 + name: "NotEqual/fail with title", 110 + fn: func(tb testing.TB) { 111 + test.NotEqual(tb, "apples", "apples", test.Title("My fruit test")) 112 + }, 113 + wantFail: true, 114 + }, 115 + { 116 + name: "EqualFunc/pass", 117 + fn: func(tb testing.TB) { 118 + test.EqualFunc(tb, []int{1, 2, 3, 4}, []int{1, 2, 3, 4}, slices.Equal) 147 119 }, 148 120 wantFail: false, 149 - wantOut: "", 150 121 }, 151 122 { 152 - name: "not equal int fail", 153 - testFunc: func(tb testing.TB) { 154 - tb.Helper() 155 - test.NotEqual(tb, 1, 1) 123 + name: "EqualFunc/fail", 124 + fn: func(tb testing.TB) { 125 + cmp := func(a, b []string) bool { return false } // Cheating 126 + test.EqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) 156 127 }, 157 128 wantFail: true, 158 - wantOut: "\nEqual\n-----\nGot:\t1\n\nExpected values to be different\n", 159 129 }, 160 130 { 161 - name: "not equal int fail with comment", 162 - testFunc: func(tb testing.TB) { 163 - tb.Helper() 164 - test.NotEqual(tb, 1, 1) // 1 != 1? 131 + name: "EqualFunc/fail with context", 132 + fn: func(tb testing.TB) { 133 + test.EqualFunc( 134 + tb, 135 + []string{"hello"}, 136 + []string{"there"}, 137 + slices.Equal, 138 + test.Context("some context here"), 139 + ) 165 140 }, 166 141 wantFail: true, 167 - wantOut: "\nEqual // 1 != 1?\n-----\nGot:\t1\n\nExpected values to be different\n", 168 142 }, 169 143 { 170 - name: "ok pass", 171 - testFunc: func(tb testing.TB) { 172 - tb.Helper() 144 + name: "EqualFunc/fail context format", 145 + fn: func(tb testing.TB) { 146 + test.EqualFunc( 147 + tb, 148 + []string{"hello"}, 149 + []string{"there"}, 150 + slices.Equal, 151 + test.Context("who's bad at testing... %s", "you"), 152 + ) 153 + }, 154 + wantFail: true, 155 + }, 156 + { 157 + name: "EqualFunc/fail with title", 158 + fn: func(tb testing.TB) { 159 + test.EqualFunc(tb, []string{"hello"}, []string{"there"}, slices.Equal, test.Title("Hello!")) 160 + }, 161 + wantFail: true, 162 + }, 163 + { 164 + name: "NotEqualFunc/pass", 165 + fn: func(tb testing.TB) { 166 + test.NotEqualFunc(tb, []int{1, 2, 3, 4}, []int{5, 6, 7, 8}, slices.Equal) 167 + }, 168 + wantFail: false, 169 + }, 170 + { 171 + name: "NotEqualFunc/fail", 172 + fn: func(tb testing.TB) { 173 + cmp := func(a, b []string) bool { return true } // Cheating 174 + test.NotEqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) 175 + }, 176 + wantFail: true, 177 + }, 178 + { 179 + name: "NotEqualFunc/fail with context", 180 + fn: func(tb testing.TB) { 181 + test.NotEqualFunc( 182 + tb, 183 + []string{"hello"}, 184 + []string{"hello"}, 185 + slices.Equal, 186 + test.Context("some context here"), 187 + ) 188 + }, 189 + wantFail: true, 190 + }, 191 + { 192 + name: "NotEqualFunc/fail context format", 193 + fn: func(tb testing.TB) { 194 + test.NotEqualFunc( 195 + tb, 196 + []string{"hello"}, 197 + []string{"hello"}, 198 + slices.Equal, 199 + test.Context("who's bad at testing... %s", "you"), 200 + ) 201 + }, 202 + wantFail: true, 203 + }, 204 + { 205 + name: "NotEqualFunc/fail with title", 206 + fn: func(tb testing.TB) { 207 + test.NotEqualFunc(tb, []string{"hello"}, []string{"hello"}, slices.Equal, test.Title("Hello!")) 208 + }, 209 + wantFail: true, 210 + }, 211 + { 212 + name: "NearlyEqual/pass", 213 + fn: func(tb testing.TB) { 214 + test.NearlyEqual(tb, 3.0000000001, 3.0) 215 + }, 216 + wantFail: false, 217 + }, 218 + { 219 + name: "NearlyEqual/fail", 220 + fn: func(tb testing.TB) { 221 + test.NearlyEqual(tb, 3.0000001, 3.0) 222 + }, 223 + wantFail: true, 224 + }, 225 + { 226 + name: "NearlyEqual/fail custom tolerance", 227 + fn: func(tb testing.TB) { 228 + test.NearlyEqual(tb, 3.2, 3.0, test.FloatEqualityThreshold(0.1)) 229 + }, 230 + wantFail: true, 231 + }, 232 + { 233 + name: "NearlyEqual/fail with context", 234 + fn: func(tb testing.TB) { 235 + test.NearlyEqual(tb, 3.0000001, 3.0, test.Context("Numbers don't work that way")) 236 + }, 237 + wantFail: true, 238 + }, 239 + { 240 + name: "Ok/pass", 241 + fn: func(tb testing.TB) { 173 242 test.Ok(tb, nil) 174 243 }, 175 244 wantFail: false, 176 - wantOut: "", 177 245 }, 178 246 { 179 - name: "ok fail", 180 - testFunc: func(tb testing.TB) { 181 - tb.Helper() 247 + name: "Ok/fail", 248 + fn: func(tb testing.TB) { 182 249 test.Ok(tb, errors.New("uh oh")) 183 250 }, 184 251 wantFail: true, 185 - wantOut: "\nNot Ok\n------\nGot:\tuh oh\nWanted:\t<nil>\n", 186 252 }, 187 253 { 188 - name: "ok fail with comment", 189 - testFunc: func(tb testing.TB) { 190 - tb.Helper() 191 - test.Ok(tb, errors.New("uh oh")) // Calling some function 254 + name: "Ok/fail with context", 255 + fn: func(tb testing.TB) { 256 + test.Ok(tb, errors.New("uh oh"), test.Context("Could not frobnicate the baz")) 192 257 }, 193 258 wantFail: true, 194 - wantOut: "\nNot Ok // Calling some function\n------\nGot:\tuh oh\nWanted:\t<nil>\n", 195 259 }, 196 260 { 197 - name: "err pass", 198 - testFunc: func(tb testing.TB) { 199 - tb.Helper() 200 - test.Err(tb, errors.New("uh oh")) 261 + name: "Ok/fail with title", 262 + fn: func(tb testing.TB) { 263 + test.Ok(tb, errors.New("uh oh"), test.Title("Bang!")) 264 + }, 265 + wantFail: true, 266 + }, 267 + { 268 + name: "Err/pass", 269 + fn: func(tb testing.TB) { 270 + test.Err(tb, errors.New("bang!")) 201 271 }, 202 272 wantFail: false, 203 - wantOut: "", 204 273 }, 205 274 { 206 - name: "err fail", 207 - testFunc: func(tb testing.TB) { 208 - tb.Helper() 275 + name: "Err/fail", 276 + fn: func(tb testing.TB) { 209 277 test.Err(tb, nil) 210 278 }, 211 279 wantFail: true, 212 - wantOut: "\nNot Err\n-------\nGot:\t<nil>\nWanted:\terror\n", 213 280 }, 214 281 { 215 - name: "err fail with comment", 216 - testFunc: func(tb testing.TB) { 217 - tb.Helper() 218 - test.Err(tb, nil) // Should have failed 282 + name: "Err/fail with context", 283 + fn: func(tb testing.TB) { 284 + test.Err(tb, nil, test.Context("Frobnicated the baz when it should have failed")) 219 285 }, 220 286 wantFail: true, 221 - wantOut: "\nNot Err // Should have failed\n-------\nGot:\t<nil>\nWanted:\terror\n", 222 287 }, 223 288 { 224 - name: "true pass", 225 - testFunc: func(tb testing.TB) { 226 - tb.Helper() 289 + name: "Err/fail with title", 290 + fn: func(tb testing.TB) { 291 + test.Err(tb, nil, test.Title("Everything is fine?")) 292 + }, 293 + wantFail: true, 294 + }, 295 + { 296 + name: "WantErr/pass error", 297 + fn: func(tb testing.TB) { 298 + test.WantErr(tb, errors.New("bang"), true) // Wanted an error and got one - should pass 299 + }, 300 + wantFail: false, 301 + }, 302 + { 303 + name: "WantErr/pass nil", 304 + fn: func(tb testing.TB) { 305 + test.WantErr(tb, nil, false) // Didn't want an error and got nil - should pass 306 + }, 307 + wantFail: false, 308 + }, 309 + { 310 + name: "WantErr/fail error", 311 + fn: func(tb testing.TB) { 312 + test.WantErr(tb, errors.New("bang"), false) // Got an error but didn't want one - should fail 313 + }, 314 + wantFail: true, 315 + }, 316 + { 317 + name: "WantErr/fail nil", 318 + fn: func(tb testing.TB) { 319 + test.WantErr(tb, nil, true) // Didn't get an error but wanted one - should fail 320 + }, 321 + wantFail: true, 322 + }, 323 + { 324 + name: "WantErr/fail with context", 325 + fn: func(tb testing.TB) { 326 + test.WantErr(tb, errors.New("bang"), false, test.Context("Errors are bad!")) 327 + }, 328 + wantFail: true, 329 + }, 330 + { 331 + name: "WantErr/fail with title", 332 + fn: func(tb testing.TB) { 333 + test.WantErr(tb, errors.New("bang"), false, test.Title("A very bad test")) 334 + }, 335 + wantFail: true, 336 + }, 337 + { 338 + name: "True/pass", 339 + fn: func(tb testing.TB) { 227 340 test.True(tb, true) 228 341 }, 229 342 wantFail: false, 230 - wantOut: "", 231 343 }, 232 344 { 233 - name: "true fail", 234 - testFunc: func(tb testing.TB) { 235 - tb.Helper() 345 + name: "True/fail", 346 + fn: func(tb testing.TB) { 236 347 test.True(tb, false) 237 348 }, 238 349 wantFail: true, 239 - wantOut: "\nNot True\n--------\nGot:\tfalse\nWanted:\ttrue\n", 240 350 }, 241 351 { 242 - name: "true fail with comment", 243 - testFunc: func(tb testing.TB) { 244 - tb.Helper() 245 - test.True(tb, false) // Comment here 352 + name: "True/fail with context", 353 + fn: func(tb testing.TB) { 354 + test.True(tb, false, test.Context("must always be true")) 246 355 }, 247 356 wantFail: true, 248 - wantOut: "\nNot True // Comment here\n--------\nGot:\tfalse\nWanted:\ttrue\n", 249 357 }, 250 358 { 251 - name: "false pass", 252 - testFunc: func(tb testing.TB) { 253 - tb.Helper() 359 + name: "True/fail with title", 360 + fn: func(tb testing.TB) { 361 + test.True(tb, false, test.Title("Argh!")) 362 + }, 363 + wantFail: true, 364 + }, 365 + { 366 + name: "False/pass", 367 + fn: func(tb testing.TB) { 254 368 test.False(tb, false) 255 369 }, 256 370 wantFail: false, 257 - wantOut: "", 258 371 }, 259 372 { 260 - name: "false fail", 261 - testFunc: func(tb testing.TB) { 262 - tb.Helper() 373 + name: "False/fail", 374 + fn: func(tb testing.TB) { 263 375 test.False(tb, true) 264 376 }, 265 377 wantFail: true, 266 - wantOut: "\nNot False\n---------\nGot:\ttrue\nWanted:\tfalse\n", 267 378 }, 268 379 { 269 - name: "false fail with comment", 270 - testFunc: func(tb testing.TB) { 271 - tb.Helper() 272 - test.False(tb, true) // Should always be false 380 + name: "False/fail with context", 381 + fn: func(tb testing.TB) { 382 + test.False(tb, true, test.Context("must always be false")) 273 383 }, 274 384 wantFail: true, 275 - wantOut: "\nNot False // Should always be false\n---------\nGot:\ttrue\nWanted:\tfalse\n", 276 385 }, 277 386 { 278 - name: "equal func pass", 279 - testFunc: func(tb testing.TB) { 280 - tb.Helper() 281 - rubbishEqual := func(a, b string) bool { 282 - return true // Always equal 283 - } 284 - test.EqualFunc(tb, "word", "different word", rubbishEqual) 285 - }, 286 - wantFail: false, 287 - wantOut: "", 288 - }, 289 - { 290 - name: "equal func fail", 291 - testFunc: func(tb testing.TB) { 292 - tb.Helper() 293 - rubbishEqual := func(a, b string) bool { 294 - return false // Never equal 295 - } 296 - test.EqualFunc(tb, "word", "word", rubbishEqual) 387 + name: "False/fail with title", 388 + fn: func(tb testing.TB) { 389 + test.False(tb, true, test.Title("Argh!")) 297 390 }, 298 391 wantFail: true, 299 - wantOut: "\nNot Equal\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n", 300 392 }, 301 393 { 302 - name: "equal func fail with comment", 303 - testFunc: func(tb testing.TB) { 304 - tb.Helper() 305 - rubbishEqual := func(a, b string) bool { 306 - return false // Never equal 307 - } 308 - test.EqualFunc(tb, "word", "word", rubbishEqual) // Uh oh 309 - }, 310 - wantFail: true, 311 - wantOut: "\nNot Equal // Uh oh\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n", 312 - }, 313 - { 314 - name: "not equal func pass", 315 - testFunc: func(tb testing.TB) { 316 - tb.Helper() 317 - rubbishNotEqual := func(a, b string) bool { 318 - return false // Never equal 319 - } 320 - test.NotEqualFunc(tb, "word", "word", rubbishNotEqual) 321 - }, 322 - wantFail: false, 323 - wantOut: "", 324 - }, 325 - { 326 - name: "not equal func fail", 327 - testFunc: func(tb testing.TB) { 328 - tb.Helper() 329 - rubbishNotEqual := func(a, b string) bool { 330 - return true // Always equal 331 - } 332 - test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual) 333 - }, 334 - wantFail: true, 335 - wantOut: "\nEqual\n-----\nGot:\tword\n\nequal(got, want) returned true\n", 336 - }, 337 - { 338 - name: "not equal func fail with comment", 339 - testFunc: func(tb testing.TB) { 340 - tb.Helper() 341 - rubbishNotEqual := func(a, b string) bool { 342 - return true // Always equal 343 - } 344 - test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual) // Bad equal 345 - }, 346 - wantFail: true, 347 - wantOut: "\nEqual // Bad equal\n-----\nGot:\tword\n\nequal(got, want) returned true\n", 348 - }, 349 - { 350 - name: "deep equal pass", 351 - testFunc: func(tb testing.TB) { 352 - tb.Helper() 353 - a := []string{"a", "b", "c"} 354 - b := []string{"a", "b", "c"} 394 + name: "Diff/pass", 395 + fn: func(tb testing.TB) { 396 + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" 397 + want := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" 355 398 356 - test.DeepEqual(tb, a, b) 357 - }, 358 - wantFail: false, 359 - wantOut: "", 360 - }, 361 - { 362 - name: "deep equal fail", 363 - testFunc: func(tb testing.TB) { 364 - tb.Helper() 365 - a := []string{"a", "b", "c"} 366 - b := []string{"d", "e", "f"} 367 - 368 - test.DeepEqual(tb, a, b) 369 - }, 370 - wantFail: true, 371 - wantOut: "\nNot Equal\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n", 372 - }, 373 - { 374 - name: "deep equal fail with comment", 375 - testFunc: func(tb testing.TB) { 376 - tb.Helper() 377 - a := []string{"a", "b", "c"} 378 - b := []string{"d", "e", "f"} 379 - 380 - test.DeepEqual(tb, a, b) // Oh no! 381 - }, 382 - wantFail: true, 383 - wantOut: "\nNot Equal // Oh no!\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n", 384 - }, 385 - { 386 - name: "want err pass when got and wanted", 387 - testFunc: func(tb testing.TB) { 388 - tb.Helper() 389 - test.WantErr(tb, errors.New("uh oh"), true) // We wanted an error and got one 390 - }, 391 - wantFail: false, 392 - wantOut: "", 393 - }, 394 - { 395 - name: "want err fail when got and not wanted", 396 - testFunc: func(tb testing.TB) { 397 - tb.Helper() 398 - test.WantErr(tb, errors.New("uh oh"), false) 399 - }, 400 - wantFail: true, 401 - wantOut: "\nWantErr\n-------\nGot:\tuh oh\nWanted:\t<nil>\n\nGot an unexpected error: uh oh\n", 402 - }, 403 - { 404 - name: "want err fail when got and not wanted with comment", 405 - testFunc: func(tb testing.TB) { 406 - tb.Helper() 407 - test.WantErr(tb, errors.New("uh oh"), false) // comment 408 - }, 409 - wantFail: true, 410 - wantOut: "\nWantErr // comment\n-------\nGot:\tuh oh\nWanted:\t<nil>\n\nGot an unexpected error: uh oh\n", 411 - }, 412 - { 413 - name: "want err pass when not got and not wanted", 414 - testFunc: func(tb testing.TB) { 415 - tb.Helper() 416 - test.WantErr(tb, nil, false) // Didn't want an error and didn't get one 417 - }, 418 - wantFail: false, 419 - wantOut: "", 420 - }, 421 - { 422 - name: "want err fail when not got but wanted", 423 - testFunc: func(tb testing.TB) { 424 - tb.Helper() 425 - test.WantErr(tb, nil, true) 426 - }, 427 - wantFail: true, 428 - wantOut: "\nWantErr\n-------\nGot:\t<nil>\nWanted:\terror\n\nWanted an error but got <nil>\n", 429 - }, 430 - { 431 - name: "want err fail when not got but wanted with comment", 432 - testFunc: func(tb testing.TB) { 433 - tb.Helper() 434 - test.WantErr(tb, nil, true) // comment 435 - }, 436 - wantFail: true, 437 - wantOut: "\nWantErr // comment\n-------\nGot:\t<nil>\nWanted:\terror\n\nWanted an error but got <nil>\n", 438 - }, 439 - { 440 - name: "file pass", 441 - testFunc: func(tb testing.TB) { 442 - tb.Helper() 443 - test.File(tb, "hello\n", filepath.Join(test.Data(t), "file.txt")) 444 - }, 445 - wantFail: false, 446 - wantOut: "", 447 - }, 448 - { 449 - name: "diff pass string", 450 - testFunc: func(tb testing.TB) { 451 - tb.Helper() 452 - test.Diff(tb, "hello", "hello") 453 - }, 454 - wantFail: false, 455 - wantOut: "", 456 - }, 457 - { 458 - name: "diff fail string", 459 - testFunc: func(tb testing.TB) { 460 - tb.Helper() 461 - test.Diff(tb, "hello", "hello there") 462 - }, 463 - wantFail: true, 464 - wantOut: fmt.Sprintf( 465 - "\nMismatch (-want, +got):\n%s\n", 466 - cmp.Diff("hello there", "hello"), 467 - ), // Output equivalent to diff 468 - }, 469 - { 470 - name: "diff pass string slice", 471 - testFunc: func(tb testing.TB) { 472 - tb.Helper() 473 - got := []string{"hello", "there"} 474 - want := []string{"hello", "there"} 475 399 test.Diff(tb, got, want) 476 400 }, 477 401 wantFail: false, 478 - wantOut: "", 479 402 }, 480 403 { 481 - name: "diff fail string slice", 482 - testFunc: func(tb testing.TB) { 483 - tb.Helper() 484 - got := []string{"hello", "there"} 485 - want := []string{"not", "me"} 404 + name: "Diff/fail", 405 + fn: func(tb testing.TB) { 406 + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" 407 + want := "Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n" 486 408 test.Diff(tb, got, want) 487 409 }, 488 410 wantFail: true, 489 - wantOut: fmt.Sprintf( 490 - "\nMismatch (-want, +got):\n%s\n", 491 - cmp.Diff([]string{"not", "me"}, []string{"hello", "there"}), 492 - ), // Output equivalent to diff 411 + }, 412 + { 413 + name: "DiffBytes/pass", 414 + fn: func(tb testing.TB) { 415 + got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") 416 + want := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") 417 + 418 + test.DiffBytes(tb, got, want) 419 + }, 420 + wantFail: false, 421 + }, 422 + { 423 + name: "DiffBytes/fail", 424 + fn: func(tb testing.TB) { 425 + got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") 426 + want := []byte("Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n") 427 + test.DiffBytes(tb, got, want) 428 + }, 429 + wantFail: true, 493 430 }, 494 431 } 495 432 ··· 497 434 t.Run(tt.name, func(t *testing.T) { 498 435 buf := &bytes.Buffer{} 499 436 tb := &TB{out: buf} 437 + snap := snapshot.New(t, snapshot.Update(*update)) 500 438 501 439 if tb.failed { 502 440 t.Fatalf("%s initial failed state should be false", tt.name) 503 441 } 504 442 505 - // Call the test function, passing in our mock TB that simply 506 - // records whether or not it would have failed and what it would 507 - // have written 508 - tt.testFunc(tb) 443 + // Call the test function, passing in the mock TB that just records 444 + // what a "real" TB would have done 445 + tt.fn(tb) 509 446 510 447 if tb.failed != tt.wantFail { 511 - t.Fatalf( 512 - "\n%s failure mismatch\n--------------\nfailed:\t%v\nwanted failure:\t%v\n", 513 - tt.name, 514 - tb.failed, 515 - tt.wantFail, 516 - ) 448 + t.Fatalf("\nIncorrect Failure\n\ntb.failed:\t%v\nwanted:\t%v\n", tb.failed, tt.wantFail) 517 449 } 518 450 519 - if got := buf.String(); got != tt.wantOut { 520 - t.Errorf( 521 - "\n%s output mismatch\n---------------\nGot:\t%s\nWanted:\t%s\n", 522 - tt.name, 523 - got, 524 - tt.wantOut, 525 - ) 451 + // Test the output matches our snapshot file, only for failed tests 452 + // as there should be no output for passed tests 453 + if !tb.failed { 454 + if buf.Len() != 0 { 455 + t.Fatalf("\nIncorrect Output\n\nA passed test should have no output, got: %s\n", buf.String()) 456 + } 457 + } else { 458 + snap.Snap(buf.String()) 526 459 } 527 460 }) 528 - } 529 - } 530 - 531 - func TestData(t *testing.T) { 532 - got := test.Data(t) 533 - 534 - cwd, err := os.Getwd() 535 - if err != nil { 536 - t.Fatalf("Test for Data could not get cwd: %v", err) 537 - } 538 - 539 - want := filepath.Join(cwd, "testdata") 540 - 541 - if got != want { 542 - t.Errorf("\nGot:\t%s\nWanted:\t%s\n", got, want) 543 461 } 544 462 } 545 463
-1
testdata/file.txt
··· 1 - hello
+35 -33
.github/workflows/CI.yml
··· 5 5 push: 6 6 branches: 7 7 - main 8 - tags: 9 - - v* 10 8 11 9 concurrency: 12 - group: ${{ github.ref }} 10 + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 13 11 cancel-in-progress: true 14 12 15 - permissions: read-all 13 + permissions: {} 16 14 17 15 jobs: 18 16 test: 19 17 name: Test 20 18 runs-on: ${{ matrix.os }} 19 + permissions: 20 + contents: read 21 21 strategy: 22 22 matrix: 23 23 os: ··· 36 36 37 37 - name: Run Tests 38 38 run: go test -race ./... 39 + env: 40 + NO_COLOR: true 39 41 40 42 cov: 41 43 name: CodeCov 42 44 runs-on: ubuntu-latest 45 + permissions: 46 + contents: read 43 47 44 48 steps: 45 49 - name: Checkout Code ··· 52 56 53 57 - name: Run Tests 54 58 run: go test -race -cover -covermode=atomic -coverprofile=./coverage.out ./... 59 + env: 60 + NO_COLOR: true 55 61 56 62 - name: Coverage 57 63 uses: codecov/codecov-action@v5 58 64 with: 59 65 files: ./coverage.out 60 - token: ${{ secrets.CODECOV_TOKEN }} 61 66 62 67 lint: 63 68 name: Lint 64 69 runs-on: ubuntu-latest 70 + permissions: 71 + contents: read 65 72 66 73 steps: 67 74 - name: Checkout Code ··· 72 79 with: 73 80 go-version-file: go.mod 74 81 75 - - name: Clean Mod Cache # See https://github.com/golangci/golangci-lint-action/issues/135 76 - run: go clean -modcache 77 - 78 82 - name: Run Linting 79 83 uses: golangci/golangci-lint-action@v6 80 84 with: 81 85 version: latest 82 86 83 - release: 84 - name: Release 87 + vulncheck: 88 + name: Vulncheck 85 89 runs-on: ubuntu-latest 86 90 permissions: 87 - contents: write 88 - 89 - needs: 90 - - test 91 - - cov 92 - - lint 93 - 94 - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 91 + contents: read 95 92 96 93 steps: 97 94 - name: Checkout Code 98 95 uses: actions/checkout@v4 96 + 97 + - name: Set up Go 98 + uses: actions/setup-go@v5 99 99 with: 100 - fetch-depth: 0 100 + go-version-file: go.mod 101 + 102 + - name: Install govulncheck 103 + run: go install golang.org/x/vuln/cmd/govulncheck@latest 101 104 102 - - name: Fetch Existing Tags 103 - run: git fetch --force --tags 105 + - name: Run govulncheck 106 + run: govulncheck ./... 104 107 105 - - name: Parse Release Version 106 - id: version 107 - run: | 108 - VERSION=${GITHUB_REF#refs/tags/v} 109 - echo "version=$VERSION" >> $GITHUB_OUTPUT 108 + typos: 109 + name: Typos 110 + runs-on: ubuntu-latest 111 + permissions: 112 + contents: read 110 113 111 - - name: Publish Draft Release 112 - uses: release-drafter/release-drafter@v6 113 - with: 114 - version: ${{ steps.version.outputs.version }} 115 - publish: true 116 - env: 117 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 + steps: 115 + - name: Checkout Code 116 + uses: actions/checkout@v4 117 + 118 + - name: Check for Typos 119 + uses: crate-ci/typos@v1.29.1
+39
.github/workflows/release.yml
··· 1 + name: Release 2 + 3 + on: 4 + push: 5 + tags: 6 + - 'v[0-9]+.[0-9]+.[0-9]+' 7 + 8 + permissions: {} 9 + 10 + jobs: 11 + release: 12 + name: Release 13 + runs-on: ubuntu-latest 14 + permissions: 15 + contents: write 16 + pull-requests: read 17 + 18 + steps: 19 + - name: Checkout Code 20 + uses: actions/checkout@v4 21 + with: 22 + fetch-depth: 0 23 + 24 + - name: Fetch Existing Tags 25 + run: git fetch --force --tags 26 + 27 + - name: Parse Release Version 28 + id: version 29 + run: | 30 + VERSION=${GITHUB_REF#refs/tags/v} 31 + echo "version=$VERSION" >> $GITHUB_OUTPUT 32 + 33 + - name: Publish Draft Release 34 + uses: release-drafter/release-drafter@v6 35 + with: 36 + version: ${{ steps.version.outputs.version }} 37 + publish: true 38 + env: 39 + GITHUB_TOKEN: ${{ github.token }}
+14
testdata/snapshots/TestTest/Diff/fail.snap.txt
··· 1 + 2 + Diff 3 + ---- 4 + diff want got 5 + --- want 6 + +++ got 7 + @@ -1,4 +1,4 @@ 8 + Some 9 + - different stuff here in this file 10 + - this line is different 11 + + stuff here in this file 12 + + lines as well wow 13 + some more stuff 14 +
+14
testdata/snapshots/TestTest/DiffBytes/fail.snap.txt
··· 1 + 2 + Diff 3 + ---- 4 + diff want got 5 + --- want 6 + +++ got 7 + @@ -1,4 +1,4 @@ 8 + Some 9 + - different stuff here in this file 10 + - this line is different 11 + + stuff here in this file 12 + + lines as well wow 13 + some more stuff 14 +
+6
testdata/snapshots/TestTest/Equal/fail.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: apples 6 + Wanted: oranges
+8
testdata/snapshots/TestTest/Equal/fail_context_format.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: apples 6 + Wanted: oranges 7 + 8 + (Apples == Oranges: false)
+8
testdata/snapshots/TestTest/Equal/fail_with_context.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: apples 6 + Wanted: oranges 7 + 8 + (Apples are not oranges!)
+6
testdata/snapshots/TestTest/Equal/fail_with_title.snap.txt
··· 1 + 2 + My fruit test 3 + ------------- 4 + 5 + Got: apples 6 + Wanted: oranges
+8
testdata/snapshots/TestTest/EqualFunc/fail.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: [hello] 6 + Wanted: [there] 7 + 8 + Because: equal(got, want) returned false
+10
testdata/snapshots/TestTest/EqualFunc/fail_context_format.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: [hello] 6 + Wanted: [there] 7 + 8 + (who's bad at testing... you) 9 + 10 + Because: equal(got, want) returned false
+10
testdata/snapshots/TestTest/EqualFunc/fail_with_context.snap.txt
··· 1 + 2 + Not Equal 3 + --------- 4 + 5 + Got: [hello] 6 + Wanted: [there] 7 + 8 + (some context here) 9 + 10 + Because: equal(got, want) returned false
+8
testdata/snapshots/TestTest/EqualFunc/fail_with_title.snap.txt
··· 1 + 2 + Hello! 3 + ------ 4 + 5 + Got: [hello] 6 + Wanted: [there] 7 + 8 + Because: equal(got, want) returned false
+6
testdata/snapshots/TestTest/Err/fail.snap.txt
··· 1 + 2 + Not Err 3 + ------- 4 + 5 + Got: <nil> 6 + Wanted: error
+8
testdata/snapshots/TestTest/Err/fail_with_context.snap.txt
··· 1 + 2 + Not Err 3 + ------- 4 + 5 + Got: <nil> 6 + Wanted: error 7 + 8 + (Frobnicated the baz when it should have failed)
+6
testdata/snapshots/TestTest/Err/fail_with_title.snap.txt
··· 1 + 2 + Everything is fine? 3 + ------------------- 4 + 5 + Got: <nil> 6 + Wanted: error
+6
testdata/snapshots/TestTest/False/fail.snap.txt
··· 1 + 2 + Not False 3 + --------- 4 + 5 + Got: true 6 + Wanted: false
+8
testdata/snapshots/TestTest/False/fail_with_context.snap.txt
··· 1 + 2 + Not False 3 + --------- 4 + 5 + Got: true 6 + Wanted: false 7 + 8 + (must always be false)
+6
testdata/snapshots/TestTest/False/fail_with_title.snap.txt
··· 1 + 2 + Argh! 3 + ----- 4 + 5 + Got: true 6 + Wanted: false
+8
testdata/snapshots/TestTest/NearlyEqual/fail.snap.txt
··· 1 + 2 + Not NearlyEqual 3 + --------------- 4 + 5 + Got: 3.0000001 6 + Wanted: 3 7 + 8 + Because: Difference 3.0000001 - 3 = 9.999999983634211e-08 exceeds maximum tolerance of 1e-08
+8
testdata/snapshots/TestTest/NearlyEqual/fail_custom_tolerance.snap.txt
··· 1 + 2 + Not NearlyEqual 3 + --------------- 4 + 5 + Got: 3.2 6 + Wanted: 3 7 + 8 + Because: Difference 3.2 - 3 = 0.20000000000000018 exceeds maximum tolerance of 0.1
+10
testdata/snapshots/TestTest/NearlyEqual/fail_with_context.snap.txt
··· 1 + 2 + Not NearlyEqual 3 + --------------- 4 + 5 + Got: 3.0000001 6 + Wanted: 3 7 + 8 + (Numbers don't work that way) 9 + 10 + Because: Difference 3.0000001 - 3 = 9.999999983634211e-08 exceeds maximum tolerance of 1e-08
+6
testdata/snapshots/TestTest/NotEqual/fail.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: apples 6 + Wanted: apples
+8
testdata/snapshots/TestTest/NotEqual/fail_context_format.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: 42 6 + Wanted: 42 7 + 8 + (42 == meaning of life: true)
+8
testdata/snapshots/TestTest/NotEqual/fail_with_context.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: 42 6 + Wanted: 42 7 + 8 + (42 is the meaning of life)
+6
testdata/snapshots/TestTest/NotEqual/fail_with_title.snap.txt
··· 1 + 2 + My fruit test 3 + ------------- 4 + 5 + Got: apples 6 + Wanted: apples
+8
testdata/snapshots/TestTest/NotEqualFunc/fail.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: [hello] 6 + Wanted: [there] 7 + 8 + Because: equal(got, want) returned true
+10
testdata/snapshots/TestTest/NotEqualFunc/fail_context_format.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: [hello] 6 + Wanted: [hello] 7 + 8 + (who's bad at testing... you) 9 + 10 + Because: equal(got, want) returned true
+10
testdata/snapshots/TestTest/NotEqualFunc/fail_with_context.snap.txt
··· 1 + 2 + Equal 3 + ----- 4 + 5 + Got: [hello] 6 + Wanted: [hello] 7 + 8 + (some context here) 9 + 10 + Because: equal(got, want) returned true
+8
testdata/snapshots/TestTest/NotEqualFunc/fail_with_title.snap.txt
··· 1 + 2 + Hello! 3 + ------ 4 + 5 + Got: [hello] 6 + Wanted: [hello] 7 + 8 + Because: equal(got, want) returned true
+6
testdata/snapshots/TestTest/Ok/fail.snap.txt
··· 1 + 2 + Not Ok 3 + ------ 4 + 5 + Got: uh oh 6 + Wanted: <nil>
+8
testdata/snapshots/TestTest/Ok/fail_with_context.snap.txt
··· 1 + 2 + Not Ok 3 + ------ 4 + 5 + Got: uh oh 6 + Wanted: <nil> 7 + 8 + (Could not frobnicate the baz)
+6
testdata/snapshots/TestTest/Ok/fail_with_title.snap.txt
··· 1 + 2 + Bang! 3 + ----- 4 + 5 + Got: uh oh 6 + Wanted: <nil>
+6
testdata/snapshots/TestTest/True/fail.snap.txt
··· 1 + 2 + Not True 3 + -------- 4 + 5 + Got: false 6 + Wanted: true
+8
testdata/snapshots/TestTest/True/fail_with_context.snap.txt
··· 1 + 2 + Not True 3 + -------- 4 + 5 + Got: false 6 + Wanted: true 7 + 8 + (must always be true)
+6
testdata/snapshots/TestTest/True/fail_with_title.snap.txt
··· 1 + 2 + Argh! 3 + ----- 4 + 5 + Got: false 6 + Wanted: true
+8
testdata/snapshots/TestTest/WantErr/fail_error.snap.txt
··· 1 + 2 + WantErr 3 + ------- 4 + 5 + Got: bang 6 + Wanted: <nil> 7 + 8 + Because: Got an unexpected error: bang
+8
testdata/snapshots/TestTest/WantErr/fail_nil.snap.txt
··· 1 + 2 + WantErr 3 + ------- 4 + 5 + Got: <nil> 6 + Wanted: error 7 + 8 + Because: Wanted an error but got <nil>
+10
testdata/snapshots/TestTest/WantErr/fail_with_context.snap.txt
··· 1 + 2 + WantErr 3 + ------- 4 + 5 + Got: bang 6 + Wanted: <nil> 7 + 8 + (Errors are bad!) 9 + 10 + Because: Got an unexpected error: bang
+8
testdata/snapshots/TestTest/WantErr/fail_with_title.snap.txt
··· 1 + 2 + A very bad test 3 + --------------- 4 + 5 + Got: bang 6 + Wanted: <nil> 7 + 8 + Because: Got an unexpected error: bang