From f4710555612c8c3f7663de994b4b48c12a08bc56 Mon Sep 17 00:00:00 2001 From: Tom Fleet Date: Mon, 6 Jan 2025 17:37:46 +0000 Subject: [PATCH] Big refresh --- .github/workflows/CI.yml | 68 +-- .github/workflows/release.yml | 39 ++ .golangci.yml | 7 + README.md | 96 +-- Taskfile.yml | 57 +- config.go | 142 +++++ go.mod | 7 +- go.sum | 4 +- test.go | 461 +++++++-------- test_test.go | 546 ++++++++---------- testdata/file.txt | 1 - .../snapshots/TestTest/Diff/fail.snap.txt | 14 + .../TestTest/DiffBytes/fail.snap.txt | 14 + .../snapshots/TestTest/Equal/fail.snap.txt | 6 + .../Equal/fail_context_format.snap.txt | 8 + .../TestTest/Equal/fail_with_context.snap.txt | 8 + .../TestTest/Equal/fail_with_title.snap.txt | 6 + .../TestTest/EqualFunc/fail.snap.txt | 8 + .../EqualFunc/fail_context_format.snap.txt | 10 + .../EqualFunc/fail_with_context.snap.txt | 10 + .../EqualFunc/fail_with_title.snap.txt | 8 + testdata/snapshots/TestTest/Err/fail.snap.txt | 6 + .../TestTest/Err/fail_with_context.snap.txt | 8 + .../TestTest/Err/fail_with_title.snap.txt | 6 + .../snapshots/TestTest/False/fail.snap.txt | 6 + .../TestTest/False/fail_with_context.snap.txt | 8 + .../TestTest/False/fail_with_title.snap.txt | 6 + .../TestTest/NearlyEqual/fail.snap.txt | 8 + .../fail_custom_tolerance.snap.txt | 8 + .../NearlyEqual/fail_with_context.snap.txt | 10 + .../snapshots/TestTest/NotEqual/fail.snap.txt | 6 + .../NotEqual/fail_context_format.snap.txt | 8 + .../NotEqual/fail_with_context.snap.txt | 8 + .../NotEqual/fail_with_title.snap.txt | 6 + .../TestTest/NotEqualFunc/fail.snap.txt | 8 + .../NotEqualFunc/fail_context_format.snap.txt | 10 + .../NotEqualFunc/fail_with_context.snap.txt | 10 + .../NotEqualFunc/fail_with_title.snap.txt | 8 + testdata/snapshots/TestTest/Ok/fail.snap.txt | 6 + .../TestTest/Ok/fail_with_context.snap.txt | 8 + .../TestTest/Ok/fail_with_title.snap.txt | 6 + .../snapshots/TestTest/True/fail.snap.txt | 6 + .../TestTest/True/fail_with_context.snap.txt | 8 + .../TestTest/True/fail_with_title.snap.txt | 6 + .../TestTest/WantErr/fail_error.snap.txt | 8 + .../TestTest/WantErr/fail_nil.snap.txt | 8 + .../WantErr/fail_with_context.snap.txt | 10 + .../TestTest/WantErr/fail_with_title.snap.txt | 8 + 48 files changed, 1035 insertions(+), 689 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 config.go delete mode 100644 testdata/file.txt create mode 100644 testdata/snapshots/TestTest/Diff/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/DiffBytes/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/Equal/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/Equal/fail_context_format.snap.txt create mode 100644 testdata/snapshots/TestTest/Equal/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/Equal/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/EqualFunc/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/EqualFunc/fail_context_format.snap.txt create mode 100644 testdata/snapshots/TestTest/EqualFunc/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/EqualFunc/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/Err/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/Err/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/Err/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/False/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/False/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/False/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/NearlyEqual/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/NearlyEqual/fail_custom_tolerance.snap.txt create mode 100644 testdata/snapshots/TestTest/NearlyEqual/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqual/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqual/fail_context_format.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqual/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqual/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqualFunc/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqualFunc/fail_context_format.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqualFunc/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/NotEqualFunc/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/Ok/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/Ok/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/Ok/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/True/fail.snap.txt create mode 100644 testdata/snapshots/TestTest/True/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/True/fail_with_title.snap.txt create mode 100644 testdata/snapshots/TestTest/WantErr/fail_error.snap.txt create mode 100644 testdata/snapshots/TestTest/WantErr/fail_nil.snap.txt create mode 100644 testdata/snapshots/TestTest/WantErr/fail_with_context.snap.txt create mode 100644 testdata/snapshots/TestTest/WantErr/fail_with_title.snap.txt diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fd6f2c7..d76737b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,19 +5,19 @@ on: push: branches: - main - tags: - - v* concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true -permissions: read-all +permissions: {} jobs: test: name: Test runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: matrix: os: @@ -36,10 +36,14 @@ jobs: - name: Run Tests run: go test -race ./... + env: + NO_COLOR: true cov: name: CodeCov runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code @@ -52,16 +56,19 @@ jobs: - name: Run Tests run: go test -race -cover -covermode=atomic -coverprofile=./coverage.out ./... + env: + NO_COLOR: true - name: Coverage uses: codecov/codecov-action@v5 with: files: ./coverage.out - token: ${{ secrets.CODECOV_TOKEN }} lint: name: Lint runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code @@ -72,46 +79,41 @@ jobs: with: go-version-file: go.mod - - name: Clean Mod Cache # See https://github.com/golangci/golangci-lint-action/issues/135 - run: go clean -modcache - - name: Run Linting uses: golangci/golangci-lint-action@v6 with: version: latest - release: - name: Release + vulncheck: + name: Vulncheck runs-on: ubuntu-latest permissions: - contents: write - - needs: - - test - - cov - - lint - - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + contents: read steps: - name: Checkout Code uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 with: - fetch-depth: 0 + go-version-file: go.mod + + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@latest - - name: Fetch Existing Tags - run: git fetch --force --tags + - name: Run govulncheck + run: govulncheck ./... - - name: Parse Release Version - id: version - run: | - VERSION=${GITHUB_REF#refs/tags/v} - echo "version=$VERSION" >> $GITHUB_OUTPUT + typos: + name: Typos + runs-on: ubuntu-latest + permissions: + contents: read - - name: Publish Draft Release - uses: release-drafter/release-drafter@v6 - with: - version: ${{ steps.version.outputs.version }} - publish: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Check for Typos + uses: crate-ci/typos@v1.29.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..679fe4d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: {} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch Existing Tags + run: git fetch --force --tags + + - name: Parse Release Version + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Publish Draft Release + uses: release-drafter/release-drafter@v6 + with: + version: ${{ steps.version.outputs.version }} + publish: true + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.golangci.yml b/.golangci.yml index 5218106..a51da54 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -36,6 +36,13 @@ linters: - unused - whitespace +issues: + exclude-rules: + - path: test_test.go + linters: + - thelper # The entire package is effectively a t.Helper + - goconst # Lots of repetition in here + linters-settings: errcheck: check-type-assertions: true diff --git a/README.md b/README.md index 44067dd..1ab9c73 100644 --- a/README.md +++ b/README.md @@ -42,46 +42,38 @@ func TestSomething(t *testing.T) { test.True(t, true) // Passes test.False(t, true) // Fails - - // Get $CWD/testdata easily - test.Data(t) // /Users/you/project/package/testdata - - // Check against contents of a file including line ending normalisation - file := filepath.Join(test.Data(t), "expected.txt") - test.File(t, "hello\n", file) - - // Just like the good old reflect.DeepEqual, but with a nicer format - test.DeepEqual(t, []string{"hello"}, []string{"world"}) // Fails } ``` -### Self Documenting Tests - -> [!TIP] -> Line comments on the line you call most `test` functions on will be shown in failure messages as additional context +### Add Additional Context -That means you can have additional context in the failure message, as well as helpful comments explaining the assertion to readers of your code +`test` provides a number of options to decorate your test log with useful context: ```go -func TestSomething(t *testing.T) { - test.Equal(t, "apples", "oranges") // Fruits are not equal +func TestDetail(t *testing.T) { + test.Equal(t, "apples", "oranges", test.Title("Fruit scramble!"), test.Context("Apples are not oranges!")) } ``` -Will get you a failure message like: +Will get you an error log in the test that looks like this... -```shell ---- FAIL: TestSomething (0.00s) - something_test.go:1: - Not Equal // Fruits are not equal - --------- +```plaintext +--- FAIL: TestDemo (0.00s) + test_test.go:501: + Fruit scramble! + --------------- + Got: apples Wanted: oranges + + (Apples are not oranges!) + +FAIL ``` ### Non Comparable Types -`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 +`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 `test.EqualFunc` and `test.NotEqualFunc` for those exact situations! 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,42 +90,15 @@ func TestNonComparableTypes(t *testing.T) { test.EqualFunc(t, a, b, sliceEqual) // Passes - // Can also use e.g. the new slices package - test.EqualFunc(t, a, b, slices.Equal[string]) // Also passes :) + // Can also use any function here + test.EqualFunc(t, a, b, slices.Equal) // Also passes :) - test.EqualFunc(t, a, c, slices.Equal[string]) // Fails + test.EqualFunc(t, a, c, slices.Equal) // Fails } ``` You can also use this same pattern for custom user defined types, structs etc. -### Rich Comparison - -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 -`test.Diff` function that produces a rich text diff for you on failure: - -```go -func TestDiff(t *testing.T) { - // Pretend these are very long, or are large structs - a := []string{"hello", "world"} - b := []string{"hello", "there"} - - test.Diff(t, a, b) -} -``` - -Will give you: - -```plain ---- FAIL: TestDiff (0.00s) - main_test.go:14: Mismatch (-want, +got): - []string{ - "hello", - - "there", - + "world", - } -``` - ### Table Driven Tests Table driven tests are great! But when you test errors too it can get a bit awkward, you have to do the `if (err != nil) != tt.wantErr` thing and I personally @@ -232,29 +197,6 @@ func TestOutput(t *testing.T) { 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. -### Golden Files - -`test` has great support for golden files: - -```go -func TestFile(t *testing.T) { - got := "some contents\n" - want := filepath.Join(test.Data(t), "golden.txt") - - test.File(t, got, want) -} -``` - -This will read the file, normalise line endings and then generate an output almost like a git diff: - -```patch ---- want -+++ got -@@ -1 +1 @@ --some file contents -+some contents -``` - ### Credits This package was created with [copier] and the [FollowTheProcess/go_copier] project template. diff --git a/Taskfile.yml b/Taskfile.yml index d940dc2..1515658 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -9,44 +9,74 @@ tasks: default: desc: List all available tasks silent: true - cmd: task --list + cmds: + - task --list tidy: desc: Tidy dependencies in go.mod and go.sum - cmd: go mod tidy + sources: + - "**/*.go" + - go.mod + - go.sum + cmds: + - go mod tidy fmt: desc: Run go fmt on all source files + sources: + - "**/*.go" preconditions: - sh: command -v golines - msg: golines not installed, see https://github.com/segmentio/golines + msg: golines not installed, run `go install github.com/segmentio/golines@latest` cmds: - go fmt ./... - - golines . --chain-split-dots --ignore-generated --write-output + - golines . --ignore-generated --write-output --max-len 120 test: desc: Run the test suite - cmd: go test -race ./... {{ .CLI_ARGS }} + sources: + - "**/*.go" + - testdata/**/*.snap.txt + cmds: + - go test -race ./... {{ .CLI_ARGS }} + env: + NO_COLOR: true bench: desc: Run all project benchmarks - cmd: go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }} + sources: + - "**/*.go" + cmds: + - go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }} lint: desc: Run the linters and auto-fix if possible - cmd: golangci-lint run --fix + sources: + - "**/*.go" + - .golangci.yml deps: - fmt preconditions: - sh: command -v golangci-lint msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation + - sh: command -v betteralign + msg: requires betteralign, run `go install github.com/dkorunic/betteralign/cmd/betteralign@latest` + + - sh: command -v typos + msg: requires typos-cli, run `brew install typos-cli` + cmds: + - betteralign -test_files -apply ./... + - golangci-lint run --fix + - typos + doc: desc: Render the pkg docs locally - cmd: pkgsite -open preconditions: - sh: command -v pkgsite - msg: pkgsite not installed, run go install golang.org/x/pkgsite/cmd/pkgsite@latest + msg: pkgsite not installed, run `go install golang.org/x/pkgsite/cmd/pkgsite@latest` + cmds: + - pkgsite -open cov: desc: Calculate test coverage and render the html @@ -64,10 +94,17 @@ tasks: sloc: desc: Print lines of code - cmd: fd . -e go | xargs wc -l | sort -nr | head + cmds: + - fd . -e go | xargs wc -l | sort -nr | head clean: desc: Remove build artifacts and other clutter cmds: - go clean ./... - rm -rf {{ .COV_DATA }} + + update: + desc: Updates dependencies in go.mod and go.sum + cmds: + - go get -u ./... + - go mod tidy diff --git a/config.go b/config.go new file mode 100644 index 0000000..b073fe3 --- /dev/null +++ b/config.go @@ -0,0 +1,142 @@ +package test + +import ( + "errors" + "fmt" + "math" + "strings" +) + +const ( + defaultFloatEqualityThreshold = 1e-8 +) + +// config holds test-specific configuration including additional context +// and how the caller wants this library to behave. +type config struct { + title string // Title of the test, shown as a header in the failure log + context string // Additional context passed by the caller + reason string // Concise reason why the test has failed, only used sparingly and not in a user option + floatEqualityThreshold float64 // The difference threshold below which two floats are considered equal +} + +// defaultConfig returns a default configuration. +func defaultConfig() config { + return config{ + floatEqualityThreshold: defaultFloatEqualityThreshold, + } +} + +// failure represents a test failure, including any set config. +type failure[T any] struct { + got T // The actual value + want T // Expected value + cfg config // Test config +} + +// String implements [fmt.Stringer] for failure, allowing it to print itself in the test log. +func (f failure[T]) String() string { + s := &strings.Builder{} + s.WriteByte('\n') + + s.WriteString(f.cfg.title) + s.WriteByte('\n') + s.WriteString(strings.Repeat("-", len(f.cfg.title))) + s.WriteString("\n\n") + + fmt.Fprintf(s, "Got:\t%+v\n", f.got) + fmt.Fprintf(s, "Wanted:\t%+v\n", f.want) + + if f.cfg.context != "" { + fmt.Fprintf(s, "\n(%s)\n", f.cfg.context) + } + + if f.cfg.reason != "" { + fmt.Fprintf(s, "\nBecause: %s\n", f.cfg.reason) + } + + return s.String() +} + +// Option is a configuration option for a test. +type Option interface { + // Apply the option to the test config, returning an error if the option + // cannot be applied for whatever reason. + apply(*config) error +} + +// option is a function adapter implementing the Option interface, analogous +// to how http.HandlerFunc implements the Handler interface. +type option func(*config) error + +// apply applies the option, implementing the Option interface for the option +// function adapter. +func (o option) apply(cfg *config) error { + return o(cfg) +} + +// FloatEqualityThreshold is an [Option] to set the maximum difference allowed between +// two floating point numbers before they are considered equal. This setting is only +// used in [NearlyEqual] and [NotNearlyEqual]. +// +// Setting threshold to ±math.Inf is an error and will fail the test. +// +// The default is 1e-8, a sensible default for most cases. +func FloatEqualityThreshold(threshold float64) Option { + f := func(cfg *config) error { + if math.IsInf(threshold, 0) { + return errors.New("cannot set floating point equality threshold to ±infinity") + } + cfg.floatEqualityThreshold = threshold + return nil + } + return option(f) +} + +// Title is an [Option] that sets the title of the test in the test failure log. +// +// The title is shown as an underlined header in the test failure, below which the +// actual and expected values will be shown. +// +// By default this will be named sensibly after the test function being called, for +// example [Equal] has a default title "Not Equal". +// +// Setting title explicitly to the empty string "" is an error and will fail the test. +// +// test.Equal(t, "apples", "oranges", test.Title("Wrong fruits!")) +func Title(title string) Option { + f := func(cfg *config) error { + if title == "" { + return errors.New("cannot set title to an empty string") + } + cfg.title = strings.TrimSpace(title) + return nil + } + return option(f) +} + +// Context is an [Option] that allows the caller to inject useful contextual information +// as to why the test failed. This can be a useful addition to the test failure output log. +// +// The signature of context allows the use of fmt print verbs to format the message in the +// same way one might use [fmt.Sprintf]. +// +// It is not necessary to include a newline character at the end of format. +// +// Setting context explicitly to the empty string "" is an error and will fail the test. +// +// For example: +// +// err := doSomethingComplicated() +// test.Ok(t, err, test.Context("something complicated failed")) +func Context(format string, args ...any) Option { + f := func(cfg *config) error { + if format == "" { + return errors.New("cannot set context to an empty string") + } + context := fmt.Sprintf(format, args...) + cfg.context = strings.TrimSpace(context) + return nil + } + return option(f) +} diff --git a/go.mod b/go.mod index c908a4a..f097469 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module github.com/FollowTheProcess/test go 1.23 -require github.com/google/go-cmp v0.6.0 - -require golang.org/x/tools v0.28.0 +require ( + github.com/FollowTheProcess/snapshot v0.1.0 + golang.org/x/tools v0.28.0 +) diff --git a/go.sum b/go.sum index 74ee480..e5ab282 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/FollowTheProcess/snapshot v0.1.0 h1:G5tWpBXVre6cTrmN1OWZLiTnqEi9IKLiewtQQYOnQqc= +github.com/FollowTheProcess/snapshot v0.1.0/go.mod h1:sJv2oq83QK5Yj6+JVts8fQIaAJjQfsxN/WtGqX8oKIg= golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= diff --git a/test.go b/test.go index 1a5a481..c8cd56d 100644 --- a/test.go +++ b/test.go @@ -1,181 +1,200 @@ -// Package test provides a lightweight, but useful extension to the std lib's testing package -// with a friendlier and more intuitive API. +// Package test provides a lightweight, but useful extension to the std lib's testing package with +// a friendlier and more intuitive API. +// +// Simple tests become trivial and test provides mechanisms for adding useful context to test failures. package test import ( - "bufio" "bytes" "errors" "fmt" "io" "math" "os" - "path/filepath" - "reflect" - "runtime" "strings" "sync" "testing" "github.com/FollowTheProcess/test/internal/colour" "github.com/FollowTheProcess/test/internal/diff" - "github.com/google/go-cmp/cmp" ) -// floatEqualityThreshold allows us to do near-equality checks for floats. -const floatEqualityThreshold = 1e-8 - -// failure represents a test failure, including context and reason. -type failure[T any] struct { - got T // What we got - want T // Expected value - title string // Title of the failure, used as a header - reason string // Optional reason for additional context - comment string // Optional line comment for context -} - -// String prints a failure. -func (f failure[T]) String() string { - var msg string - if f.comment != "" { - msg = fmt.Sprintf( - "\n%s // %s\n%s\nGot:\t%+v\nWanted:\t%+v\n", - f.title, - f.comment, - strings.Repeat("-", len(f.title)), - f.got, - f.want, - ) - } else { - msg = fmt.Sprintf( - "\n%s\n%s\nGot:\t%+v\nWanted:\t%+v\n", - f.title, - strings.Repeat("-", len(f.title)), - f.got, - f.want, - ) - } - - if f.reason != "" { - // Bolt the reason on the end - msg = fmt.Sprintf("%s\n%s\n", msg, f.reason) - } - - return msg -} - // Equal fails if got != want. // // test.Equal(t, "apples", "apples") // Passes // test.Equal(t, "apples", "oranges") // Fails -func Equal[T comparable](tb testing.TB, got, want T) { +func Equal[T comparable](tb testing.TB, got, want T, options ...Option) { tb.Helper() + cfg := defaultConfig() + cfg.title = "Not Equal" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("Equal: could not apply options: %v", err) + return + } + } + if got != want { fail := failure[T]{ - got: got, - want: want, - title: "Not Equal", - comment: getComment(), + got: got, + want: want, + cfg: cfg, } tb.Fatal(fail.String()) } } -// NearlyEqual is like Equal but for floating point numbers where typically equality often fails. -// -// If the difference between got and want is sufficiently small, they are considered equal. +// NotEqual is the opposite of [Equal], it fails if got == want. // -// test.NearlyEqual(t, 3.0000000001, 3.0) // Passes, close enough to be considered equal -// test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different -func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T) { +// test.NotEqual(t, 10, 42) // Passes +// test.NotEqual(t, 42, 42) // Fails +func NotEqual[T comparable](tb testing.TB, got, want T, options ...Option) { tb.Helper() - diff := math.Abs(float64(got - want)) - if diff >= floatEqualityThreshold { + cfg := defaultConfig() + cfg.title = "Equal" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("NotEqual: could not apply options: %v", err) + return + } + } + + if got == want { fail := failure[T]{ - got: got, - want: want, - title: "Not NearlyEqual", - reason: fmt.Sprintf( - "Difference %v exceeds maximum tolerance of %v", - diff, - floatEqualityThreshold, - ), - comment: getComment(), + got: got, + want: want, + cfg: cfg, } tb.Fatal(fail.String()) } } -// EqualFunc is like Equal but allows the user to pass a custom comparator, useful -// when the items to be compared do not implement the comparable generic constraint +// EqualFunc is like [Equal] but accepts a custom comparator function, useful +// when the items to be compared do not implement the comparable generic constraint. +// +// The signature of the comparator is such that standard library functions such as +// [slices.Equal] or [maps.Equal] can be used. // // The comparator should return true if the two items should be considered equal. -func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) { +// +// test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Passes +// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Fails +func EqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { tb.Helper() + cfg := defaultConfig() + cfg.title = "Not Equal" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("EqualFunc: could not apply options: %v", err) + return + } + } + if !equal(got, want) { + cfg.reason = "equal(got, want) returned false" fail := failure[T]{ - got: got, - want: want, - title: "Not Equal", - reason: "equal(got, want) returned false", - comment: getComment(), + got: got, + want: want, + cfg: cfg, } tb.Fatal(fail.String()) } } -// NotEqual fails if got == want. +// NotEqualFunc is like [Equal] but accepts a custom comparator function, useful +// when the items to be compared do not implement the comparable generic constraint. // -// test.NotEqual(t, "apples", "oranges") // Passes -// test.NotEqual(t, "apples", "apples") // Fails -func NotEqual[T comparable](tb testing.TB, got, want T) { +// The signature of the comparator is such that standard library functions such as +// [slices.Equal] or [maps.Equal] can be used. +// +// The comparator should return true if the two items should be considered equal. +// +// test.EqualFunc(t, []int{1, 2, 3}, []int{1, 2, 3}, slices.Equal) // Fails +// test.EqualFunc(t, []int{1, 2, 3}, []int{4, 5, 6}, slices.Equal) // Passes +func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool, options ...Option) { tb.Helper() - if got == want { - if comment := getComment(); comment != "" { - tb.Fatalf( - "\nEqual // %s\n%s\nGot:\t%+v\n\nExpected values to be different\n", - comment, - strings.Repeat("-", len("Equal")), - got, - ) - } else { - tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nExpected values to be different\n", strings.Repeat("-", len("Equal")), got) + cfg := defaultConfig() + cfg.title = "Equal" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("NotEqualFunc: could not apply options: %v", err) + return } } + + if equal(got, want) { + cfg.reason = "equal(got, want) returned true" + fail := failure[T]{ + got: got, + want: want, + cfg: cfg, + } + tb.Fatal(fail.String()) + } } -// NotEqualFunc is like NotEqual but allows the user to pass a custom comparator, useful -// when the items to be compared do not implement the comparable generic constraint +// NearlyEqual is like [Equal] but for floating point numbers where absolute equality often fails. // -// The comparator should return true if the two items should be considered equal. -func NotEqualFunc[T any](tb testing.TB, got, want T, equal func(a, b T) bool) { +// If the difference between got and want is sufficiently small, they are considered equal. This threshold +// defaults to 1e-8 but can be configured with the [FloatEqualityThreshold] option. +// +// test.NearlyEqual(t, 3.0000000001, 3.0) // Passes, close enough to be considered equal +// test.NearlyEqual(t, 3.0000001, 3.0) // Fails, too different +func NearlyEqual[T ~float32 | ~float64](tb testing.TB, got, want T, options ...Option) { tb.Helper() - if equal(got, want) { - if comment := getComment(); comment != "" { - tb.Fatalf( - "\nEqual // %s\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n", - comment, - strings.Repeat("-", len("Equal")), - got, - ) - } else { - tb.Fatalf("\nEqual\n%s\nGot:\t%+v\n\nequal(got, want) returned true\n", strings.Repeat("-", len("Equal")), got) + cfg := defaultConfig() + cfg.title = "Not NearlyEqual" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("NearlyEqual: could not apply options: %v", err) + return } } + + diff := math.Abs(float64(got - want)) + if diff > cfg.floatEqualityThreshold { + cfg.reason = fmt.Sprintf( + "Difference %v - %v = %v exceeds maximum tolerance of %v", + got, + want, + diff, + cfg.floatEqualityThreshold, + ) + fail := failure[T]{ + got: got, + want: want, + cfg: cfg, + } + tb.Fatal(fail.String()) + } } // Ok fails if err != nil. // // err := doSomething() // test.Ok(t, err) -func Ok(tb testing.TB, err error) { +func Ok(tb testing.TB, err error, options ...Option) { tb.Helper() + cfg := defaultConfig() + cfg.title = "Not Ok" + + for _, option := range options { + if optionErr := option.apply(&cfg); optionErr != nil { + tb.Fatalf("Ok: could not apply options: %v", optionErr) + return + } + } + if err != nil { fail := failure[error]{ - got: err, - want: nil, - title: "Not Ok", - comment: getComment(), + got: err, + want: nil, + cfg: cfg, } tb.Fatal(fail.String()) } @@ -183,36 +202,57 @@ func Ok(tb testing.TB, err error) { // Err fails if err == nil. // -// err := shouldReturnErr() +// err := shouldFail() // test.Err(t, err) -func Err(tb testing.TB, err error) { +func Err(tb testing.TB, err error, options ...Option) { tb.Helper() + cfg := defaultConfig() + cfg.title = "Not Err" + + for _, option := range options { + if optionErr := option.apply(&cfg); optionErr != nil { + tb.Fatalf("Err: could not apply options: %v", optionErr) + return + } + } + if err == nil { fail := failure[error]{ - got: nil, - want: errors.New("error"), - title: "Not Err", - comment: getComment(), + got: nil, + want: errors.New("error"), + cfg: cfg, } tb.Fatal(fail.String()) } } -// WantErr fails if you got an error and didn't want it, or if you -// didn't get an error but wanted one. +// WantErr fails if you got an error and didn't want it, or if you didn't +// get an error but wanted one. // -// It simplifies checking for errors in table driven tests where on any -// iteration err may or may not be nil. +// It greatly simplifies checking for errors in table driven tests where an error +// may or may not be nil on any given test case. // // test.WantErr(t, errors.New("uh oh"), true) // Passes, got error when we wanted one // test.WantErr(t, errors.New("uh oh"), false) // Fails, got error but didn't want one // test.WantErr(t, nil, true) // Fails, wanted an error but didn't get one // test.WantErr(t, nil, false) // Passes, didn't want an error and didn't get one -func WantErr(tb testing.TB, err error, want bool) { +func WantErr(tb testing.TB, err error, want bool, options ...Option) { tb.Helper() + cfg := defaultConfig() + cfg.title = "WantErr" + + for _, option := range options { + if optionErr := option.apply(&cfg); optionErr != nil { + tb.Fatalf("WantErr: could not apply options: %v", optionErr) + return + } + } + if (err != nil) != want { - var reason string - var wanted error + var ( + reason string + wanted error + ) if want { reason = fmt.Sprintf("Wanted an error but got %v", err) wanted = errors.New("error") @@ -220,126 +260,89 @@ func WantErr(tb testing.TB, err error, want bool) { reason = fmt.Sprintf("Got an unexpected error: %v", err) wanted = nil } - fail := failure[any]{ - got: err, - want: wanted, - title: "WantErr", - reason: reason, - comment: getComment(), + cfg.reason = reason + fail := failure[error]{ + got: err, + want: wanted, + cfg: cfg, } tb.Fatal(fail.String()) } } -// True fails if v is false. +// True fails if got is false. // // test.True(t, true) // Passes // test.True(t, false) // Fails -func True(tb testing.TB, v bool) { +func True(tb testing.TB, got bool, options ...Option) { tb.Helper() - if !v { + cfg := defaultConfig() + cfg.title = "Not True" + + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("True: could not apply options: %v", err) + return + } + } + + if !got { fail := failure[bool]{ - got: v, - want: true, - title: "Not True", - comment: getComment(), + got: got, + want: true, + cfg: cfg, } tb.Fatal(fail.String()) } } -// False fails if v is true. +// False fails if got is true. // // test.False(t, false) // Passes // test.False(t, true) // Fails -func False(tb testing.TB, v bool) { +func False(tb testing.TB, got bool, options ...Option) { tb.Helper() - if v { - fail := failure[bool]{ - got: v, - want: false, - title: "Not False", - comment: getComment(), - } - tb.Fatal(fail.String()) - } -} + cfg := defaultConfig() + cfg.title = "Not False" -// Diff fails if got != want and provides a rich diff. -func Diff(tb testing.TB, got, want any) { - // TODO: Nicer output for diff, don't like the +got -want thing - tb.Helper() - if diff := cmp.Diff(want, got); diff != "" { - tb.Fatalf("\nMismatch (-want, +got):\n%s\n", diff) + for _, option := range options { + if err := option.apply(&cfg); err != nil { + tb.Fatalf("False: could not apply options: %v", err) + return + } } -} -// DeepEqual fails if reflect.DeepEqual(got, want) == false. -func DeepEqual(tb testing.TB, got, want any) { - tb.Helper() - if !reflect.DeepEqual(got, want) { - fail := failure[any]{ - got: got, - want: want, - title: "Not Equal", - reason: "reflect.DeepEqual(got, want) returned false", - comment: getComment(), + if got { + fail := failure[bool]{ + got: got, + want: false, + cfg: cfg, } tb.Fatal(fail.String()) } } -// Data returns the filepath to the testdata directory for the current package. -// -// When running tests, Go will change the cwd to the directory of the package under test. This means -// that reference data stored in $CWD/testdata can be easily retrieved in the same way for any package. -// -// The $CWD/testdata directory is a Go idiom, common practice, and is completely ignored by the go tool. -// -// Data makes no guarantee that $CWD/testdata exists, it simply returns it's path. -// -// file := filepath.Join(test.Data(t), "test.txt") -func Data(tb testing.TB) string { +// Diff fails if the two strings got and want are not equal and provides a rich +// unified diff of the two for easy comparison. +func Diff(tb testing.TB, got, want string) { tb.Helper() - cwd, err := os.Getwd() - if err != nil { - tb.Fatalf("could not get $CWD: %v", err) - } - return filepath.Join(cwd, "testdata") + if diff := diff.Diff("want", []byte(want), "got", []byte(got)); diff != nil { + tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff))) + } } -// File fails if got does not match the contents of the given file. -// -// It takes a string and the path of a file to compare, use [Data] to obtain -// the path to the current packages testdata directory. -// -// If the contents differ, the test will fail with output similar to executing git diff -// on the contents. -// -// Files with differing line endings (e.g windows CR LF \r\n vs unix LF \n) will be normalised to -// \n prior to comparison so this function will behave identically across multiple platforms. -// -// test.File(t, "hello\n", "expected.txt") -func File(tb testing.TB, got, file string) { +// DiffBytes fails if the two []byte got and want are not equal and provides a rich +// unified diff of the two for easy comparison. +func DiffBytes(tb testing.TB, got, want []byte) { tb.Helper() - f, err := filepath.Abs(file) - if err != nil { - tb.Fatalf("could not make %s absolute: %v", file, err) - } - contents, err := os.ReadFile(f) - if err != nil { - tb.Fatalf("could not read %s: %v", f, err) - } - contents = bytes.ReplaceAll(contents, []byte("\r\n"), []byte("\n")) - - if diff := diff.Diff(f, contents, "got", []byte(got)); diff != nil { - tb.Fatalf("\nMismatch\n--------\n%s\n", prettyDiff(string(diff))) + if diff := diff.Diff("want", want, "got", got); diff != nil { + tb.Fatalf("\nDiff\n----\n%s\n", prettyDiff(string(diff))) } } -// CaptureOutput captures and returns data printed to stdout and stderr by the provided function fn, allowing +// CaptureOutput captures and returns data printed to [os.Stdout] and [os.Stderr] by the provided function fn, allowing // you to test functions that write to those streams and do not have an option to pass in an [io.Writer]. // // If the provided function returns a non nil error, the test is failed with the error logged as the reason. @@ -429,46 +432,6 @@ func CaptureOutput(tb testing.TB, fn func() error) (stdout, stderr string) { return capturedStdout, capturedStderr } -// getComment loads a Go line comment from a line where a test function has been called. -// -// If any error happens or there is no comment, an empty string is returned so as not -// to influence the test with an unrelated error. -func getComment() string { - skip := 2 // Skip 2 frames, one for this function, the other for the calling test function - _, file, line, ok := runtime.Caller(skip) - if !ok { - return "" - } - - f, err := os.Open(file) - if err != nil { - return "" - } - defer f.Close() - - currentLine := 1 // Line numbers in source files start from 1 - scanner := bufio.NewScanner(f) - for scanner.Scan() { - // Skip through until we get to the line returned from runtime.Caller - if currentLine != line { - currentLine++ - continue - } - - _, comment, ok := strings.Cut(scanner.Text(), "//") - if !ok { - // There was no comment on this line - return "" - } - - // Now comment will be everything from the "//" until the end of the line - return strings.TrimSpace(comment) - } - - // Didn't find one - return "" -} - // prettyDiff takes a string diff in unified diff format and colourises it for easier viewing. func prettyDiff(diff string) string { lines := strings.Split(diff, "\n") diff --git a/test_test.go b/test_test.go index 0191b6e..b7c0c71 100644 --- a/test_test.go +++ b/test_test.go @@ -3,16 +3,19 @@ package test_test import ( "bytes" "errors" + "flag" "fmt" "io" "os" - "path/filepath" + "slices" "testing" + "github.com/FollowTheProcess/snapshot" "github.com/FollowTheProcess/test" - "github.com/google/go-cmp/cmp" ) +var update = flag.Bool("update", false, "Update snapshots") + // TB is a fake implementation of [testing.TB] that simply records in internal // state whether or not it would have failed and what it would have written. type TB struct { @@ -33,463 +36,397 @@ func (t *TB) Fatalf(format string, args ...any) { fmt.Fprintf(t.out, format, args...) } -func TestPassFail(t *testing.T) { +func TestTest(t *testing.T) { tests := []struct { - testFunc func(tb testing.TB) // The test function we're... testing - wantOut string // What we wanted the TB to print + fn func(tb testing.TB) // The test function we're... testing? name string // Name of the test case - wantFail bool // Whether we wanted the testFunc to fail it's TB + wantFail bool // Whether it should fail }{ { - name: "equal string pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Equal(tb, "apples", "apples") // These obviously are equal + name: "Equal/pass", + fn: func(tb testing.TB) { + test.Equal(tb, "apples", "apples") }, - wantFail: false, // Should pass - wantOut: "", // And write no output + wantFail: false, }, { - name: "equal string fail", - testFunc: func(tb testing.TB) { - tb.Helper() + name: "Equal/fail", + fn: func(tb testing.TB) { test.Equal(tb, "apples", "oranges") }, wantFail: true, - wantOut: "\nNot Equal\n---------\nGot:\tapples\nWanted:\toranges\n", }, { - name: "equal string fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Equal(tb, "apples", "oranges") // apples are not oranges + name: "Equal/fail with context", + fn: func(tb testing.TB) { + test.Equal(tb, "apples", "oranges", test.Context("Apples are not oranges!")) }, wantFail: true, - wantOut: "\nNot Equal // apples are not oranges\n---------\nGot:\tapples\nWanted:\toranges\n", }, { - name: "equal int pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Equal(tb, 1, 1) + name: "Equal/fail context format", + fn: func(tb testing.TB) { + test.Equal(tb, "apples", "oranges", test.Context("Apples == Oranges: %v", false)) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "equal int fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Equal(tb, 1, 42) + name: "Equal/fail with title", + fn: func(tb testing.TB) { + test.Equal(tb, "apples", "oranges", test.Title("My fruit test")) }, wantFail: true, - wantOut: "\nNot Equal\n---------\nGot:\t1\nWanted:\t42\n", }, { - name: "nearly equal pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NearlyEqual(tb, 3.0000000001, 3.0) + name: "NotEqual/pass", + fn: func(tb testing.TB) { + test.NotEqual(tb, "apples", "oranges") }, wantFail: false, - wantOut: "", }, { - name: "nearly equal fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NearlyEqual(tb, 3.0000001, 3.0) + name: "NotEqual/fail", + fn: func(tb testing.TB) { + test.NotEqual(tb, "apples", "apples") }, wantFail: true, - wantOut: "\nNot NearlyEqual\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n", }, { - name: "nearly equal fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NearlyEqual(tb, 3.0000001, 3.0) // Ooof so close + name: "NotEqual/fail with context", + fn: func(tb testing.TB) { + test.NotEqual(tb, 42, 42, test.Context("42 is the meaning of life")) }, wantFail: true, - wantOut: "\nNot NearlyEqual // Ooof so close\n---------------\nGot:\t3.0000001\nWanted:\t3\n\nDifference 9.999999983634211e-08 exceeds maximum tolerance of 1e-08\n", }, { - name: "not equal string pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, "apples", "oranges") // Should pass, these aren't equal + name: "NotEqual/fail context format", + fn: func(tb testing.TB) { + test.NotEqual(tb, 42, 42, test.Context("42 == meaning of life: %v", true)) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "not equal string fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, "apples", "apples") + name: "NotEqual/fail with title", + fn: func(tb testing.TB) { + test.NotEqual(tb, "apples", "apples", test.Title("My fruit test")) }, wantFail: true, - wantOut: "\nEqual\n-----\nGot:\tapples\n\nExpected values to be different\n", }, { - name: "not equal string fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, "apples", "apples") // different apples + name: "EqualFunc/pass", + fn: func(tb testing.TB) { + test.EqualFunc(tb, []int{1, 2, 3, 4}, []int{1, 2, 3, 4}, slices.Equal) + }, + wantFail: false, + }, + { + name: "EqualFunc/fail", + fn: func(tb testing.TB) { + cmp := func(a, b []string) bool { return false } // Cheating + test.EqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) }, wantFail: true, - wantOut: "\nEqual // different apples\n-----\nGot:\tapples\n\nExpected values to be different\n", }, { - name: "not equal int pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, 1, 42) + name: "EqualFunc/fail with context", + fn: func(tb testing.TB) { + test.EqualFunc( + tb, + []string{"hello"}, + []string{"there"}, + slices.Equal, + test.Context("some context here"), + ) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "not equal int fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, 1, 1) + name: "EqualFunc/fail context format", + fn: func(tb testing.TB) { + test.EqualFunc( + tb, + []string{"hello"}, + []string{"there"}, + slices.Equal, + test.Context("who's bad at testing... %s", "you"), + ) }, wantFail: true, - wantOut: "\nEqual\n-----\nGot:\t1\n\nExpected values to be different\n", }, { - name: "not equal int fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.NotEqual(tb, 1, 1) // 1 != 1? + name: "EqualFunc/fail with title", + fn: func(tb testing.TB) { + test.EqualFunc(tb, []string{"hello"}, []string{"there"}, slices.Equal, test.Title("Hello!")) }, wantFail: true, - wantOut: "\nEqual // 1 != 1?\n-----\nGot:\t1\n\nExpected values to be different\n", }, { - name: "ok pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Ok(tb, nil) + name: "NotEqualFunc/pass", + fn: func(tb testing.TB) { + test.NotEqualFunc(tb, []int{1, 2, 3, 4}, []int{5, 6, 7, 8}, slices.Equal) }, wantFail: false, - wantOut: "", }, { - name: "ok fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Ok(tb, errors.New("uh oh")) + name: "NotEqualFunc/fail", + fn: func(tb testing.TB) { + cmp := func(a, b []string) bool { return true } // Cheating + test.NotEqualFunc(tb, []string{"hello"}, []string{"there"}, cmp) }, wantFail: true, - wantOut: "\nNot Ok\n------\nGot:\tuh oh\nWanted:\t\n", }, { - name: "ok fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Ok(tb, errors.New("uh oh")) // Calling some function + name: "NotEqualFunc/fail with context", + fn: func(tb testing.TB) { + test.NotEqualFunc( + tb, + []string{"hello"}, + []string{"hello"}, + slices.Equal, + test.Context("some context here"), + ) }, wantFail: true, - wantOut: "\nNot Ok // Calling some function\n------\nGot:\tuh oh\nWanted:\t\n", }, { - name: "err pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Err(tb, errors.New("uh oh")) + name: "NotEqualFunc/fail context format", + fn: func(tb testing.TB) { + test.NotEqualFunc( + tb, + []string{"hello"}, + []string{"hello"}, + slices.Equal, + test.Context("who's bad at testing... %s", "you"), + ) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "err fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Err(tb, nil) + name: "NotEqualFunc/fail with title", + fn: func(tb testing.TB) { + test.NotEqualFunc(tb, []string{"hello"}, []string{"hello"}, slices.Equal, test.Title("Hello!")) }, wantFail: true, - wantOut: "\nNot Err\n-------\nGot:\t\nWanted:\terror\n", }, { - name: "err fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Err(tb, nil) // Should have failed + name: "NearlyEqual/pass", + fn: func(tb testing.TB) { + test.NearlyEqual(tb, 3.0000000001, 3.0) }, - wantFail: true, - wantOut: "\nNot Err // Should have failed\n-------\nGot:\t\nWanted:\terror\n", + wantFail: false, }, { - name: "true pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.True(tb, true) + name: "NearlyEqual/fail", + fn: func(tb testing.TB) { + test.NearlyEqual(tb, 3.0000001, 3.0) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "true fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.True(tb, false) + name: "NearlyEqual/fail custom tolerance", + fn: func(tb testing.TB) { + test.NearlyEqual(tb, 3.2, 3.0, test.FloatEqualityThreshold(0.1)) }, wantFail: true, - wantOut: "\nNot True\n--------\nGot:\tfalse\nWanted:\ttrue\n", }, { - name: "true fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.True(tb, false) // Comment here + name: "NearlyEqual/fail with context", + fn: func(tb testing.TB) { + test.NearlyEqual(tb, 3.0000001, 3.0, test.Context("Numbers don't work that way")) }, wantFail: true, - wantOut: "\nNot True // Comment here\n--------\nGot:\tfalse\nWanted:\ttrue\n", }, { - name: "false pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.False(tb, false) + name: "Ok/pass", + fn: func(tb testing.TB) { + test.Ok(tb, nil) }, wantFail: false, - wantOut: "", }, { - name: "false fail", - testFunc: func(tb testing.TB) { - tb.Helper() - test.False(tb, true) + name: "Ok/fail", + fn: func(tb testing.TB) { + test.Ok(tb, errors.New("uh oh")) }, wantFail: true, - wantOut: "\nNot False\n---------\nGot:\ttrue\nWanted:\tfalse\n", }, { - name: "false fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.False(tb, true) // Should always be false + name: "Ok/fail with context", + fn: func(tb testing.TB) { + test.Ok(tb, errors.New("uh oh"), test.Context("Could not frobnicate the baz")) }, wantFail: true, - wantOut: "\nNot False // Should always be false\n---------\nGot:\ttrue\nWanted:\tfalse\n", }, { - name: "equal func pass", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishEqual := func(a, b string) bool { - return true // Always equal - } - test.EqualFunc(tb, "word", "different word", rubbishEqual) + name: "Ok/fail with title", + fn: func(tb testing.TB) { + test.Ok(tb, errors.New("uh oh"), test.Title("Bang!")) + }, + wantFail: true, + }, + { + name: "Err/pass", + fn: func(tb testing.TB) { + test.Err(tb, errors.New("bang!")) }, wantFail: false, - wantOut: "", }, { - name: "equal func fail", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishEqual := func(a, b string) bool { - return false // Never equal - } - test.EqualFunc(tb, "word", "word", rubbishEqual) + name: "Err/fail", + fn: func(tb testing.TB) { + test.Err(tb, nil) }, wantFail: true, - wantOut: "\nNot Equal\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n", }, { - name: "equal func fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishEqual := func(a, b string) bool { - return false // Never equal - } - test.EqualFunc(tb, "word", "word", rubbishEqual) // Uh oh + name: "Err/fail with context", + fn: func(tb testing.TB) { + test.Err(tb, nil, test.Context("Frobnicated the baz when it should have failed")) }, wantFail: true, - wantOut: "\nNot Equal // Uh oh\n---------\nGot:\tword\nWanted:\tword\n\nequal(got, want) returned false\n", }, { - name: "not equal func pass", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishNotEqual := func(a, b string) bool { - return false // Never equal - } - test.NotEqualFunc(tb, "word", "word", rubbishNotEqual) + name: "Err/fail with title", + fn: func(tb testing.TB) { + test.Err(tb, nil, test.Title("Everything is fine?")) + }, + wantFail: true, + }, + { + name: "WantErr/pass error", + fn: func(tb testing.TB) { + test.WantErr(tb, errors.New("bang"), true) // Wanted an error and got one - should pass }, wantFail: false, - wantOut: "", }, { - name: "not equal func fail", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishNotEqual := func(a, b string) bool { - return true // Always equal - } - test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual) + name: "WantErr/pass nil", + fn: func(tb testing.TB) { + test.WantErr(tb, nil, false) // Didn't want an error and got nil - should pass }, - wantFail: true, - wantOut: "\nEqual\n-----\nGot:\tword\n\nequal(got, want) returned true\n", + wantFail: false, }, { - name: "not equal func fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - rubbishNotEqual := func(a, b string) bool { - return true // Always equal - } - test.NotEqualFunc(tb, "word", "different word", rubbishNotEqual) // Bad equal + name: "WantErr/fail error", + fn: func(tb testing.TB) { + test.WantErr(tb, errors.New("bang"), false) // Got an error but didn't want one - should fail }, wantFail: true, - wantOut: "\nEqual // Bad equal\n-----\nGot:\tword\n\nequal(got, want) returned true\n", }, { - name: "deep equal pass", - testFunc: func(tb testing.TB) { - tb.Helper() - a := []string{"a", "b", "c"} - b := []string{"a", "b", "c"} - - test.DeepEqual(tb, a, b) + name: "WantErr/fail nil", + fn: func(tb testing.TB) { + test.WantErr(tb, nil, true) // Didn't get an error but wanted one - should fail }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "deep equal fail", - testFunc: func(tb testing.TB) { - tb.Helper() - a := []string{"a", "b", "c"} - b := []string{"d", "e", "f"} - - test.DeepEqual(tb, a, b) + name: "WantErr/fail with context", + fn: func(tb testing.TB) { + test.WantErr(tb, errors.New("bang"), false, test.Context("Errors are bad!")) }, wantFail: true, - wantOut: "\nNot Equal\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n", }, { - name: "deep equal fail with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - a := []string{"a", "b", "c"} - b := []string{"d", "e", "f"} - - test.DeepEqual(tb, a, b) // Oh no! + name: "WantErr/fail with title", + fn: func(tb testing.TB) { + test.WantErr(tb, errors.New("bang"), false, test.Title("A very bad test")) }, wantFail: true, - wantOut: "\nNot Equal // Oh no!\n---------\nGot:\t[a b c]\nWanted:\t[d e f]\n\nreflect.DeepEqual(got, want) returned false\n", }, { - name: "want err pass when got and wanted", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, errors.New("uh oh"), true) // We wanted an error and got one + name: "True/pass", + fn: func(tb testing.TB) { + test.True(tb, true) }, wantFail: false, - wantOut: "", }, { - name: "want err fail when got and not wanted", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, errors.New("uh oh"), false) + name: "True/fail", + fn: func(tb testing.TB) { + test.True(tb, false) + }, + wantFail: true, + }, + { + name: "True/fail with context", + fn: func(tb testing.TB) { + test.True(tb, false, test.Context("must always be true")) }, wantFail: true, - wantOut: "\nWantErr\n-------\nGot:\tuh oh\nWanted:\t\n\nGot an unexpected error: uh oh\n", }, { - name: "want err fail when got and not wanted with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, errors.New("uh oh"), false) // comment + name: "True/fail with title", + fn: func(tb testing.TB) { + test.True(tb, false, test.Title("Argh!")) }, wantFail: true, - wantOut: "\nWantErr // comment\n-------\nGot:\tuh oh\nWanted:\t\n\nGot an unexpected error: uh oh\n", }, { - name: "want err pass when not got and not wanted", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, nil, false) // Didn't want an error and didn't get one + name: "False/pass", + fn: func(tb testing.TB) { + test.False(tb, false) }, wantFail: false, - wantOut: "", }, { - name: "want err fail when not got but wanted", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, nil, true) + name: "False/fail", + fn: func(tb testing.TB) { + test.False(tb, true) }, wantFail: true, - wantOut: "\nWantErr\n-------\nGot:\t\nWanted:\terror\n\nWanted an error but got \n", }, { - name: "want err fail when not got but wanted with comment", - testFunc: func(tb testing.TB) { - tb.Helper() - test.WantErr(tb, nil, true) // comment + name: "False/fail with context", + fn: func(tb testing.TB) { + test.False(tb, true, test.Context("must always be false")) }, wantFail: true, - wantOut: "\nWantErr // comment\n-------\nGot:\t\nWanted:\terror\n\nWanted an error but got \n", }, { - name: "file pass", - testFunc: func(tb testing.TB) { - tb.Helper() - test.File(tb, "hello\n", filepath.Join(test.Data(t), "file.txt")) + name: "False/fail with title", + fn: func(tb testing.TB) { + test.False(tb, true, test.Title("Argh!")) }, - wantFail: false, - wantOut: "", + wantFail: true, }, { - name: "diff pass string", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Diff(tb, "hello", "hello") + name: "Diff/pass", + fn: func(tb testing.TB) { + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" + want := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" + + test.Diff(tb, got, want) }, wantFail: false, - wantOut: "", }, { - name: "diff fail string", - testFunc: func(tb testing.TB) { - tb.Helper() - test.Diff(tb, "hello", "hello there") + name: "Diff/fail", + fn: func(tb testing.TB) { + got := "Some\nstuff here in this file\nlines as well wow\nsome more stuff\n" + want := "Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n" + test.Diff(tb, got, want) }, wantFail: true, - wantOut: fmt.Sprintf( - "\nMismatch (-want, +got):\n%s\n", - cmp.Diff("hello there", "hello"), - ), // Output equivalent to diff }, { - name: "diff pass string slice", - testFunc: func(tb testing.TB) { - tb.Helper() - got := []string{"hello", "there"} - want := []string{"hello", "there"} - test.Diff(tb, got, want) + name: "DiffBytes/pass", + fn: func(tb testing.TB) { + got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") + want := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") + + test.DiffBytes(tb, got, want) }, wantFail: false, - wantOut: "", }, { - name: "diff fail string slice", - testFunc: func(tb testing.TB) { - tb.Helper() - got := []string{"hello", "there"} - want := []string{"not", "me"} - test.Diff(tb, got, want) + name: "DiffBytes/fail", + fn: func(tb testing.TB) { + got := []byte("Some\nstuff here in this file\nlines as well wow\nsome more stuff\n") + want := []byte("Some\ndifferent stuff here in this file\nthis line is different\nsome more stuff\n") + test.DiffBytes(tb, got, want) }, wantFail: true, - wantOut: fmt.Sprintf( - "\nMismatch (-want, +got):\n%s\n", - cmp.Diff([]string{"not", "me"}, []string{"hello", "there"}), - ), // Output equivalent to diff }, } @@ -497,52 +434,33 @@ func TestPassFail(t *testing.T) { t.Run(tt.name, func(t *testing.T) { buf := &bytes.Buffer{} tb := &TB{out: buf} + snap := snapshot.New(t, snapshot.Update(*update)) if tb.failed { t.Fatalf("%s initial failed state should be false", tt.name) } - // Call the test function, passing in our mock TB that simply - // records whether or not it would have failed and what it would - // have written - tt.testFunc(tb) + // Call the test function, passing in the mock TB that just records + // what a "real" TB would have done + tt.fn(tb) if tb.failed != tt.wantFail { - t.Fatalf( - "\n%s failure mismatch\n--------------\nfailed:\t%v\nwanted failure:\t%v\n", - tt.name, - tb.failed, - tt.wantFail, - ) + t.Fatalf("\nIncorrect Failure\n\ntb.failed:\t%v\nwanted:\t%v\n", tb.failed, tt.wantFail) } - if got := buf.String(); got != tt.wantOut { - t.Errorf( - "\n%s output mismatch\n---------------\nGot:\t%s\nWanted:\t%s\n", - tt.name, - got, - tt.wantOut, - ) + // Test the output matches our snapshot file, only for failed tests + // as there should be no output for passed tests + if !tb.failed { + if buf.Len() != 0 { + t.Fatalf("\nIncorrect Output\n\nA passed test should have no output, got: %s\n", buf.String()) + } + } else { + snap.Snap(buf.String()) } }) } } -func TestData(t *testing.T) { - got := test.Data(t) - - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("Test for Data could not get cwd: %v", err) - } - - want := filepath.Join(cwd, "testdata") - - if got != want { - t.Errorf("\nGot:\t%s\nWanted:\t%s\n", got, want) - } -} - func TestCapture(t *testing.T) { t.Run("happy", func(t *testing.T) { // Some fake user function that writes to stdout and stderr diff --git a/testdata/file.txt b/testdata/file.txt deleted file mode 100644 index ce01362..0000000 --- a/testdata/file.txt +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/testdata/snapshots/TestTest/Diff/fail.snap.txt b/testdata/snapshots/TestTest/Diff/fail.snap.txt new file mode 100644 index 0000000..c9d3db8 --- /dev/null +++ b/testdata/snapshots/TestTest/Diff/fail.snap.txt @@ -0,0 +1,14 @@ + +Diff +---- +diff want got +--- want ++++ got +@@ -1,4 +1,4 @@ + Some +- different stuff here in this file +- this line is different ++ stuff here in this file ++ lines as well wow + some more stuff + diff --git a/testdata/snapshots/TestTest/DiffBytes/fail.snap.txt b/testdata/snapshots/TestTest/DiffBytes/fail.snap.txt new file mode 100644 index 0000000..c9d3db8 --- /dev/null +++ b/testdata/snapshots/TestTest/DiffBytes/fail.snap.txt @@ -0,0 +1,14 @@ + +Diff +---- +diff want got +--- want ++++ got +@@ -1,4 +1,4 @@ + Some +- different stuff here in this file +- this line is different ++ stuff here in this file ++ lines as well wow + some more stuff + diff --git a/testdata/snapshots/TestTest/Equal/fail.snap.txt b/testdata/snapshots/TestTest/Equal/fail.snap.txt new file mode 100644 index 0000000..1938712 --- /dev/null +++ b/testdata/snapshots/TestTest/Equal/fail.snap.txt @@ -0,0 +1,6 @@ + +Not Equal +--------- + +Got: apples +Wanted: oranges diff --git a/testdata/snapshots/TestTest/Equal/fail_context_format.snap.txt b/testdata/snapshots/TestTest/Equal/fail_context_format.snap.txt new file mode 100644 index 0000000..d3ef727 --- /dev/null +++ b/testdata/snapshots/TestTest/Equal/fail_context_format.snap.txt @@ -0,0 +1,8 @@ + +Not Equal +--------- + +Got: apples +Wanted: oranges + +(Apples == Oranges: false) diff --git a/testdata/snapshots/TestTest/Equal/fail_with_context.snap.txt b/testdata/snapshots/TestTest/Equal/fail_with_context.snap.txt new file mode 100644 index 0000000..4b8b4a2 --- /dev/null +++ b/testdata/snapshots/TestTest/Equal/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Not Equal +--------- + +Got: apples +Wanted: oranges + +(Apples are not oranges!) diff --git a/testdata/snapshots/TestTest/Equal/fail_with_title.snap.txt b/testdata/snapshots/TestTest/Equal/fail_with_title.snap.txt new file mode 100644 index 0000000..1b70abc --- /dev/null +++ b/testdata/snapshots/TestTest/Equal/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +My fruit test +------------- + +Got: apples +Wanted: oranges diff --git a/testdata/snapshots/TestTest/EqualFunc/fail.snap.txt b/testdata/snapshots/TestTest/EqualFunc/fail.snap.txt new file mode 100644 index 0000000..af2d1f4 --- /dev/null +++ b/testdata/snapshots/TestTest/EqualFunc/fail.snap.txt @@ -0,0 +1,8 @@ + +Not Equal +--------- + +Got: [hello] +Wanted: [there] + +Because: equal(got, want) returned false diff --git a/testdata/snapshots/TestTest/EqualFunc/fail_context_format.snap.txt b/testdata/snapshots/TestTest/EqualFunc/fail_context_format.snap.txt new file mode 100644 index 0000000..ffa504b --- /dev/null +++ b/testdata/snapshots/TestTest/EqualFunc/fail_context_format.snap.txt @@ -0,0 +1,10 @@ + +Not Equal +--------- + +Got: [hello] +Wanted: [there] + +(who's bad at testing... you) + +Because: equal(got, want) returned false diff --git a/testdata/snapshots/TestTest/EqualFunc/fail_with_context.snap.txt b/testdata/snapshots/TestTest/EqualFunc/fail_with_context.snap.txt new file mode 100644 index 0000000..c40eb0d --- /dev/null +++ b/testdata/snapshots/TestTest/EqualFunc/fail_with_context.snap.txt @@ -0,0 +1,10 @@ + +Not Equal +--------- + +Got: [hello] +Wanted: [there] + +(some context here) + +Because: equal(got, want) returned false diff --git a/testdata/snapshots/TestTest/EqualFunc/fail_with_title.snap.txt b/testdata/snapshots/TestTest/EqualFunc/fail_with_title.snap.txt new file mode 100644 index 0000000..985edae --- /dev/null +++ b/testdata/snapshots/TestTest/EqualFunc/fail_with_title.snap.txt @@ -0,0 +1,8 @@ + +Hello! +------ + +Got: [hello] +Wanted: [there] + +Because: equal(got, want) returned false diff --git a/testdata/snapshots/TestTest/Err/fail.snap.txt b/testdata/snapshots/TestTest/Err/fail.snap.txt new file mode 100644 index 0000000..3ccc892 --- /dev/null +++ b/testdata/snapshots/TestTest/Err/fail.snap.txt @@ -0,0 +1,6 @@ + +Not Err +------- + +Got: +Wanted: error diff --git a/testdata/snapshots/TestTest/Err/fail_with_context.snap.txt b/testdata/snapshots/TestTest/Err/fail_with_context.snap.txt new file mode 100644 index 0000000..a6c8fe9 --- /dev/null +++ b/testdata/snapshots/TestTest/Err/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Not Err +------- + +Got: +Wanted: error + +(Frobnicated the baz when it should have failed) diff --git a/testdata/snapshots/TestTest/Err/fail_with_title.snap.txt b/testdata/snapshots/TestTest/Err/fail_with_title.snap.txt new file mode 100644 index 0000000..b797d74 --- /dev/null +++ b/testdata/snapshots/TestTest/Err/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +Everything is fine? +------------------- + +Got: +Wanted: error diff --git a/testdata/snapshots/TestTest/False/fail.snap.txt b/testdata/snapshots/TestTest/False/fail.snap.txt new file mode 100644 index 0000000..90e8b08 --- /dev/null +++ b/testdata/snapshots/TestTest/False/fail.snap.txt @@ -0,0 +1,6 @@ + +Not False +--------- + +Got: true +Wanted: false diff --git a/testdata/snapshots/TestTest/False/fail_with_context.snap.txt b/testdata/snapshots/TestTest/False/fail_with_context.snap.txt new file mode 100644 index 0000000..273a7dc --- /dev/null +++ b/testdata/snapshots/TestTest/False/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Not False +--------- + +Got: true +Wanted: false + +(must always be false) diff --git a/testdata/snapshots/TestTest/False/fail_with_title.snap.txt b/testdata/snapshots/TestTest/False/fail_with_title.snap.txt new file mode 100644 index 0000000..1f40acf --- /dev/null +++ b/testdata/snapshots/TestTest/False/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +Argh! +----- + +Got: true +Wanted: false diff --git a/testdata/snapshots/TestTest/NearlyEqual/fail.snap.txt b/testdata/snapshots/TestTest/NearlyEqual/fail.snap.txt new file mode 100644 index 0000000..9e539da --- /dev/null +++ b/testdata/snapshots/TestTest/NearlyEqual/fail.snap.txt @@ -0,0 +1,8 @@ + +Not NearlyEqual +--------------- + +Got: 3.0000001 +Wanted: 3 + +Because: Difference 3.0000001 - 3 = 9.999999983634211e-08 exceeds maximum tolerance of 1e-08 diff --git a/testdata/snapshots/TestTest/NearlyEqual/fail_custom_tolerance.snap.txt b/testdata/snapshots/TestTest/NearlyEqual/fail_custom_tolerance.snap.txt new file mode 100644 index 0000000..970206d --- /dev/null +++ b/testdata/snapshots/TestTest/NearlyEqual/fail_custom_tolerance.snap.txt @@ -0,0 +1,8 @@ + +Not NearlyEqual +--------------- + +Got: 3.2 +Wanted: 3 + +Because: Difference 3.2 - 3 = 0.20000000000000018 exceeds maximum tolerance of 0.1 diff --git a/testdata/snapshots/TestTest/NearlyEqual/fail_with_context.snap.txt b/testdata/snapshots/TestTest/NearlyEqual/fail_with_context.snap.txt new file mode 100644 index 0000000..a77be34 --- /dev/null +++ b/testdata/snapshots/TestTest/NearlyEqual/fail_with_context.snap.txt @@ -0,0 +1,10 @@ + +Not NearlyEqual +--------------- + +Got: 3.0000001 +Wanted: 3 + +(Numbers don't work that way) + +Because: Difference 3.0000001 - 3 = 9.999999983634211e-08 exceeds maximum tolerance of 1e-08 diff --git a/testdata/snapshots/TestTest/NotEqual/fail.snap.txt b/testdata/snapshots/TestTest/NotEqual/fail.snap.txt new file mode 100644 index 0000000..f347e6f --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqual/fail.snap.txt @@ -0,0 +1,6 @@ + +Equal +----- + +Got: apples +Wanted: apples diff --git a/testdata/snapshots/TestTest/NotEqual/fail_context_format.snap.txt b/testdata/snapshots/TestTest/NotEqual/fail_context_format.snap.txt new file mode 100644 index 0000000..baffd4f --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqual/fail_context_format.snap.txt @@ -0,0 +1,8 @@ + +Equal +----- + +Got: 42 +Wanted: 42 + +(42 == meaning of life: true) diff --git a/testdata/snapshots/TestTest/NotEqual/fail_with_context.snap.txt b/testdata/snapshots/TestTest/NotEqual/fail_with_context.snap.txt new file mode 100644 index 0000000..a5fe020 --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqual/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Equal +----- + +Got: 42 +Wanted: 42 + +(42 is the meaning of life) diff --git a/testdata/snapshots/TestTest/NotEqual/fail_with_title.snap.txt b/testdata/snapshots/TestTest/NotEqual/fail_with_title.snap.txt new file mode 100644 index 0000000..77d1cef --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqual/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +My fruit test +------------- + +Got: apples +Wanted: apples diff --git a/testdata/snapshots/TestTest/NotEqualFunc/fail.snap.txt b/testdata/snapshots/TestTest/NotEqualFunc/fail.snap.txt new file mode 100644 index 0000000..4961510 --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqualFunc/fail.snap.txt @@ -0,0 +1,8 @@ + +Equal +----- + +Got: [hello] +Wanted: [there] + +Because: equal(got, want) returned true diff --git a/testdata/snapshots/TestTest/NotEqualFunc/fail_context_format.snap.txt b/testdata/snapshots/TestTest/NotEqualFunc/fail_context_format.snap.txt new file mode 100644 index 0000000..3b21659 --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqualFunc/fail_context_format.snap.txt @@ -0,0 +1,10 @@ + +Equal +----- + +Got: [hello] +Wanted: [hello] + +(who's bad at testing... you) + +Because: equal(got, want) returned true diff --git a/testdata/snapshots/TestTest/NotEqualFunc/fail_with_context.snap.txt b/testdata/snapshots/TestTest/NotEqualFunc/fail_with_context.snap.txt new file mode 100644 index 0000000..3cb8f5e --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqualFunc/fail_with_context.snap.txt @@ -0,0 +1,10 @@ + +Equal +----- + +Got: [hello] +Wanted: [hello] + +(some context here) + +Because: equal(got, want) returned true diff --git a/testdata/snapshots/TestTest/NotEqualFunc/fail_with_title.snap.txt b/testdata/snapshots/TestTest/NotEqualFunc/fail_with_title.snap.txt new file mode 100644 index 0000000..892edb2 --- /dev/null +++ b/testdata/snapshots/TestTest/NotEqualFunc/fail_with_title.snap.txt @@ -0,0 +1,8 @@ + +Hello! +------ + +Got: [hello] +Wanted: [hello] + +Because: equal(got, want) returned true diff --git a/testdata/snapshots/TestTest/Ok/fail.snap.txt b/testdata/snapshots/TestTest/Ok/fail.snap.txt new file mode 100644 index 0000000..9f57dde --- /dev/null +++ b/testdata/snapshots/TestTest/Ok/fail.snap.txt @@ -0,0 +1,6 @@ + +Not Ok +------ + +Got: uh oh +Wanted: diff --git a/testdata/snapshots/TestTest/Ok/fail_with_context.snap.txt b/testdata/snapshots/TestTest/Ok/fail_with_context.snap.txt new file mode 100644 index 0000000..b8a3b75 --- /dev/null +++ b/testdata/snapshots/TestTest/Ok/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Not Ok +------ + +Got: uh oh +Wanted: + +(Could not frobnicate the baz) diff --git a/testdata/snapshots/TestTest/Ok/fail_with_title.snap.txt b/testdata/snapshots/TestTest/Ok/fail_with_title.snap.txt new file mode 100644 index 0000000..062edad --- /dev/null +++ b/testdata/snapshots/TestTest/Ok/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +Bang! +----- + +Got: uh oh +Wanted: diff --git a/testdata/snapshots/TestTest/True/fail.snap.txt b/testdata/snapshots/TestTest/True/fail.snap.txt new file mode 100644 index 0000000..9588803 --- /dev/null +++ b/testdata/snapshots/TestTest/True/fail.snap.txt @@ -0,0 +1,6 @@ + +Not True +-------- + +Got: false +Wanted: true diff --git a/testdata/snapshots/TestTest/True/fail_with_context.snap.txt b/testdata/snapshots/TestTest/True/fail_with_context.snap.txt new file mode 100644 index 0000000..3c7f225 --- /dev/null +++ b/testdata/snapshots/TestTest/True/fail_with_context.snap.txt @@ -0,0 +1,8 @@ + +Not True +-------- + +Got: false +Wanted: true + +(must always be true) diff --git a/testdata/snapshots/TestTest/True/fail_with_title.snap.txt b/testdata/snapshots/TestTest/True/fail_with_title.snap.txt new file mode 100644 index 0000000..cbdfe40 --- /dev/null +++ b/testdata/snapshots/TestTest/True/fail_with_title.snap.txt @@ -0,0 +1,6 @@ + +Argh! +----- + +Got: false +Wanted: true diff --git a/testdata/snapshots/TestTest/WantErr/fail_error.snap.txt b/testdata/snapshots/TestTest/WantErr/fail_error.snap.txt new file mode 100644 index 0000000..86c06b6 --- /dev/null +++ b/testdata/snapshots/TestTest/WantErr/fail_error.snap.txt @@ -0,0 +1,8 @@ + +WantErr +------- + +Got: bang +Wanted: + +Because: Got an unexpected error: bang diff --git a/testdata/snapshots/TestTest/WantErr/fail_nil.snap.txt b/testdata/snapshots/TestTest/WantErr/fail_nil.snap.txt new file mode 100644 index 0000000..50dceae --- /dev/null +++ b/testdata/snapshots/TestTest/WantErr/fail_nil.snap.txt @@ -0,0 +1,8 @@ + +WantErr +------- + +Got: +Wanted: error + +Because: Wanted an error but got diff --git a/testdata/snapshots/TestTest/WantErr/fail_with_context.snap.txt b/testdata/snapshots/TestTest/WantErr/fail_with_context.snap.txt new file mode 100644 index 0000000..0081a8f --- /dev/null +++ b/testdata/snapshots/TestTest/WantErr/fail_with_context.snap.txt @@ -0,0 +1,10 @@ + +WantErr +------- + +Got: bang +Wanted: + +(Errors are bad!) + +Because: Got an unexpected error: bang diff --git a/testdata/snapshots/TestTest/WantErr/fail_with_title.snap.txt b/testdata/snapshots/TestTest/WantErr/fail_with_title.snap.txt new file mode 100644 index 0000000..12599ac --- /dev/null +++ b/testdata/snapshots/TestTest/WantErr/fail_with_title.snap.txt @@ -0,0 +1,8 @@ + +A very bad test +--------------- + +Got: bang +Wanted: + +Because: Got an unexpected error: bang