pax_global_header00006660000000000000000000000064150503746740014525gustar00rootroot0000000000000052 comment=9c1f04110d049b8fdeea15a8e5d3d40c2bcba3d6 check-1.9.0/000077500000000000000000000000001505037467400126115ustar00rootroot00000000000000check-1.9.0/.editorconfig000066400000000000000000000005341505037467400152700ustar00rootroot00000000000000# Doc: https://EditorConfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true max_line_length = 94 [*.md] indent_size = 2 [*.{yml,yaml}] indent_size = 2 [{*.go,go.mod,go.work}] indent_style = tab indent_size = unset [*.proto] indent_size = 2 check-1.9.0/.gitattributes000066400000000000000000000007251505037467400155100ustar00rootroot00000000000000# /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.9.0/.github/000077500000000000000000000000001505037467400141515ustar00rootroot00000000000000check-1.9.0/.github/commitlint.config.js000066400000000000000000000001101505037467400201220ustar00rootroot00000000000000module.exports = { extends: ['@commitlint/config-conventional'], }; check-1.9.0/.github/dependabot.yml000066400000000000000000000005141505037467400170010ustar00rootroot00000000000000version: 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.9.0/.github/workflows/000077500000000000000000000000001505037467400162065ustar00rootroot00000000000000check-1.9.0/.github/workflows/_dependency-review.yml000066400000000000000000000003441505037467400225060ustar00rootroot00000000000000name: Dependency review on: pull_request: branches: [main] permissions: contents: read pull-requests: write jobs: main: uses: powerman/.github/.github/workflows/dependency-review.yml@main secrets: inherit check-1.9.0/.github/workflows/_lint-pr-title.yml000066400000000000000000000004051505037467400215730ustar00rootroot00000000000000name: Lint PR title on: pull_request_target: types: [opened, edited, synchronize, reopened] branches: [main] permissions: pull-requests: write jobs: main: uses: powerman/.github/.github/workflows/lint-pr-title.yml@main secrets: inherit check-1.9.0/.github/workflows/release.yml000066400000000000000000000024301505037467400203500ustar00rootroot00000000000000name: Release on: push: # To create/update release PR and to make a release. pull_request: # To update release PR after manually changing version for the next release. types: [edited] permissions: contents: write # To create/update release_pr branch, create a release and a tag. pull-requests: write # To create/update PR from release_pr branch. id-token: write # For cosign signing. env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} jobs: release-pr: uses: powerman/workflows/.github/workflows/release-pr.yml@v0.4.3 secrets: TOKEN: ${{ secrets.RELEASE_TOKEN }} # Mark release as non-draft and latest. finalize: needs: [release-pr] if: ${{ needs.release-pr.outputs.result == 'released' }} permissions: contents: write # To update the GitHub release. timeout-minutes: 5 runs-on: ubuntu-latest steps: - name: Publish release uses: softprops/action-gh-release@v2 with: token: ${{ env.GITHUB_TOKEN }} tag_name: ${{ needs.release-pr.outputs.version }} body: ${{ needs.release-pr.outputs.changelog }} draft: false prerelease: ${{ needs.release-pr.outputs.prerelease }} make_latest: ${{ needs.release-pr.outputs.prerelease != 'true' }} check-1.9.0/.github/workflows/test.yml000066400000000000000000000015221505037467400177100ustar00rootroot00000000000000name: Test on: workflow_dispatch: push: branches: [main] pull_request: branches: [main] # Cancel testing of a previous commit for the same branch. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: write # To update gh-badges branch. jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v5 - uses: jdx/mise-action@v2 - run: git reset --hard # Work around https://github.com/jdx/mise/discussions/5910. - uses: powerman/.github/.github/actions/cache-go-and-tools@main - run: mise run ci - uses: powerman/.github/.github/actions/coverage-badge@main if: github.ref_name == 'main' with: total_cmd: "mise run -q cover:go:total | tail -n 1 | sed -e 's/.*)//'" check-1.9.0/.gitignore000066400000000000000000000011621505037467400146010ustar00rootroot00000000000000# /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 (* doesn't match /) to dir "name" anywhere # **/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 … /.cache/ **/*mise.local.toml **/*mise/config.local.toml check-1.9.0/.golangci.yml000066400000000000000000000335531505037467400152060ustar00rootroot00000000000000# Origin: https://github.com/powerman/golangci-lint-strict version 2.4.0 version: "2" linters: default: all disable: - containedctx # Questionable. - contextcheck # Questionable. - cyclop # Prefer gocognit. - dogsled # Questionable (assignment to how many blank identifiers is not okay?). - dupl - forcetypeassert # Questionable (often we actually want panic). - gocyclo # Prefer gocognit. - 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). - wsl_v5 # Questionable (too much style differences, hard to consider). settings: decorder: disable-init-func-first-check: false # `init` funcs have to be declared before all other functions. depguard: rules: main: deny: - pkg: math/rand$ desc: use math/rand/v2 - 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 embeddedstructfieldcheck: forbid-mutex: true 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. fatcontext: check-struct-pointers: true forbidigo: forbid: - pattern: ^print(ln)?$ exclude-godoc-examples: false analyze-types: true funcorder: struct-method: false gochecksumtype: default-signifies-exhaustive: false include-shared-interfaces: true gocognit: min-complexity: 20 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. gomodguard: blocked: versions: - github.com/cenkalti/backoff: version: < 4.0.0 reason: use actual version gosec: excludes: - G104 # Audit errors not checked 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. iface: enable: - identical # Identifies interfaces in the same package that have identical method sets. - unused # Identifies interfaces that are not used anywhere in the same package where the interface is defined. - opaque # Identifies functions that return interfaces, but the actual returned value is always a single concrete implementation. - unexported # Identifies interfaces that are not exported but are used in exported functions or methods. importas: alias: - pkg: errors 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 nilnil: detect-opposite: true 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: enforce-switch-style arguments: - allowNoDefault - name: error-naming - name: error-return - name: error-strings - name: errorf - name: exported - name: file-header - name: file-length-limit - name: filename-format - 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-build-tag - name: redundant-import-alias - name: redundant-test-main-exit - 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-date - 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-format - name: unnecessary-stmt - name: unreachable-code - name: unused-parameter - name: unused-receiver - name: use-any - name: use-errors-new - name: use-fmt-print - 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 msg-style: capitalized 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: rules: json: snake yaml: kebab xml: camel toml: camel bson: camel avro: snake mapstructure: kebab envconfig: upperSnake whatever: snake 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-date-month: true time-month: true time-layout: true crypto-hash: true default-rpc-path: true sql-isolation-level: true tls-signature-scheme: true wrapcheck: report-internal-errors: true exclusions: rules: - path: (.+)\.go$ text: declaration of "(log|err|ctx)" shadows - path: (.+)\.go$ text: 'missing cases in switch of type \S+: \S+_UNSPECIFIED$' - path: _test\.go|testing(_.*)?\.go linters: - bodyclose - dupl - errcheck - forcetypeassert - funlen - gochecknoglobals - gochecknoinits - gocognit - goconst - gosec - maintidx - reassign - source: '[Cc]onst' # 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 - linters: - lll source: '^//go:generate ' paths: - \.[\w-]+\.go$ # Use this pattern to name auto-generated files. formatters: enable: - gci - gofmt - gofumpt - goimports - golines - swaggo settings: 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. no-inline-comments: true no-prefix-comments: true gofmt: rewrite-rules: - pattern: interface{} replacement: any - pattern: a[b:len(a)] replacement: a[b:] golines: max-len: 200 tab-len: 8 exclusions: generated: strict paths: - \.[\w-]+\.go$ # Use this pattern to name auto-generated files. issues: max-issues-per-linter: 0 max-same-issues: 0 output: sort-order: - linter - severity - file # filepath, line, and column. show-stats: false run: timeout: 1m modules-download-mode: readonly severity: default: error check-1.9.0/CHANGELOG.md000066400000000000000000000265021505037467400144270ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.9.0] - 2025-08-17 [1.9.0]: https://github.com/powerman/check/compare/v1.8.0..v1.9.0 ## [1.8.0] - 2024-10-25 ### 📚 Documentation - **(README)** Update by @powerman in [5382254] ### 📦️ Dependencies - **(deps)** Bump google.golang.org/grpc from 1.53.0 to 1.56.3 by @dependabot[bot] in [#89] - **(deps)** Bump golang.org/x/sys from 0.6.0 to 0.26.0 by @dependabot[bot] in [#86] - **(deps)** Bump github.com/smartystreets/goconvey from 1.7.2 to 1.8.1 by @dependabot[bot] in [#85] - **(deps)** Bump google.golang.org/protobuf from 1.30.0 to 1.34.2 by @dependabot[bot] in [#87] [1.8.0]: https://github.com/powerman/check/compare/v1.7.0..v1.8.0 [#89]: https://github.com/powerman/check/pull/89 [#86]: https://github.com/powerman/check/pull/86 [#85]: https://github.com/powerman/check/pull/85 [#87]: https://github.com/powerman/check/pull/87 [5382254]: https://github.com/powerman/check/commit/538225430af4489a383c36a15c23e6ef4dc86ca0 ## [1.7.0] - 2023-03-18 ### 🔔 Changed - Err support multi-errors and errors.Is by @powerman in [6b218cd] [1.7.0]: https://github.com/powerman/check/compare/v1.6.0..v1.7.0 [6b218cd]: https://github.com/powerman/check/commit/6b218cd72255955ba0504260a4f1159145534f19 ## [1.6.0] - 2021-08-15 ### 🔔 Changed - DeepEqual supports .Equal method by @powerman in [#27] [1.6.0]: https://github.com/powerman/check/compare/v1.5.0..v1.6.0 [#27]: https://github.com/powerman/check/pull/27 ## [1.5.0] - 2021-07-02 ### 🚀 Added - Add MustAll by @powerman in [81cbe58] [1.5.0]: https://github.com/powerman/check/compare/v1.4.0..v1.5.0 [81cbe58]: https://github.com/powerman/check/commit/81cbe586fde79267a5fe0dd3111f93cb83589c8d ## [1.4.0] - 2021-06-27 ### 🚀 Added - Add Error by @powerman in [63f7b90] [1.4.0]: https://github.com/powerman/check/compare/v1.3.1..v1.4.0 [63f7b90]: https://github.com/powerman/check/commit/63f7b9084df9d1ea7a5d0aa7705b9f21e9ad6719 ## [1.3.1] - 2021-02-01 ### 🐛 Fixed - **(Err)** Report actual error instead of unwrapped by @powerman in [6fa4756] [1.3.1]: https://github.com/powerman/check/compare/v1.3.0..v1.3.1 [6fa4756]: https://github.com/powerman/check/commit/6fa475608450c67fba35c3377f84db58ac6f7a5e ## [1.3.0] - 2020-11-05 ### 🚀 Added - Add protobuf and gRPC support by @powerman in [#6] [1.3.0]: https://github.com/powerman/check/compare/v1.2.1..v1.3.0 [#6]: https://github.com/powerman/check/pull/6 ## [1.2.1] - 2019-11-07 ### 🚀 Added - Add support for windows by @alisonatwork in [93aa4b1] [1.2.1]: https://github.com/powerman/check/compare/v1.2.0..v1.2.1 [93aa4b1]: https://github.com/powerman/check/commit/93aa4b156beb819afc8868dd547c52bf9ce65a38 ## [1.2.0] - 2019-10-30 ### 🚀 Added - Add support for errors.Unwrap by @powerman in [b24f303] [1.2.0]: https://github.com/powerman/check/compare/v1.1.0..v1.2.0 [b24f303]: https://github.com/powerman/check/commit/b24f303f46c22a6f30a99e37e1ecfb4bad78f48c ## [1.1.0] - 2019-05-26 ### 🚀 Added - Add errors.Cause support by @powerman in [4cdcdfb] [1.1.0]: https://github.com/powerman/check/compare/v1.0.1..v1.1.0 [4cdcdfb]: https://github.com/powerman/check/commit/4cdcdfb6ede67672b4dc8283df66078b36eb0a85 ## [1.0.1] - 2018-11-13 [1.0.1]: https://github.com/powerman/check/compare/v1.0.0..v1.0.1 ## [1.0.0] - 2018-11-10 ### 🔔 Changed - Add coveralls by @powerman in [016f18f] - Replace T{} with added T(), add TODO() by @powerman in [cb216a7] - Improve doc by @powerman in [24d5c10] - Report todo tests by @powerman in [cd799c8] - Update Contents by @powerman in [00da268] - Update doc by @powerman in [abb19f7] - Improve report formatting by @powerman in [2981353] - Improve Equal tests by @powerman in [8f8bdc0] - Improve BytesEqual tests by @powerman in [4c17639] - Improve Contains tests by @powerman in [f8dc6ad] - Improve HasKey tests by @powerman in [a8642a1] - Comment unsafe by @powerman in [bf0888e] - Improve Len tests by @powerman in [4a9ef6c] - Improve tests by @powerman in [75a9524] - Improve numeric tests by @powerman in [64af321] - Mark failed todo checker names with TODO by @powerman in [c754721] - Improve HasType tests by @powerman in [c392d93] - Cleanup tests by @powerman in [034b842] - Improve test coverage by @powerman in [e36403b] - Improve reporting to goconvey by @powerman in [b2ac659] - Add GO_TEST_COLOR by @powerman in [13aa1c9] - Add go.mod by @powerman in [e6010ba] ### 🐛 Fixed - Fix isZero by @powerman in [199e5c9] [1.0.0]: https://github.com/powerman/check/compare/v0.9.0..v1.0.0 [016f18f]: https://github.com/powerman/check/commit/016f18fc814098da2756aada4b6a9522a3b4e96d [cb216a7]: https://github.com/powerman/check/commit/cb216a75e3f2347eec4a14ff01e5bf5fd1f94a99 [24d5c10]: https://github.com/powerman/check/commit/24d5c10044bbebb4b75e3920af404313712704e8 [cd799c8]: https://github.com/powerman/check/commit/cd799c8b7f8da7927d7337a88010554c758910c0 [00da268]: https://github.com/powerman/check/commit/00da26874f019617f0a41ac1c582cb9c433a1bd1 [abb19f7]: https://github.com/powerman/check/commit/abb19f7653dc87a5c7de041f707635d4bb8e863f [2981353]: https://github.com/powerman/check/commit/29813538efb3d015db9b41ec1839b9f69790412d [8f8bdc0]: https://github.com/powerman/check/commit/8f8bdc0a029ce0fd04f3e7ce8f741b63d36a4bba [4c17639]: https://github.com/powerman/check/commit/4c17639100b5d836956b76c2729863f3452272b6 [f8dc6ad]: https://github.com/powerman/check/commit/f8dc6adc4949c732e73940792056c4e1c6218c9f [a8642a1]: https://github.com/powerman/check/commit/a8642a196813c7966df46da39d0f0acf793b1803 [bf0888e]: https://github.com/powerman/check/commit/bf0888e342b03df85485057473edcc2a9e866f17 [199e5c9]: https://github.com/powerman/check/commit/199e5c94e048db83ddc3af4810a5e2c57de9c9dc [4a9ef6c]: https://github.com/powerman/check/commit/4a9ef6c07b9cd3cdd9aec428927969701d685a42 [75a9524]: https://github.com/powerman/check/commit/75a952452d58defc33e3b1259d02a13d888af2e7 [64af321]: https://github.com/powerman/check/commit/64af321e259b79113aad6726363e68bf0aee43f6 [c754721]: https://github.com/powerman/check/commit/c754721f7011e998b745bdac46e85b2798b38f0f [c392d93]: https://github.com/powerman/check/commit/c392d93333291f11934d8bf7f8b5aea5caa5454e [034b842]: https://github.com/powerman/check/commit/034b842a504a77b0f346d7dc0236b554bfd62cba [e36403b]: https://github.com/powerman/check/commit/e36403b215233f8c2936fab4ac1170e197e7f84a [b2ac659]: https://github.com/powerman/check/commit/b2ac65961bc056b651fbd0b679fdd70ac1644b1b [13aa1c9]: https://github.com/powerman/check/commit/13aa1c931498ded72df7cfff481a867aa5ee04d4 [e6010ba]: https://github.com/powerman/check/commit/e6010baaadc2fdbc65753f30c703234a0747d33f ## [0.9.0] - 2017-12-25 ### 🔔 Changed - Initial commit by @powerman in [f213e1d] - Initial implementation by @powerman in [443b1ce] - Add circleci by @powerman in [91c338e] - Require Go 1.9 by @powerman in [a7b7545] - Match(nil, regex) always fail by @powerman in [5381196] - Improve expected/actual output, add diff by @powerman in [6c0f9d7] - Improve dump formatting by @powerman in [9ba9a08] - More doc. Fix Nil. Change Panic. New checkers. by @powerman in [be74982] - Cleanup by @powerman in [20a9c7b] - Update README by @powerman in [3dbd1b9] - Update README by @powerman in [d5af7ef] - Update README by @powerman in [5f59a20] - Add colors in terminal by @powerman in [712bbb9] - Add support for custom checkers by @powerman in [4c16b2b] - Add checks: less/greater/etc. by @powerman in [21a821f] - More doc/tests by @powerman in [c71eac7] - Simplify Should by @powerman in [f06f74c] - Contains use map values, add HasKey by @powerman in [25b2fd8] - Relax matching .(string) by @powerman in [e1ee2a4] - Equal support time by @powerman in [a1c5d26] - NotEqual support time by @powerman in [6b906e7] - Less/Greater support time by @powerman in [11bec60] - Add Between by @powerman in [de3c52b] - Add HasPrefix/HasSuffix by @powerman in [dcfb512] - Add JSONEqual by @powerman in [90ef3c5] - Add HasType, Implements by @powerman in [e3f7879] - Add InDelta by @powerman in [2a81110] - Add InSMAPE by @powerman in [08a829d] - Update README by @powerman in [ed10da8] - Update doc formatting by @powerman in [65bc32b] - Update README by @powerman in [a2866ee] - Add contents by @powerman in [ddcdd86] [0.9.0]: https://github.com/powerman/check/compare/%40%7B10year%7D..v0.9.0 [f213e1d]: https://github.com/powerman/check/commit/f213e1d4629aa64b98cd73fb019308e48e11aaf1 [443b1ce]: https://github.com/powerman/check/commit/443b1ce9f3037526fe7fbc3eac91b87fe82a032c [91c338e]: https://github.com/powerman/check/commit/91c338eaedd4ead4503881818182efe316b2700f [a7b7545]: https://github.com/powerman/check/commit/a7b7545d622cf15d1fdb79710f9c5fa18faa85f5 [5381196]: https://github.com/powerman/check/commit/53811967f8ce1c77d0cfcbb877153e430ad61636 [6c0f9d7]: https://github.com/powerman/check/commit/6c0f9d7635984e409a45db8a24bccceead4050ac [9ba9a08]: https://github.com/powerman/check/commit/9ba9a08c77daf96af405f46627ada24c94b6b4b3 [be74982]: https://github.com/powerman/check/commit/be7498261cb64ce24a6201a1ba7f49804e8b0e79 [20a9c7b]: https://github.com/powerman/check/commit/20a9c7b7b34612b07cbca7e9e727f0797eff3c78 [3dbd1b9]: https://github.com/powerman/check/commit/3dbd1b9d43df77bae925f19a332dcf5c91bf3fb3 [d5af7ef]: https://github.com/powerman/check/commit/d5af7ef4a3923abbf385869bf464a91c3188bd4a [5f59a20]: https://github.com/powerman/check/commit/5f59a20447218bf5661d3be595192e6dcbff3e04 [712bbb9]: https://github.com/powerman/check/commit/712bbb9db5cdc781620982198c9bcafff40b3163 [4c16b2b]: https://github.com/powerman/check/commit/4c16b2bb7c992a308132c409be79f3d42f8c1473 [21a821f]: https://github.com/powerman/check/commit/21a821f9da895018beceb7e904456f9e7c207c0d [c71eac7]: https://github.com/powerman/check/commit/c71eac7a87b4eab46a4571bc58f89bd9b32de965 [f06f74c]: https://github.com/powerman/check/commit/f06f74c8b812172a0e7c8c5a14f7fbfe9f38839e [25b2fd8]: https://github.com/powerman/check/commit/25b2fd8b4f4ad38dd93968ff0e81836ee3b2c635 [e1ee2a4]: https://github.com/powerman/check/commit/e1ee2a48bee4d6063ea3792d64bb8f214b0f99aa [a1c5d26]: https://github.com/powerman/check/commit/a1c5d26178621039643c1f3e3798d2da3c37cad1 [6b906e7]: https://github.com/powerman/check/commit/6b906e7eddee4d774177466166f6dcbea326e663 [11bec60]: https://github.com/powerman/check/commit/11bec60bb5cd00534dbbef375aafbf9c1a4518bb [de3c52b]: https://github.com/powerman/check/commit/de3c52b30825e249a60be4e71b7fe59e0f56b0bc [dcfb512]: https://github.com/powerman/check/commit/dcfb5120aac9f3ba08e4c4af65c75a2353b5b440 [90ef3c5]: https://github.com/powerman/check/commit/90ef3c5fa7c88c5638a39a951faceab833398e99 [e3f7879]: https://github.com/powerman/check/commit/e3f787991d2a75e36e0fa001e9c2412d47b17444 [2a81110]: https://github.com/powerman/check/commit/2a81110aaab1b0eb4d76f19b411f888a46aebdd4 [08a829d]: https://github.com/powerman/check/commit/08a829d719bc226044e45c9402435687beea9abe [ed10da8]: https://github.com/powerman/check/commit/ed10da898ed1ad8f6361435dd38de7c9953e5e4f [65bc32b]: https://github.com/powerman/check/commit/65bc32b096959cf93322c7f2a30c31dffe2ceeca [a2866ee]: https://github.com/powerman/check/commit/a2866eecb8fc7119954e82a2a3f3bb2fc6520cde [ddcdd86]: https://github.com/powerman/check/commit/ddcdd86d5f849a577909e69194088f93c5ca4d51 check-1.9.0/LICENSE000066400000000000000000000020531505037467400136160ustar00rootroot00000000000000MIT 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.9.0/README.md000066400000000000000000000070101505037467400140660ustar00rootroot00000000000000# check [![License MIT](https://img.shields.io/badge/license-MIT-royalblue.svg)](LICENSE) [![Go version](https://img.shields.io/github/go-mod/go-version/powerman/check?color=blue)](https://go.dev/) [![Test](https://img.shields.io/github/actions/workflow/status/powerman/check/test.yml?label=test)](https://github.com/powerman/check/actions/workflows/test.yml) [![Coverage Status](https://raw.githubusercontent.com/powerman/check/gh-badges/coverage.svg)](https://github.com/powerman/check/actions/workflows/test.yml) [![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?color=blue)](https://github.com/powerman/check/releases/latest) [![Go Reference](https://pkg.go.dev/badge/github.com/powerman/check.svg)](https://pkg.go.dev/github.com/powerman/check) 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.9.0/check.go000066400000000000000000001175401505037467400142250ustar00rootroot00000000000000package 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 { 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 { 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 { 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 { 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.9.0/check_test.go000066400000000000000000001367321505037467400152700ustar00rootroot00000000000000//nolint:err113 // 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" ) 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") //nolint:errname // Consistent var name. ) 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,staticcheck // 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) }, `panic called with nil argument`) //nolint:govet // Testing nil panic. todo.PanicNotMatch(func() { panic(nil) }, `panic called with nil argument`) //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.9.0/color.go000066400000000000000000000015651505037467400142650ustar00rootroot00000000000000package 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.9.0/color_bsd.go000066400000000000000000000004371505037467400151120ustar00rootroot00000000000000//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.9.0/color_linux.go000066400000000000000000000002751505037467400155010ustar00rootroot00000000000000//go: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.9.0/color_other.go000066400000000000000000000003321505037467400154550ustar00rootroot00000000000000//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.9.0/color_windows.go000066400000000000000000000003271505037467400160320ustar00rootroot00000000000000//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.9.0/doc.go000066400000000000000000000114271505037467400137120ustar00rootroot00000000000000// 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.9.0/dump.go000066400000000000000000000074271505037467400141170ustar00rootroot00000000000000package 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.9.0/dump_test.go000066400000000000000000000055551505037467400151560ustar00rootroot00000000000000package 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.9.0/flags.go000066400000000000000000000005471505037467400142420ustar00rootroot00000000000000package 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.9.0/go.mod000066400000000000000000000011171505037467400137170ustar00rootroot00000000000000module github.com/powerman/check go 1.24 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.35.0 google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.7 ) require ( github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/smarty/assertions v1.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect ) check-1.9.0/go.sum000066400000000000000000000062521505037467400137510ustar00rootroot00000000000000github.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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY= github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI= 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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= check-1.9.0/goconvey.go000066400000000000000000000015311505037467400147710ustar00rootroot00000000000000package 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) err := json.NewEncoder(&buf).Encode(report) if err != nil { return err } fmt.Fprintln(&buf, ",") fmt.Fprintln(&buf, reporting.CloseJson) _, err = buf.WriteTo(os.Stdout) return err } check-1.9.0/mise.lock000066400000000000000000000012601505037467400144170ustar00rootroot00000000000000[tools.actionlint] version = "1.7.7" backend = "aqua:rhysd/actionlint" [tools.gh] version = "2.76.2" backend = "aqua:cli/cli" [tools.git-cliff] version = "2.10.0" backend = "aqua:orhun/git-cliff" [tools.go] version = "1.25.0" backend = "core:go" [tools."go:github.com/daixiang0/gci"] version = "0.13.7" backend = "go:github.com/daixiang0/gci" [tools."go:golang.org/x/tools/cmd/goimports"] version = "0.36.0" backend = "go:golang.org/x/tools/cmd/goimports" [tools.gofumpt] version = "0.8.0" backend = "aqua:mvdan/gofumpt" [tools.golangci-lint] version = "2.3.0" backend = "aqua:golangci/golangci-lint" [tools.gotestsum] version = "1.12.3" backend = "aqua:gotestyourself/gotestsum" check-1.9.0/mise.toml000066400000000000000000000053161505037467400144500ustar00rootroot00000000000000[settings] experimental = true # Required to use lockfile. lockfile = true # DO NOT FORGET TO `touch mise.lock` if mise.lock does not exist! [tools] go = 'latest' #--- Format # Command goimports updates your Go import lines, adding missing ones and removing unreferenced ones. "go:golang.org/x/tools/cmd/goimports" = "latest" # GCI, a tool that control golang package import order and make it always deterministic. "go:github.com/daixiang0/gci" = "latest" # A stricter gofmt. gofumpt = "latest" #--- Lint # Static checker for GitHub Actions workflow files. actionlint = 'latest' # Fast linters runner for Go. golangci-lint = 'latest' #--- Test # 'go test' runner with output optimized for humans. gotestsum = 'latest' #--- Release # A highly customizable Changelog Generator that follows Conventional Commit specifications. git-cliff = 'latest' # GitHub's official command line tool. gh = 'latest' [vars] cover = '.cache/cover.out' [tasks.'changelog:skip-commit'] description = 'Add commit hash to .cliffignore to exclude from CHANGELOG' usage = 'arg "" help="Git revision (e.g. HEAD or a1b2c4d)"' run = 'git rev-parse --verify "${usage_commit}" >> .cliffignore' [tasks.fmt] description = 'Format all files' depends = ['fmt:*'] [tasks.lint] description = 'Run all linters' depends = ['lint:*'] [tasks.test] description = 'Run all tests' depends = ['test:*'] [tasks.default] description = 'Run all linters and tests' depends = ['lint', 'test'] [tasks.ci] description = 'Run all CI tasks' depends = ['fmt', 'lint', 'test'] run = '! git status --porcelain | tee /dev/stderr | grep -q .' # Is working tree clean after fmt? [tasks.'fmt:go'] description = 'Format Go code' run = 'goimports -w . && gci write -s standard -s default -s localmodule . && gofumpt -w .' [tasks.'lint:workflows'] description = 'Lint GitHub Action workflows' run = 'actionlint' [tasks.'lint:go'] description = 'Lint Go files' run = 'golangci-lint run' [tasks.'test:go'] description = 'Run Go tests for a whole project' wait_for = ['lint:*'] # Avoid interleaved output with linters. run = 'gotestsum -- -race -timeout=60s ./...' [tasks.'cover:go:total'] description = 'Show Go test coverage total' depends = 'cover:go:generate' run = 'go tool cover -func={{vars.cover}} | tail -n 1 | xargs echo' [tasks.'cover:go:browse'] description = 'Show Go test coverage in a browser' depends = 'cover:go:generate' run = 'go tool cover -html={{vars.cover}}' [tasks.'cover:go:generate'] hide = true depends = 'cachedir' sources = ['**/*.go'] outputs = ['{{vars.cover}}'] run = ''' gotestsum -- \ -coverpkg="$(go list ./... | paste -s -d,)" \ -coverprofile {{vars.cover}} \ ./... ''' [tasks.cachedir] hide = true run = 'mkdir -p .cache' check-1.9.0/stats.go000066400000000000000000000045751505037467400143110ustar00rootroot00000000000000package 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.9.0/stats_main_test.go000066400000000000000000000001731505037467400163420ustar00rootroot00000000000000package check_test import ( "testing" "github.com/powerman/check" ) func TestMain(m *testing.M) { check.TestMain(m) } check-1.9.0/util.go000066400000000000000000000025631505037467400141230ustar00rootroot00000000000000package 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.9.0/util_test.go000066400000000000000000000011271505037467400151550ustar00rootroot00000000000000package 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), "") }