pax_global_header00006660000000000000000000000064147066705120014522gustar00rootroot0000000000000052 comment=538225430af4489a383c36a15c23e6ef4dc86ca0 check-1.8.0/000077500000000000000000000000001470667051200126055ustar00rootroot00000000000000check-1.8.0/.gitattributes000066400000000000000000000007251470667051200155040ustar00rootroot00000000000000# /name - apply (* doesn't match /) to file "name" beginning in project root # na/me - apply (* doesn't match /) to file "na/me" anywhere # name - apply (* do match /) to file "name" anywhere # name/** - apply … to dir … # **/name - apply (* doesn't match /) to file "name" in any dir including project root # na/**/me - apply (* doesn't match /) to file "na/me", "na/*/me", "na/*/*/me", … go.sum binary *.*.go binary check-1.8.0/.github/000077500000000000000000000000001470667051200141455ustar00rootroot00000000000000check-1.8.0/.github/commitlint.config.js000066400000000000000000000001101470667051200201160ustar00rootroot00000000000000module.exports = { extends: ['@commitlint/config-conventional'], }; check-1.8.0/.github/dependabot.yml000066400000000000000000000005141470667051200167750ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'daily' commit-message: prefix: 'chore(ci)' - package-ecosystem: 'gomod' directory: '/' schedule: interval: 'daily' commit-message: prefix: 'chore(deps)' open-pull-requests-limit: 10 check-1.8.0/.github/workflows/000077500000000000000000000000001470667051200162025ustar00rootroot00000000000000check-1.8.0/.github/workflows/CI&CD.yml000066400000000000000000000025101470667051200174730ustar00rootroot00000000000000name: CI/CD on: push: tags: [v*] branches: [master] pull_request: branches: [master] jobs: test: runs-on: 'ubuntu-latest' timeout-minutes: 30 strategy: matrix: go: - 'stable' # Also used for goveralls. - 'oldstable' - '^1.20' steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} check-latest: true cache: false - run: go version - uses: actions/cache@v4 id: cache-go-with-tools with: path: | ~/go/pkg/mod ~/.cache/go-build ~/.cache/golangci-lint .buildcache key: v1-go-with-tools-${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('0-tools.go') }}-${{ hashFiles('go.sum') }} restore-keys: | v1-go-with-tools-${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('0-tools.go') }}- v1-go-with-tools-${{ runner.os }}-${{ matrix.go }}- - run: scripts/test - name: Report code coverage env: COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} if: env.COVERALLS_TOKEN && matrix.go == 'stable' run: |- scripts/cover .buildcache/bin/goveralls -coverprofile=.buildcache/cover.out -service=GitHub check-1.8.0/.github/workflows/Create-GitHub-release.yml000066400000000000000000000012741470667051200227320ustar00rootroot00000000000000name: Create GitHub release on: push: tags: [v*] jobs: create-release: runs-on: ubuntu-latest timeout-minutes: 3 steps: - name: Checkout uses: actions/checkout@v4 - name: Generate changelog id: changelog uses: metcalfc/changelog-generator@v4 with: myToken: ${{ secrets.GITHUB_TOKEN }} - name: Create release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: ${{ github.ref }} body: ${{ steps.changelog.outputs.changelog }} draft: false prerelease: false check-1.8.0/.github/workflows/Lint-PR-name.yml000066400000000000000000000007311470667051200210710ustar00rootroot00000000000000name: Lint PR name on: pull_request: branches: [master] types: [opened, edited, synchronize, reopened] jobs: lint-PR-name: runs-on: ubuntu-latest timeout-minutes: 3 steps: - uses: actions/checkout@v4 - name: Install dependencies run: npm install @commitlint/config-conventional@16.0.0 - uses: JulienKode/pull-request-name-linter-action@v0.5.0 with: configuration-path: '.github/commitlint.config.js' check-1.8.0/.github/workflows/Update-Go-doc.yml000066400000000000000000000003431470667051200212550ustar00rootroot00000000000000name: Update Go module doc on: push: tags: [v*] jobs: update-doc: runs-on: ubuntu-latest timeout-minutes: 3 steps: - name: Pull new module version uses: andrewslotin/go-proxy-pull-action@v1 check-1.8.0/.gitignore000066400000000000000000000010741470667051200145770ustar00rootroot00000000000000# /name - exclude path (* doesn't match /) to file/dir "name" beginning in project root # na/me - exclude path (* doesn't match /) to file/dir "na/me" anywhere # name - exclude path (* do match /) to file/dir "name" anywhere # name/ - exclude path … to dir … # **/name - exclude path (* doesn't match /) to file/dir "name" in any dir including project root # na/**/me - exclude path (* doesn't match /) to file/dir "na/me", "na/*/me", "na/*/*/me", … # !name - include previously excluded path … /.buildcache/ check-1.8.0/.golangci.yml000066400000000000000000000272471470667051200152050ustar00rootroot00000000000000# Origin: https://github.com/powerman/golangci-lint-strict version 1.61.0 run: modules-download-mode: readonly output: sort-results: true sort-order: - linter - severity - file # Filepath, line, and column. linters-settings: decorder: disable-init-func-first-check: false # `init` funcs have to be declared before all other functions. depguard: rules: main: deny: - pkg: github.com/prometheus/common/log desc: Should be replaced by standard lib log/slog package - pkg: github.com/sirupsen/logrus desc: Should be replaced by standard lib log/slog package - pkg: github.com/go-errors/errors desc: Should be replaced by standard lib errors package - pkg: github.com/pkg/errors desc: Should be replaced by standard lib errors package - pkg: github.com/prometheus/client_golang/prometheus/promauto desc: Not allowed because it uses global variables - pkg: github.com/golang/protobuf desc: Should be replaced by google.golang.org/protobuf package dupl: threshold: 100 # Tokens. errcheck: exclude-functions: - encoding/json.Marshal # Required because of errchkjson.check-error-free-encoding. - encoding/json.MarshalIndent # Required because of errchkjson.check-error-free-encoding. errchkjson: check-error-free-encoding: true report-no-exported: true # Encoded struct must have exported fields. exhaustive: check: - switch - map explicit-exhaustive-map: true # Only check maps with "//exhaustive:enforce" comment. exhaustruct: include: - ^$ # Only check structs which domain.tld/package/name.structname match this regexp. forbidigo: forbid: - ^print(ln)?$ exclude-godoc-examples: false analyze-types: true funlen: ignore-comments: true gci: sections: - standard # Standard section: captures all standard packages. - default # Default section: contains all imports that could not be matched to another section type. - localmodule # Local module section: contains all local packages. This section is not present unless explicitly enabled. gocognit: min-complexity: 20 goconst: ignore-tests: true gocritic: enable-all: true disabled-checks: - exposedSyncMutex # Questionable. - hugeParam # Premature optimization. - paramTypeCombine # Questionable. - switchTrue # Questionable. - todoCommentWithoutDetail # Questionable. - yodaStyleExpr # Questionable. settings: captLocal: paramsOnly: false # Do not restrict checker to params only. ruleguard: failOn: all truncateCmp: skipArchDependent: false # Do not skip int/uint/uintptr types. underef: skipRecvDeref: false unnamedResult: checkExported: true godot: exclude: - :$ # Allow line followed by details in next line(s). - '^\s*- ' # Allow line with a list item. godox: keywords: - BUG # Marks issues that should be moved to issue tracker before merging. - FIXME # Marks issues that should be resolved before merging. - DEBUG # Marks temporary code that should be removed before merging. gofmt: rewrite-rules: - pattern: interface{} replacement: any - pattern: a[b:len(a)] replacement: a[b:] gomodguard: blocked: versions: - github.com/cenkalti/backoff: version: < 4.0.0 reason: use actual version gosec: excludes: - G104 # Audit errors not checked exclude-generated: true config: global: audit: true govet: enable-all: true disable: - fieldalignment settings: shadow: strict: true grouper: import-require-single-import: true # Use a single 'import' declaration. importas: alias: - pkg: net/url alias: urlpkg loggercheck: require-string-key: true # Logging keys must be inlined constant strings. no-printf-like: true misspell: mode: restricted # Check only comments. nestif: min-complexity: 4 nolintlint: require-explanation: true # Disable linters this way: //nolint:first,second // Reason here. require-specific: true # Do not allow //nolint without specific linter name(s). paralleltest: ignore-missing: true # Do not require `t.Parallel()` everywhere. ignore-missing-subtests: true # Do not require `t.Parallel()` in all subtests. reassign: patterns: - .* # Check all global variables. revive: rules: - name: add-constant disabled: true # Duplicates goconst and mnd linters. - name: argument-limit disabled: true # Questionable. - name: atomic - name: banned-characters arguments: [] # [ "Ω","Σ","σ", "7" ] - name: bare-return disabled: true # Questionable (in some cases bare return improves readability). - name: blank-imports - name: bool-literal-in-expr - name: call-to-gc - name: cognitive-complexity disabled: true # Duplicates gocognit linter. - name: comment-spacings arguments: - nolint # Allow //nolint without a space. - name: comments-density disabled: true # Questionable. - name: confusing-naming disabled: true # Questionable (valid use case: Method() as a thin wrapper for method()). - name: confusing-results - name: constant-logical-expr - name: context-as-argument - name: context-keys-type - name: cyclomatic disabled: true # Duplicates cyclop and gocyclo linters. - name: datarace - name: deep-exit - name: defer - name: dot-imports - name: duplicated-imports - name: early-return - name: empty-block disabled: true # https://github.com/mgechev/revive/issues/386 - name: empty-lines - name: enforce-map-style arguments: - make # Use `make(map[A]B)` instead of literal `map[A]B{}`. - name: enforce-repeated-arg-type-style disabled: true # Questionable (short form for similar args and full otherwise may improve readability). - name: enforce-slice-style disabled: true # Questionable (sometimes we need a nil slice, sometimes not nil). - name: error-naming - name: error-return - name: error-strings - name: errorf - name: exported - name: file-header - name: flag-parameter - name: function-length disabled: true # Duplicates funlen linter. - name: function-result-limit disabled: true # Questionable. - name: get-return - name: identical-branches - name: if-return - name: import-alias-naming - name: import-shadowing - name: imports-blocklist - name: increment-decrement - name: indent-error-flow - name: line-length-limit disabled: true # Duplicates lll linter. - name: max-control-nesting - name: max-public-structs disabled: true # Questionable. - name: modifies-parameter - name: modifies-value-receiver - name: nested-structs disabled: true # Questionable (useful in tests, may worth enabling for non-tests). - name: optimize-operands-order - name: package-comments - name: range - name: range-val-address - name: range-val-in-closure - name: receiver-naming - name: redefines-builtin-id - name: redundant-import-alias - name: string-format arguments: - - fmt.Errorf[0] - /(^|[^\.!?])$/ - must not end in punctuation - - panic - /^[^\n]*$/ - must not contain line breaks - name: string-of-int - name: struct-tag - name: superfluous-else - name: time-equal - name: time-naming - name: unchecked-type-assertion disabled: true # Duplicates errcheck and forcetypeassert linters. - name: unconditional-recursion - name: unexported-naming - name: unexported-return - name: unhandled-error disabled: true # Duplicates errcheck linter. - name: unnecessary-stmt - name: unreachable-code - name: unused-parameter - name: unused-receiver - name: use-any - name: useless-break - name: var-declaration - name: var-naming - name: waitgroup-by-value rowserrcheck: packages: - github.com/jmoiron/sqlx - github.com/powerman/sqlxx sloglint: context: scope static-msg: true key-naming-case: snake forbidden-keys: - time # Used by standard slog.JSONHandler or slog.TextHandler. - level # Used by standard slog.JSONHandler or slog.TextHandler. - msg # Used by standard slog.JSONHandler or slog.TextHandler. - source # Used by standard slog.JSONHandler or slog.TextHandler. tagalign: order: - json - yaml - yml - toml - env - mod - mapstructure - binding - validate strict: true tagliatelle: case: use-field-name: true rules: json: snake yaml: kebab xml: camel toml: camel bson: camel avro: snake mapstructure: kebab envconfig: upperSnake testifylint: enable-all: true testpackage: skip-regexp: .*_internal_test\.go thelper: test: name: false # Allow *testing.T param to have any name, not only `t`. usestdlibvars: time-weekday: true time-month: true time-layout: true crypto-hash: true default-rpc-path: true sql-isolation-level: true tls-signature-scheme: true linters: enable-all: true disable: - containedctx # Questionable. - contextcheck # Questionable. - cyclop # Prefer gocognit. - dogsled # Questionable (assignment to how many blank identifiers is not okay?). - dupl - execinquery # Deprecated. - exportloopref # Deprecated. - forcetypeassert # Questionable (often we actually want panic). - gocyclo # Prefer gocognit. - gomnd # Deprecated. - interfacebloat # Questionable. - ireturn # Questionable (is returning unexported types better?). - lll # Questionable (sometimes long lines improve readability). - nlreturn # Questionable (often no blank line before return improve readability). - nonamedreturns # Questionable (named return act as a documentation). - perfsprint # Questionable (force performance over readability and sometimes safety). - varnamelen - wrapcheck # Questionable (see https://github.com/tomarrell/wrapcheck/issues/1). - wsl # Questionable (too much style differences, hard to consider). issues: exclude: - declaration of "(log|err|ctx)" shadows - 'missing cases in switch of type \S+: \S+_UNSPECIFIED$' exclude-rules: - path: _test\.go|testing(_.*)?\.go linters: - bodyclose - dupl - errcheck - forcetypeassert - funlen - gochecknoglobals - gochecknoinits - gocognit - goconst - gosec - maintidx - reassign - source: const # Define global const-vars like: var SomeGlobal = []int{42} // Const. linters: - gochecknoglobals - path: _test\.go|testing(_.*)?\.go text: (unnamedResult|exitAfterDefer|rangeValCopy|unnecessaryBlock) linters: - gocritic - path: _test\.go text: '"t" shadows' linters: - govet - path: ^(.*/)?embed.go$ linters: - gochecknoglobals exclude-use-default: false # Disable default exclude patterns. exclude-files: - \.[\w-]+\.go$ # Use this pattern to name auto-generated files. max-issues-per-linter: 0 max-same-issues: 0 severity: default-severity: error check-1.8.0/0-tools.go000066400000000000000000000015541470667051200144360ustar00rootroot00000000000000//go:build generate // NOTE: Prefix 0- in this file's name ensures that `go generate ./...` processes it first. //go:generate mkdir -p .buildcache/bin //go:generate -command GOINSTALL env "GOBIN=$PWD/.buildcache/bin" go install //go:generate -command INSTALL-SHELLCHECK sh -c ".buildcache/bin/shellcheck --version 2>/dev/null | grep -wq \"$DOLLAR{DOLLAR}{1}\" || curl -sSfL https://github.com/koalaman/shellcheck/releases/download/v\"$DOLLAR{DOLLAR}{1}\"/shellcheck-v\"$DOLLAR{DOLLAR}{1}\".\"$(uname)\".x86_64.tar.xz | tar xJf - -C .buildcache/bin --strip-components=1 shellcheck-v\"$DOLLAR{DOLLAR}{1}\"/shellcheck" -sh package tools //go:generate GOINSTALL github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 //go:generate GOINSTALL github.com/mattn/goveralls@v0.0.12 //go:generate GOINSTALL gotest.tools/gotestsum@v1.12.0 //go:generate INSTALL-SHELLCHECK 0.10.0 check-1.8.0/LICENSE000066400000000000000000000020531470667051200136120ustar00rootroot00000000000000MIT License Copyright (c) 2017 Alex Efros Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. check-1.8.0/README.md000066400000000000000000000064551470667051200140760ustar00rootroot00000000000000# check [![Go Reference](https://pkg.go.dev/badge/github.com/powerman/check.svg)](https://pkg.go.dev/github.com/powerman/check) [![CI/CD](https://github.com/powerman/check/actions/workflows/CI&CD.yml/badge.svg)](https://github.com/powerman/check/actions/workflows/CI&CD.yml) [![Coverage Status](https://coveralls.io/repos/github/powerman/check/badge.svg?branch=master)](https://coveralls.io/github/powerman/check?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/powerman/check)](https://goreportcard.com/report/github.com/powerman/check) [![Release](https://img.shields.io/github/v/release/powerman/check)](https://github.com/powerman/check/releases/latest) Helpers to complement Go [testing](https://golang.org/pkg/testing/) package. Write tests with ease and fun! This package is like [testify/assert](https://godoc.org/github.com/test-go/testify/assert) on steroids. :) ## Features - Compelling output from failed tests: - Very easy-to-read dumps for expected and actual values. - Same text diff you loved in testify/assert. - Also visual diff in [GoConvey](http://goconvey.co/) web UI, if you use it (recommended). - Statistics with amount of passed/failed checks. - Colored output in terminal. - 100% compatible with testing package - check package just provide convenient wrappers for `*testing.T` methods and doesn't introduce new concepts like BDD, custom test suite or unusual execution flow. - All checks you may ever need! :) - Very easy to add your own check functions. - Concise, handy and consistent API, without dot-import! ## Quickstart Just wrap each (including subtests) `*testing.T` using `check.T()` and write tests as usually with testing package. Call new methods provided by this package to have more clean/concise test code and cool dump/diff. ```go import "github.com/powerman/check" func TestSomething(tt *testing.T) { t := check.T(tt) t.Equal(2, 2) t.Log("You can use new t just like usual *testing.T") t.Run("Subtests/Parallel example", func(tt *testing.T) { t := check.T(tt) t.Parallel() t.NotEqual(2, 3, "should not be 3!") obj, err := NewObj() if t.Nil(err) { t.Match(obj.field, `^\d+$`) } }) } ``` To get optional statistics about executed checkers add: ```go func TestMain(m *testing.M) { check.TestMain(m) } ``` When use goconvey tool, to get nice diff in web UI [add](https://github.com/smartystreets/goconvey/issues/513): ```go import _ "github.com/smartystreets/goconvey/convey" ``` ## Installation Require [Go 1.9](https://golang.org/doc/go1.9#test-helper). ```sh go get github.com/powerman/check ``` ## TODO - Doc: - [ ] Add testable examples. - [ ] Show how text diff and stats looks like (both text and screenshot with colors). - [ ] Show how `goconvey` diff looks like. - Questionable: - [ ] Support custom checkers from gocheck etc.? - [ ] Provide a way to force binary dump for utf8.Valid `string`/`[]byte`? - [ ] Count skipped tests (will have to overload `Skip`, `Skipf`, `SkipNow`)? - Complicated: - [ ] Show line of source_test.go with failed test (like gocheck). - [ ] Auto-detect missed `t:=check.T(tt)` - try to intercept `Run()` and `Parallel()` for detecting using wrong `t` (looks like golangci-lint's tparallel catch at least `Parallel()` case). check-1.8.0/check.go000066400000000000000000001177631470667051200142300ustar00rootroot00000000000000package check import ( "bytes" "encoding/json" "errors" "fmt" "math" "reflect" "regexp" "strings" "testing" "time" pkgerrors "github.com/pkg/errors" //nolint:depguard // By design. "github.com/powerman/deepequal" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" ) //nolint:gochecknoglobals // Const. var ( typString = reflect.TypeOf("") typBytes = reflect.TypeOf([]byte(nil)) typFloat64 = reflect.TypeOf(0.0) ) // C wraps *testing.T to make it convenient to call checkers in test. type C struct { *testing.T todo bool must bool } const ( nameActual = "Actual" nameExpected = "Expected" ) // Parallel implements an internal workaround which have no visible // effect, so you should just call t.Parallel() as you usually do - it // will work as expected. func (t *C) Parallel() { t.Helper() // Goconvey anyway doesn't provide -test.cpu= and mixed output of // parallel tests result in reporting failed tests at wrong places // and with wrong failed tests count in web UI. if !flags.detect().conveyJSON { t.T.Parallel() } } // T creates and returns new *C, which wraps given tt and supposed to be // used inplace of it, providing you with access to many useful helpers in // addition to standard methods of *testing.T. // // It's convenient to rename Test function's arg from t to something // else, create wrapped variable with usual name t and use only t: // // func TestSomething(tt *testing.T) { // t := check.T(tt) // // use only t in test and don't touch tt anymore // } func T(tt *testing.T) *C { //nolint:thelper // With check we name it tt! return &C{T: tt} } // TODO creates and returns new *C, which have only one difference from // original one: every passing check is now handled as failed and vice // versa (this doesn't affect boolean value returned by check). // You can continue using both old and new *C at same time. // // Swapping passed/failed gives you ability to temporary mark some failed // test as passed. For example, this may be useful to avoid broken builds // in CI. This is often better than commenting, deleting or skipping // broken test because it will continue to execute, and eventually when // reason why it fails will be fixed this test will became failed again - // notifying you the mark can and should be removed from this test now. // // func TestSomething(tt *testing.T) { // t := check.T(tt) // // Normal tests. // t.True(true) // // If you need to mark just one/few broken tests: // t.TODO().True(false) // t.True(true) // // If there are several broken tests mixed with working ones: // todo := t.TODO() // t.True(true) // todo.True(false) // t.True(true) // if todo.True(false) { // panic("never here") // } // // If all tests below this point are broken: // t = t.TODO() // t.True(false) // ... // } func (t *C) TODO() *C { return &C{T: t.T, todo: true, must: t.must} } // MustAll creates and returns new *C, which have only one difference from // original one: every failed check will interrupt test using t.FailNow. // You can continue using both old and new *C at same time. // // This provides an easy way to turn all checks into assertion. func (t *C) MustAll() *C { return &C{T: t.T, todo: t.todo, must: true} } func (t *C) pass() { statsMu.Lock() defer statsMu.Unlock() if stats[t.T] == nil { stats[t.T] = newTestStat(t.Name(), false) } if t.todo { stats[t.T].forged.value++ } else { stats[t.T].passed.value++ } } func (t *C) fail() { statsMu.Lock() defer statsMu.Unlock() if stats[t.T] == nil { stats[t.T] = newTestStat(t.Name(), false) } stats[t.T].failed.value++ } func (t *C) report(ok bool, msg []any, checker string, name []string, args []any) bool { //nolint:revive // False positive. t.Helper() if ok != t.todo { t.pass() return ok } if t.todo { checker = "TODO " + checker } dump := make([]dump, 0, len(args)) for _, arg := range args { dump = append(dump, newDump(arg)) } failure := new(bytes.Buffer) fmt.Fprintf(failure, "%s\nChecker: %s%s%s\n", format(msg...), ansiYellow, checker, ansiReset, ) failureShort := failure.String() // Reverse order to show Actual: last. for i := len(dump) - 1; i >= 0; i-- { fmt.Fprintf(failure, "%-10s", name[i]+":") switch name[i] { case nameActual: fmt.Fprint(failure, ansiRed) default: fmt.Fprint(failure, ansiGreen) } fmt.Fprintf(failure, "%s%s", dump[i], ansiReset) } failureLong := failure.String() wantDiff := len(dump) == 2 && name[0] == nameActual && name[1] == nameExpected //nolint:gosec // False positive. if wantDiff { //nolint:nestif // No idea how to simplify. if reportToGoConvey(dump[0].String(), dump[1].String(), failureShort) == nil { t.Fail() } else { fmt.Fprintf(failure, "\n%s", colouredDiff(dump[0].diff(dump[1]))) t.Errorf("%s\n", failure) } } else { if reportToGoConvey("", "", failureLong) == nil { t.Fail() } else { t.Errorf("%s\n", failure) } } t.fail() if t.must { t.FailNow() } return ok } func (t *C) reportShould1(funcName string, actual any, msg []any, ok bool) bool { t.Helper() return t.report(ok, msg, "Should "+funcName, []string{nameActual}, []any{actual}) } func (t *C) reportShould2(funcName string, actual, expected any, msg []any, ok bool) bool { t.Helper() return t.report(ok, msg, "Should "+funcName, []string{nameActual, nameExpected}, []any{actual, expected}) } func (t *C) report0(msg []any, ok bool) bool { t.Helper() return t.report(ok, msg, callerFuncName(1), []string{}, []any{}) } func (t *C) report1(actual any, msg []any, ok bool) bool { t.Helper() return t.report(ok, msg, callerFuncName(1), []string{nameActual}, []any{actual}) } func (t *C) report2(actual, expected any, msg []any, ok bool) bool { t.Helper() checker, arg2Name := callerFuncName(1), nameExpected if strings.Contains(checker, "Match") { arg2Name = "Regex" } return t.report(ok, msg, checker, []string{nameActual, arg2Name}, []any{actual, expected}) } func (t *C) report3(actual, expected1, expected2 any, msg []any, ok bool) bool { t.Helper() checker, arg2Name, arg3Name := callerFuncName(1), "arg1", "arg2" switch { case strings.Contains(checker, "Between"): arg2Name, arg3Name = "Min", "Max" case strings.Contains(checker, "Delta"): arg2Name, arg3Name = nameExpected, "Delta" case strings.Contains(checker, "SMAPE"): arg2Name, arg3Name = nameExpected, "SMAPE" } return t.report(ok, msg, checker, []string{nameActual, arg2Name, arg3Name}, []any{actual, expected1, expected2}) } // Must interrupt test using t.FailNow if called with false value. // // This provides an easy way to turn any check into assertion: // // t.Must(t.Nil(err)) func (t *C) Must(continueTest bool, msg ...any) { //nolint:revive // False positive. t.Helper() t.report0(msg, continueTest) if !continueTest { t.FailNow() } } type ( // ShouldFunc1 is like Nil or Zero. ShouldFunc1 func(t *C, actual any) bool // ShouldFunc2 is like Equal or Match. ShouldFunc2 func(t *C, actual, expected any) bool ) // Should use user-provided check function to do actual check. // // anyShouldFunc must have type ShouldFunc1 or ShouldFunc2. It should // return true if check was successful. There is no need to call t.Error // in anyShouldFunc - this will be done automatically when it returns. // // args must contain at least 1 element for ShouldFunc1 and at least // 2 elements for ShouldFunc2. // Rest of elements will be processed as usual msg ...interface{} param. // // Example: // // func bePositive(_ *check.C, actual interface{}) bool { // return actual.(int) > 0 // } // func TestCustomCheck(tt *testing.T) { // t := check.T(tt) // t.Should(bePositive, 42, "custom check!!!") // } func (t *C) Should(anyShouldFunc any, args ...any) bool { t.Helper() switch f := anyShouldFunc.(type) { case func(t *C, actual any) bool: return t.should1(f, args...) case func(t *C, actual, expected any) bool: return t.should2(f, args...) default: panic("anyShouldFunc is not a ShouldFunc1 or ShouldFunc2") } } func (t *C) should1(f ShouldFunc1, args ...any) bool { t.Helper() if len(args) < 1 { panic("not enough params for " + funcName(f)) } actual, msg := args[0], args[1:] return t.reportShould1(funcName(f), actual, msg, f(t, actual)) } func (t *C) should2(f ShouldFunc2, args ...any) bool { t.Helper() const minArgs = 2 if len(args) < minArgs { panic("not enough params for " + funcName(f)) } actual, expected, msg := args[0], args[1], args[2:] return t.reportShould2(funcName(f), actual, expected, msg, f(t, actual, expected)) } // Nil checks for actual == nil. // // There is one subtle difference between this check and Go `== nil` (if // this surprises you then you should read // https://golang.org/doc/faq#nil_error first): // // var intPtr *int // var empty interface{} // var notEmpty interface{} = intPtr // t.True(intPtr == nil) // TRUE // t.True(empty == nil) // TRUE // t.True(notEmpty == nil) // FALSE // // When you call this function your actual value will be stored in // interface{} argument, and this makes any typed nil pointer value `!= // nil` inside this function (just like in example above happens with // notEmpty variable). // // As it is very common case to check some typed pointer using Nil this // check has to work around and detect nil even if usual `== nil` return // false. But this has nasty side effect: if actual value already was of // interface type and contains some typed nil pointer (which is usually // bad thing and should be avoid) then Nil check will pass (which may be // not what you want/expect): // // t.Nil(nil) // TRUE // t.Nil(intPtr) // TRUE // t.Nil(empty) // TRUE // t.Nil(notEmpty) // WARNING: also TRUE! // // Second subtle case is less usual: uintptr(0) is sorta nil, but not // really, so Nil(uintptr(0)) will fail. Nil(unsafe.Pointer(nil)) will // also fail, for the same reason. Please do not use this and consider // this behaviour undefined, because it may change in the future. func (t *C) Nil(actual any, msg ...any) bool { t.Helper() return t.report1(actual, msg, isNil(actual)) } func isNil(actual any) bool { switch val := reflect.ValueOf(actual); val.Kind() { case reflect.Invalid: return actual == nil case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice: return val.IsNil() case reflect.Uintptr, reflect.UnsafePointer: // Subtle cases documented above. case reflect.Interface: // ??? // Can't be nil: case reflect.Struct, reflect.Array, reflect.Bool, reflect.String: case reflect.Complex128, reflect.Complex64, reflect.Float32, reflect.Float64: case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8: case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8: } return false } // NotNil checks for actual != nil. // // See Nil about subtle case in check logic. func (t *C) NotNil(actual any, msg ...any) bool { t.Helper() return t.report0(msg, !isNil(actual)) } // Error is equivalent to Log followed by Fail. // // It is like t.Errorf with TODO() and statistics support. func (t *C) Error(msg ...any) { t.Helper() t.report0(msg, false) } // True checks for cond == true. // // This can be useful to use your own custom checks, but this way you // won't get nice dump/diff for actual/expected values. You'll still have // statistics about passed/failed checks and it's shorter than usual: // // if !cond { // t.Errorf(msg...) // } func (t *C) True(cond bool, msg ...any) bool { t.Helper() return t.report0(msg, cond) } // False checks for cond == false. func (t *C) False(cond bool, msg ...any) bool { t.Helper() return t.report0(msg, !cond) } // Equal checks for actual == expected. // // Note: For time.Time it uses actual.Equal(expected) instead. func (t *C) Equal(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, isEqual(actual, expected)) } func isEqual(actual, expected any) bool { switch actual := actual.(type) { case time.Time: return actual.Equal(expected.(time.Time)) default: return actual == expected } } // EQ is a synonym for Equal. func (t *C) EQ(actual, expected any, msg ...any) bool { t.Helper() return t.Equal(actual, expected, msg...) } // NotEqual checks for actual != expected. func (t *C) NotEqual(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !isEqual(actual, expected)) } // NE is a synonym for NotEqual. func (t *C) NE(actual, expected any, msg ...any) bool { t.Helper() return t.NotEqual(actual, expected, msg...) } // BytesEqual checks for bytes.Equal(actual, expected). // // Hint: BytesEqual([]byte{}, []byte(nil)) is true (unlike DeepEqual). func (t *C) BytesEqual(actual, expected []byte, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, bytes.Equal(actual, expected)) } // NotBytesEqual checks for !bytes.Equal(actual, expected). // // Hint: NotBytesEqual([]byte{}, []byte(nil)) is false (unlike NotDeepEqual). func (t *C) NotBytesEqual(actual, expected []byte, msg ...any) bool { t.Helper() return t.report1(actual, msg, !bytes.Equal(actual, expected)) } // DeepEqual checks for reflect.DeepEqual(actual, expected). // It will also use Equal method for types which implements it // (e.g. time.Time, decimal.Decimal, etc.). // It will use proto.Equal for protobuf messages. func (t *C) DeepEqual(actual, expected any, msg ...any) bool { t.Helper() protoActual, proto1 := actual.(protoreflect.ProtoMessage) protoExpected, proto2 := expected.(protoreflect.ProtoMessage) if proto1 && proto2 { return t.report2(actual, expected, msg, proto.Equal(protoActual, protoExpected)) } return t.report2(actual, expected, msg, deepequal.DeepEqual(actual, expected)) } // NotDeepEqual checks for !reflect.DeepEqual(actual, expected). // It will also use Equal method for types which implements it // (e.g. time.Time, decimal.Decimal, etc.). // It will use proto.Equal for protobuf messages. func (t *C) NotDeepEqual(actual, expected any, msg ...any) bool { t.Helper() protoActual, proto1 := actual.(protoreflect.ProtoMessage) protoExpected, proto2 := expected.(protoreflect.ProtoMessage) if proto1 && proto2 { return t.report1(actual, msg, !proto.Equal(protoActual, protoExpected)) } return t.report1(actual, msg, !deepequal.DeepEqual(actual, expected)) } // Match checks for regex.MatchString(actual). // // Regex type can be either *regexp.Regexp or string. // // Actual type can be: // - string - will match with actual // - []byte - will match with string(actual) // - []rune - will match with string(actual) // - fmt.Stringer - will match with actual.String() // - error - will match with actual.Error() // - nil - will not match (even with empty regex) func (t *C) Match(actual, regex any, msg ...any) bool { t.Helper() ok := isMatch(&actual, regex) return t.report2(actual, regex, msg, ok) } // isMatch updates actual to be a real string used for matching, to make // dump easier to understand, but this result in losing type information. func isMatch(actual *any, regex any) bool { //nolint:gocritic // False positive. if *actual == nil { return false } if !stringify(actual) { panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil") } s := (*actual).(string) //nolint:forcetypeassert // False positive. switch v := regex.(type) { case *regexp.Regexp: return v.MatchString(s) case string: return regexp.MustCompile(v).MatchString(s) } panic("regex is not a *regexp.Regexp or string") } func stringify(arg *any) bool { //nolint:gocritic // False positive. switch v := (*arg).(type) { case nil: return false case error: *arg = v.Error() case fmt.Stringer: *arg = v.String() default: typ := reflect.TypeOf(*arg) switch typ.Kind() { //nolint:exhaustive // Covered by default case. case reflect.String: case reflect.Slice: switch typ.Elem().Kind() { //nolint:exhaustive // Covered by default case. case reflect.Uint8, reflect.Int32: default: return false } default: return false } *arg = reflect.ValueOf(*arg).Convert(typString).Interface() } return true } // NotMatch checks for !regex.MatchString(actual). // // See Match about supported actual/regex types and check logic. func (t *C) NotMatch(actual, regex any, msg ...any) bool { t.Helper() ok := !isMatch(&actual, regex) return t.report2(actual, regex, msg, ok) } // Contains checks is actual contains substring/element expected. // // Element of array/slice/map is checked using == expected. // // Type of expected depends on type of actual: // - if actual is a string, then expected should be a string // - if actual is an array, then expected should have array's element type // - if actual is a slice, then expected should have slice's element type // - if actual is a map, then expected should have map's value type // // Hint: In a map it looks for a value, if you need to look for a key - // use HasKey instead. func (t *C) Contains(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, isContains(actual, expected)) } func isContains(actual, expected any) (found bool) { switch valActual := reflect.ValueOf(actual); valActual.Kind() { //nolint:exhaustive // Covered by default case. case reflect.String: strActual := valActual.Convert(typString).Interface().(string) //nolint:forcetypeassert // False positive. valExpected := reflect.ValueOf(expected) if valExpected.Kind() != reflect.String { panic("expected underlying type is not a string") } strExpected := valExpected.Convert(typString).Interface().(string) //nolint:forcetypeassert // False positive. found = strings.Contains(strActual, strExpected) case reflect.Map: if valActual.Type().Elem() != reflect.TypeOf(expected) { panic("expected type not match actual element type") } keys := valActual.MapKeys() for i := 0; i < len(keys) && !found; i++ { found = valActual.MapIndex(keys[i]).Interface() == expected } case reflect.Slice, reflect.Array: if valActual.Type().Elem() != reflect.TypeOf(expected) { panic("expected type not match actual element type") } for i := 0; i < valActual.Len() && !found; i++ { found = valActual.Index(i).Interface() == expected } default: panic("actual is not a string, array, slice or map") } return found } // NotContains checks is actual not contains substring/element expected. // // See Contains about supported actual/expected types and check logic. func (t *C) NotContains(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !isContains(actual, expected)) } // HasKey checks is actual has key expected. func (t *C) HasKey(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, hasKey(actual, expected)) } func hasKey(actual, expected any) bool { return reflect.ValueOf(actual).MapIndex(reflect.ValueOf(expected)).IsValid() } // NotHasKey checks is actual has no key expected. func (t *C) NotHasKey(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !hasKey(actual, expected)) } // Zero checks is actual is zero value of it's type. func (t *C) Zero(actual any, msg ...any) bool { t.Helper() return t.report1(actual, msg, isZero(actual)) } func isZero(actual any) bool { if isNil(actual) { return true } else if typ := reflect.TypeOf(actual); typ.Comparable() { // Not Func, Map, Slice, Array with non-comparable // elements, Struct with non-comparable fields. return actual == reflect.Zero(typ).Interface() } else if typ.Kind() == reflect.Array { zero := true val := reflect.ValueOf(actual) for i := 0; i < val.Len() && zero; i++ { zero = isZero(val.Index(i).Interface()) } return zero } // Func, Struct with non-comparable fields. // Non-nil Map, Slice. return false } // NotZero checks is actual is not zero value of it's type. func (t *C) NotZero(actual any, msg ...any) bool { t.Helper() return t.report1(actual, msg, !isZero(actual)) } // Len checks is len(actual) == expected. func (t *C) Len(actual any, expected int, msg ...any) bool { t.Helper() l := reflect.ValueOf(actual).Len() return t.report2(l, expected, msg, l == expected) } // NotLen checks is len(actual) != expected. func (t *C) NotLen(actual any, expected int, msg ...any) bool { t.Helper() l := reflect.ValueOf(actual).Len() return t.report2(l, expected, msg, l != expected) } // Err checks is actual error is the same as expected error. // // If errors.Is() fails then it'll use more sofiscated logic: // // It tries to recursively unwrap actual before checking using // errors.Unwrap() and github.com/pkg/errors.Cause(). // In case of multi-error (Unwrap() []error) it use only first error. // // It will use proto.Equal for gRPC status errors. // // They may be a different instances, but must have same type and value. // // Checking for nil is okay, but using Nil(actual) instead is more clean. func (t *C) Err(actual, expected error, msg ...any) bool { t.Helper() actual2 := unwrapErr(actual) equal := fmt.Sprintf("%#v", actual2) == fmt.Sprintf("%#v", expected) _, proto1 := actual2.(interface{ GRPCStatus() *status.Status }) _, proto2 := expected.(interface{ GRPCStatus() *status.Status }) if proto1 || proto2 { equal = proto.Equal(status.Convert(actual2).Proto(), status.Convert(expected).Proto()) } if !equal { equal = errors.Is(actual, expected) } return t.report2(actual, expected, msg, equal) } func unwrapErr(err error) (actual error) { defer func() { _ = recover() }() actual = err for { actual = pkgerrors.Cause(actual) var unwrapped error switch wrapped := actual.(type) { //nolint:errorlint // False positive. case interface{ Unwrap() error }: unwrapped = wrapped.Unwrap() case interface{ Unwrap() []error }: unwrappeds := wrapped.Unwrap() if len(unwrappeds) > 0 { unwrapped = unwrappeds[0] } } if unwrapped == nil { break } actual = unwrapped } return actual } // NotErr checks is actual error is not the same as expected error. // // It tries to recursively unwrap actual before checking using // errors.Unwrap() and github.com/pkg/errors.Cause(). // In case of multi-error (Unwrap() []error) it use only first error. // // It will use !proto.Equal for gRPC status errors. // // They must have either different types or values (or one should be nil). // Different instances with same type and value will be considered the // same error, and so is both nil. // // Finally it'll use !errors.Is(). func (t *C) NotErr(actual, expected error, msg ...any) bool { t.Helper() actual2 := unwrapErr(actual) notEqual := fmt.Sprintf("%#v", actual2) != fmt.Sprintf("%#v", expected) _, proto1 := actual2.(interface{ GRPCStatus() *status.Status }) _, proto2 := expected.(interface{ GRPCStatus() *status.Status }) if proto1 || proto2 { notEqual = !proto.Equal(status.Convert(actual2).Proto(), status.Convert(expected).Proto()) } if notEqual { notEqual = !errors.Is(actual, expected) } return t.report1(actual, msg, notEqual) } // Panic checks is actual() panics. // // It is able to detect panic(nil)… but you should try to avoid using this. func (t *C) Panic(actual func(), msg ...any) bool { t.Helper() didPanic := true func() { defer func() { _ = recover() }() actual() didPanic = false }() return t.report0(msg, didPanic) } // NotPanic checks is actual() don't panics. // // It is able to detect panic(nil)… but you should try to avoid using this. func (t *C) NotPanic(actual func(), msg ...any) bool { t.Helper() didPanic := true func() { defer func() { _ = recover() }() actual() didPanic = false }() return t.report0(msg, !didPanic) } // PanicMatch checks is actual() panics and panic text match regex. // // Regex type can be either *regexp.Regexp or string. // // In case of panic(nil) it will match like panic(""). func (t *C) PanicMatch(actual func(), regex any, msg ...any) bool { t.Helper() var panicVal any didPanic := true func() { defer func() { panicVal = recover() }() actual() didPanic = false }() if !didPanic { return t.report0(msg, false) } switch panicVal.(type) { case string, error: default: panicVal = fmt.Sprintf("%#v", panicVal) } ok := isMatch(&panicVal, regex) return t.report2(panicVal, regex, msg, ok) } // PanicNotMatch checks is actual() panics and panic text not match regex. // // Regex type can be either *regexp.Regexp or string. // // In case of panic(nil) it will match like panic(""). func (t *C) PanicNotMatch(actual func(), regex any, msg ...any) bool { t.Helper() var panicVal any didPanic := true func() { defer func() { panicVal = recover() }() actual() didPanic = false }() if !didPanic { return t.report0(msg, false) } switch panicVal.(type) { case string, error: default: panicVal = fmt.Sprintf("%#v", panicVal) } ok := !isMatch(&panicVal, regex) return t.report2(panicVal, regex, msg, ok) } // Less checks for actual < expected. // // Both actual and expected must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) Less(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, isLess(actual, expected)) } func isLess(actual, expected any) bool { switch v1, v2 := reflect.ValueOf(actual), reflect.ValueOf(expected); v1.Kind() { //nolint:exhaustive // Covered by default case. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v1.Int() < v2.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v1.Uint() < v2.Uint() case reflect.Float32, reflect.Float64: return v1.Float() < v2.Float() case reflect.String: return v1.String() < v2.String() default: if actualTime, ok := actual.(time.Time); ok { return actualTime.Before(expected.(time.Time)) } } panic("actual is not a number, string or time.Time") } // LT is a synonym for Less. func (t *C) LT(actual, expected any, msg ...any) bool { t.Helper() return t.Less(actual, expected, msg...) } // LessOrEqual checks for actual <= expected. // // Both actual and expected must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) LessOrEqual(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !isGreater(actual, expected)) } func isGreater(actual, expected any) bool { switch v1, v2 := reflect.ValueOf(actual), reflect.ValueOf(expected); v1.Kind() { //nolint:exhaustive // Covered by default case. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v1.Int() > v2.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v1.Uint() > v2.Uint() case reflect.Float32, reflect.Float64: return v1.Float() > v2.Float() case reflect.String: return v1.String() > v2.String() default: if actualTime, ok := actual.(time.Time); ok { return actualTime.After(expected.(time.Time)) } } panic("actual is not a number, string or time.Time") } // LE is a synonym for LessOrEqual. func (t *C) LE(actual, expected any, msg ...any) bool { t.Helper() return t.LessOrEqual(actual, expected, msg...) } // Greater checks for actual > expected. // // Both actual and expected must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) Greater(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, isGreater(actual, expected)) } // GT is a synonym for Greater. func (t *C) GT(actual, expected any, msg ...any) bool { t.Helper() return t.Greater(actual, expected, msg...) } // GreaterOrEqual checks for actual >= expected. // // Both actual and expected must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) GreaterOrEqual(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !isLess(actual, expected)) } // GE is a synonym for GreaterOrEqual. func (t *C) GE(actual, expected any, msg ...any) bool { t.Helper() return t.GreaterOrEqual(actual, expected, msg...) } // Between checks for min < actual < max. // // All three actual, min and max must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) Between(actual, minimum, maximum any, msg ...any) bool { t.Helper() return t.report3(actual, minimum, maximum, msg, isBetween(actual, minimum, maximum)) } func isBetween(actual, minimum, maximum any) bool { switch v, vmin, vmax := reflect.ValueOf(actual), reflect.ValueOf(minimum), reflect.ValueOf(maximum); v.Kind() { //nolint:exhaustive // Covered by default case. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return vmin.Int() < v.Int() && v.Int() < vmax.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return vmin.Uint() < v.Uint() && v.Uint() < vmax.Uint() case reflect.Float32, reflect.Float64: return vmin.Float() < v.Float() && v.Float() < vmax.Float() case reflect.String: return vmin.String() < v.String() && v.String() < vmax.String() default: if actualTime, ok := actual.(time.Time); ok { minTime := minimum.(time.Time) //nolint:forcetypeassert // Want panic. maxTime := maximum.(time.Time) //nolint:forcetypeassert // Want panic. return minTime.Before(actualTime) && actualTime.Before(maxTime) } } panic("actual is not a number, string or time.Time") } // NotBetween checks for actual <= min or max <= actual. // // All three actual, min and max must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) NotBetween(actual, minimum, maximum any, msg ...any) bool { t.Helper() return t.report3(actual, minimum, maximum, msg, !isBetween(actual, minimum, maximum)) } // BetweenOrEqual checks for min <= actual <= max. // // All three actual, min and max must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) BetweenOrEqual(actual, minimum, maximum any, msg ...any) bool { t.Helper() return t.report3(actual, minimum, maximum, msg, isBetween(actual, minimum, maximum) || isEqual(actual, minimum) || isEqual(actual, maximum)) } // NotBetweenOrEqual checks for actual < min or max < actual. // // All three actual, min and max must be either: // - signed integers // - unsigned integers // - floats // - strings // - time.Time func (t *C) NotBetweenOrEqual(actual, minimum, maximum any, msg ...any) bool { t.Helper() return t.report3(actual, minimum, maximum, msg, !(isBetween(actual, minimum, maximum) || isEqual(actual, minimum) || isEqual(actual, maximum))) } // InDelta checks for expected-delta <= actual <= expected+delta. // // All three actual, expected and delta must be either: // - signed integers // - unsigned integers // - floats // - time.Time (in this case delta must be time.Duration) func (t *C) InDelta(actual, expected, delta any, msg ...any) bool { t.Helper() return t.report3(actual, expected, delta, msg, isInDelta(actual, expected, delta)) } func isInDelta(actual, expected, delta any) bool { switch v, e, d := reflect.ValueOf(actual), reflect.ValueOf(expected), reflect.ValueOf(delta); v.Kind() { //nolint:exhaustive // Covered by default case. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: minimum, maximum := e.Int()-d.Int(), e.Int()+d.Int() return minimum <= v.Int() && v.Int() <= maximum case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: minimum, maximum := e.Uint()-d.Uint(), e.Uint()+d.Uint() return minimum <= v.Uint() && v.Uint() <= maximum case reflect.Float32, reflect.Float64: minimum, maximum := e.Float()-d.Float(), e.Float()+d.Float() return minimum <= v.Float() && v.Float() <= maximum default: if actualTime, ok := actual.(time.Time); ok { expectedTime := expected.(time.Time) //nolint:forcetypeassert // Want panic. dur := delta.(time.Duration) //nolint:forcetypeassert // Want panic. minTime, maxTime := expectedTime.Add(-dur), expectedTime.Add(dur) return minTime.Before(actualTime) && actualTime.Before(maxTime) || actualTime.Equal(minTime) || actualTime.Equal(maxTime) } } panic("actual is not a number or time.Time") } // NotInDelta checks for actual < expected-delta or expected+delta < actual. // // All three actual, expected and delta must be either: // - signed integers // - unsigned integers // - floats // - time.Time (in this case delta must be time.Duration) func (t *C) NotInDelta(actual, expected, delta any, msg ...any) bool { t.Helper() return t.report3(actual, expected, delta, msg, !isInDelta(actual, expected, delta)) } // InSMAPE checks that actual and expected have a symmetric mean absolute // percentage error (SMAPE) is less than given smape. // // Both actual and expected must be either: // - signed integers // - unsigned integers // - floats // // Allowed smape values are: 0.0 < smape < 100.0. // // Used formula returns SMAPE value between 0 and 100 (percents): // - 0.0 when actual == expected // - ~0.5 when they differs in ~1% // - ~5 when they differs in ~10% // - ~20 when they differs in 1.5 times // - ~33 when they differs in 2 times // - 50.0 when they differs in 3 times // - ~82 when they differs in 10 times // - 99.0+ when actual and expected differs in 200+ times // - 100.0 when only one of actual or expected is 0 or one of them is // positive while another is negative func (t *C) InSMAPE(actual, expected any, smape float64, msg ...any) bool { t.Helper() return t.report3(actual, expected, smape, msg, isInSMAPE(actual, expected, smape)) } func isInSMAPE(actual, expected any, smape float64) bool { if !(0 < smape && smape < 100) { panic("smape is not in allowed range: 0 < smape < 100") } a := reflect.ValueOf(actual).Convert(typFloat64).Float() e := reflect.ValueOf(expected).Convert(typFloat64).Float() if a == 0 && e == 0 { return true // avoid division by zero in legal use case } return 100*math.Abs(e-a)/(math.Abs(e)+math.Abs(a)) < smape } // NotInSMAPE checks that actual and expected have a symmetric mean // absolute percentage error (SMAPE) is greater than or equal to given // smape. // // See InSMAPE about supported actual/expected types and check logic. func (t *C) NotInSMAPE(actual, expected any, smape float64, msg ...any) bool { t.Helper() return t.report3(actual, expected, smape, msg, !isInSMAPE(actual, expected, smape)) } // HasPrefix checks for strings.HasPrefix(actual, expected). // // Both actual and expected may have any of these types: // - string - will use as is // - []byte - will convert with string() // - []rune - will convert with string() // - fmt.Stringer - will convert with actual.String() // - error - will convert with actual.Error() // - nil - check will always fail func (t *C) HasPrefix(actual, expected any, msg ...any) bool { t.Helper() ok := isHasPrefix(&actual, &expected) return t.report2(actual, expected, msg, ok) } // isHasPrefix updates actual and expected to be a real string used for check, // to make dump easier to understand, but this result in losing type information. func isHasPrefix(actual, expected *any) bool { //nolint:gocritic // False positive. if *actual == nil || *expected == nil { return false } if !stringify(actual) { panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil") } if !stringify(expected) { panic("expected is not a string, []byte, []rune, fmt.Stringer, error or nil") } return strings.HasPrefix((*actual).(string), (*expected).(string)) } // NotHasPrefix checks for !strings.HasPrefix(actual, expected). // // See HasPrefix about supported actual/expected types and check logic. func (t *C) NotHasPrefix(actual, expected any, msg ...any) bool { t.Helper() ok := !isHasPrefix(&actual, &expected) return t.report2(actual, expected, msg, ok) } // HasSuffix checks for strings.HasSuffix(actual, expected). // // Both actual and expected may have any of these types: // - string - will use as is // - []byte - will convert with string() // - []rune - will convert with string() // - fmt.Stringer - will convert with actual.String() // - error - will convert with actual.Error() // - nil - check will always fail func (t *C) HasSuffix(actual, expected any, msg ...any) bool { t.Helper() ok := isHasSuffix(&actual, &expected) return t.report2(actual, expected, msg, ok) } // isHasSuffix updates actual and expected to be a real string used for check, // to make dump easier to understand, but this result in losing type information. func isHasSuffix(actual, expected *any) bool { //nolint:gocritic // False positive. if *actual == nil || *expected == nil { return false } if !stringify(actual) { panic("actual is not a string, []byte, []rune, fmt.Stringer, error or nil") } if !stringify(expected) { panic("expected is not a string, []byte, []rune, fmt.Stringer, error or nil") } return strings.HasSuffix((*actual).(string), (*expected).(string)) } // NotHasSuffix checks for !strings.HasSuffix(actual, expected). // // See HasSuffix about supported actual/expected types and check logic. func (t *C) NotHasSuffix(actual, expected any, msg ...any) bool { t.Helper() ok := !isHasSuffix(&actual, &expected) return t.report2(actual, expected, msg, ok) } // JSONEqual normalize formatting of actual and expected (if they're valid // JSON) and then checks for bytes.Equal(actual, expected). // // Both actual and expected may have any of these types: // - string // - []byte // - json.RawMessage // - *json.RawMessage // - nil // // In case any of actual or expected is nil or empty or (for string or // []byte) is invalid JSON - check will fail. func (t *C) JSONEqual(actual, expected any, msg ...any) bool { t.Helper() ok := isJSONEqual(actual, expected) if !ok { if buf := jsonify(actual); len(buf) != 0 { actual = buf } if buf := jsonify(expected); len(buf) != 0 { expected = buf } } return t.report2(actual, expected, msg, ok) } func isJSONEqual(actual, expected any) bool { jsonActual, jsonExpected := jsonify(actual), jsonify(expected) return len(jsonActual) != 0 && len(jsonExpected) != 0 && bytes.Equal(jsonActual, jsonExpected) } func jsonify(arg any) json.RawMessage { switch v := (arg).(type) { case nil: return nil case json.RawMessage: return v case *json.RawMessage: if v == nil { return nil } return *v } buf := reflect.ValueOf(arg).Convert(typBytes).Interface().([]byte) //nolint:forcetypeassert // Want panic. var v any err := json.Unmarshal(buf, &v) if err != nil { return nil } buf, err = json.Marshal(v) if err != nil { return nil } return buf } // HasType checks is actual has same type as expected. func (t *C) HasType(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, reflect.TypeOf(actual) == reflect.TypeOf(expected)) } // NotHasType checks is actual has not same type as expected. func (t *C) NotHasType(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, reflect.TypeOf(actual) != reflect.TypeOf(expected)) } // Implements checks is actual implements interface pointed by expected. // // You must use pointer to interface type in expected: // // t.Implements(os.Stdin, (*io.Reader)(nil)) func (t *C) Implements(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, isImplements(actual, expected)) } func isImplements(actual, expected any) bool { typActual := reflect.TypeOf(actual) if typActual.Kind() != reflect.Ptr { typActual = reflect.PointerTo(typActual) } return typActual.Implements(reflect.TypeOf(expected).Elem()) } // NotImplements checks is actual does not implements interface pointed by expected. // // You must use pointer to interface type in expected: // // t.NotImplements(os.Stdin, (*fmt.Stringer)(nil)) func (t *C) NotImplements(actual, expected any, msg ...any) bool { t.Helper() return t.report2(actual, expected, msg, !isImplements(actual, expected)) } check-1.8.0/check_test.go000066400000000000000000001364321470667051200152610ustar00rootroot00000000000000//nolint:goerr113 // It's just a test. package check_test import ( "encoding/json" "errors" "fmt" "io" "net" "os" "reflect" "regexp" "testing" "time" pkgerrors "github.com/pkg/errors" //nolint:depguard // By design. "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/powerman/check" ) func init() { time.Local = time.UTC } type ( myInt int myString string myStruct struct { i int s string } myError struct{ s string } ) func (e myError) Error() string { return e.s } var ( // Zero values for standard types. zBool bool zInt int zInt8 int8 zInt16 int16 zInt32 int32 zInt64 int64 zUint uint zUint8 uint8 zUint16 uint16 zUint32 uint32 zUint64 uint64 zUintptr uintptr zFloat32 float32 zFloat64 float64 zArray0 [0]int zArray1 [1]int zChan chan int zFunc func() zIface any zMap map[int]int zSlice []int zString string zStruct struct{} // zUnsafe unsafe.Pointer // don't like to import unsafe. zBoolPtr *bool zIntPtr *int zInt8Ptr *int8 zInt16Ptr *int16 zInt32Ptr *int32 zInt64Ptr *int64 zUintPtr *uint zUint8Ptr *uint8 zUint16Ptr *uint16 zUint32Ptr *uint32 zUint64Ptr *uint64 zUintptrPtr *uintptr zFloat32Ptr *float32 zFloat64Ptr *float64 zArray0Ptr *[0]int zArray1Ptr *[1]int zChanPtr *chan int zFuncPtr *func() zIfacePtr *any zMapPtr *map[int]int zSlicePtr *[]int zStringPtr *string zStructPtr *struct{} // zUnsafePtr *unsafe.Pointer // don't like to import unsafe // Zero values for named types. zMyInt myInt zMyString myString zJSON json.RawMessage zJSONPtr *json.RawMessage zTime time.Time zProto emptypb.Empty // Initialized but otherwise zero-like values. vChan = make(chan int) vFunc = func() {} vIface any = zIntPtr vMap = make(map[int]int) vSlice = make([]int, 0) // Non-zero values. xBool = true xInt = -42 xInt8 int8 = -8 xInt16 int16 = -16 xInt32 int32 = -32 xInt64 int64 = -64 xUint uint = 42 xUint8 uint8 = 8 xUint16 uint16 = 16 xUint32 uint32 = 32 xUint64 uint64 = 64 xUintptr uintptr = 0xDEADBEEF xFloat32 float32 = -3.2 xFloat64 = 6.4 xArray1 = [1]int{-1} xChan = make(chan int, 1) xFunc = func() { panic(nil) } xIface io.Reader = os.Stdin xMap = map[int]int{2: -2, 3: -3, 5: -5} xSlice = []int{3, 5, 8} xString = "" xStruct = myStruct{i: 10, s: "ten"} // xUnsafe = unsafe.Pointer(&xUintptr) // don't like to import unsafe. xBoolPtr = &xBool xIntPtr = &xInt xInt8Ptr = &xInt8 xInt16Ptr = &xInt16 xInt32Ptr = &xInt32 xInt64Ptr = &xInt64 xUintPtr = &xUint xUint8Ptr = &xUint8 xUint16Ptr = &xUint16 xUint32Ptr = &xUint32 xUint64Ptr = &xUint64 xUintptrPtr = &xUintptr xFloat32Ptr = &xFloat32 xFloat64Ptr = &xFloat64 xArray1Ptr = &xArray1 xChanPtr = &xChan xFuncPtr = &xFunc xIfacePtr = &xIface xMapPtr = &xMap xSlicePtr = &xSlice xStringPtr = &xString xStructPtr = &xStruct // xUnsafePtr *unsafe.Pointer = &xUnsafe // don't like to import unsafe. xMyInt myInt = 31337 xMyString myString = "xyz" xJSON json.RawMessage = []byte(`{"s":"ten","i":10}`) xJSONPtr = &xJSON xTime = time.Now() xTimeEST = xTime.In(func() *time.Location { loc, _ := time.LoadLocation("EST"); return loc }()) xProto = timestamppb.Now() xGRPCErr = status.Error(codes.Unknown, "unknown") ) func TestTODO(tt *testing.T) { t := check.T(tt) // Normal tests. t.True(true) // If you need to mark just one/few broken tests: t.TODO().True(false) t.True(true) // If there are several broken tests mixed with working ones: todo := t.TODO() t.True(true) todo.True(false) t.True(true) if todo.True(false) { panic("never here") } // If all tests below this point are broken: t = t.TODO() t.True(false) // Second TODO() doesn't switch it off: t = t.TODO() t.True(false) } func TestError(tt *testing.T) { t := check.T(tt) t = t.TODO() t.Error() t.Error("message") t.Error("format: %q", "message") } func TestMustAll(tt *testing.T) { t := check.T(tt).MustAll() t.Nil(nil) t.NotNil(false) t.TODO().Nil(false) t.TODO().NotNil(nil) } func TestMust(tt *testing.T) { t := check.T(tt) t.Must(t.Nil(nil)) t.Must(t.NotNil(false)) } func bePositive(_ *check.C, actual any) bool { return actual.(int) > 0 } func beEqual(_ *check.C, actual, expected any) bool { return actual == expected } func TestCheckerShould(tt *testing.T) { t := check.T(tt) t.Should(bePositive, 42, "custom check!!!") t.Panic(func() { t.Should(bePositive, "42", "bad arg type") }) t.TODO().Should(func(_ *check.C, _ any) bool { return false }, 42) t.Should(beEqual, 123, 123) t.TODO().Should(beEqual, 123, 124) t.Panic(func() { t.Should(func() {}, nil) }) t.Panic(func() { t.Should(bePositive) }) t.Panic(func() { t.Should(beEqual, nil) }) } func TestCheckerNilTrue(tt *testing.T) { t := check.T(tt) todo := t.TODO() // Ensure expected values t.Equal(zBool, false) // gometalinter hates zBool==false t.True(zInt == 0) t.True(zInt8 == 0) t.True(zInt16 == 0) t.True(zInt32 == 0) t.True(zInt64 == 0) t.True(zUint == 0) t.True(zUint8 == 0) t.True(zUint16 == 0) t.True(zUint32 == 0) t.True(zUint64 == 0) t.True(zUintptr == 0) t.True(zFloat32 == 0) t.True(zFloat64 == 0) t.True(zArray0 == [0]int{}) t.True(zArray1 == [1]int{}) t.True(zChan == nil) t.True(zFunc == nil) t.True(zIface == nil) t.True(zMap == nil) t.True(zSlice == nil) t.True(zString == "") t.True(zStruct == struct{}{}) // // t.True(zUnsafe == nil) t.True(zBoolPtr == nil) t.True(zIntPtr == nil) t.True(zInt8Ptr == nil) t.True(zInt16Ptr == nil) t.True(zInt32Ptr == nil) t.True(zInt64Ptr == nil) t.True(zUintPtr == nil) t.True(zUint8Ptr == nil) t.True(zUint16Ptr == nil) t.True(zUint32Ptr == nil) t.True(zUint64Ptr == nil) t.True(zUintptrPtr == nil) t.True(zFloat32Ptr == nil) t.True(zFloat64Ptr == nil) t.True(zArray0Ptr == nil) t.True(zArray1Ptr == nil) t.True(zChanPtr == nil) t.True(zFuncPtr == nil) t.True(zIfacePtr == nil) t.True(zMapPtr == nil) t.True(zSlicePtr == nil) t.True(zStringPtr == nil) t.True(zStructPtr == nil) // // t.True(zUnsafePtr == nil) t.True(zMyInt == 0) t.True(zMyString == "") t.True(zJSON == nil) t.True(zJSONPtr == nil) t.True(zTime.Equal(time.Time{})) t.False(vChan == nil) t.False(vFunc == nil) t.False(vIface == nil) t.False(vMap == nil) t.False(vSlice == nil) // Subtle case when t.Nil() differs from == nil. zIface = zIntPtr t.Nil(zIface) t.False(zIface == nil) zIface = nil t.Nil(zIface) t.True(zIface == nil) cases := []struct { equalNil bool isNil bool actual any }{ {true, true, nil}, {false, false, zBool}, {false, false, zInt}, {false, false, zInt8}, {false, false, zInt16}, {false, false, zInt32}, {false, false, zInt64}, {false, false, zUint}, {false, false, zUint8}, {false, false, zUint16}, {false, false, zUint32}, {false, false, zUint64}, {false, false, zUintptr}, {false, false, zFloat32}, {false, false, zFloat64}, {false, false, zArray0}, {false, false, zArray1}, {false, true, zChan}, {false, true, zFunc}, {true, true, zIface}, {false, true, zMap}, {false, true, zSlice}, {false, false, zString}, {false, false, zStruct}, // {false, false, zUnsafe}, {false, true, zBoolPtr}, {false, true, zIntPtr}, {false, true, zInt8Ptr}, {false, true, zInt16Ptr}, {false, true, zInt32Ptr}, {false, true, zInt64Ptr}, {false, true, zUintPtr}, {false, true, zUint8Ptr}, {false, true, zUint16Ptr}, {false, true, zUint32Ptr}, {false, true, zUint64Ptr}, {false, true, zUintptrPtr}, {false, true, zFloat32Ptr}, {false, true, zFloat64Ptr}, {false, true, zArray0Ptr}, {false, true, zArray1Ptr}, {false, true, zChanPtr}, {false, true, zFuncPtr}, {false, true, zIfacePtr}, {false, true, zMapPtr}, {false, true, zSlicePtr}, {false, true, zStringPtr}, {false, true, zStructPtr}, // {false, true, zUnsafePtr}, {false, false, zMyInt}, {false, false, zMyString}, {false, true, zJSON}, {false, true, zJSONPtr}, {false, false, zTime}, {false, false, vChan}, {false, false, vFunc}, {false, true, vIface}, // WARNING false-positive (documented) {false, false, vMap}, {false, false, vSlice}, } for i, v := range cases { msg := fmt.Sprintf("case %d: %#v", i, v.actual) if v.equalNil { t.True(v.actual == nil, msg) } else { t.False(v.actual == nil, msg) } if v.isNil { t.Nil(v.actual, msg) todo.NotNil(v.actual, msg) } else { todo.Nil(v.actual, msg) t.NotNil(v.actual, msg) } } } func TestCheckerEqual(tt *testing.T) { t := check.T(tt) todo := t.TODO() cases := []struct { comparable bool actual any actual2 any }{ {true, zBool, xBool}, {true, zInt, xInt}, {true, zInt8, xInt8}, {true, zInt16, xInt16}, {true, zInt32, xInt32}, {true, zInt64, xInt64}, {true, zUint, xUint}, {true, zUint8, xUint8}, {true, zUint16, xUint16}, {true, zUint32, xUint32}, {true, zUint64, xUint64}, {true, zUintptr, xUintptr}, {true, zFloat32, xFloat32}, {true, zFloat64, xFloat64}, {true, zArray0, xArray1}, {true, zArray1, xArray1}, {true, zChan, xChan}, {false, zFunc, xFunc}, {true, zIface, xIface}, {false, zMap, xMap}, {false, zSlice, xSlice}, {true, zString, xString}, {true, zStruct, xStruct}, {true, zBoolPtr, xBoolPtr}, {true, zIntPtr, xIntPtr}, {true, zInt8Ptr, xInt8Ptr}, {true, zInt16Ptr, xInt16Ptr}, {true, zInt32Ptr, xInt32Ptr}, {true, zInt64Ptr, xInt64Ptr}, {true, zUintPtr, xUintPtr}, {true, zUint8Ptr, xUint8Ptr}, {true, zUint16Ptr, xUint16Ptr}, {true, zUint32Ptr, xUint32Ptr}, {true, zUint64Ptr, xUint64Ptr}, {true, zUintptrPtr, xUintptrPtr}, {true, zFloat32Ptr, xFloat32Ptr}, {true, zFloat64Ptr, xFloat64Ptr}, {true, zArray0Ptr, xArray1Ptr}, {true, zArray1Ptr, xArray1Ptr}, {true, zChanPtr, xChanPtr}, {true, zFuncPtr, xFuncPtr}, {true, zIfacePtr, xIfacePtr}, {true, zMapPtr, xMapPtr}, {true, zSlicePtr, xSlicePtr}, {true, zStringPtr, xStringPtr}, {true, zStructPtr, xStructPtr}, {true, zMyInt, xMyInt}, {true, zMyString, xMyString}, {false, zJSON, xJSON}, {true, zJSONPtr, xJSONPtr}, {true, zTime, xTime}, {false, zProto, xProto}, //nolint:govet // This is dirty (copylocks), but it's a test. {true, vChan, xChan}, {false, vFunc, xFunc}, {true, vIface, xIface}, {false, vMap, xMap}, {false, vSlice, xSlice}, {true, "one\ntwo\nend", "one\nTWO\nend"}, {true, io.EOF, io.ErrUnexpectedEOF}, {true, t, tt}, {true, int64(42), int32(42)}, {false, []byte{}, []byte(nil)}, } for _, v := range cases { if v.comparable { t.Equal(v.actual, v.actual) t.EQ(v.actual, v.actual) t.DeepEqual(v.actual, v.actual) todo.NotEqual(v.actual, v.actual) todo.NE(v.actual, v.actual) todo.NotDeepEqual(v.actual, v.actual) t.Equal(v.actual2, v.actual2) t.EQ(v.actual2, v.actual2) t.DeepEqual(v.actual2, v.actual2) todo.NotEqual(v.actual2, v.actual2) todo.NE(v.actual2, v.actual2) todo.NotDeepEqual(v.actual2, v.actual2) todo.Equal(v.actual, v.actual2) todo.EQ(v.actual, v.actual2) todo.DeepEqual(v.actual, v.actual2) t.NotEqual(v.actual, v.actual2) t.NE(v.actual, v.actual2) t.NotDeepEqual(v.actual, v.actual2) } else { t.Panic(func() { t.Equal(v.actual, v.actual) }) t.Panic(func() { t.EQ(v.actual, v.actual) }) t.Panic(func() { t.NotEqual(v.actual, v.actual) }) t.Panic(func() { t.NE(v.actual, v.actual) }) if reflect.TypeOf(v.actual).Kind() != reflect.Func { t.DeepEqual(v.actual, v.actual) todo.NotDeepEqual(v.actual, v.actual) t.DeepEqual(v.actual2, v.actual2) todo.NotDeepEqual(v.actual2, v.actual2) todo.DeepEqual(v.actual, v.actual2) t.NotDeepEqual(v.actual, v.actual2) } } } // No alternative value for .actual2. t.Equal(nil, nil) t.EQ(nil, nil) t.DeepEqual(nil, nil) todo.NotEqual(nil, nil) todo.NE(nil, nil) todo.NotDeepEqual(nil, nil) // Equal match, DeepEqual not match. t.False(xTime == xTimeEST) //nolint:revive // Need == instead of Equal() here. t.Equal(xTime, xTimeEST) t.EQ(xTime, xTimeEST) t.DeepEqual(xTime, xTimeEST) todo.NotEqual(xTime, xTimeEST) todo.NE(xTime, xTimeEST) todo.NotDeepEqual(xTime, xTimeEST) // Equal not match or panic, DeepEqual match. type notComparable struct { s string is []int } cases = []struct { comparable bool actual any actual2 any }{ {true, io.EOF, errors.New("EOF")}, {true, &testing.T{}, &testing.T{}}, {false, []byte{2, 5}, []byte{2, 5}}, {false, notComparable{"a", []int{3, 5}}, notComparable{"a", []int{3, 5}}}, {false, zProto, zProto}, //nolint:govet // This is dirty (copylocks), but it's a test. {false, xGRPCErr, xGRPCErr}, } for _, v := range cases { if v.comparable { t.False(v.actual == v.actual2) todo.Equal(v.actual, v.actual2) todo.EQ(v.actual, v.actual2) t.NotEqual(v.actual, v.actual2) t.NE(v.actual, v.actual2) } t.DeepEqual(v.actual, v.actual2) todo.NotDeepEqual(v.actual, v.actual2) } } func TestCheckerBytesEqual(tt *testing.T) { t := check.T(tt) todo := t.TODO() cases := []struct { equal bool actual []byte expected []byte }{ {true, nil, nil}, {true, []byte(nil), []byte(nil)}, {true, []byte{}, []byte{}}, {true, []byte(nil), nil}, {true, []byte{}, nil}, {true, []byte(nil), []byte{}}, {true, []byte{0}, []byte{0}}, {false, []byte{0}, nil}, {false, []byte{0}, []byte(nil)}, {false, []byte{0}, []byte{}}, {false, []byte{0}, []byte{0, 0}}, } for _, v := range cases { if v.equal { t.BytesEqual(v.actual, v.expected) todo.NotBytesEqual(v.actual, v.expected) } else { todo.BytesEqual(v.actual, v.expected) t.NotBytesEqual(v.actual, v.expected) } } } func TestCheckerMatch(tt *testing.T) { t := check.T(tt) todo := t.TODO() types := []struct { actual bool expected bool zero any }{ {true, false, nil}, {false, false, zBool}, {false, false, zInt}, {false, false, zInt8}, {false, false, zInt16}, {false, false, zInt32}, {false, false, zInt64}, {false, false, zUint}, {false, false, zUint8}, {false, false, zUint16}, {false, false, zUint32}, {false, false, zUint64}, {false, false, zUintptr}, {false, false, zFloat32}, {false, false, zFloat64}, {false, false, zArray0}, {false, false, zArray1}, {false, false, zChan}, {false, false, zFunc}, {false, false, zIface}, {false, false, zMap}, {false, false, zSlice}, {true, true, zString}, {false, false, zStruct}, {false, false, zBoolPtr}, {false, false, zIntPtr}, {false, false, zInt8Ptr}, {false, false, zInt16Ptr}, {false, false, zInt32Ptr}, {false, false, zInt64Ptr}, {false, false, zUintPtr}, {false, false, zUint8Ptr}, {false, false, zUint16Ptr}, {false, false, zUint32Ptr}, {false, false, zUint64Ptr}, {false, false, zUintptrPtr}, {false, false, zFloat32Ptr}, {false, false, zFloat64Ptr}, {false, false, zArray0Ptr}, {false, false, zArray1Ptr}, {false, false, zChanPtr}, {false, false, zFuncPtr}, {false, false, zIfacePtr}, {false, false, zMapPtr}, {false, false, zSlicePtr}, {false, false, zStringPtr}, {false, false, zStructPtr}, {false, false, zMyInt}, {true, false, zMyString}, {true, false, zJSON}, {false, false, zJSONPtr}, {true, false, zTime}, {true, false, time.Sunday}, {true, false, errors.New("")}, {true, false, []byte(nil)}, {true, false, []rune(nil)}, {true, true, regexp.MustCompile("")}, // it's also a Stringer {false, false, (*regexp.Regexp)(nil)}, {false, false, regexp.Regexp{}}, } for i, va := range types { for j, ve := range types { msg := fmt.Sprintf("case %d/%d: %#v, %#v", i, j, va.zero, ve.zero) switch va.zero.(type) { case nil: todo.Match(va.zero, ve.zero, msg) default: if va.actual && ve.expected { t.Match(va.zero, ve.zero, msg) } else { t.Panic(func() { t.Match(va.zero, ve.zero) }, msg) } } } } cases := []struct { actual any regexMatch any regexNotMatch any }{ {"", `^$`, `.`}, {myString("Test"), regexp.MustCompile(`st$`), regexp.MustCompile(`ST$`)}, {[]byte(nil), `^$`, `nil`}, {[]byte("Test"), regexp.MustCompile(`st$`), regexp.MustCompile(`ST$`)}, {[]rune(nil), `^$`, `nil`}, {[]rune("Test"), regexp.MustCompile(`st$`), regexp.MustCompile(`ST$`)}, {zTime, `00:00:00`, `01:01:01`}, {time.Sunday, regexp.MustCompile(`^Sun`), regexp.MustCompile(`Sun$`)}, {errors.New(""), `^$`, `nil`}, {io.EOF, regexp.MustCompile(`^EO`), regexp.MustCompile(`EO$`)}, } for _, v := range cases { t.Match(v.actual, v.regexMatch) todo.Match(v.actual, v.regexNotMatch) } // No value for .regexMatch. todo.Match(nil, ``) todo.Match(nil, regexp.MustCompile(``)) t.NotMatch(nil, ``) t.NotMatch(nil, regexp.MustCompile(``)) } func TestCheckerContains(tt *testing.T) { t := check.T(tt) failures := []struct { panic bool actual any expected any }{ {true, nil, nil}, {true, zBool, zBool}, {true, zInt, zInt}, {true, zInt8, zInt8}, {true, zInt16, zInt16}, {true, zInt32, zInt32}, {true, zInt64, zInt64}, {true, zUint, zUint}, {true, zUint8, zUint8}, {true, zUint16, zUint16}, {true, zUint32, zUint32}, {true, zUint64, zUint64}, {true, zUintptr, zUintptr}, {true, zFloat32, zFloat32}, {true, zFloat64, zFloat64}, {true, zArray0, zBool}, {false, zArray0, xInt}, {true, zArray1, zBool}, {false, zArray1, xInt}, {true, zChan, zChan}, {true, zFunc, zFunc}, {true, zIface, zIface}, {true, zMap, zBool}, {false, zMap, xInt}, {true, zSlice, zBool}, {false, zSlice, xInt}, {true, zString, zBool}, {false, zString, xString}, {true, zStruct, zStruct}, {true, zBoolPtr, zBoolPtr}, {true, zIntPtr, zIntPtr}, {true, zInt8Ptr, zInt8Ptr}, {true, zInt16Ptr, zInt16Ptr}, {true, zInt32Ptr, zInt32Ptr}, {true, zInt64Ptr, zInt64Ptr}, {true, zUintPtr, zUintPtr}, {true, zUint8Ptr, zUint8Ptr}, {true, zUint16Ptr, zUint16Ptr}, {true, zUint32Ptr, zUint32Ptr}, {true, zUint64Ptr, zUint64Ptr}, {true, zUintptrPtr, zUintptrPtr}, {true, zFloat32Ptr, zFloat32Ptr}, {true, zFloat64Ptr, zFloat64Ptr}, {true, zArray0Ptr, zArray0Ptr}, {true, zArray1Ptr, zArray1Ptr}, {true, zChanPtr, zChanPtr}, {true, zFuncPtr, zFuncPtr}, {true, zIfacePtr, zIfacePtr}, {true, zMapPtr, zMapPtr}, {true, zSlicePtr, zSlicePtr}, {true, zStringPtr, zStringPtr}, {true, zStructPtr, zStructPtr}, {true, zMyInt, zMyInt}, {true, zMyString, zBool}, {false, zMyString, xString}, {true, zJSON, zBool}, {false, zJSON, xUint8}, {true, zJSONPtr, zJSONPtr}, {true, zTime, zTime}, } for i, v := range failures { msg := fmt.Sprintf("case %d: %#v, %#v", i, v.actual, v.expected) if v.panic { t.Panic(func() { t.Contains(v.actual, v.expected) }, msg) } else { t.NotContains(v.actual, v.expected, msg) } } t.Contains("", "") t.Contains("Test", "") t.Contains(myString("Test"), "es") t.Contains([...]time.Time{zTime, xTime, xTimeEST}, xTime) t.Contains([]*time.Time{&zTime, &xTime, &xTimeEST}, &xTime) t.Contains([]byte("Test"), byte('e')) t.Contains([]rune("Test"), 'e') t.Contains(map[int]string{2: "two", 5: "five", 10: "ten"}, "five") t.Contains(map[string]int{"two": 2, "five": 5, "ten": 10}, 5) t.NotContains(map[string]int{"two": 2, "five": 5, "ten": 10}, 0) } func TestCheckerHasKey(tt *testing.T) { t := check.T(tt) failures := []struct { panic bool actual any expected any }{ {true, nil, nil}, {true, zBool, zBool}, {true, zInt, zInt}, {true, zInt8, zInt8}, {true, zInt16, zInt16}, {true, zInt32, zInt32}, {true, zInt64, zInt64}, {true, zUint, zUint}, {true, zUint8, zUint8}, {true, zUint16, zUint16}, {true, zUint32, zUint32}, {true, zUint64, zUint64}, {true, zUintptr, zUintptr}, {true, zFloat32, zFloat32}, {true, zFloat64, zFloat64}, {true, zArray0, zArray0}, {true, zArray1, zArray1}, {true, zChan, zChan}, {true, zFunc, zFunc}, {true, zIface, zIface}, {true, zMap, zBool}, {false, zMap, zInt}, {true, zSlice, zSlice}, {true, zString, zString}, {true, zStruct, zStruct}, {true, zBoolPtr, zBoolPtr}, {true, zIntPtr, zIntPtr}, {true, zInt8Ptr, zInt8Ptr}, {true, zInt16Ptr, zInt16Ptr}, {true, zInt32Ptr, zInt32Ptr}, {true, zInt64Ptr, zInt64Ptr}, {true, zUintPtr, zUintPtr}, {true, zUint8Ptr, zUint8Ptr}, {true, zUint16Ptr, zUint16Ptr}, {true, zUint32Ptr, zUint32Ptr}, {true, zUint64Ptr, zUint64Ptr}, {true, zUintptrPtr, zUintptrPtr}, {true, zFloat32Ptr, zFloat32Ptr}, {true, zFloat64Ptr, zFloat64Ptr}, {true, zArray0Ptr, zArray0Ptr}, {true, zArray1Ptr, zArray1Ptr}, {true, zChanPtr, zChanPtr}, {true, zFuncPtr, zFuncPtr}, {true, zIfacePtr, zIfacePtr}, {true, zMapPtr, zMapPtr}, {true, zSlicePtr, zSlicePtr}, {true, zStringPtr, zStringPtr}, {true, zStructPtr, zStructPtr}, {true, zMyInt, zMyInt}, {true, zMyString, zMyString}, {true, zJSON, zJSON}, {true, zJSONPtr, zJSONPtr}, {true, zTime, zTime}, } for i, v := range failures { msg := fmt.Sprintf("case %d: %#v, %#v", i, v.actual, v.expected) if v.panic { t.Panic(func() { t.HasKey(v.actual, v.expected) }, msg) } else { t.NotHasKey(v.actual, v.expected, msg) } } t.HasKey(map[int]string{2: "two", 5: "five", 10: "ten"}, 5) t.HasKey(map[string]int{"two": 2, "five": 5, "ten": 10}, "five") t.NotHasKey(map[string]int{"two": 2, "five": 5, "ten": 10}, "") } func TestCheckerZero(tt *testing.T) { t := check.T(tt) todo := t.TODO() cases := []struct { zero any notzero any }{ {zBool, xBool}, {zInt, xInt}, {zInt8, xInt8}, {zInt16, xInt16}, {zInt32, xInt32}, {zInt64, xInt64}, {zUint, xUint}, {zUint8, xUint8}, {zUint16, xUint16}, {zUint32, xUint32}, {zUint64, xUint64}, {zUintptr, xUintptr}, {zFloat32, xFloat32}, {zFloat64, xFloat64}, {zArray0, xArray1}, {zArray1, xArray1}, {zChan, xChan}, {zFunc, xFunc}, {zIface, xIface}, {zMap, xMap}, {zSlice, xSlice}, {zString, xString}, {zStruct, xStruct}, {zBoolPtr, xBoolPtr}, {zIntPtr, xIntPtr}, {zInt8Ptr, xInt8Ptr}, {zInt16Ptr, xInt16Ptr}, {zInt32Ptr, xInt32Ptr}, {zInt64Ptr, xInt64Ptr}, {zUintPtr, xUintPtr}, {zUint8Ptr, xUint8Ptr}, {zUint16Ptr, xUint16Ptr}, {zUint32Ptr, xUint32Ptr}, {zUint64Ptr, xUint64Ptr}, {zUintptrPtr, xUintptrPtr}, {zFloat32Ptr, xFloat32Ptr}, {zFloat64Ptr, xFloat64Ptr}, {zArray0Ptr, xArray1Ptr}, {zArray1Ptr, xArray1Ptr}, {zChanPtr, xChanPtr}, {zFuncPtr, xFuncPtr}, {zIfacePtr, xIfacePtr}, {zMapPtr, xMapPtr}, {zSlicePtr, xSlicePtr}, {zStringPtr, xStringPtr}, {zStructPtr, xStructPtr}, {zMyInt, xMyInt}, {zMyString, xMyString}, {zJSON, xJSON}, {zJSONPtr, xJSONPtr}, {zTime, xTime}, {nil, vChan}, {nil, vFunc}, {vIface, xIface}, {nil, vMap}, {nil, vSlice}, {[0][]int{}, [1][]int{{1}}}, {[2][]int{nil, nil}, [2][]int{nil, {}}}, {[2][2][2]int{1: {1: {1: 0}}}, [2][2][2]int{1: {1: {1: 1}}}}, } for i, v := range cases { msg := fmt.Sprintf("case %d: %#v, %#v", i, v.zero, v.notzero) t.Zero(v.zero, msg) todo.Zero(v.notzero, msg) t.NotZero(v.notzero, msg) todo.NotZero(v.zero, msg) } t.Zero(nil) todo.NotZero(nil) } func TestCheckerLen(tt *testing.T) { t := check.T(tt) cases := []struct { panic bool actual any len int }{ {true, nil, 0}, {true, zBool, 0}, {true, zInt, 0}, {true, zInt8, 0}, {true, zInt16, 0}, {true, zInt32, 0}, {true, zInt64, 0}, {true, zUint, 0}, {true, zUint8, 0}, {true, zUint16, 0}, {true, zUint32, 0}, {true, zUint64, 0}, {true, zUintptr, 0}, {true, zFloat32, 0}, {true, zFloat64, 0}, {false, zArray0, 1}, {false, zArray1, 0}, {false, zChan, 1}, {true, zFunc, 0}, {true, zIface, 0}, {false, zMap, 1}, {false, zSlice, 1}, {false, zString, 1}, {true, zStruct, 0}, {true, zBoolPtr, 0}, {true, zIntPtr, 0}, {true, zInt8Ptr, 0}, {true, zInt16Ptr, 0}, {true, zInt32Ptr, 0}, {true, zInt64Ptr, 0}, {true, zUintPtr, 0}, {true, zUint8Ptr, 0}, {true, zUint16Ptr, 0}, {true, zUint32Ptr, 0}, {true, zUint64Ptr, 0}, {true, zUintptrPtr, 0}, {true, zFloat32Ptr, 0}, {true, zFloat64Ptr, 0}, // {true, zArray0Ptr, 0}, // {true, zArray1Ptr, 0}, {true, zChanPtr, 0}, {true, zFuncPtr, 0}, {true, zIfacePtr, 0}, {true, zMapPtr, 0}, {true, zSlicePtr, 0}, {true, zStringPtr, 0}, {true, zStructPtr, 0}, {true, zMyInt, 0}, {false, zMyString, 1}, {false, zJSON, 1}, {true, zJSONPtr, 0}, {true, zTime, 0}, } for _, v := range cases { t.Run("", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() if v.panic { t.Panic(func() { t.Len(v.actual, v.len) }) } else { todo.Len(v.actual, v.len) t.NotLen(v.actual, v.len) } }) } todo := t.TODO() t.Len(zArray0, 0) t.Len(zArray1, 1) c := make(chan int, 5) t.Len(c, 0) todo.NotLen(c, 0) c <- 42 t.Len(c, 1) todo.NotLen(c, 1) m := make(map[string]int, 10) t.Len(m, 0) m["one"] = 1 m["ten"] = 10 t.Len(m, 2) t.Len(json.RawMessage("тест"), 8) t.Len([]rune("тест"), 4) t.Len(myString("test"), 4) t.Len("тест", 8) } func TestCheckerOrdered(t *testing.T) { cases := []struct { panic bool min any mid any max any }{ {true, nil, nil, nil}, {true, zBool, xBool, xBool}, {false, xInt, xInt + 1, xInt + 2}, {false, xInt8, xInt8 + 1, xInt8 + 2}, {false, xInt16, xInt16 + 1, xInt16 + 2}, {false, xInt32, xInt32 + 1, xInt32 + 2}, {false, xInt64, xInt64 + 1, xInt64 + 2}, {false, xUint, xUint + 1, xUint + 2}, {false, xUint8, xUint8 + 1, xUint8 + 2}, {false, xUint16, xUint16 + 1, xUint16 + 2}, {false, xUint32, xUint32 + 1, xUint32 + 2}, {false, xUint64, xUint64 + 1, xUint64 + 2}, {false, xUintptr, xUintptr + 1, xUintptr + 2}, {false, xFloat32, xFloat32 + 1, xFloat32 + 2}, {false, xFloat64, xFloat64 + 1, xFloat64 + 2}, {true, zArray0, zArray0, zArray0}, {true, zArray1, xArray1, xArray1}, {true, zChan, xChan, xChan}, {true, zFunc, xFunc, xFunc}, {true, zIface, xIface, xIface}, {true, zMap, xMap, xMap}, {true, zSlice, xSlice, xSlice}, {false, xString, xString + "1", xString + "2"}, {true, zStruct, xStruct, xStruct}, {true, zBoolPtr, xBoolPtr, xBoolPtr}, {true, zIntPtr, xIntPtr, xIntPtr}, {true, zInt8Ptr, xInt8Ptr, xInt8Ptr}, {true, zInt16Ptr, xInt16Ptr, xInt16Ptr}, {true, zInt32Ptr, xInt32Ptr, xInt32Ptr}, {true, zInt64Ptr, xInt64Ptr, xInt64Ptr}, {true, zUintPtr, xUintPtr, xUintPtr}, {true, zUint8Ptr, xUint8Ptr, xUint8Ptr}, {true, zUint16Ptr, xUint16Ptr, xUint16Ptr}, {true, zUint32Ptr, xUint32Ptr, xUint32Ptr}, {true, zUint64Ptr, xUint64Ptr, xUint64Ptr}, {true, zUintptrPtr, xUintptrPtr, xUintptrPtr}, {true, zFloat32Ptr, xFloat32Ptr, xFloat32Ptr}, {true, zFloat64Ptr, xFloat64Ptr, xFloat64Ptr}, {true, zArray0Ptr, zArray0Ptr, zArray0Ptr}, {true, zArray1Ptr, xArray1Ptr, xArray1Ptr}, {true, zChanPtr, xChanPtr, xChanPtr}, {true, zFuncPtr, xFuncPtr, xFuncPtr}, {true, zIfacePtr, xIfacePtr, xIfacePtr}, {true, zMapPtr, xMapPtr, xMapPtr}, {true, zSlicePtr, xSlicePtr, xSlicePtr}, {true, zStringPtr, xStringPtr, xStringPtr}, {true, zStructPtr, xStructPtr, xStructPtr}, {false, xMyInt, xMyInt + 1, xMyInt + 2}, {false, xMyString, xMyString + "1", xMyString + "2"}, {true, xJSON, xJSON, xJSON}, {true, xJSONPtr, xJSONPtr, xJSONPtr}, {false, xTime, xTime.Add(time.Millisecond), xTime.Add(time.Second)}, } t.Run("Less", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for _, v := range cases { actual, expected := v.min, v.max if v.panic { t.Panic(func() { t.Less(actual, expected) }) t.Panic(func() { t.LT(actual, expected) }) t.Panic(func() { t.LessOrEqual(actual, expected) }) t.Panic(func() { t.LE(actual, expected) }) } else { t.Less(actual, expected) t.LT(actual, expected) t.LessOrEqual(actual, expected) t.LessOrEqual(actual, actual) t.LE(actual, expected) t.LE(actual, actual) actual, expected = expected, actual todo.Less(actual, expected) todo.LT(actual, expected) todo.LessOrEqual(actual, expected) todo.LE(actual, expected) } } }) t.Run("Greater", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for _, v := range cases { actual, expected := v.min, v.max if v.panic { t.Panic(func() { t.Greater(actual, expected) }) t.Panic(func() { t.GT(actual, expected) }) t.Panic(func() { t.GreaterOrEqual(actual, expected) }) t.Panic(func() { t.GE(actual, expected) }) } else { todo.Greater(actual, expected) todo.GT(actual, expected) todo.GreaterOrEqual(actual, expected) todo.GE(actual, expected) actual, expected = expected, actual t.Greater(actual, expected) t.GT(actual, expected) t.GreaterOrEqual(actual, expected) t.GreaterOrEqual(actual, actual) t.GE(actual, expected) t.GE(actual, actual) } } }) t.Run("Between", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for _, v := range cases { minimum, middle, maximum := v.min, v.mid, v.max if v.panic { t.Panic(func() { t.Between(middle, minimum, maximum) }) t.Panic(func() { t.BetweenOrEqual(middle, minimum, maximum) }) t.Panic(func() { t.NotBetween(minimum, middle, maximum) }) t.Panic(func() { t.NotBetweenOrEqual(minimum, middle, maximum) }) } else { t.Between(middle, minimum, maximum) t.BetweenOrEqual(middle, minimum, maximum) t.BetweenOrEqual(middle, middle, maximum) t.BetweenOrEqual(middle, minimum, middle) todo.NotBetween(middle, minimum, maximum) todo.NotBetweenOrEqual(middle, minimum, maximum) todo.NotBetweenOrEqual(middle, middle, maximum) todo.NotBetweenOrEqual(middle, minimum, middle) t.NotBetween(minimum, middle, maximum) t.NotBetween(maximum, minimum, middle) t.NotBetweenOrEqual(minimum, middle, maximum) t.NotBetweenOrEqual(maximum, minimum, middle) todo.Between(minimum, middle, maximum) todo.Between(maximum, minimum, middle) todo.BetweenOrEqual(minimum, middle, maximum) todo.BetweenOrEqual(maximum, minimum, middle) } } }) } func TestCheckerApprox(t *testing.T) { cases := []struct { panic bool actual any expected any delta any smape float64 }{ {true, nil, nil, nil, 0}, {true, zBool, xBool, xBool, 0}, {false, xInt, xInt + 5, 7, 10.0}, {false, xInt8, xInt8 + 5, 7, 50.0}, {false, xInt16, xInt16 + 5, 7, 20.0}, {false, xInt32, xInt32 + 5, 7, 10.0}, {false, xInt64, xInt64 + 5, 7, 5.0}, {false, xUint, xUint + 5, uint(7), 6.0}, {false, xUint8, xUint8 + 5, uint(7), 30.0}, {false, xUint16, xUint16 + 5, uint(7), 20.0}, {false, xUint32, xUint32 + 5, uint(7), 10.0}, {false, xUint64, xUint64 + 5, uint(7), 5.0}, {false, xUintptr, xUintptr + 5, uint(7), 0.0000001}, {false, xFloat32, xFloat32 - 5, 7.0, 50.0}, {false, xFloat64, xFloat64 + 5, 7.0, 33.0}, {true, zArray0, zArray0, zArray0, 0}, {true, zArray1, xArray1, xArray1, 0}, {true, zChan, xChan, xChan, 0}, {true, zFunc, xFunc, xFunc, 0}, {true, zIface, xIface, xIface, 0}, {true, zMap, xMap, xMap, 0}, {true, zSlice, xSlice, xSlice, 0}, {true, xString, xString, xString, 0}, {true, zStruct, xStruct, xStruct, 0}, {true, zBoolPtr, xBoolPtr, xBoolPtr, 0}, {true, zIntPtr, xIntPtr, xIntPtr, 0}, {true, zInt8Ptr, xInt8Ptr, xInt8Ptr, 0}, {true, zInt16Ptr, xInt16Ptr, xInt16Ptr, 0}, {true, zInt32Ptr, xInt32Ptr, xInt32Ptr, 0}, {true, zInt64Ptr, xInt64Ptr, xInt64Ptr, 0}, {true, zUintPtr, xUintPtr, xUintPtr, 0}, {true, zUint8Ptr, xUint8Ptr, xUint8Ptr, 0}, {true, zUint16Ptr, xUint16Ptr, xUint16Ptr, 0}, {true, zUint32Ptr, xUint32Ptr, xUint32Ptr, 0}, {true, zUint64Ptr, xUint64Ptr, xUint64Ptr, 0}, {true, zUintptrPtr, xUintptrPtr, xUintptrPtr, 0}, {true, zFloat32Ptr, xFloat32Ptr, xFloat32Ptr, 0}, {true, zFloat64Ptr, xFloat64Ptr, xFloat64Ptr, 0}, {true, zArray0Ptr, zArray0Ptr, zArray0Ptr, 0}, {true, zArray1Ptr, xArray1Ptr, xArray1Ptr, 0}, {true, zChanPtr, xChanPtr, xChanPtr, 0}, {true, zFuncPtr, xFuncPtr, xFuncPtr, 0}, {true, zIfacePtr, xIfacePtr, xIfacePtr, 0}, {true, zMapPtr, xMapPtr, xMapPtr, 0}, {true, zSlicePtr, xSlicePtr, xSlicePtr, 0}, {true, zStringPtr, xStringPtr, xStringPtr, 0}, {true, zStructPtr, xStructPtr, xStructPtr, 0}, {false, xMyInt, xMyInt + 5, 7, 0.01}, {true, xMyString, xMyString, xMyString, 0}, {true, xJSON, xJSON, xJSON, 0}, {true, xJSONPtr, xJSONPtr, xJSONPtr, 0}, {false, xTime, xTime.Add(5 * time.Second), 7 * time.Second, 0}, } t.Run("Delta", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for _, v := range cases { if v.panic { t.Panic(func() { t.InDelta(v.actual, v.expected, v.delta) }) t.Panic(func() { t.NotInDelta(v.actual, v.expected, v.delta) }) } else { t.InDelta(v.actual, v.expected, v.delta) t.InDelta(v.expected, v.actual, v.delta) todo.NotInDelta(v.actual, v.expected, v.delta) todo.NotInDelta(v.expected, v.actual, v.delta) t.NotInDelta(v.actual, v.expected, half(v.delta)) t.NotInDelta(v.expected, v.actual, half(v.delta)) todo.InDelta(v.actual, v.expected, half(v.delta)) todo.InDelta(v.expected, v.actual, half(v.delta)) } } }) t.Run("SMAPE", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for _, v := range cases { if v.panic || v.smape == 0 { t.Panic(func() { t.InSMAPE(v.actual, v.expected, v.smape) }) t.Panic(func() { t.NotInSMAPE(v.actual, v.expected, v.smape) }) } else { t.InSMAPE(v.actual, v.expected, v.smape) t.InSMAPE(v.expected, v.actual, v.smape) todo.NotInSMAPE(v.actual, v.expected, v.smape) todo.NotInSMAPE(v.expected, v.actual, v.smape) t.NotInSMAPE(v.actual, v.expected, half(v.smape).(float64)) t.NotInSMAPE(v.expected, v.actual, half(v.smape).(float64)) todo.InSMAPE(v.actual, v.expected, half(v.smape).(float64)) todo.InSMAPE(v.expected, v.actual, half(v.smape).(float64)) } } t.InSMAPE(0, 0, 0.5) t.InSMAPE(0.0, 0.0, 0.5) }) } func half(v any) any { if v, ok := v.(time.Duration); ok { return v / 2 } switch val := reflect.ValueOf(v); val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return val.Int() / 2 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return val.Uint() / 2 case reflect.Float32, reflect.Float64: return val.Float() / 2 case reflect.Complex128, reflect.Complex64: // ??? // No meaningful "half": case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct, reflect.Bool, reflect.String: case reflect.Chan, reflect.Func, reflect.Interface, reflect.Invalid: case reflect.Ptr, reflect.UnsafePointer: } panic(fmt.Sprintf("can't get half from %#v", v)) } func TestCheckerSubstring(t *testing.T) { cases := []struct { panic bool actual any prefix string suffix string }{ {true, xBool, "", ""}, {true, xInt, "", ""}, {true, xInt8, "", ""}, {true, xInt16, "", ""}, {true, xInt32, "", ""}, {true, xInt64, "", ""}, {true, xUint, "", ""}, {true, xUint8, "", ""}, {true, xUint16, "", ""}, {true, xUint32, "", ""}, {true, xUint64, "", ""}, {true, xUintptr, "", ""}, {true, xFloat32, "", ""}, {true, xFloat64, "", ""}, {true, zArray0, "", ""}, {true, xArray1, "", ""}, {true, xChan, "", ""}, {true, xFunc, "", ""}, {true, xIface, "", ""}, {true, xMap, "", ""}, {true, xSlice, "", ""}, {false, xString, ""}, {true, xStruct, "", ""}, {true, xBoolPtr, "", ""}, {true, xIntPtr, "", ""}, {true, xInt8Ptr, "", ""}, {true, xInt16Ptr, "", ""}, {true, xInt32Ptr, "", ""}, {true, xInt64Ptr, "", ""}, {true, xUintPtr, "", ""}, {true, xUint8Ptr, "", ""}, {true, xUint16Ptr, "", ""}, {true, xUint32Ptr, "", ""}, {true, xUint64Ptr, "", ""}, {true, xUintptrPtr, "", ""}, {true, xFloat32Ptr, "", ""}, {true, xFloat64Ptr, "", ""}, {true, zArray0Ptr, "", ""}, {true, xArray1Ptr, "", ""}, {true, xChanPtr, "", ""}, {true, xFuncPtr, "", ""}, {true, xIfacePtr, "", ""}, {true, xMapPtr, "", ""}, {true, xSlicePtr, "", ""}, {true, xStringPtr, "", ""}, {true, xStructPtr, "", ""}, {true, xMyInt, "", ""}, {false, xMyString, "xy", "yz"}, {false, xJSON, "{", "}"}, {true, xJSONPtr, "", ""}, {false, zTime, "0001-01-01", "UTC"}, {false, []byte("String"), "Str", "ing"}, {false, []rune("Symbol"), "Sym", "bol"}, {false, time.Sunday, "Sun", "day"}, {false, io.EOF, "EO", "OF"}, } substrings := []struct { prefix any suffix any }{ {time.Sunday.String(), time.Monday.String()}, {[]byte(time.Sunday.String()), []byte(time.Monday.String())}, {[]rune(time.Sunday.String()), []rune(time.Monday.String())}, {time.Sunday, time.Monday}, {errors.New(time.Sunday.String()), errors.New(time.Monday.String())}, } t.Run("HasPrefix", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for i, v := range cases { msg := fmt.Sprintf("case %d: %#v, %#v, %#v", i, v.actual, v.prefix, v.suffix) if v.panic { t.Panic(func() { t.HasPrefix(v.actual, v.prefix) }, msg) t.Panic(func() { t.NotHasPrefix(v.actual, v.prefix) }, msg) t.Panic(func() { t.HasPrefix("", v.actual) }, msg) t.Panic(func() { t.NotHasPrefix("", v.actual) }, msg) } else { t.HasPrefix(v.actual, v.prefix, msg) todo.HasPrefix(v.actual, v.suffix, msg) t.NotHasPrefix(v.actual, v.suffix, msg) todo.NotHasPrefix(v.actual, v.prefix, msg) } } for _, v := range substrings { t.HasPrefix("Sunday Monday", v.prefix) todo.NotHasPrefix("Sunday Monday", v.prefix) } todo.HasPrefix(nil, "") t.NotHasPrefix(nil, "") todo.HasPrefix("", nil) t.NotHasPrefix("", nil) t.HasPrefix("", "") todo.NotHasPrefix("", "") t.HasPrefix("x", "") todo.NotHasPrefix("x", "") }) t.Run("HasSuffix", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() for i, v := range cases { msg := fmt.Sprintf("case %d: %#v, %#v, %#v", i, v.actual, v.suffix, v.suffix) if v.panic { t.Panic(func() { t.HasSuffix(v.actual, v.suffix) }, msg) t.Panic(func() { t.NotHasSuffix(v.actual, v.suffix) }, msg) t.Panic(func() { t.HasSuffix("", v.actual) }, msg) t.Panic(func() { t.NotHasSuffix("", v.actual) }, msg) } else { t.HasSuffix(v.actual, v.suffix, msg) todo.HasSuffix(v.actual, v.prefix, msg) t.NotHasSuffix(v.actual, v.prefix, msg) todo.NotHasSuffix(v.actual, v.suffix, msg) } } for _, v := range substrings { t.HasSuffix("Sunday Monday", v.suffix) todo.NotHasSuffix("Sunday Monday", v.suffix) } todo.HasSuffix(nil, "") t.NotHasSuffix(nil, "") todo.HasSuffix("", nil) t.NotHasSuffix("", nil) t.HasSuffix("", "") todo.NotHasSuffix("", "") t.HasSuffix("x", "") todo.NotHasSuffix("x", "") }) } func TestJSONEqual(tt *testing.T) { t := check.T(tt) todo := t.TODO() cases := []struct { panic bool json any }{ {false, nil}, {true, zBool}, {true, zInt}, {true, zInt8}, {true, zInt16}, {true, zInt32}, {true, zInt64}, {true, zUint}, {true, zUint8}, {true, zUint16}, {true, zUint32}, {true, zUint64}, {true, zUintptr}, {true, zFloat32}, {true, zFloat64}, {true, zArray0}, {true, zArray1}, {true, zChan}, {true, zFunc}, {false, zIface}, // nil {true, zMap}, {true, zSlice}, {false, zString}, {true, zStruct}, {true, zBoolPtr}, {true, zIntPtr}, {true, zInt8Ptr}, {true, zInt16Ptr}, {true, zInt32Ptr}, {true, zInt64Ptr}, {true, zUintPtr}, {true, zUint8Ptr}, {true, zUint16Ptr}, {true, zUint32Ptr}, {true, zUint64Ptr}, {true, zUintptrPtr}, {true, zFloat32Ptr}, {true, zFloat64Ptr}, {true, zArray0Ptr}, {true, zArray1Ptr}, {true, zChanPtr}, {true, zFuncPtr}, {true, zIfacePtr}, {true, zMapPtr}, {true, zSlicePtr}, {true, zStringPtr}, {true, zStructPtr}, {true, zMyInt}, {false, zMyString}, {false, zJSON}, {false, zJSONPtr}, {true, zTime}, {false, []byte(nil)}, {false, []byte{}}, } for i, v := range cases { if v.panic { t.Panic(func() { t.JSONEqual(v.json, `{}`, i) }) t.Panic(func() { t.JSONEqual(`{}`, v.json) }) } else { todo.JSONEqual(v.json, v.json) } } invalid := `{"a":1,"b":[2]` invalidRaw := json.RawMessage(invalid) todo.JSONEqual(invalid, invalid) todo.JSONEqual([]byte(invalid), []byte(invalid)) todo.JSONEqual(&invalidRaw, invalid) todo.JSONEqual(&invalidRaw, invalid+"}") todo.JSONEqual(invalidRaw, []byte(invalid)) t.JSONEqual(invalidRaw, invalidRaw) t.JSONEqual(&invalidRaw, &invalidRaw) t.JSONEqual(&invalidRaw, invalidRaw) t.JSONEqual(invalidRaw, &invalidRaw) validRaw := json.RawMessage(invalid + "}") valid := []any{ `{ "b" : [ 2],"a" :1} `, []byte(` { "b": [2 ],"a": 1}`), validRaw, &validRaw, } for _, actual := range valid { for _, expected := range valid { t.JSONEqual(actual, expected) } } } func TestHasType(tt *testing.T) { t := check.T(tt) todo := t.TODO() vs := []any{ zBool, zInt, zInt8, zInt16, zInt32, zInt64, zUint, zUint8, zUint16, zUint32, zUint64, zUintptr, zFloat32, zFloat64, zArray0, zArray1, zChan, zFunc, zIface, // nil zMap, zSlice, zString, zStruct, zBoolPtr, zIntPtr, zInt8Ptr, zInt16Ptr, zInt32Ptr, zInt64Ptr, zUintPtr, zUint8Ptr, zUint16Ptr, zUint32Ptr, zUint64Ptr, zUintptrPtr, zFloat32Ptr, zFloat64Ptr, zArray0Ptr, zArray1Ptr, zChanPtr, zFuncPtr, zIfacePtr, zMapPtr, zSlicePtr, zStringPtr, zStructPtr, zMyInt, zMyString, zJSON, zJSONPtr, zTime, } for i, actual := range vs { for j, expected := range vs { if i == j { t.HasType(actual, expected) todo.NotHasType(actual, expected) } else { t.NotHasType(actual, expected) todo.HasType(actual, expected) } } } t.HasType(vChan, zChan) t.HasType(vFunc, zFunc) t.HasType(vIface, zIntPtr) t.HasType(vMap, zMap) t.HasType(vSlice, zSlice) var reader io.Reader t.HasType(reader, nil) t.HasType(&reader, (*io.Reader)(nil)) t.NotHasType(&reader, nil) t.HasType(os.Stdin, (*os.File)(nil)) t.NotHasType(os.Stdin, &reader) t.HasType(true, zBool) t.HasType(42, zInt) t.HasType("test", zString) t.HasType([]byte("test"), []byte(nil)) t.HasType([]byte("test"), []byte{}) t.HasType(new(int), zIntPtr) t.NotHasType(json.RawMessage([]byte("test")), []byte("test")) } func TestCheckers(t *testing.T) { t.Run("Err", func(tt *testing.T) { t := check.T(tt) t.Parallel() cases := []struct { err bool deepEqual bool equal bool actual error expected error }{ {true, true, true, nil, nil}, //nolint:dupword // Commented code. // {false, false, false, (*net.OpError)(nil), &net.OpError{}}, {false, false, false, (*net.OpError)(nil), nil}, {false, false, false, nil, (*net.OpError)(nil)}, {true, true, true, (*net.OpError)(nil), (*net.OpError)(nil)}, {true, true, false, &net.OpError{}, &net.OpError{}}, {true, true, true, io.EOF, io.EOF}, {true, true, false, io.EOF, errors.New("EOF")}, {false, false, false, pkgerrors.New("EOF"), io.EOF}, {false, false, false, pkgerrors.New("EOF"), errors.New("EOF")}, {true, true, false, pkgerrors.New("EOF"), pkgerrors.New("EOF")}, {true, false, false, pkgerrors.WithStack(io.EOF), io.EOF}, {true, false, false, pkgerrors.Wrap(io.EOF, "wrapped"), io.EOF}, {true, false, false, pkgerrors.Wrap(io.EOF, "wrapped"), errors.New("EOF")}, {true, false, false, pkgerrors.Wrap(pkgerrors.Wrap(io.EOF, "wrapped"), "wrapped2"), io.EOF}, {true, false, false, fmt.Errorf("wrapped: %w", io.EOF), io.EOF}, {true, false, false, fmt.Errorf("wrapped: %w", io.EOF), errors.New("EOF")}, {false, false, false, fmt.Errorf("wrapped: %w", io.EOF), &myError{"EOF"}}, {false, false, false, fmt.Errorf("wrapped: %w", &myError{"EOF"}), io.EOF}, {true, false, false, fmt.Errorf("wrapped[]: %w %w", io.EOF, &myError{"EOF"}), io.EOF}, {true, false, false, fmt.Errorf("wrapped[]: %w %w", &myError{"EOF"}, io.EOF), io.EOF}, {true, false, false, fmt.Errorf("wrapped[]: %w %w", &myError{"EOF"}, io.EOF), &myError{"EOF"}}, {false, false, false, fmt.Errorf("wrapped[]: %w %w", io.EOF, &myError{"EOF"}), &myError{"EOF"}}, {true, false, false, fmt.Errorf("wrapped2: %w", fmt.Errorf("wrapped: %w", io.EOF)), io.EOF}, {true, false, false, fmt.Errorf("wrapped2: %w", pkgerrors.Wrap(io.EOF, "wrapped")), io.EOF}, {true, false, false, pkgerrors.Wrap(fmt.Errorf("wrapped: %w", io.EOF), "wrapped2"), io.EOF}, {true, false, false, pkgerrors.Wrap(pkgerrors.Wrap(fmt.Errorf("wrapped4: %w", fmt.Errorf("wrapped3: %w", pkgerrors.Wrap(fmt.Errorf("wrapped: %w", io.EOF), "wrapped2"))), "wrapped5"), "wrapped6"), io.EOF}, {false, false, false, io.EOF, &myError{"EOF"}}, {true, true, true, xGRPCErr, xGRPCErr}, {true, true, false, xGRPCErr, status.Error(codes.Unknown, "unknown")}, {false, false, false, xGRPCErr, nil}, } for _, v := range cases { t.Run("", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() if v.err { t.Err(v.actual, v.expected) todo.NotErr(v.actual, v.expected) } else { todo.Err(v.actual, v.expected) t.NotErr(v.actual, v.expected) } if v.equal { t.Equal(v.actual, v.expected) } else { t.NotEqual(v.actual, v.expected) } if v.deepEqual { t.DeepEqual(v.actual, v.expected) } else { t.NotDeepEqual(v.actual, v.expected) } }) } }) t.Run("Panic", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() todo.Panic(func() {}) t.NotPanic(func() {}) t.Panic(func() { panic(nil) }) //nolint:govet // Testing nil panic. todo.NotPanic(func() { panic(nil) }) //nolint:govet // Testing nil panic. t.Panic(func() { panic("") }) t.Panic(func() { panic("oops") }) t.Panic(func() { panic(t) }) todo.NotPanic(func() { panic("") }) todo.NotPanic(func() { panic("oops") }) todo.NotPanic(func() { panic(t) }) }) t.Run("PanicMatch", func(tt *testing.T) { t := check.T(tt) todo := t.TODO() t.Parallel() t.Panic(func() { t.PanicMatch(func() { panic(0) }, nil) }) t.Panic(func() { t.PanicMatch(func() { panic(0) }, t) }) t.NotPanic(func() { t.PanicMatch(func() { panic(0) }, `0`) }) todo.PanicMatch(func() {}, ``) todo.PanicNotMatch(func() {}, ``) todo.PanicMatch(func() {}, `test`) todo.PanicNotMatch(func() {}, `test`) t.PanicMatch(func() { panic(nil) }, ``) //nolint:govet // Testing nil panic. todo.PanicNotMatch(func() { panic(nil) }, ``) //nolint:govet // Testing nil panic. t.PanicMatch(func() { panic(nil) }, `^$`) //nolint:govet // Testing nil panic. todo.PanicNotMatch(func() { panic(nil) }, `^$`) //nolint:govet // Testing nil panic. t.PanicNotMatch(func() { panic(nil) }, `test`) //nolint:govet // Testing nil panic. todo.PanicMatch(func() { panic(nil) }, `test`) //nolint:govet // Testing nil panic. t.PanicMatch(func() { panic("") }, regexp.MustCompile(`^$`)) t.PanicMatch(func() { panic("oops") }, `(?i)Oops`) t.PanicMatch(func() { panic(t) }, `^&check.C{`) t.PanicNotMatch(func() { panic("") }, regexp.MustCompile(`.`)) t.PanicNotMatch(func() { panic("oops") }, `(?-i)Oops`) todo.PanicNotMatch(func() { panic(t) }, `^&check.C{`) }) t.Run("Implements", func(tt *testing.T) { t := check.T(tt) t.Parallel() t.Implements(t, (*testing.TB)(nil)) t.Implements(os.Stdin, (*io.Reader)(nil)) t.Implements(os.Stdin, &xIface) t.Implements(*os.Stdin, (*io.Reader)(nil)) t.Implements(time.Time{}, (*fmt.Stringer)(nil)) t.Implements(&time.Time{}, (*fmt.Stringer)(nil)) t.NotImplements(os.Stdin, (*fmt.Stringer)(nil)) t.NotImplements(&os.Stdin, (*io.Reader)(nil)) t.NotImplements(new(int), (*io.Reader)(nil)) }) } check-1.8.0/color.go000066400000000000000000000015651470667051200142610ustar00rootroot00000000000000package check import ( "os" "strings" ) //nolint:gochecknoglobals // By design. var ( ansiGreen = "\033[32m" ansiYellow = "\033[33m" ansiRed = "\033[31m" ansiReset = "\033[0m" ) func init() { //nolint:gochecknoinits // By design. if !wantColor() { ansiGreen, ansiYellow, ansiRed, ansiReset = "", "", "", "" } } func wantColor() bool { return strings.Contains(os.Getenv("TERM"), "color") && (isTerminal() || os.Getenv("GO_TEST_COLOR") != "") } func colouredDiff(diff string) string { lines := strings.SplitAfter(diff, "\n") for i := range lines { switch { case strings.HasPrefix(lines[i], "--- "): case strings.HasPrefix(lines[i], "+++ "): case strings.HasPrefix(lines[i], "-"): lines[i] = ansiGreen + lines[i] + ansiReset case strings.HasPrefix(lines[i], "+"): lines[i] = ansiRed + lines[i] + ansiReset } } return strings.Join(lines, "") } check-1.8.0/color_bsd.go000066400000000000000000000004371470667051200151060ustar00rootroot00000000000000//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package check import ( "os" "golang.org/x/sys/unix" ) func isTerminal() bool { _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TIOCGETA) return err == nil } check-1.8.0/color_linux.go000066400000000000000000000003151470667051200154700ustar00rootroot00000000000000//go:build linux // +build linux package check import ( "os" "golang.org/x/sys/unix" ) func isTerminal() bool { _, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETS) return err == nil } check-1.8.0/color_other.go000066400000000000000000000003321470667051200154510ustar00rootroot00000000000000//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !windows // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!windows package check func isTerminal() bool { return false } check-1.8.0/color_windows.go000066400000000000000000000003271470667051200160260ustar00rootroot00000000000000//go:build windows // +build windows package check import ( "os" "syscall" ) func isTerminal() bool { var mode uint32 err := syscall.GetConsoleMode(syscall.Handle(os.Stdout.Fd()), &mode) return err == nil } check-1.8.0/doc.go000066400000000000000000000114271470667051200137060ustar00rootroot00000000000000// Package check provide helpers to complement Go testing package. // // # Features // // This package is like testify/assert on steroids. :) // // - Compelling output from failed tests: // - Very easy-to-read dumps for expected and actual values. // - Same text diff you loved in testify/assert. // - Also visual diff in GoConvey web UI, if you use it (recommended). // - Statistics with amount of passed/failed checks. // - Colored output in terminal. // - 100% compatible with testing package - check package just provide // convenient wrappers for *testing.T methods and doesn't introduce // new concepts like BDD, custom test suite or unusual execution flow. // - All checks you may ever need! :) // - Very easy to add your own check functions. // - Concise, handy and consistent API, without dot-import! // // # Quickstart // // Just wrap each (including subtests) *testing.T using check.T() and write // tests as usually with testing package. Call new methods provided by // this package to have more clean/concise test code and cool dump/diff. // // import "github.com/powerman/check" // // func TestSomething(tt *testing.T) { // t := check.T(tt) // t.Equal(2, 2) // t.Log("You can use new t just like usual *testing.T") // t.Run("Subtests/Parallel example", func(tt *testing.T) { // t := check.T(tt) // t.Parallel() // t.NotEqual(2, 3, "should not be 3!") // obj, err := NewObj() // if t.Nil(err) { // t.Match(obj.field, `^\d+$`) // } // }) // } // // To get optional statistics about executed checkers add: // // func TestMain(m *testing.M) { check.TestMain(m) } // // When use goconvey tool, to get nice diff in web UI add: // // import _ "github.com/smartystreets/goconvey/convey" // // # Hints // // ★ How to check for errors: // // // If you just want nil: // t.Nil(err) // t.Err(err, nil) // // // Check for (absence of) concrete (possibly wrapped) error: // t.Err(err, io.EOF) // t.NotErr(err, io.EOF) // nil is not io.EOF, so it's ok too // // // When need to match by error's text: // t.Match(err, "file.*permission") // // // Use Equal ONLY when checking for same instance: // t.Equal(io.EOF, io.EOF) // this works // t.Equal(io.EOF, errors.New("EOF")) // this doesn't work! // t.Err(io.EOF, errors.New("EOF")) // this works // t.DeepEqual(io.EOF, errors.New("EOF")) // this works too // // ★ Each check returns bool, so you can easily skip problematic code: // // if t.Nil(err) { // t.Match(obj.field, `^\d+$`) // } // // ★ You can turn any check into assertion to stop test immediately: // // t.Must(t.Nil(err)) // // ★ You can turn all checks into assertions to stop test immediately: // // t = t.MustAll() // t.Nil(err) // // ★ You can provide extra description to each check: // // t.Equal(got, want, "Just msg: will Print(), % isn't special") // t.Equal(got, want, "Msg with args: will Printf(): %v", extra) // // ★ There are short synonyms for checks implementing usual ==, !=, etc.: // // t.EQ(got, want) // same as t.Equal // t.NE(got, want) // same as t.NotEqual // t.LT(got, want) // same as t.Less // t.LE(got, want) // same as t.LessOrEqual // t.GT(got, want) // same as t.Greater // t.GE(got, want) // same as t.GreaterOrEqual // // ★ If you need custom check, which isn't available out-of-box - see // Should checker, it'll let you plug in your own checker with ease. // // ★ It will panic when called with arg of wrong type - because this // means bug in your test. // // ★ If you don't see colors in `go test` output it may happens because of // two reasons: either your $TERM doesn't contain substring "color" or // you're running `go test path/to/your/package`. To force colored output // in last case just set this environment variable: // // export GO_TEST_COLOR=1 // // # Contents // // There are few special functions (assertion, custom checkers, etc.). // // Error // Must // MustAll // Should // TODO // // Everything else are just trivial (mostly) checkers which works in // obvious way and accept values of any types which makes sense (and // panics on everything else). // // Nil NotNil // Zero NotZero // True False // // Equal NotEqual EQ NE // DeepEqual NotDeepEqual // Err NotErr // BytesEqual NotBytesEqual // JSONEqual // // Greater LessOrEqual GT LE // Less GreaterOrEqual LT GE // Between NotBetween // BetweenOrEqual NotBetweenOrEqual // InDelta NotInDelta // InSMAPE NotInSMAPE // // Len NotLen // Match NotMatch // HasPrefix NotHasPrefix // HasSuffix NotHasSuffix // HasKey NotHasKey // Contains NotContains // // HasType NotHasType // Implements NotImplements // // Panic NotPanic // PanicMatch PanicNotMatch package check check-1.8.0/dump.go000066400000000000000000000074271470667051200141130ustar00rootroot00000000000000package check import ( "bytes" "encoding/json" "fmt" "reflect" "strconv" "strings" "unicode/utf8" "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" ) //nolint:gochecknoglobals // Const. var spewCfg = spew.ConfigState{ Indent: " ", DisablePointerAddresses: true, DisableCapacities: true, SortKeys: true, SpewKeys: true, } type dump struct { dump string indirectType reflect.Type } // String returns dump of value given to newDump. func (v dump) String() string { return v.dump } func (v dump) diff(expected dump) string { if v.indirectType != expected.indirectType { return "" } if !strings.ContainsRune(v.dump[:len(v.dump)-1], '\n') && !strings.ContainsRune(expected.dump[:len(expected.dump)-1], '\n') { return "" } diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ A: difflib.SplitLines(expected.dump), B: difflib.SplitLines(v.dump), FromFile: "Expected", FromDate: "", ToFile: "Actual", ToDate: "", Context: 1, }) if err != nil { return "" } return "Diff:\n" + diff } // newDump prepare i dump using spew.Sdump in most cases and custom // improved dump for these cases: // - nil: remove "(interface{})" prefix // - byte: use 0xFF instead of decimal // - rune: use quoted char instead of number for valid runes // - string: use this instead of quoted single-line: // - valid utf8: don't quote ", show multiline strings on separate lines // - invalid utf8: use hexdump like for []byte // // - []byte: same as string instead of hexdump for valid utf8 // - []rune: use quoted char instead of number for valid runes in list // - json.RawMessage: indent, then same as string. func newDump(i any) (d dump) { //nolint:gocyclo,gocognit,funlen,cyclop // By design. d.dump = spewCfg.Sdump(i) if i == nil { d.dump = "\n" return d } val := reflect.ValueOf(i) typ := reflect.TypeOf(i) kind := typ.Kind() if kind == reflect.Ptr { if val.IsNil() { return d } val = val.Elem() typ = typ.Elem() kind = typ.Kind() } d.indirectType = typ switch { case typ == reflect.TypeOf(json.RawMessage(nil)): v := val.Bytes() var buf bytes.Buffer if json.Indent(&buf, v, "", " ") == nil { d.dump = fmt.Sprintf("(%T) (len=%d) '\n%s\n'\n", i, len(v), buf.String()) } case kind == reflect.Uint8: v := byte(val.Uint()) d.dump = fmt.Sprintf("(%T) 0x%02X\n", i, v) case kind == reflect.Int32: v := rune(val.Int()) if utf8.ValidRune(v) { d.dump = fmt.Sprintf("(%T) %q\n", i, v) } case kind == reflect.Slice && typ.Elem().Kind() == reflect.Int32: valid := true for k := 0; k < val.Len() && valid; k++ { valid = valid && utf8.ValidRune(rune(val.Index(k).Int())) } if valid { d.dump = fmt.Sprintf("(%T) %q\n", i, i) } case kind == reflect.String: v := val.String() if utf8.ValidString(v) { d.dump = fmt.Sprintf("(%T) (len=%d) %s\n", i, len(v), quote(v)) } else { d.dump = strings.Replace(spewCfg.Sdump([]byte(v)), "([]uint8)", fmt.Sprintf("(%T)", i), 1) } case kind == reflect.Slice && typ.Elem().Kind() == reflect.Uint8: v := val.Bytes() if len(v) > 0 && utf8.Valid(v) || len(v) == 0 && !val.IsNil() { d.dump = fmt.Sprintf("(%T) (len=%d) %s\n", i, len(v), quote(string(v))) } } return d } // quote like %#v, except keep \n and " unquoted for readability. func quote(s string) string { r := []rune(strconv.Quote(s)) q := r[:0] var multiline, esc bool for _, c := range r[1 : len(r)-1] { if esc { esc = false switch c { case 'n': c = '\n' multiline = true case '"': default: q = append(q, '\\') } } else if c == '\\' { esc = true continue } q = append(q, c) } if multiline { return fmt.Sprintf("'\n%s\n'", string(q)) } return fmt.Sprintf("'%s'", string(q)) } check-1.8.0/dump_test.go000066400000000000000000000055551470667051200151520ustar00rootroot00000000000000package check //nolint:testpackage // Testing unexported identifiers. import ( "encoding/json" "io" "testing" "time" _ "github.com/smartystreets/goconvey/convey" ) func TestDump(tt *testing.T) { t := T(tt) type ( myBool bool myInt int myInt32 int32 myInt64 int64 myUint uint myUint64 uint64 myByte byte myRune rune myUintptr uintptr myFloat64 float64 myString string myRunes []rune myBytes []byte myStruct struct { i int s string } ) var ( j = json.RawMessage(`[{"key":"one","value":1},{"key":"two","value":2}]`) jnil *json.RawMessage ) cases := []struct { improved bool i any }{ {true, nil}, {false, true}, {false, myBool(true)}, {false, -42}, {false, myInt(-42)}, {false, int32(-32)}, {false, myInt32(-32)}, {false, int64(-64)}, {false, myInt64(-64)}, {false, uint(42)}, {false, myUint(42)}, {false, uint64(64)}, {false, myUint64(64)}, {true, byte(10)}, {true, myByte(10)}, {true, byte(255)}, {true, myByte(255)}, {true, rune(0)}, {true, myRune(0)}, {true, ' '}, {true, myRune(' ')}, {true, ' '}, {true, myRune(' ')}, {true, '\n'}, {true, myRune('\n')}, {true, '€'}, {true, myRune('€')}, {false, uintptr(0)}, {false, myUintptr(0)}, {false, uintptr(42)}, {false, myUintptr(42)}, {false, 0.0}, {false, myFloat64(0.0)}, {false, time.Monday}, {false, [0]int{}}, {false, [2]int{}}, {false, []int(nil)}, {false, []int{}}, {false, []int{1: 0}}, {false, chan int(nil)}, {false, make(chan int)}, {false, chan<- int(make(chan int, 2))}, {false, (func())(nil)}, {false, func(_ int) int { return 0 }}, {false, io.EOF}, {false, map[int]int(nil)}, {false, map[int]int{2: 0}}, {false, make(map[int]int, 2)}, {false, (*int)(nil)}, {true, ""}, {true, myString("")}, {true, " "}, {true, myString(" ")}, {true, "\\`'\""}, {true, myString("\\`'\"")}, {true, "€"}, {true, myString("€")}, {true, "\x01\x02\x03\n\xff\xff"}, {true, myString("\x01\x02\x03\n\xff\xff")}, {true, "line1\nline2"}, {true, myString("line1\nline2")}, {false, []byte(nil)}, {false, myBytes(nil)}, {true, []byte{}}, {true, myBytes{}}, {false, []byte("\x01\x02\x03\n\xff\xff")}, {false, myBytes("\x01\x02\x03\n\xff\xff")}, {true, []byte("line1\nvery long line2")}, {true, myBytes("line1\nvery long line2")}, {true, j}, {true, myBytes(j)}, {false, jnil}, {true, &j}, {true, []rune{}}, {true, myRunes{}}, {true, []rune{0, ' ', ' ', '\n', '€'}}, {true, myRunes{0, ' ', ' ', '\n', '€'}}, {false, time.Time{}}, {false, time.Now()}, {false, struct { i int s string }{0, ""}}, {false, myStruct{0, ""}}, } for _, v := range cases { dumpOld, dumpNew := spewCfg.Sdump(v.i), newDump(v.i).String() if v.improved { t.NotEqual(dumpNew, dumpOld) } else { t.Equal(dumpNew, dumpOld) } } } check-1.8.0/flags.go000066400000000000000000000005461470667051200142350ustar00rootroot00000000000000package check import ( "flag" "sync" ) type peekFlags struct { sync.Once conveyJSON bool } //nolint:gochecknoglobals // By design. var flags peekFlags func (p *peekFlags) detect() *peekFlags { flags.Do(func() { flag.Visit(func(f *flag.Flag) { if f.Name == "convey-json" { p.conveyJSON = f.Value.String() == "true" } }) }) return p } check-1.8.0/go.mod000066400000000000000000000011571470667051200137170ustar00rootroot00000000000000module github.com/powerman/check go 1.20 require ( github.com/davecgh/go-spew v1.1.1 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 github.com/powerman/deepequal v0.1.0 github.com/smartystreets/goconvey v1.8.1 golang.org/x/sys v0.26.0 google.golang.org/grpc v1.56.3 google.golang.org/protobuf v1.34.2 ) require ( github.com/golang/protobuf v1.5.3 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/smarty/assertions v1.15.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect ) check-1.8.0/go.sum000066400000000000000000000056701470667051200137500ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/powerman/deepequal v0.1.0 h1:sVwtyTsBuYIvdbLR1O2wzRY63YgPqdGZmk/o80l+C/U= github.com/powerman/deepequal v0.1.0/go.mod h1:3k7aG/slufBhUANdN67o/UPg8i5YaiJ6FmibWX0cn04= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= check-1.8.0/goconvey.go000066400000000000000000000015321470667051200147660ustar00rootroot00000000000000package check import ( "bytes" "encoding/json" "errors" "fmt" "os" "github.com/smartystreets/goconvey/convey/reporting" ) var errNoGoConvey = errors.New("goconvey not detected") func reportToGoConvey(actual, expected, failure string) error { if !flags.detect().conveyJSON { return errNoGoConvey } testFile, testLine, funcLine := callerTestFileLines() report := reporting.ScopeResult{ File: testFile, Line: funcLine, Assertions: []*reporting.AssertionResult{{ File: testFile, Line: testLine, Expected: expected, Actual: actual, Failure: failure, }}, } var buf bytes.Buffer fmt.Fprintln(&buf, reporting.OpenJson) if err := json.NewEncoder(&buf).Encode(report); err != nil { return err } fmt.Fprintln(&buf, ",") fmt.Fprintln(&buf, reporting.CloseJson) _, err := buf.WriteTo(os.Stdout) return err } check-1.8.0/scripts/000077500000000000000000000000001470667051200142745ustar00rootroot00000000000000check-1.8.0/scripts/cover000077500000000000000000000005321470667051200153400ustar00rootroot00000000000000#!/bin/bash export PATH="$PWD/.buildcache/bin:$PATH" set -e -o pipefail go generate 0-tools.go gotestsum -- \ -coverpkg="$(go list ./... | paste -s -d,)" \ -coverprofile .buildcache/cover.out \ "$@" ./... go tool cover -func=.buildcache/cover.out | tail -n 1 | xargs echo test -n "$CI" || go tool cover -html=.buildcache/cover.out check-1.8.0/scripts/test000077500000000000000000000002671470667051200152060ustar00rootroot00000000000000#!/bin/bash export PATH="$PWD/.buildcache/bin:$PATH" set -x -e -o pipefail go generate 0-tools.go shellcheck scripts/* golangci-lint run gotestsum -- -race -timeout=60s "$@" ./... check-1.8.0/stats.go000066400000000000000000000045751470667051200143050ustar00rootroot00000000000000package check import ( "fmt" "os" "sort" "strings" "sync" "testing" ) type counter struct { name string value int force bool color string size int } func (c counter) String() (s string) { if c.value != 0 || c.force { color := c.color if c.value == 0 { color = ansiReset } s = fmt.Sprintf("%s%*d %s%s", color, c.size, c.value, c.name, ansiReset) } else { s = strings.Repeat(" ", c.size+1+len(c.name)) } return s } type testStat struct { name string passed counter forged counter failed counter } func newTestStat(desc string, force bool) *testStat { return &testStat{ name: desc, passed: counter{force: force, name: "passed", color: ansiGreen}, forged: counter{force: force, name: "todo", color: ansiYellow}, failed: counter{force: force, name: "failed", color: ansiRed}, } } func (c testStat) String() string { return fmt.Sprintf("checks: %s %s %s\t%s", c.passed, c.forged, c.failed, c.name) } //nolint:gochecknoglobals // By design. var ( statsMu sync.Mutex stats = make(map[*testing.T]*testStat) ) // Report output statistics about passed/failed checks. // It should be called from TestMain after m.Run(), for ex.: // // func TestMain(m *testing.M) { // code := m.Run() // check.Report() // os.Exit(code) // } // // If this is all you need - just use TestMain instead. func Report() { statsMu.Lock() defer statsMu.Unlock() total := newTestStat("(total)", true) ts := make([]*testing.T, 0, len(stats)) for t := range stats { ts = append(ts, t) total.passed.value += stats[t].passed.value total.forged.value += stats[t].forged.value total.failed.value += stats[t].failed.value } total.passed.size = digits(total.passed.value) total.forged.size = digits(total.forged.value) total.failed.size = digits(total.failed.value) if testing.Verbose() { sort.Slice(ts, func(a, b int) bool { return ts[a].Name() < ts[b].Name() }) for _, t := range ts { stats[t].passed.size = total.passed.size stats[t].forged.size = total.forged.size stats[t].failed.size = total.failed.size fmt.Printf(" %s\n", stats[t]) } } fmt.Printf(" %s\n", total) } // TestMain provides same default implementation as used by testing // package with extra Report call to output statistics. Usage: // // func TestMain(m *testing.M) { check.TestMain(m) } func TestMain(m *testing.M) { code := m.Run() Report() os.Exit(code) //nolint:revive // By design. } check-1.8.0/stats_main_test.go000066400000000000000000000001731470667051200163360ustar00rootroot00000000000000package check_test import ( "testing" "github.com/powerman/check" ) func TestMain(m *testing.M) { check.TestMain(m) } check-1.8.0/util.go000066400000000000000000000025631470667051200141170ustar00rootroot00000000000000package check import ( "fmt" "math" "path/filepath" "reflect" "runtime" "strings" ) func callerTestFileLines() (file string, line int, funcLine int) { pc, file, line, ok := runtime.Caller(0) myfile := file for stack := 1; ok && samePackage(myfile, file); stack++ { pc, file, line, ok = runtime.Caller(stack) } if f := runtime.FuncForPC(pc); f != nil { _, funcLine = f.FileLine(f.Entry()) } return file, line, funcLine } func samePackage(basefile, file string) bool { return filepath.Dir(basefile) == filepath.Dir(file) && !strings.HasSuffix(file, "_test.go") } func callerFuncName(stack int) string { pc, _, _, _ := runtime.Caller(stack + 1) return strings.TrimPrefix(funcNameAt(pc), "(*C).") } func funcName(f any) string { return funcNameAt(reflect.ValueOf(f).Pointer()) } func funcNameAt(pc uintptr) string { name := "" if f := runtime.FuncForPC(pc); f != nil { name = f.Name() if i := strings.LastIndex(name, "/"); i != -1 { name = name[i+1:] } if i := strings.Index(name, "."); i != -1 { name = name[i+1:] } } return name } func format(msg ...any) string { if len(msg) > 1 { return fmt.Sprintf(msg[0].(string), msg[1:]...) } return fmt.Sprint(msg...) } // digits return amount of decimal digits in number. func digits(number int) int { if number == 0 { return 1 } return int(math.Floor(math.Log10(float64(number)) + 1)) } check-1.8.0/util_test.go000066400000000000000000000011271470667051200151510ustar00rootroot00000000000000package check //nolint:testpackage // Testing unexported identifiers. import ( "regexp" "testing" ) func TestFormat(tt *testing.T) { t := T(tt) cases := []struct { args []any want string }{ {[]any{}, ""}, {[]any{"msg"}, "msg"}, {[]any{"%s", "msg"}, "msg"}, {[]any{"one", "two"}, "one%!(EXTRA string=two)"}, {[]any{42}, "42"}, {[]any{regexp.MustCompile(".*")}, ".*"}, } for i, v := range cases { t.Equal(format(v.args...), v.want, i) } } func TestCaller(tt *testing.T) { t := T(tt) t.Equal(callerFuncName(0), "TestCaller") t.Equal(callerFuncName(1000), "") }