Compare commits

...

533 Commits

Author SHA1 Message Date
Bracken 5ac6528bff
Merge pull request #1716 from brackendawson/update-maintainers
Propose Christophe Colombier (ccoVeille) as approver
2025-03-24 18:30:37 +01:00
Bracken Dawson d0e0f4961b
Propose Christophe Colombier (ccoVeille) as approver 2025-03-23 18:36:20 +01:00
Bracken Dawson b561f16e87
Correct maintainers list
arjunmahishi is actually only an approver, at the moment.
2025-03-23 18:33:47 +01:00
Bracken a948a8c402
Merge pull request #1674 from alexandear/refactor/simplify-with-sprintf-q
refactor: use %q and %T to simplify fmt.Sprintf
2025-03-23 14:23:15 +01:00
Bracken c3915e850a
Merge branch 'master' into refactor/simplify-with-sprintf-q 2025-03-23 14:22:18 +01:00
Bracken 16020e8cbc
Merge pull request #1687 from alexandear/docs-format-go-examples
README: apply gofmt to examples
2025-03-23 12:09:57 +01:00
Oleksandr Redko 75df9d50d4 Update mdsf 2025-03-23 12:40:01 +02:00
Oleksandr Redko 33be8f984a Move to jobs.build 2025-03-23 12:20:44 +02:00
Oleksandr Redko a9e8aed155 Remove mistakenly added .ci.gofmt.sh 2025-03-23 12:18:52 +02:00
Bracken 3b8bd9bf7d
Merge pull request #1614 from DevotedHealth/mauclair-call-stack-perf
Call stack perf change for CallerInfo
2025-03-22 23:13:13 +01:00
Bracken 1e7fb5865a
Merge pull request #1709 from techfg/chore/issue-1621-update-docs
chore: update docs for Unset #1621
2025-03-22 12:44:25 +01:00
techfg c6ac9bb91d chore: update per PR feedback 2025-03-21 06:25:01 -07:00
Bracken 65f73866c0
Merge pull request #1710 from greg0ire/doc-goroutine-restriction
Document consequences of calling t.FailNow()
2025-03-21 13:27:31 +01:00
Grégoire Paris b1c9368f81
Improve existing docs 2025-03-21 13:21:25 +01:00
Bracken 5a5ac85551
Merge pull request #1062 from lambdanis/fix-fail-message-formatting
Fix failure message formatting for Positive and Negative asserts
2025-03-21 10:15:07 +01:00
Bracken 53e0c918d4
Merge branch 'master' into fix-fail-message-formatting 2025-03-21 10:10:08 +01:00
Grégoire Paris 89086b0757
Document consequences of calling t.FailNow()
These comments are adapted from t.FailNow()'s own documentation.

Closes #1701
2025-03-19 20:50:44 +01:00
techfg 098128fd10 chore: update docs for Unset #1621 2025-03-18 12:48:09 -07:00
Bracken f784abc221
Merge pull request #1345 from craig65535/fix-assert-erroras
assert.ErrorAs: log target type
2025-03-18 18:04:00 +00:00
Oleksandr Redko dfda68b86f Verify formatting of code snippets in CI 2025-01-20 19:49:35 +02:00
Oleksandr Redko 3cf0926564 docs: Format examples in README 2025-01-07 11:35:15 +01:00
Craig Davison c60c3bd7fb dereference target 2025-01-03 14:34:16 -07:00
Craig Davison 1c717c00c1 Add return 2024-12-31 15:35:31 -07:00
Craig Davison ccb5e7f656 Remove redundant returns 2024-12-31 15:26:24 -07:00
Craig Davison ca6698b8a1 assert.ErrorAs: log target type 2024-12-31 15:09:40 -07:00
Bracken 7c367bb7bc
Merge pull request #1684 from alexandear/refactor-replace-deprecated-ioutil
Replace deprecated io/ioutil with io and os
2024-12-21 13:10:41 +00:00
Bracken e6a990c21d
Merge pull request #1688 from alexandear/docs-fix-typo
docs: Fix typo in README
2024-12-21 13:09:58 +00:00
Mike Auclair cfee2346d7 review feedback 2024-12-17 18:18:56 +00:00
Oleksandr Redko f8c628e5a1 docs: Fix typo in README 2024-12-16 23:14:16 +02:00
Oleksandr Redko 014ae9a7a4 Replace deprecated io/ioutil with io and os 2024-12-10 14:21:03 +02:00
Oleksandr Redko 30f3cef5ad Apply suggestions from code review
Co-authored-by: Olivier Mengué <dolmen@cpan.org>
2024-12-10 14:08:46 +02:00
Oleksandr Redko d57bac8721 refactor: use %q to simplify fmt.Sprintf 2024-11-24 18:28:03 +02:00
Bracken 89cbdd9e7b
Merge pull request #1626 from arjun-1/fix-functional-options-diff-indirect-calls
fix: compare functional option names for indirect calls
2024-11-12 22:58:45 +00:00
Bracken 07bac606be
Merge pull request #1667 from sikehish/flaky
Increase timeouts in Test_Mock_Called_blocks to reduce flakiness in CI
2024-10-28 20:19:38 +00:00
sikehish 716de8dff4 Increase timeouts in Test_Mock_Called_blocks to reduce flakiness in CI 2024-10-29 00:00:56 +05:30
Hisham Akmal 118fb83466
NotSame should fail if args are not pointers #1661 (#1664)
## Summary
Reduces the confusion assosciated with NotSame that previously would
check nothing if any of the values is not a pointer. The changes made
were tested using TestSame, TestNotSame, and Test_samePointers tests,
and the changes did clear the tests.

## Changes
1. Modified samePointers to return another bool value(ok) that would
determine if the 2 values are of pointer type or not, while the returned
"same" bool value would determine if the 2 pointers are same.
2. Modified assert.NotSame to call Fail() if the 2 values are either
i)pointers pointing to the same address or ii)either/both of the values
are not pointers.
3. Modified assert.Same to call Fail() if the 2 values are either
i)pointers not pointing to the same address or ii)either/both of the
values are not pointers.
4. Modified Test_samePointers to handle the new behavior of samePointers
by checking both if the inputs are pointers (ok) and if they point to
the same object (same).

## Motivation
Ensure NotSame accurately verifies pointer sameness by handling
non-pointer inputs explicitly, improving clarity and reducing potential
misuse.

## Related issues
Closes #1661

---------

Co-authored-by: Bracken <abdawson@gmail.com>
2024-10-28 11:08:04 +00:00
Arjun Dhawan 7d99b2b43d attempt 2 2024-10-27 08:07:57 +01:00
Arjun Dhawan 05f87c0160 more similar 2024-10-27 08:05:00 +01:00
Arjun Dhawan ea7129e006 better fmt 2024-10-27 08:02:53 +01:00
Bracken a1b9c9efe3
Merge pull request #1663 from ybrustin/master
Fix issue #1662 (comparing infs should fail)
2024-10-26 19:43:05 +01:00
Bracken 8302de98b1
Merge branch 'master' into master 2024-10-26 19:41:18 +01:00
Bracken 89352f7958
Merge pull request #1518 from hendrywiranto/adjust-readme-remove-v2
README: replace Testify V2 notice with @dolmen's V2 manifesto
2024-10-26 19:33:39 +01:00
hendry.wiranto 2780579e15 review: use proper github md 2024-10-26 23:18:03 +07:00
hendry.wiranto 8f049b0122 link maintainer manifesto on readme instead of v2 notice 2024-10-26 23:18:03 +07:00
Arjun Dhawan be992afabf trial 2024-10-24 13:28:30 +02:00
Arjun Dhawan fb67df6392 no regression 2024-10-24 11:40:45 +02:00
Arjun Dhawan 55bac84354 reword tests 2024-10-24 11:30:00 +02:00
Arjun Dhawan 822223ec34 fix name regression 2024-10-24 11:30:00 +02:00
Arjun Dhawan 22d3bd5def in order to remain compatible with go1..19, don't compare function names (but do include them in the diff output) 2024-10-24 11:30:00 +02:00
Arjun Dhawan 9c174eb41c fix: compare functional option names for indirect calls
Closes Functional Options testing broken for indirect calls #1380
2024-10-24 11:30:00 +02:00
Bracken 7efaf15f33
Merge pull request #1586 from redachl/fix/equal-exported-values-accepts-everything
assert.EqualExportedValues: accepts everything
2024-10-23 23:39:08 +01:00
Bracken dce9e58ee3
Merge branch 'master' into fix/equal-exported-values-accepts-everything 2024-10-23 23:37:57 +01:00
Bracken 75a239b2fd
Merge pull request #1610 from Neokil/generate-better-comments-for-require-package
Generate better comments for require package
2024-10-23 23:37:20 +01:00
Bracken f9ccf14f6f
Merge branch 'master' into generate-better-comments-for-require-package 2024-10-23 23:36:08 +01:00
Yaroslav Brustinov 6555fd4da6 Fix issue #1662 (comparing infs should fail) 2024-10-16 17:25:23 +03:00
Bracken a012e45d18
Merge pull request #1636 from rainhq/jayd3e.fix_return_arguments
Record Return Arguments of a Call
2024-10-16 14:07:58 +01:00
Simon Schulte 3b2754b72f remove failure note on require-comments 2024-10-15 13:54:51 +02:00
Bracken 2fc4e39394
Merge pull request #1421 from stevenh/fix/suite-test-failures
suite: fix test failures
2024-10-06 16:49:23 +01:00
Bracken f2227519d6
Merge branch 'master' into fix/suite-test-failures 2024-10-06 16:48:07 +01:00
Bracken 50d5b7e695
Merge pull request #1651 from alexandear/mock-fix-not-before-comment
mock: fix doc comment for NotBefore
2024-10-04 16:15:03 +01:00
Bracken 55ebaca027
Merge pull request #1650 from alexandear/assert-fix-comment-typos
assert: fix typos in comments
2024-10-04 16:12:39 +01:00
Oleksandr Redko ba3e7c34d5 mock: fix doc comment for NotBefore 2024-10-04 18:06:40 +03:00
Oleksandr Redko 2063e81696 assert: fix typos in comments 2024-10-04 18:02:45 +03:00
Bracken 7f489726a5
Merge pull request #1129 from palsivertsen/not-error-as
Add assertion for NotErrorAs
2024-10-04 13:38:32 +01:00
Pål Sivertsen f844b269df Review: Expand NotErrorAs func docs
https://github.com/stretchr/testify/pull/1129#discussion_r1787490770
2024-10-04 12:43:45 +02:00
Pål Sivertsen dc100b1be3 Review: Drop doc line and fix typo
Review feedback:
https://github.com/stretchr/testify/pull/1129#discussion_r1786495803
2024-10-04 12:43:45 +02:00
Pal Sivertsen aade8450b3 Improve tests for ErrorIs/ErrorAs
Checks that the assertion result matches what's set in `testing.T`.
2024-10-04 12:43:45 +02:00
Pal Sivertsen 3380867632 Add NotErrorAs assertion
The library already had assertions for `ErrorIs`, `NotErrorIs` and
`ErrorAs`. This commit adds the `NotErrorAs` assertion which is the
inverse of `ErrorAs`.
2024-10-04 12:43:45 +02:00
Olivier Mengué 95d1f9c2ad
Merge pull request #1645 from brackendawson/no-parallel-suite
Document suite's lack of support for t.Parallel
2024-10-04 12:26:06 +02:00
Bracken Dawson fed9ee68dc Document suite's lack of support for t.Parallel 2024-10-04 12:24:15 +02:00
Bracken 5dc934f9aa
Merge pull request #1629 from HaraldNordgren/patch-1
.github/workflows: Run tests for Go 1.22
2024-10-03 17:22:59 +01:00
Bracken c4b8421a1f
Merge pull request #1637 from ReyOrtiz/master
mock: in order mock calls
2024-10-02 17:23:18 +01:00
Bracken 85a526818c
Merge branch 'master' into master 2024-10-02 17:21:55 +01:00
Bracken 16a09b7086
Merge pull request #1644 from spirin/unexpected-method-caller-info
mock: caller information for unexpected method call
2024-10-01 10:31:37 +01:00
Spirin f3f7181b01
Merge branch 'master' into unexpected-method-caller-info 2024-10-01 02:22:07 +03:00
spirin d62ca68bf5 tests 2024-10-01 02:21:38 +03:00
Bracken 428847e363
Merge pull request #1571 from stretchr/mock-simplify-FunctionalOptions-impl
mock: simplify implementation of FunctionalOptions
2024-09-30 23:30:50 +01:00
Bracken 72e3b61028
Merge branch 'master' into mock-simplify-FunctionalOptions-impl 2024-09-30 23:28:38 +01:00
spirin ea6964c2e9 mock: caller information for unexpected method call 2024-09-30 21:33:46 +03:00
Reynier Ortiz f17409f81f mock: in order mock calls
(requested changes applied)
2024-09-25 12:03:55 -04:00
Reynier Ortiz bdb1271ed8 mock: in order mock calls 2024-09-25 11:26:01 -04:00
Steven Hartland e943930404 fix(suite): test failures
Fix TestFailfastSuite when run with go test flag -count=X where X
greater than 1.
2024-09-09 10:09:18 +01:00
Reynier Ortiz 7268a5bc0b mock: in order mock calls 2024-09-06 09:18:07 -04:00
Joseph Dallago da63673a11
Now properly record the ReturnArguments as part of the call. 2024-08-29 22:05:04 +03:00
Harald Nordgren 52df55490e
.github/workflows: Run tests for Go 1.22 2024-07-24 13:39:26 +02:00
Mike Auclair 7f10816c93 review feedback 2024-06-25 11:50:49 +00:00
Mike Auclair 176474a4c9 cleanup 2024-06-24 20:42:26 +00:00
Mike Auclair 28e0be5092 refill stack frame buffer after it's exhausted 2024-06-24 18:11:07 +00:00
Mike Auclair 4a90eff4ae fix 2024-06-24 16:50:27 +00:00
Mike Auclair 3ca01f4bc3 Stop querying for stack frames multiple times on CallerInfo() 2024-06-24 16:39:18 +00:00
Simon Schulte bc04bb85a2
Merge branch 'master' into generate-better-comments-for-require-package 2024-06-17 09:16:44 +02:00
Vladimir Kochnev b074924938
assert: collect.FailNow() should not panic (#1481)
## Summary
`collect.FailNow()` should exit goroutine without a panic to be usable
with `require` package.

## Changes
`collect.FailNow()` just does `runtime.Goexit()` instead of `panic()`.
For example `FailNow()` from `testing` package [behaves
similarly](https://cs.opensource.google/go/go/+/refs/tags/go1.21.2:src/testing/testing.go;l=973).

## Motivation

I just want `require` package to be usable with `EventuallyWithT` e.g. I
want this example to pass but it panics:

```go
package main

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRequireEventuallyWithT(t *testing.T) {
	state := 0
	require.EventuallyWithT(t, func(c *assert.CollectT) {
		defer func() { state += 1 }()
		require.True(c, state == 2)
	}, 100*time.Millisecond, 10*time.Millisecond)
}
```

See https://go.dev/play/p/Oqd95IT7qxQ

## Related issues
Fixes https://github.com/stretchr/testify/issues/1396
Fixes https://github.com/stretchr/testify/issues/1457
2024-06-13 22:09:51 +02:00
Simon Schulte f71de4a756 updated message 2024-06-13 18:02:19 +02:00
Simon Schulte 592e4e3c00
Merge branch 'stretchr:master' into generate-better-comments-for-require-package 2024-06-13 18:01:22 +02:00
Simon Schulte 044c46a89f
Update require.go.tmpl
Co-authored-by: Olivier Mengué <dolmen@cpan.org>
2024-06-13 15:40:29 +02:00
Maria Ines Parnisari 84619f5c3c
fix: grammar in godoc for NotErrorIs(f) functions (#1607)
Grammar fixes in doc: _asserts that at none_ -> _asserts that none_

$ grep 'that at none' */*.go
assert/assertion_format.go:// NotErrorIsf asserts that at none of the errors in err's chain matches target.
assert/assertion_forward.go:// NotErrorIs asserts that at none of the errors in err's chain matches target.
assert/assertion_forward.go:// NotErrorIsf asserts that at none of the errors in err's chain matches target.
assert/assertions.go:// NotErrorIs asserts that at none of the errors in err's chain matches target.
require/require.go:// NotErrorIs asserts that at none of the errors in err's chain matches target.
require/require.go:// NotErrorIsf asserts that at none of the errors in err's chain matches target.
require/require_forward.go:// NotErrorIs asserts that at none of the errors in err's chain matches target.
require/require_forward.go:// NotErrorIsf asserts that at none of the errors in err's chain matches target.
2024-06-13 14:57:39 +02:00
Simon Schulte 9326036bf5 Generate better comments for require package
The comments for the require package were just copied over
from the assert package when generating the functions.
This could lead to confusion because
1. The code-examples were showing examples using the
assert package instead of the require package
2. The function-documentation was not mentioning that
the functions were calling `t.FailNow()` which is some
critical information when using this package.
2024-06-13 06:52:14 +02:00
Bracken 1b4fca7679
Merge pull request #1600 from hendrywiranto/not-element-match
assert: new assertion NotElementsMatch
2024-05-29 01:08:10 +01:00
hendry.wiranto cb4e70cf8d review: match fail msg with params 2024-05-28 21:36:34 +07:00
hendry.wiranto 7af3ed34c2 feat: new assertion NotElementsMatch 2024-05-28 21:36:34 +07:00
Reda Chlieh df81388b27
Merge branch 'master' into fix/equal-exported-values-accepts-everything 2024-05-23 10:26:47 +02:00
Sean Marciniak 6b275adbf7
Merge pull request #1579 from stretchr/pluggable-yaml-package
assert: make YAML dependency pluggable via build tags
2024-05-23 15:19:21 +09:30
Sean Marciniak b661f0ade2
Merge pull request #1587 from kevinburkesegment/regex-bytes
assert.Regexp: handle []byte array properly
2024-05-22 11:19:21 +09:30
Reda Chlieh 109f4286cf
Merge branch 'master' into fix/equal-exported-values-accepts-everything 2024-05-20 11:32:27 +02:00
Olivier Mengué 3c0c0e6443
Merge pull request #1593 from brackendawson/EqualValuesDoc
Correctly document EqualValues behavior
2024-05-16 13:55:04 +02:00
Bracken Dawson 32766084e4
Correctly document EqualValues behavior 2024-04-29 13:49:40 +01:00
Bracken 8d4dcbbccb
Merge pull request #1569 from stretchr/mock-more-AnythingOfTypeArgument-alternatives
mock: document more alternatives to deprecated AnythingOfTypeArgument
2024-04-23 16:15:28 +00:00
Olivier Mengué 8c324a0bbd
mock: simpler deprecation doc for AnythingOfTypeArgument
Co-authored-by: Bracken <abdawson@gmail.com>
2024-04-23 15:23:21 +02:00
Olivier Mengué f32ff5b3cb
Merge pull request #1575 from stretchr/dependabot/github_actions/softprops/action-gh-release-2
CI: bump softprops/action-gh-release from 1 to 2
2024-04-23 14:56:52 +02:00
dependabot[bot] e33bd6fdd1
build(deps): bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-23 12:37:40 +00:00
Olivier Mengué d4a63f5b89 mock: simplify implementation of FunctionalOptions
Remove unnecessary use of reflect in the implementation of
mock.FunctionalOptions().
2024-04-22 23:10:37 +02:00
Kevin Burke a9e6121b1c
assert: handle []byte array properly
The regexp package works more efficiently on bytes; if you have bytes,
it is easier to pass these directly to assert.Regexp than to convert
them to a string first.

In addition, FindIndex/FindStringIndex are unnecessary here because we
immediately throw away the result - let's just call Match() instead.
2024-04-17 12:18:13 -07:00
Olivier Mengué a61e9e59d6
Merge pull request #1588 from JonCrowther/fix-eventuallywithtf-example
assert: fix example for EventuallyWithT, EventuallyWithTf
2024-04-17 08:33:52 +02:00
Jonathan Crowther 4ec7678c61 Correct the EventuallyWithT and EventuallyWithTf example 2024-04-12 14:19:10 -04:00
Reda Chlieh 42d887f28b
Remove type validation in EqualExportedValues 2024-04-11 22:54:14 +02:00
Ania Kapuścińska 0e5b59666a Fix failure message formatting for Positive and Negative asserts 2024-04-11 17:41:38 +02:00
Bracken 352d2438b9
Merge pull request #1580 from JohnEndson/master
assert: fix typo in comment
2024-04-02 09:13:05 +00:00
Bracken 726249eca2
Merge pull request #1582 from myxo/master
Fix time.Time compare
2024-04-01 15:42:55 +00:00
Olivier Mengué d3dbb19355 assert: make YAML dependency pluggable via build tags
Make the YAML dependency required for {assert,require}.YAMLEq{,f} pluggable.

The implementation can be selected using build tags:
- testify_yaml_default (default): gopkg.in/yaml.v3 is used, like before
- testify_yaml_fail: YAML deserialization is not implemented and always
  fails. So assert.YAMLEq always fails. This is useful if the test suite
  package doesn't use assert.YAMLEq (very common case).
- testify_yaml_custom: the github.com/stretchr/testify/assert/yaml
  package exposes an Unmarshal variable of type func([]byte, any) error
  (same as gopkg.in/yaml.v3) that allows to plug any alternate
  implementation. For example github.com/goccy/go-yaml.Unmarshal.
  This allows to avoid the link constraints of the license of
  gopkg.in/yaml.v3 (see PR stretchr/testify#1120).

Usage: go test -tags testify_yaml_fail

To install the alternate implementation with testify_yaml_custom:

	//go:build testify_yaml_custom

	package my_pkg_test

	import (
		goyaml "github.com/goccy/go-yaml"
		"github.com/stretchr/testify/assert/yaml"
	)

	func init() {
		yaml.Unmarshal = goyaml.Unmarshal
	}
2024-03-31 22:37:21 +02:00
myxo 17b83c52e4 Fix time.Time compare 2024-03-29 08:40:17 +03:00
JohnEndson a2fbbfe71b assert: fix typo in comment
Signed-off-by: JohnEndson <jiayuzhen@outlook.com>
2024-03-29 11:39:16 +08:00
Olivier Mengué be3fbeb943
Merge pull request #1568 from stretchr/doc-testifylint
README: link out to the excellent testifylint
2024-03-28 23:14:32 +01:00
Olivier Mengué edd74b24a1
Merge pull request #1433 from snirye/master
mock: fix FunctionalOptions docs
2024-03-22 14:01:47 +01:00
Bracken 740a5e83fa
Suggest golangci-lint in README.md as well as doc.go
Co-authored-by: Olivier Mengué <dolmen@cpan.org>
2024-03-20 12:51:19 +00:00
Olivier Mengué 404159f5fa
Merge pull request #1566 from stretchr/assert-deprecate-CompareType
assert: deprecate CompareType
2024-03-20 00:49:17 +01:00
Olivier Mengué a155d2a49d assert: restore CompareType, but mark it as deprecated
Reinstate CompareType for full backward compatibility, but mark it as
deprecated.
2024-03-20 00:47:39 +01:00
Olivier Mengué 39442a4e4e assert: drop CompareType
Rename CompareType to compareResult to make it private. This type is
used only in internal functions and is of no use for external users and
should never have been made public.

CompareType has been introduced by 0b4ff03cda
and appeared in releases since v1.6.0.

Note: strictly speaking, this is a breaking change, but no external user
should have ever used assert.CompareType as it made no sense in the assert
API. A search on GitHub has revealed no use of it.
https://github.com/search?q=assert.CompareType+language%3AGo&type=Repositories&ref=advsearch&l=Go&l=
2024-03-20 00:47:39 +01:00
Olivier Mengué 74e1cbebab assert: minor refactor of compareTwoValues tests
Refactor compareTwoValues tests to free the 'compareResult' symbol name
(for incoming use of this name in next commit).
2024-03-20 00:47:39 +01:00
Olivier Mengué a71299064b assert: refactor Test_compareTwoValuesCorrectCompareResult
Refactor Test_compareTwoValuesCorrectCompareResult table test to be more
explicit: "compareTypes" renamed to "allowedResults".
2024-03-20 00:47:39 +01:00
Snir Yehuda da1e1476cb
Merge branch 'master' into master 2024-03-18 09:47:57 +02:00
Olivier Mengué 3c302f75ae
mock: improve doc (#1570)
## Summary
Improve API doc of package
[`mock`](https://pkg.go.dev/github.com/stretchr/testify/mock):
- add godoc links
- indent example code

## Motivation
Readability.
2024-03-10 11:42:04 +01:00
Bracken Dawson 1dedc83b8f
Add testifylint to godocs too
I checked the render locally.
2024-03-07 15:09:29 +00:00
Olivier Mengué aca1890ec1 mock: document more alternatives to deprecated AnythingOfTypeArgument 2024-03-07 11:59:12 +01:00
Bracken Dawson bfa3ee96e3
Link out to the excellent testifylint 2024-03-07 00:13:49 +00:00
Snir Yehuda f1b5324b90 assert.FunctionalOptions: fix go doc 2024-03-06 11:28:50 +03:00
Olivier Mengué 632a26080f
Merge pull request #820 from TomWright/master
Do not get argument again unnecessarily in Arguments.Error()
2024-03-06 03:05:06 +01:00
Tom Wright cab2acc70f Do not get argument again unnecessarily in Arguments.Error() 2024-03-06 03:03:10 +01:00
Olivier Mengué edb801534f
Merge pull request #1565 from stretchr/gitignore-test-binaries
.gitignore: ignore "go test -c" binaries
2024-03-05 12:09:22 +01:00
Olivier Mengué 89ffab03a5
Merge pull request #1562 from stretchr/make-tHelper-a-type-alias
assert: make tHelper a type alias
2024-03-05 10:48:10 +01:00
Fahim Bagar 8585d8de96
Add PanicAssertionFunc (#1337, #730)
Add a `PanicAssertionFunc` to ease writing table-driven tests for panic
assertion.

Closes #730
2024-03-05 10:13:30 +01:00
Olivier Mengué e5e71998af gitignore: ignore "go test -c" binaries
Ignore binaries produced by "go test -c".
2024-03-04 22:37:21 +01:00
Olivier Mengué 85fabe7c5c assert: make tHelper a type alias
The tHelper interface is defined only for internal usage. We don't need
a "hard" type for it as we don't define methods on it. Let's make is
just a alias to the anonymous interface it represents.

Note: we are already using type aliases elswhere, and type aliases were
introduced a long time ago in Go 1.9.
2024-03-03 18:29:24 +01:00
Bracken bb548d0473
Merge pull request #1552 from stretchr/dependabot/go_modules/github.com/stretchr/objx-0.5.2
build(deps): bump github.com/stretchr/objx from 0.5.1 to 0.5.2
2024-02-29 14:36:18 +00:00
dependabot[bot] 814075f391
build(deps): bump github.com/stretchr/objx from 0.5.1 to 0.5.2
Bumps [github.com/stretchr/objx](https://github.com/stretchr/objx) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/stretchr/objx/releases)
- [Commits](https://github.com/stretchr/objx/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/objx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-29 11:18:15 +00:00
Bracken e045612245
Merge pull request #1339 from bogdandrutu/uintptr
Add support to compare uintptr
2024-02-28 16:20:56 +00:00
Bracken 5b6926d686
Merge pull request #1385 from hslatman/not-implements
Add `NotImplements` and variants
2024-02-28 16:02:08 +00:00
Bracken 9f97d67703
Merge pull request #1550 from stretchr/release-notes
Include the auto-release notes in releases
2024-02-28 09:53:47 +00:00
Bracken Dawson bcb0d3fe49
Include the auto-release notes in releases 2024-02-27 16:35:20 +00:00
Bracken fb770f8238
Merge pull request #1247 from ccoVeille/typos
fix typos in comments and tests
2024-02-25 11:59:52 +00:00
ccoVeille 85d8bb6eea
fix typos in comments, tests and github templates 2024-02-24 23:48:24 +01:00
Bracken e2741fa4e9
Merge pull request #1548 from arjunmahishi/msgAndArgs
http_assertions: honour the msgAndArgs provided with each assertion
2024-02-23 17:40:20 +00:00
Arjun Mahishi 6e59f20c0d http_assertions: assert that the msgAndArgs actually works in tests
This commit also adds the method `Failed() bool` to the mockTestingT
struct. This is usefull for asserting failure in tests
2024-02-23 22:41:24 +05:30
Arjun Mahishi 840f800f1a http_assertions: Include msgAndArgs is some of the existing tests
A mix of using and not using msgAndArgs will ensure that there is no
regression.
2024-02-23 21:20:50 +05:30
Bracken e65c014fc9
Merge pull request #1248 from guettli/patch-1
README: drop link to gorc
2024-02-23 12:59:46 +00:00
Arjun Mahishi 135b468c5a assert: honour the msgAndArgs provided in InEpsilon 2024-02-23 00:23:39 +05:30
Arjun Mahishi ecdde720b4 http_assertions: honour the msgAndArgs provided with each assertion 2024-02-23 00:16:29 +05:30
Bracken 9f0ad86b78
Merge pull request #1517 from Lucaber/equalExportedValues-ptr
Support Pointer to Struct in EqualExportedValues
2024-02-22 17:16:44 +00:00
Bracken 7c847e2503
Merge pull request #1545 from hendrywiranto/update-setupgo-v5
Update Github workflows setup-go to V5
2024-02-21 23:13:52 +00:00
hendry.wiranto 898b77d0bb try to update to v5 2024-02-21 22:31:33 +07:00
Bracken 7caada5a3b
Merge pull request #1540 from stretchr/build-badge
Update the build status badge
2024-02-20 16:23:09 +00:00
Bracken Dawson e099480d59
Update the build status badge 2024-02-20 12:36:59 +00:00
Bracken 4b9af26649
Merge pull request #1538 from stretchr/issue-templates
[chore] Add issue templates
2024-02-20 10:18:57 +00:00
Arjun Mahishi a5087d7793 [chore] Issue templates: Add link to the discussion page 2024-02-20 11:20:33 +05:30
Arjun Mahishi bd22f81c8f [chore] Issue templates: Switch from bold to H2 headings 2024-02-19 22:53:39 +05:30
Arjun Mahishi 28efcd1170 [chore] Fix typos and improve hints in the issue templates 2024-02-19 22:49:15 +05:30
Bracken fef12e7dc3
Merge pull request #1537 from stretchr/fix-time-equal
Revert "assert: ObjectsAreEqual: use time.Equal for time.Time type"
2024-02-19 12:42:59 +00:00
Arjun Mahishi 5ed61202ae
[chore] Add issue templates 2024-02-19 10:33:44 +05:30
Bracken Dawson 7b3de08425
Revert "assert: ObjectsAreEqual: use time.Equal for time.Time type"
This reverts commit 34763e0df3.

time.Time.Equal only tests that the two instances refer to the same instant, but time.Time also carries zone information, so this caused two non-equal instances to be considered equal.
2024-02-18 20:43:45 +00:00
Bracken 14ffa908e6
Merge pull request #1535 from arjunmahishi/fix-suit-deadlock
suite: fix deadlock in suite.Require()/Assert()
2024-02-18 18:34:49 +00:00
Arjun Mahishi c41592ba5f suite: fix deadlock in suite.Require()/Assert()
As pointed out in issue #1520, if the suite is not initialised properly
(buy calling the Run function), then calling suite.Require() or
suite.Assert() will result in a deadlock.

This commit fixes that by panicking if the suite is not initialised
properly. This is justified because, the suite is intended to be
triggered in the right way. If the user does not do that, this panic will
nudge them in the right direction.

It has to be a panic because, at this point, we don't have access to any
testing.T context to gracefully call a t.Fail(). Also, these two
functions are not expected to return an error.

Fixes #1520
2024-02-18 23:55:01 +05:30
Bracken c719de3088
Merge pull request #1331 from ianrose14/ianrose/assertexpectations-skipped
Ensure AssertExpectations does not fail in skipped tests
2024-02-17 17:50:13 +00:00
Bracken 0e75f9941b
Merge pull request #1485 from kevinburkesegment/len-format
assert: better formatting for Len() error
2024-02-16 15:58:11 +00:00
Kevin Burke 89c0872acd
try to do the whole format string 2024-02-15 11:35:07 -07:00
Kevin Burke f6ed021e60
assert: shorten cases 2024-02-15 11:35:07 -07:00
Kevin Burke f7fedd9f85
assert: better formatting for Len() error
Previously, the use of %s with array objects meant you would get an
error like this:

    "[%!s(int=1) %!s(int=2) %!s(int=3)]\" should have 4 item(s), but has 3

Use %v instead, which provides a much nicer error.

    "[1 2 3]" should have 4 item(s), but has 3

Fixes #1482.
2024-02-15 11:35:07 -07:00
Bracken 5911e38e09
Merge pull request #1531 from arjunmahishi/fix-equal-values
assert: Fix EqualValues to handle overflow/underflow
2024-02-14 15:22:36 +00:00
Arjun Mahishi 4c4d0118a6 assert: Fix EqualValues to handle overflow/underflow
The underlying function ObjectsAreEqualValues did not handle
overflow/underflow of values while converting one type to another
for comparison. For example:

    EqualValues(t, int(270), int8(14))

would return true, even though the values are not equal. Because, when
you convert int(270) to int8, it overflows and becomes 14 (270 % 256 = 14)

This commit fixes that by making sure that the conversion always happens
from the smaller type to the larger type, and then comparing the values.
Additionally, this commit also seperates out the test cases of
ObjectsAreEqualValues from TestObjectsAreEqual.

Fixes #1462
2024-02-14 18:12:21 +05:30
Bracken d25ac14e7d
Merge pull request #1533 from brackendawson/maintainers
Update maintainers
2024-02-14 11:07:13 +00:00
Bracken Dawson d3b6816104
Add emeritus I missed 2024-02-13 23:35:29 +00:00
Bracken Dawson a67cee3a98
Update maintainers
I think I've captured the current list of active committers.
2024-02-13 17:02:43 +00:00
Sean Marciniak c3b0c9b4f5
Merge pull request #1502 from stretchr/suite-fix-reporting-of-panic-location
suite: fix recoverAndFailOnPanic to report test failure at the right location
2024-02-01 08:44:43 +10:30
Olivier Mengué 2be68b5e9e suite: fix recoverAndFailOnPanic to report at the right location
Add calls to t.Helper() to fix the location where the panic is reported
as a test failure.
2024-01-31 16:01:43 +01:00
Olivier Mengué b139300e7e
Merge pull request #1450 from stretchr/CI-go1.21
CI: use 'stable' and 'oldstable' indirections to refers to Go versions
2024-01-31 16:00:29 +01:00
Olivier Mengué c86e139511 CI: add go 1.21 2024-01-31 15:57:41 +01:00
Sean Marciniak 9b9a3b48b1
Merge pull request #1412 from stretchr/fix-flaky-TestEventuallyIssue805
assert: fix TestEventuallyTimeout
2024-01-26 13:58:29 +10:30
Sean Marciniak 0feb1d9baf
Merge branch 'master' into fix-flaky-TestEventuallyIssue805 2024-01-25 09:12:58 +10:30
Sean Marciniak 648a7937c5
Merge pull request #1416 from stretchr/mock-simplify-matcher-check
mock: optimize argument matching checks
2024-01-22 08:48:09 +10:30
Sean Marciniak 24e57f1a77
Merge pull request #1444 from stretchr/mock-refactor-TestIsArgsEqual
mock: refactor TestIsArgsEqual
2024-01-22 08:44:43 +10:30
Sean Marciniak 12f05f76ab
Merge pull request #1453 from stretchr/fix-1292-dep-cycle-with-objx
Fix dependency cycle with objx #1292
2024-01-22 08:44:18 +10:30
Sean Marciniak 14d4b9bcc6
Merge pull request #1483 from stretchr/fix-1231-InEpsilonSlice-expected-actual-order
assert.InEpsilonSlice: fix expected/actual order and other improvements
2024-01-22 08:43:45 +10:30
Sean Marciniak 92533fad9b
Merge pull request #1506 from stretchr/assert-simplify-isNil
assert: simplify isNil implementation
2024-01-22 08:39:01 +10:30
Sean Marciniak 1f53b4e175
Merge pull request #1505 from stretchr/more-unsafe.Pointer-tests
assert: improve unsafe.Pointer tests
2024-01-18 03:08:17 +10:30
Luca Berneking 74a35d55d5 Support Pointer to Struct in EqualExportedValues
This PRs allows to `EqualExportedValues` pointers to structs.
2023-12-08 12:42:36 +01:00
Olivier Mengué cbcc423cdf assert: simplify isNil implementation
Simpler and faster (also less allocations) in internal isNil().
2023-11-09 16:40:41 +01:00
Olivier Mengué 858080fbab assert: more unsafe.Pointer tests
1. Isolate tests that use the "unsafe" package in a separate package
   assert/internal/unsafetests. That way the assert package is not
   tainted with unsafe.
2. Remove one reference to the private assert.isNil() in assert tests.
3. Add more tests of assert.Nil and assert.NotNil with unsafe.Pointer.
2023-11-09 16:32:25 +01:00
Olivier Mengué db8608ed63
suite: fix subtest names (fix #1501) (#1504)
* suite: fix TestSubtestPanic failure (#1501)

The subtest of TestSubtestPanic is expected to fail, but that must not
make the testuite of package 'suite' to fail. So call Skip to make 'go
test' to ignore that expected failure.

* suite: fix subtests names

We are doing dirty things with testing.InternalTest. It looks like test
names should have the same prefix as the parent test, like if they were
true subtests (testing.T.Run).

So until we drop our usage of testing.InternamTest, let's rename the
tests.

Also added a few checks of the testing.RunTests result.
2023-11-09 10:44:27 +10:00
Craig Davison 331c520966 Improve readability 2023-10-31 15:54:18 +01:00
Craig Davison 5f48c62606 address some review comments 2023-10-31 15:54:18 +01:00
Craig Davison b7c378d6bd captureTestingT.checkResultAndErrMsg 2023-10-31 15:54:18 +01:00
Craig Davison 002647e9f8 TestErrorIs/TestNotErrorIs: check error contents 2023-10-31 15:54:18 +01:00
Justin Chadwell 5105b61304 Add map support doc comments to Subset and NotSubset
Signed-off-by: Justin Chadwell <me@jedevc.com>
2023-10-31 00:24:17 +01:00
Oleksandr Redko 43b2ef12b3 Change to use godoc 2023-10-31 00:07:12 +01:00
Oleksandr Redko 056e9e6bfa docs: Fix deprecation formatting for http 2023-10-31 00:07:12 +01:00
Ian Rose 1837f62a5f Ensure AssertExpectations does not fail in skipped tests 2023-10-31 00:00:55 +01:00
Olivier Mengué 21ba5d23bb
Merge branch 'master' into uintptr 2023-10-30 23:51:22 +01:00
Linus Barth 7df1a82a31 test: add suite tests for panicking of subtests 2023-10-30 22:51:08 +01:00
Linus Barth a4a54a4597 fix: panic behavior for subtests
This fix adds panic handling for subtests which will achieve:
- subtests will fail for the correct test context when panicking
- the test execution is not stopped; the next subtest will be executed
2023-10-30 22:51:08 +01:00
Linus Barth 65318c364a improve: tests for asserting test names in subtests 2023-10-30 22:51:08 +01:00
Linus Barth 130d340262 improve: move comment to msgAndArgs-param in test 2023-10-30 22:51:08 +01:00
Linus Barth 855d1c6784 test: testing.T correctness in subtests setup/teardown 2023-10-30 22:51:08 +01:00
Linus Barth 82022eeb0d test: call order of setup/teardown for subtests 2023-10-30 22:51:08 +01:00
Linus Barth 4526456fa4 improve: defer-style in Suite.Run() 2023-10-30 22:51:08 +01:00
Linus Barth 737a765d89 fix: SetupSubTest and TearDownSubTest execution order
There were two problems with the order of execution in the Suite.Run() method:
- One could not access the correct testing context ("s.T()") inside the SetupSubTest and TearDownSubTest methods. If the testing context was used for e.g. assertions of mocks in the TearDownSubTest, the results would not be "attached" to the correct test in the test output.
- The behavior was different to the order of execution for "root" tests (see lines 167-201) regarding the SetupTest and TearDownTest methods. This could confuse users of the library.

Also the logic to be deferred was joined together. This was fine beforehand because a panic in the TearDownSubTest would have had no influence on the "suite.SetT(oldT)". Now since a panic in the TearDownSubTest would lead to omitting the "suite.SetT(oldT)" this defer was split into two separate defers.
2023-10-30 22:51:08 +01:00
Oleksandr Redko 351d2776c6 Revert some changes 2023-10-30 22:41:11 +01:00
Oleksandr Redko 375474cd3c suite: refactor test assertions 2023-10-30 22:41:11 +01:00
Harald Nordgren fc1dee9921 Deprecate EqualExportedValues 2023-10-24 09:21:34 +02:00
Kevin Burke 7f962d56e4 .github: use latest Go versions
Also update the runners to their latest versions.
2023-10-24 09:10:36 +02:00
Olivier Mengué f8dcfd6618 assert.InEpsilonSlice: mention index of error in fail message 2023-10-18 18:46:08 +02:00
Olivier Mengué 7d383ba732 assert.InEpsilonSlice: refactor 2023-10-18 18:46:08 +02:00
Olivier Mengué f7fbc7da15 assert.InEpsilonSlice: fix order of expected vs actual (#1231) 2023-10-18 18:46:08 +02:00
Olivier Mengué 8fd5aae061 assert.InEpsilonSlice: remove redundant check
Remove check of actual's kind redundant with Type()'s check.
2023-10-18 18:46:08 +02:00
Olivier Mengué f728d3c50f assert.InEpsilonSlice: refactor
Remove multiple calls to reflect.Value.Len()
2023-10-18 18:46:08 +02:00
Olivier Mengué b5dec80529 assert.InEpsilonSlice: add more slice checks 2023-10-18 18:46:08 +02:00
Olivier Mengué ab3b9743a7 assert.InEpsilonSlice: refactor 2023-10-18 18:46:08 +02:00
Olivier Mengué 5dca985ff4 assert: remove logs in TestEventuallyTimeout 2023-10-16 21:14:11 +02:00
Olivier Mengué e8837d5396 assert: fix TestEventuallyTimeout
Fix TestEventuallyIssue805 which was flaky because of the use of a timer
that has exactly the same duration as the Eventually timout. But time is
not a synchronization primitive.

Now we use channels to ensure that the condition is still running and
Eventually times out before checking its return value.

The test is also renamed to TestEventuallyTimeout to more clearly show
its purpose.
2023-10-16 21:12:04 +02:00
Olivier Mengué f19cdfc9fe
Merge branch 'master' into patch-1 2023-10-16 19:23:14 +02:00
Herman Slatman 4e56e1ee06 Add `NotImplements` and variants 2023-10-16 14:27:51 +02:00
Olivier Mengué 307c9344b8 mock: refactor IsType
Reduce calls to reflect.Type in implementation of mock.IsType by
extracting the type early.

This also avoids to keep alive references to the argument value.
2023-10-16 13:57:23 +02:00
Olivier Mengué 413628c0f4 mock: simplify Arguments.Diff
Use a type switch instead of reflect.TypeOf comparisons.
2023-10-16 13:57:23 +02:00
Zachary Becker 4ae48e988c Combine switch cases, difference wrapped in if statement 2023-10-16 00:55:57 +02:00
Zachary Becker 2f7efa2451 Fix bug where array is treated as slice in EqualExportedValues 2023-10-16 00:55:57 +02:00
hidu ce5c2b684b assert: fix httpCode and HTTPBody occur panic when http.Handler read body 2023-10-15 23:34:32 +02:00
Grzegorz Burzyński 89920137cd collect errors from condition in defer 2023-10-13 08:53:17 +02:00
Grzegorz Burzyński 4ed68e1bca fix: make EventuallyWithT concurrency safe 2023-10-13 08:53:17 +02:00
dependabot[bot] 11a6452626 Bump actions/setup-go from 3.2.0 to 4.1.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.2.0 to 4.1.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3.2.0...v4.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-10 17:30:50 +02:00
dependabot[bot] c1ca192909 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-10 17:25:08 +02:00
tscales 34763e0df3 assert: ObjectsAreEqual: use time.Equal for time.Time type 2023-10-10 17:14:01 +02:00
Oleksandr Redko 1ee798c140 Remove canConvert because Go 1.16 is dropped from support 2023-10-10 14:28:34 +02:00
Olivier Mengué 838caeaac1 deps: exclude old testify to break the dependency cycle from objx
In go.mod exclude the old version of testify brought by objx. This
allows to break the dependency cycle and completely remove the
dependency link to old versions of dependencies (some of which had
security issues).

Closes #1292.

go mod edit -exclude=github.com/stretchr/testify@v1.8.2 && go.mod
2023-08-09 16:24:51 +02:00
Olivier Mengué 975128c5e6 deps: upgrade objx to v0.5.1 to fix dep cycle
See https://github.com/stretchr/objx/pull/140

go get github.com/stretchr/objx@v0.5.1
2023-08-09 16:18:00 +02:00
Olivier Mengué 882382d845
Merge pull request #1441 from stretchr/mock-deprecate-AnythingOfTypeArgument
mock: deprecate AnythingOfTypeArgument (#1434)
2023-08-08 11:46:46 +02:00
Olivier Mengué f24f3ba986 mock: deprecate AnythingOfTypeArgument (#1434)
Deprecate mock.AnythingOfTypeArgument which should never have been
publicly exposed.

AnythingOfTypeArgument is now a type alias to the private
anythingOfTypeArgument and is marked as Deprecated in godoc.
The AnythingOfType constructor is still available.

The aim is to completely remove that alias.
2023-08-08 11:05:09 +02:00
Olivier Mengué 0e90845e22
Merge pull request #1445 from stretchr/assert-refactor-getLen
assert: refactor internal func getLen()
2023-08-08 11:04:26 +02:00
Olivier Mengué 9e58631a03 assert: reverse return values of internal getLen()
Reverse order of return values of internal function getLen() to be more
consistent with stdlib (ex: os.LookupEnv).

Old: func (any) (ok bool, length int)
New: func (any) (length int, ok bool)
2023-08-08 11:01:35 +02:00
Olivier Mengué 7a796b8f87 assert: simplify getLen() implementation 2023-08-08 11:01:35 +02:00
Anant Vyas a23f5db224
mock: move regexp compilation outside of Called (#631)
Co-authored-by: Olivier Mengué <dolmen@cpan.org>
2023-08-01 10:07:29 +02:00
Ivan Pesenti a392378178 Fixed typos
"Setup" means an existing arrangeament while "set up" is an action.
2023-07-31 19:15:21 +02:00
Olivier Mengué 81667ad920 assert: fix flaky TestNeverTrue
Fix flaky TestNeverTrue: use a channel to make a synchronized list of
return values to avoid a race condition.
2023-07-31 19:06:57 +02:00
Olivier Mengué c740480fc1 mock: refactor TestIsArgsEqual
Refactor tests to use 'append' to build copies of slices instead of
manual loops.

Thanks golangci-lint.
2023-07-30 20:51:12 +02:00
Olivier Mengué 862e41010c
Merge pull request #1360 from hikyaru-suzuki/feature/update_logging_output
mock: AssertExpectations log reason only on failure
2023-07-29 05:52:15 +02:00
Olivier Mengué 486eb6f08c
Merge pull request #1406 from dolmen-go/use-std-header-for-generated-code
codegen: use standard header for generated files
2023-07-22 08:51:09 +02:00
Olivier Mengué af4d8a66cf
Merge pull request #1320 from lesichkovm/patch-1
Fix adding ? when no values passed
2023-07-11 22:52:16 +02:00
Olivier Mengué 78aedbf433
Merge pull request #1409 from SuperQ/test_old_versions
CI: run tests from go1.17
2023-07-06 13:09:59 +02:00
SuperQ 95683d1a6c
Add tests for old Go versions
Test old Go versions all the way back to 1.17.

Signed-off-by: SuperQ <superq@gmail.com>
2023-07-06 12:59:57 +02:00
Olivier Mengué 0a3163c6c4
Merge pull request #1410 from alexandear/docs-fix-typos
doc: Fix typo in godoc

Also fix typos in tests (assertion messages).
2023-07-06 12:33:18 +02:00
Oleksandr Redko 37814a1755 docs: Fix typos in tests and comments 2023-07-06 13:14:39 +03:00
Olivier Mengué 34b86428f4
Merge pull request #1346 from LandonTClipp/mockery_docs
README: update mockery link to point to doc on Github Pages
2023-07-05 12:52:28 +02:00
Olivier Mengué 4f6e609334
Merge branch 'master' into mockery_docs 2023-07-05 12:49:36 +02:00
Olivier Mengué 204612911e
README: fix link to mockery tool 2023-07-05 12:48:13 +02:00
Olivier Mengué 6bfed73816
Merge pull request #1349 from davidjb/patch-1
README: fix URLs to point to go.dev/pkg.go.dev
2023-07-05 12:39:07 +02:00
Olivier Mengué c6ffad3f5c
Merge pull request #1389 from echarrod/master
Correct spelling/grammar in README and comments
2023-07-05 12:18:24 +02:00
Olivier Mengué b247874fc3
Merge pull request #1367 from stretchr/mvdkleijn-patch-1
Remove mvdkleijn from MAINTAINERS.md on his request
2023-07-05 12:01:22 +02:00
Olivier Mengué 945f91b9b9
Merge pull request #1392 from programmer04/improve-docs
Document that require is not safe to call in created goroutines
2023-07-05 11:59:09 +02:00
Olivier Mengué 68bbf7ae46
Merge pull request #1394 from SuperQ/go_min
Set Go version to 1.19 (oldstable) in go.mod
2023-07-05 11:57:03 +02:00
Olivier Mengué 5ca0755b51 codegen: use standard header for generated files
Use the standard header in generated Go sources.
See https://go.dev/s/generatedcode.
2023-07-02 21:06:15 +02:00
SuperQ b5eddf779a
Fix Go modules version
The `go` directive in go.mod is meant to indicate the minimum supported
version.

See: https://go.dev/doc/modules/gomod-ref#go

Signed-off-by: SuperQ <superq@gmail.com>
2023-06-02 16:11:11 +02:00
Jakub Warczarek e18a70d446
Document that require is not safe to call in created goroutines 2023-05-31 11:34:43 +02:00
Martti T f97607b898
Create GitHub release when new release tag is pushed (#1354) 2023-05-30 19:38:40 +10:00
Ed 9f0f17fe64 Correct spelling/grammar 2023-05-26 10:38:59 +01:00
Harald Nordgren 4c93d8f201
EqualExportedValues: Handle nested pointer, slice and map fields (#1379)
* EqualExportedValues: Handle pointer and slice fields

* Update assert/assertions.go

Co-authored-by: Michael Pu <michael.pu123@gmail.com>

* Reduce redundant calls to 'copyExportedFields'

* Update comments

* Add support for maps

* Update Go version support to 1.19 and onward

* Re-generate after rebasing

---------

Co-authored-by: Michael Pu <michael.pu123@gmail.com>
2023-05-11 07:42:04 +10:00
Tobias Krischer 4b2f4d2bcf
add EventuallyWithT assertion (#1264)
* add EventuallyWithT assertion

* Change "EventuallyWithT" condition acceptance to no-errors raised

This change updates the "EventuallyWithT" assertion variants (regular, formatted,
requirement) to consider a condition as "met" if no assertion errors were raised
in a tick.

This allows to write easier conditions which simply contain assertions, without
needing to return a bool. The equivalent of a condition returning true in the
previous implementation would be a a condition with a single "assert.True(..)" call.

* Declare the "Collect.Copy(T)" method as a testing helper

* run go generate

---------

Co-authored-by: Arik Kfir <arik@kfirs.com>
2023-05-10 06:58:49 +10:00
Nisheeth Barthwal b3106d772c
allow testing for functional options (#1023)
* allow testing for functional options

* add linting docs

* use reflect magic to obtain FunctionalOption name

Co-authored-by: dillonstreator <dillonstreator@gmail.com>

---------

Co-authored-by: dillonstreator <dillonstreator@gmail.com>
2023-05-10 06:55:48 +10:00
wwade 437071b948
assert: fix error message formatting for NotContains (#1362)
* assert: rename and refactor TestContainsFailMessage

I've renamed it to TestContainsNotContains and added a test case
structure so that I can use this test to validate the failure messages
from both Contains and NotContains,

There are no functional changes here, strictly refactoring. A new test
case will be added in a subsequent change.

* assert: fix error message formatting for NotContains

It was using "%s" to format the s and contains arguments, but these
could be any type that supports iteration such as a map. In this case
of NotContains failing against a map, it would print something like:

   "map[one:%!s(int=1)]" should not contain "one"

Fix this using "%#v" as was already done for Contains and added test
cases covering both the "len()" / iterable failure messages as well as
the Contains/NotContains failure case.

The new message for this example map would be something like:

    map[string]int{"one":1} should not contain "one"
2023-05-05 14:05:51 +10:00
Martijn 02647d3471
Remove myself from MAINTAINERS.md
As I am supposed to be a maintainer but have almost zero access to actually manage the project, I've found it difficult to help out. Since then my time has become fragmented and as such I'm removing myself as maintainer.
2023-04-07 08:55:49 +02:00
Michael Pu c5fc9d6b6b
Compare public elements of struct (#1309)
* Implement checking only exported fields

Co-authored-by: Anthony Chang <anthony-chang@users.noreply.github.com>

* Update comment

* Run go generate

* Make compatiable with Go 1.16.5

* Fix go generate files

* Fix white space changes

* Fix whitespace changes

* Fix whitespace changes in gogenerate files

---------

Co-authored-by: Anthony Chang <anthony-chang@users.noreply.github.com>
2023-03-14 22:01:35 +10:00
鈴木 光 75b9b6dfff update: log reason output only if failed 2023-03-09 18:49:15 +09:00
David Beitey d099934742 docs: Update URLs in README 2023-03-05 12:32:34 +10:00
Daniel White f36bfe3c33
Fix Subset/NotSubset when map is missing keys from the subset (#1261)
`MapIndex` returns a zero `reflect.Value` if the map value does not
exist. This now aligns more closely with `InDeltaMapValues` by
checking `reflect/Value.IsValid`.

The use of `recover()` was hiding this error by setting the result of
the test to be "false" despite the test suite passing. This led to
flapping tests where they would succeed if the panic occurred on a
missing key before comparing key values that didn't match!

I've ensured the test suite now asserts on the expected error message
and added another example where the subset has keys not found on the
map under test.
2023-02-25 22:46:30 +10:00
Landon Clipp 0291ba6dc2
https 2023-02-11 01:59:47 -06:00
LandonTClipp d2db48976a Update mockery link to Github Pages 2023-02-11 01:23:36 -06:00
Bogdan Drutu f7b49d3ed7
Add support to compare uintptr
Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>
2023-02-04 16:27:21 -08:00
Artem V. Navrotskiy 0ab3ce1249
Fix `CallerInfo()` source file paths (#1288) 2023-01-03 20:57:11 +10:00
lisitsky 2b00d33aec
Fix Call.Unset() panic (issue #1236) (#1250)
Unset changes len of a `ExpectedCalls` slice during iteration while using index from original slice, and under some conditions it tries to reslice element beyond of the slice boundaries. That causes panic.

The proposed solution uses independent write index to count elements kept in output slice.

Tests (the simplest and more complicated cases) and comments are included.
2023-01-03 20:44:42 +10:00
Milan Lesichkov c029f419b8
Fix adding ? when no values passed 2022-12-22 10:45:24 +00:00
sunpeng 9acc22213e
fix: fix bug for check unsafe.Pointer isNil (#1319) 2022-12-20 19:39:16 +10:00
Vadym Tishchenko 1333b5d3bd
Add sub-tests to Suite (#1246)
Co-authored-by: Vadym Tishchenko <v.tishchenko@evopay.com.ua>
2022-11-02 21:46:59 +10:00
dependabot[bot] b747d7c5f8
Bump github.com/stretchr/objx from 0.4.0 to 0.5.0 (#1283)
Bumps [github.com/stretchr/objx](https://github.com/stretchr/objx) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/stretchr/objx/releases)
- [Commits](https://github.com/stretchr/objx/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/objx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-20 20:50:56 +10:00
Thomas Güttler afd76b48e3
gorc last update is nine years old
I think it makes no sense to link to it.
2022-08-11 21:51:04 +02:00
Stavros Ntentos 181cea6eab
impr: `CallerInfo` should print full paths to the terminal (#1201)
* impr: `CallerInfo` should print full paths to the terminal

I am proposing this simple change, which changes this output
```
--- FAIL: TestABC (0.00s)
    --- FAIL: TestABC/C (0.00s)
        /this/is/a/path/to/file_test.go:258:
            	Error Trace:	file_test.go:258
            	            				file_test.go:748
            	Error:      	Not equal:
...
```
to this:
```
--- FAIL: TestABC (0.00s)
    --- FAIL: TestABC/C (0.00s)
        /this/is/a/path/to/file_test.go:258:
            	Error Trace:	/this/is/a/path/to/file_test.go:258
            	            				/this/is/a/path/to/file_test.go:748
            	Error:      	Not equal:
...
```

With the latter output, it is much more straightforward to find the file
you are looking for, even though in the displayed case, the file is the same.

However, for VSCodium, the case is a little more helpful, since VSCodium's
terminal is smart enough to recognize the output, and make links out of that input.

Signed-off-by: Stavros Ntentos <133706+stdedos@users.noreply.github.com>

* test: fix the tests that depended on the previous behavior

Signed-off-by: Stavros Ntentos <133706+stdedos@users.noreply.github.com>

Co-authored-by: Stavros Ntentos <11300730-stavros-relex@users.noreply.gitlab.com>
2022-06-29 20:56:06 +10:00
Bracken cf1284f8dd
Allow mock expectations to be ordered (#1106)
* Allow mock expectations to be ordered

* Only say another call if it has been called before
2022-06-28 22:13:45 +10:00
Oladapo Ajala 66eef0ef3a
fix: assert.MapSubset (or just support maps in assert.Subset) (#1178)
* WIP: added map key value check in subset

* upgraded subset & notsubset to check handle maps
2022-06-28 21:24:53 +10:00
Ruan Moolman 2fab6dffcf
Add WithinTimeRange method (#1188)
* Add WithinTimeRange method

* Run ./.ci.generate.sh

* Rename WithinTimeRange to WithinRange

* Rename WithinRange expected parameter to actual

* Capitalise start parameter at start of error message

* Improve WithinRange example
2022-06-28 21:14:39 +10:00
Edward Raigosa b5ce165710
fixing panic in calls to assertion with nil m.mutex (#1212)
* fixing panic in calls to assertion with nil m.mutex

This reverts a change that was made in https://github.com/stretchr/testify/pull/1182
The PR makes m.mutex a pointer which now needs to be checked but it's not checked for nil everywhere.

This should also help with these issues:
- https://github.com/stretchr/testify/issues/1208
- https://github.com/stretchr/testify/issues/1210

* Revert throwing out the lock because other concurrent calls can already have it locked

* fix go vet copy lock by using pointer

* fix obj assignment for passing test
2022-06-24 10:11:59 +10:00
Bracken c206b2e823
Mock can be deadlocked by a panic (#1157)
If an argumentMatcher function panics and AssertExpectations is deferred then the test would deadlock.
2022-06-23 19:42:21 +10:00
nicoche 1b73601ae8
suite: correctly set stats on test panic (#1195) 2022-06-23 19:40:15 +10:00
Paul Dufour ba1076d8b3
Add .Unset method to mock (#982)
* Add .Off method to mock

* Update README.md

* Update mock.go

* Update mock_test.go

* Update README.md

* Fix tests

* Add unset test

* remove prints

* fix test

* update readme
2022-06-22 19:31:35 +10:00
Ryan Leung c31ea0312f
Support comparing byte slice (#1202)
* support comparing byte slice

Signed-off-by: Ryan Leung <rleungx@gmail.com>

* address the comment

Signed-off-by: Ryan Leung <rleungx@gmail.com>
2022-06-21 14:54:58 +10:00
Tony Abboud 48391ba5eb
Fix panic in AssertExpectations for mocks without expectations (#1207)
Co-authored-by: Tony Abboud <tabboud@palantir.com>
2022-06-21 07:08:00 +10:00
Adam Luzsi 840cb80149
arrays value types in a zero-initialized state are considered empty (#1126)
* fix .Empty assertion with Array types

* refactor .Empty assertion for array types
2022-06-20 20:44:27 +10:00
dependabot[bot] 07dc7ee5ab
Bump actions/setup-go from 3.1.0 to 3.2.0 (#1191)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 20:32:41 +10:00
dependabot[bot] c33fc8d30d
Bump actions/checkout from 2 to 3 (#1163)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-20 20:23:43 +10:00
Boyan Soubachov 3c33e07c4c
Added Go 1.18.1 as a build/supported version (#1182)
* Added Go 1.18.1 as a build/supported version
Removed Go 1.15.13 as a build version as it's no longer supported

* Fix mutex passed by value for the Mock struct

* Add mutex initialisation for Mock

Co-authored-by: Boyan Soubachov <bsoubachov@atlassian.com>
2022-06-14 20:50:25 +10:00
dependabot[bot] e2b56b3a38 Bump github.com/stretchr/objx from 0.1.0 to 0.4.0
Bumps [github.com/stretchr/objx](https://github.com/stretchr/objx) from 0.1.0 to 0.4.0.
- [Release notes](https://github.com/stretchr/objx/releases)
- [Commits](https://github.com/stretchr/objx/compare/v0.1.0...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/objx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-14 20:44:02 +10:00
Karol Lassak 41453c009a Update gopkg.in/yaml.v3 2022-06-06 19:52:45 +10:00
Jeff Widman 285adcc5ce Update go versions in build matrix
These are the latest versions listed on https://go.dev/dl/
2022-05-13 21:12:09 +10:00
dependabot[bot] 6e7fab43fc Bump actions/setup-go from 2 to 3.1.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.1.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-13 21:10:54 +10:00
Weizhen Wang 106ec21d14 use RWMutex
Signed-off-by: Weizhen Wang <wangweizhen@pingcap.com>
2022-05-04 20:46:29 +10:00
Weizhen Wang a409ccf19e fix data race in the suit
Signed-off-by: Weizhen Wang <wangweizhen@pingcap.com>
2022-05-04 20:46:29 +10:00
cuishuang 35864782d2 assert: fix typo
Signed-off-by: cuishuang <imcusg@gmail.com>
2022-05-04 20:29:57 +10:00
Jeff Widman 7797738693 Update versions supported to include `go 1.16`
Rather than directly adding `1.16`, I thought better to reword to state the minimum version supported... this way the line doesn't have to get updated with every new release, but instead only when dropping releases.
2022-03-17 22:47:27 +10:00
RmbRT 083ff1c044 Fixed didPanic to now detect panic(nil).
Previously, the function would not detect panic(nil) calls.
In didPanic, removed the anonymous function call, instead,
added named return values. Added extra test cases for the
panic(nil) call.
2022-03-16 07:08:43 +10:00
Torkel Rogstad 1e36bfe104 Use cross Go version compatible build tag syntax 2022-02-15 20:26:04 +10:00
Torkel Rogstad e798dc2763 Add docs on 1.17 build tags 2022-02-15 20:26:04 +10:00
Torkel Rogstad 83198c2c50 assert: guard CanConvert call in backward compatible wrapper 2022-02-15 20:26:04 +10:00
Torkel Rogstad 087b655c75 assert: allow comparing time.Time 2022-02-15 20:26:04 +10:00
Ilia Kravets 7bcf74e94f fix msgAndArgs forwarding 2022-02-10 20:09:05 +10:00
Ilia Kravets c29de71342 add tests for correct msgAndArgs forwarding
len(msgAndArgs)>1 should lead to fmt.Sprintf()
2022-02-10 20:09:05 +10:00
Boyan Soubachov f87e2b2119 Update builds
* Add Go 1.17.6 to the build versions
* Remove Go 1.14
* Remove unused TravisCI file
2022-02-10 20:07:02 +10:00
Menno ab6dc32628 fix linting errors in /assert package 2021-08-24 21:55:23 +10:00
Menno edff5a049b fix funtion name 2021-08-24 21:55:23 +10:00
Menno 5c61ef97ae fix potential nil-pointer dereference
the fix gracefully fails the assertion instead of panicking unresolved.
2021-08-24 21:55:23 +10:00
perrydunn e209ca88af Improve mock.MatchedBy failed comparison Diff message 2021-08-24 20:26:52 +10:00
Matthieu MOREL a9de4f065a Create dependabot.yml 2021-07-20 16:55:38 +02:00
Menno fd9e1fb0e1 Fix NaN similarity checks
Plus minor alignments in code.
2021-07-09 20:35:46 +10:00
Boyan Soubachov ee42bbe4ab Use multiple run commands 2021-07-08 22:18:43 +10:00
Boyan Soubachov 57bf675175 Update go versions 2021-07-08 22:18:43 +10:00
Boyan Soubachov 6c59e0f73d Convert CI to use GitHub Actions 2021-07-08 22:18:43 +10:00
neilisaac 6241f9ab99 Add String method to Mock to fix #625 2021-04-27 21:38:32 +10:00
Harald Nordgren dc5c261377 Make sure time.Time comparison produces a helpful diff. closes #989 2021-04-27 21:16:43 +10:00
Alun Evans 6990a05d54 Add ErrorContains 2021-01-16 12:32:05 +11:00
Yongxin Wang bf646ea5b3 add test to check that Helper is called 2021-01-15 21:06:28 +11:00
Yongxin Wang 6f81fdf1db fix the output source file in test output 2021-01-15 21:06:28 +11:00
Tomáš Procházka a2f7dbf150 Change Readme to reflect supported Go versions 2021-01-15 21:03:40 +11:00
Bo Sunesen acba37e5db Only use repeatability if no repeatability left 2021-01-13 20:54:11 +11:00
Bo Sunesen eb8c41ec07 Add more tests to mock package 2021-01-13 20:54:11 +11:00
Bo Sunesen a5830c56d3 Extract method to evaluate closest match 2021-01-13 20:54:11 +11:00
Bo Sunesen 1962448488 Use Repeatability as tie-breaker for closest match 2021-01-13 20:54:11 +11:00
Acho Arnold 92707c0b2d Fixed the link to not point to assert only 2020-11-03 21:39:35 +11:00
Acho Arnold 05dd0b2b35 Updated the readme to point to pkg.dev 2020-11-03 21:39:35 +11:00
Graham Jenson c26b7f39f8 Update assertions.go
lower to 10
2020-11-03 21:38:56 +11:00
Graham Jenson 8fb4b2442e [Fix] The most recent changes to golang/protobuf breaks the spew Circular data structure detection
The latest update to golang/protobuf (I am on v1.4.2) break the spew circular data structure detection. This means that when calling `assert.Equal(t, proto1, proto2)` will not only fail, but also enter an infinite recursion.

Given spew is not being actively maintained, and tesitfy should set some upper bound, we need to set a `MaxDepth` to some very high number, so at least the test will not run for a very long time and finish and fail quickly.
2020-11-03 21:38:56 +11:00
Masaya Hayashi dc8af7208c add generated code for positive/negative assertion 2020-11-03 21:37:46 +11:00
Masaya Hayashi 1544508911 add assert positive/negative 2020-11-03 21:37:46 +11:00
RyuseiNomi 54d05a4e18 modify function name and docstrings 2020-08-18 21:58:29 +10:00
RyuseiNomi 44accac0f5 revise test case name of example of mock section 2020-08-18 21:58:29 +10:00
alexpantyukhin cf221cc875 add tests and fix message 2020-08-14 20:45:51 +10:00
alexpantyukhin c74c0d3a7f add fnctions into requres 2020-08-14 20:45:51 +10:00
alexpantyukhin a9284e66a9 add assertion order 2020-08-14 20:45:51 +10:00
Martijn a3bed97cf3 assert: s/rune/r/ to avoid "rune" predeclared ident shadowing #642
Thanks @quasllyte
2020-08-03 19:54:30 +10:00
Boyan ed4976c764 Revert "Fix PR comments"
This reverts commit 1ebd9c5791.
2020-07-27 22:28:46 +10:00
arseny b09b5a43a5 Fix go mod 2020-07-27 21:16:21 +10:00
arseny 415d89281b Fix time.Time not equals bug
Adding test
2020-07-27 21:16:21 +10:00
arseny 1ebd9c5791 Fix PR comments 2020-07-27 21:16:21 +10:00
Pal Sivertsen 6a6c303c3c Change Readme to reflect supported go versions 2020-07-20 20:40:44 +10:00
Pal Sivertsen 95a9d909e9 Add wrapper functions for errors Is and As funcs
This commit adds wrapper functions for Is and As functions from the
errors package.
2020-07-20 20:40:44 +10:00
Boyan Soubachov 38a7ed3d85
Added testify v2 feedback form link 2020-07-17 08:11:02 +10:00
Boyan Soubachov 404e6fa1be
Added link for testify v2 survey 2020-07-17 08:10:04 +10:00
Boyan 87a988cffb Remove Go 1.10 from Travis builds 2020-06-28 22:12:10 +10:00
Torkel Rogstad 51595dcf94 Allow comparing custom comparable types
Prior to this commit, this would not work:

```go
type myInt int
assert.Less(t, myInt(1), myInt(2)) // panic!
```

We add some type conversions, similarly to how `ObjectsAreEqualValues`
work. This allows us to compare `myInt` to `myInt`, as well as `myInt`
to `int`.
2020-06-22 19:40:53 +10:00
Ivo van der Wijk b8f7d52a4a Rewrite test loops into individual test cases 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 8a501b0fac make testcase definition local 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 4bbffeac6c table-ify TestElementsMatch 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 52b38ca424 table-ify, refactor (Not)Subset() 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 0929293466 refactor, table-ify TestContains / TestNotContains 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 67a4d91853 table-ify ObjectsAreEqual() 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 5717c498e9 table-ify Equal() 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 408bc6703a keep type definition local to function where used 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 590942c47f table-ify Exactly 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 9ffb85bbec clarify sets of tests 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 51b7cfe385 rewrite NotEqual() tests to table tests 2020-06-13 21:33:52 +10:00
Ivo van der Wijk 07d1e00890 first attempt at table-ifying NotEqualValues tests 2020-06-13 21:33:52 +10:00
Ivo van der Wijk f50e178a9f first attempt at table-ifying NotEqualValues tests 2020-06-13 21:33:52 +10:00
Boyan f654a9112b Update Go versions in Travis
* Updated the versions of Go we run on to reflect our support protocol
2020-06-05 20:48:45 +10:00
Boyan 3184a9e141 This reverts commit 0a813b5898. 2020-06-05 20:47:15 +10:00
Boyan e2b269ecc5 This reverts commit 2adb7b54b7. 2020-06-05 20:47:15 +10:00
Boyan 6353e56395 This reverts commit 9d083cac4a. 2020-06-05 20:47:15 +10:00
Boyan 656132404a This reverts commit 484fff1ace. 2020-06-05 20:47:15 +10:00
Boyan 46420cf544 This reverts commit 1a43b8334a. 2020-06-05 20:47:15 +10:00
Boyan 303198d014 Revert "allow body for HTTPBodyContains and HTTPBodyNotContains for
POST" PR #938

This reverts commit 136026fb25.
2020-06-05 20:47:15 +10:00
Boyan e7cc868148 Update TravisCI config 2020-06-01 20:09:00 +10:00
Ivo van der Wijk 004e3cb722 commit generated files 2020-05-27 08:39:56 +10:00
Ivo van der Wijk ac1463f956 Implement NotEqualValues 2020-05-27 08:39:56 +10:00
Matt Gorzka e72b029e2a new regex 2020-05-14 20:01:26 +10:00
zjx158094 d4e7ca1687 address comments 2020-05-14 19:53:33 +10:00
zjx158094 8329c5daa7 add test 2020-05-14 19:53:33 +10:00
zjx20 3ec00f620a assertion_compare: fix format string for numbers 2020-05-14 19:53:33 +10:00
Pedro de Brito 15aff29f35 docs: update Comparison function comment 2020-05-06 18:14:40 +10:00
ariley d76ac5e41f Fix comment 2020-05-05 08:16:34 +10:00
ariley 1bbde5e52a Change Require to Error 2020-05-05 08:16:34 +10:00
ariley f96052c82a Remove 1.8 and 1.9 tests. Updated failfast test 2020-05-05 08:16:34 +10:00
ariley 93bea66f75 Added TestSuiteWithFailfast 2020-05-05 08:16:34 +10:00
ariley cd58006fe6 Change how tearDownAllSuite is run to aviod deadlock when running with failfast 2020-05-05 08:16:34 +10:00
ariley 097ec799df Change how tearDownAllSuite is run to aviod deadlock when running with failfast 2020-05-05 08:16:34 +10:00
Bryan Boreham f7ef284eb4 Add a benchmark to time trivial call of NotNil 2020-05-04 21:01:24 +10:00
Bryan Boreham e734bda58c Avoid calling Helper() on the happy path.
Helper() in the standard Go runtime fetches a stack trace from the
runtime, so is slow for calls that are made many times.

Helper() only makes a difference if the call throws an error, so move
it after the test in straightforward cases.
2020-05-04 21:01:24 +10:00
gohargasparyan 136026fb25 add test cases 2020-05-04 07:47:34 +10:00
gohargasparyan 1a43b8334a add test cases 2020-05-04 07:47:34 +10:00
gohargasparyan 484fff1ace didn't notice should run go generate 2020-05-04 07:47:34 +10:00
gohargasparyan 9d083cac4a go format 2020-05-04 07:47:34 +10:00
gohargasparyan 2adb7b54b7 correct missing parameters in require and require_forward 2020-05-04 07:47:34 +10:00
gohargasparyan 0a813b5898 allow body for HTTPBodyContains and HTTPBodyNotContains for POST 2020-05-04 07:47:34 +10:00
David Wragg ca8e08c131 Support uint in InDelta and InEpsilon
InDelta and InEpsilon assertions on uint values would fail with
the error "Parameters must be numerical".
2020-04-29 21:29:26 +10:00
Yaroslav Kolomiiets 3bf8d0aa5e callString not to panic on nil
Fixes stretchr/testify#936.
2020-04-29 21:10:54 +10:00
Dave Henderson d3decad621 Update dependency to gopkg.in/yaml.v3
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2020-04-28 07:45:39 +10:00
sarathsp06 35d4bf5bd4 panic: add .Panic() to mock.Call
Panic mocks a function call that panics with th specfied message
2020-04-27 19:52:16 +10:00
Boyan 2566b66989 Resolve conflicts 2020-04-24 07:38:44 +10:00
Aaron N. Brock f238e4b70a Move teardown defers to before setup 2020-04-24 07:38:44 +10:00
ariley f43aa3c488 Change to guard clause 2020-04-23 15:52:53 +02:00
ariley a41f2db807 Add warning in log when no tests are run within a suite 2020-04-23 15:52:53 +02:00
Boyan 36f3f1ec85 Add motivation and example usage 2020-04-08 19:05:01 +10:00
Boyan 1752a4b8e5 Add GitHub pull request template 2020-04-08 19:05:01 +10:00
Martijn b1dfcec1fe Add mvdkleijn as extra maintainer 2020-04-07 09:01:36 -06:00
Angel_Kitty 46796a5c6a Update License file copyright year 2020-04-07 15:16:17 +02:00
Angel_Kitty 581db3dfa7 Update License file copyright year 2020-04-07 15:16:17 +02:00
Angel_Kitty 2c1f261278 Update License file copyright year 2020-04-07 15:16:17 +02:00
Martijn bb468cc94d Remove unneeded return statements 2020-04-06 07:42:56 +10:00
Martijn 28b7455875 Ran go generate ./... 2020-04-06 07:42:56 +10:00
Martijn e0afeb10e3 Fix whitespace issues 2020-04-06 07:42:56 +10:00
Martijn 1454493cee Add test for HTTPStatusCode
For purposes of the test, status code 101 is checked as this does not overlap with the other assertions. (HTTPSuccess, HTTPRedirect and HTTPError)
2020-04-06 07:42:56 +10:00
Martijn f0828adbb3 Add HTTPStatusCode assertion
Allows for asserting a specific HTTP status code was returned instead of a general success, error or redirect assertion.
2020-04-06 07:42:56 +10:00
hectorj 012967472b Make InEpsilon fail when given a NaN
fixes #918
2020-04-02 22:34:25 +11:00
YukinoKamei dfba5a4e3a add ) 2020-03-30 15:55:14 +11:00
Martin Sucha f6cbfc0d03 Print more details in ElementsMatch
It is not very helpful to print that the lengths differ when an
assertion fails, since that does not reveal what the cause of the issue
might be.

Let's print which elements are extra in each list, that should convey
the relevant information to the user. Also use spew to format the
objects, similar to what Equal does, to make the output more readable.
2020-03-14 21:17:20 +11:00
Esdras Beleza de Noronha 961bfee4b1 Add function to return if all tests in a suite passed 2020-03-11 19:42:32 +11:00
Esdras Beleza de Noronha f37e428318 Use comma ok idiom 2020-03-11 19:42:32 +11:00
Esdras Beleza de Noronha 89909913cc Fix var block with only one variable 2020-03-11 19:42:32 +11:00
Esdras Beleza de Noronha 9feda7c901 Change imports for consistency 2020-03-11 19:42:32 +11:00
Esdras Beleza de Noronha fdf3f01101 Update documentation 2020-03-11 19:42:32 +11:00
Esdras Beleza e8910bb335 Add stats to suites and tests 2020-03-11 19:42:32 +11:00
Henry Blyth cb23521296 Test verbose Contains Fail message
If asserting an error contained in a string, includeElement will fail
but Contains will confusingly print both values as strings, which can
look like a testify problem instead of an assertion failure.
2020-03-10 08:59:40 +11:00
Jordan Christiansen c12dcedf28 Include the type in assert.Contains error messages
Fixes #828
2020-03-10 08:59:40 +11:00
alexpantyukhin 6be346c1f1 rename order files to compare files 2020-03-09 08:00:09 +11:00
Alexander Pantyukhin 9388656beb fix language 2020-03-06 19:44:08 +11:00
Alexander Pantyukhin 2ca25e3fac fix for assertions 2020-03-06 19:44:08 +11:00
Alexander Pantyukhin ec73f449b3 add new tests 2020-03-06 19:44:08 +11:00
Alexander Pantyukhin 0b4ff03cda update assertion_order.go according review notes 2020-03-06 19:44:08 +11:00
Alexander Pantyukhin ad53dbbf0a put constants instead of values. 2020-03-06 19:44:08 +11:00
alexpantyukhin d583a38000 fix with gofmt tool 2020-03-06 19:44:08 +11:00
alexpantyukhin f4b48264a4 fix fmt 2020-03-06 19:44:08 +11:00
alexpantyukhin c106be4ce3 refactoring of assertion_order 2020-03-06 19:44:08 +11:00
Christian Muehlhaeuser 02b2656991 Handle ineffectual assignments
Removed ineffectual assignments.
2020-03-06 16:18:13 +11:00
Christian Muehlhaeuser 1c7f4ef084 Avoid unnecessary conversions
No need to convert values here.
2020-03-06 14:40:56 +11:00
Christian Muehlhaeuser 3acde138ca Format code with goimports
Fixed formatting and import order.
2020-03-06 14:39:08 +11:00
Christian Muehlhaeuser 08b5acc756 Fixed tautological errors
err can't ever be not nil here, unless the Go language specs are changing.
2020-03-06 14:38:03 +11:00
Christian Muehlhaeuser 8bb674980e Simplify code
- Direct boolean comparisons
- Don't use Sprintf when we can use the String() function
2020-03-06 14:36:53 +11:00
Christian Muehlhaeuser fbbf8a0782 Removed unused variables
Probably just became obsolete by now.
2020-03-06 14:31:21 +11:00
Christian Muehlhaeuser c5d499e514 Simplify go code
Just reads a bit nicer, in my opinion.
2020-03-06 14:30:34 +11:00
Hiram Chirino 310548cda6 fix: error diff was not being show for large values
If you were comparing values which when formatted were longer than about 64k then the actual, expected and diff messages were not printed to the console.
2020-03-04 06:47:27 +11:00
Boyan b7d60e3a8c Test go modules on and off for master builds 2020-02-28 06:47:40 -07:00
Luan Santos afd4130c14 Disable using Stringer/error interfaces for diffing
`spew`'s default formatter will call `String()` or `Error()` in structs
that implement the `fmt.Stringer` or `error` interfaces. Depending on
the implementation of those, the diff can become quite useless to read
(see the example struct I used for the test case in this commit).

This changes `spew`'s configuration to `DisableMethods` so that it will
always use it's own pretty printer. This makes testing structs less
surprising and generally more useful, without tying the tests to the
implementation of `String()` (the user here can always chose to
`require.Equal(a.String(), b.String())` if testing those is important to
them.
2020-02-26 08:50:07 -07:00
Ogoyukin 9f1c28b404 The mock.IsMethodCallable function is refactored 2020-02-26 16:09:38 +11:00
Ogoyukin 3a72ffb6d8 Refactored Test_Mock_IsMethodCallable 2020-02-26 16:09:38 +11:00
Ogoyukin 6bf5d94027 Update comment 2020-02-26 16:09:38 +11:00
Ogoyukin 4a3d527660 Refactored 'isArgsEqual' 2020-02-26 16:09:38 +11:00
Ogoyukin 36e077c09a Refactored 'IsMethodCallable' 2020-02-26 16:09:38 +11:00
Ogoyukin 42aafee8d7 Added function 'IsMethodCallable', which checking that the method can be called. 2020-02-26 16:09:38 +11:00
Boyan 3ebf1ddaeb Revert PR #867 2020-02-20 07:56:11 +11:00
Corey McGregor 624f997379 fix(suite): Add Fatalf method to suite.TestingT for gomock compatibility 2020-02-19 07:05:10 +11:00
Olaf Alders 5d2970ff94 Fix typo in CONTRIBUTING.md 2020-02-19 07:03:40 +11:00
Olaf Alders 2aadfe8adc Grammatical change in documentation for Run()
There was a switch between singular and plural which made this line a
little bit hard to parse.
2020-02-19 07:03:40 +11:00
Boyan Soubachov 55d8b5740c Add interface tests for suite.T 2020-02-06 07:41:25 +11:00
Chris K 9dfcf7c562 suite: make suite.TestingT satisfy mock.TestingT
...by adding Logf method.
2020-02-05 20:52:22 +11:00
Daniel Cormier 518a1491c7 IsType: A type-safe way of checking argument type
Like `assert.IsType(...)`, `mock.IsType` is used to check that the
type of an argument is the expected type. This is an alternative
to `AnythingOfType`.
2020-01-31 08:08:47 +11:00
Keynan Pratt ea72eb9159 added Log/Skip methods as required by CI tests 2020-01-29 11:08:28 +11:00
Keynan Pratt dca7be2281 updated TestingSuite interface to use TestingT 2020-01-29 11:08:28 +11:00
Keynan Pratt d3e61647c0 removed Helper method from TestingT interface 2020-01-29 11:08:28 +11:00
Keynan Pratt 045d838faf changed dependency to interface, s.t. consumers can easily mock the testing.T type 2020-01-29 11:08:28 +11:00
Torkel Rogstad 17a1e1d4bf Add Never assertion
Never asserts that a conditon isn't met within a given timeframe.
2020-01-29 09:11:59 +11:00
Duncan Mac-Vicar P ce229281f0 assert.Eventually: fail the testcase if condition is not met
Fixes #837
2020-01-29 09:05:22 +11:00
Boyan bda8848f6f Use latest Go master instead of dev
* Changed the latest Go version of the Travis build to be master instead of dev
2020-01-29 09:02:27 +11:00
Boyan Soubachov 9cc41e665b Check go generate on builds
* Check if we have mismatching `go generate ./...` output on builds
2020-01-29 07:21:51 +11:00
Boyan Soubachov 12fe0eb94c Remove redundant files
* Removed redundant `dep` files as we now use Go moduels
* Correct import order for package_test.go
2020-01-28 10:21:55 -07:00
Dinesh Kumar 8c465a0c8e changing time.Duration equality mismatch output from int64 to readable format. Fixes #626
(cherry picked from commit 637cd144ddae7a3792bcb5c74a3bf3a071c0a250)
2020-01-16 14:34:20 +11:00
devdinu 3c60a0e014 Merging suite_order_test in suite test, removing anonymous defer func call 2020-01-16 14:06:22 +11:00
Dinesh Kumar e7b6c14305 Remove parallel as that makes goroutines deadlock 2020-01-16 14:06:22 +11:00
Dinesh Kumar 0d3c8ce9f8 fixing suite teardown ordering issue
(cherry picked from commit 92c097a5af7b4bbbe3a66c2e7484f68194cd555a)
2020-01-16 14:06:22 +11:00
Boyan Soubachov 858f37ff9b Allow nil-function Equal comparisons 2019-12-23 07:34:01 -07:00
Boyan Soubachov 22d5528225 Fix InDelta docstring
* Fixed the docstring for the InDelta function.
2019-12-23 07:31:11 -07:00
George Lesica 41d0ae8564 Add some project docs 2019-12-13 09:29:10 +02:00
Boyan Soubachov 7660131ef3 Update readme to include Slack channels 2019-12-12 14:10:24 -07:00
Boyan Soubachov 28b40b159e Improve tests 2019-12-12 12:41:57 -07:00
Boyan Soubachov 4b71b28738 WIP 2019-12-12 12:41:57 -07:00
Boyan Soubachov 7b3a490010 Rename 2019-12-12 12:41:57 -07:00
Boyan Soubachov 937e12391f Added negative dir and file tests
* Added NotFileExists test
* Added NotDirExists test
* Cleaned-up some comment formatting
2019-12-12 12:41:57 -07:00
Leigh McCulloch 9a14481b90 Add go generated files 2019-12-10 08:32:02 -07:00
Leigh McCulloch 5b0b9669b6 Reduce double errors 2019-12-10 08:32:02 -07:00
Leigh McCulloch 367102ea5a Add PanicsWithError
# What
Add `PanicsWithError` that behaves like `PanicsWithValue` but requires
the value to be an `error` and compares an error string against the
error in the same fashion as `EqualError`.

# Why
`PanicsWithValue` is very useful with the value that will panic is a
basic type that can be easily defined in a test, but is less useful for
validating an error because error values may not be easily recreated in
a test to exactly match the error that will be generated in the moment.
This can be because an error will contain fields that aren't exported
and the test may not be able to define the exact error. Most of the time
testing error objects just testing the string is sufficient. This
concept is already supported by this module in the existing `EqualError`
function.
2019-12-10 08:32:02 -07:00
Leigh McCulloch 59bec1f2ff Get the modules aware importer via the function that was available in Go 1.11 and earlier 2019-12-09 10:12:49 -07:00
Leigh McCulloch 940aba6697 Remove special vendor handling since there is no vendor directory 2019-12-09 10:12:49 -07:00
Leigh McCulloch f2b3a9bb9b Simplify go vet command and use standard method for veting all packages 2019-12-09 10:12:49 -07:00
Leigh McCulloch 3b0d317f67 Fix code not gofmtd 2019-12-09 10:12:49 -07:00
Leigh McCulloch 7369010b35 Run all travis scripts properly 2019-12-09 10:12:49 -07:00
Leigh McCulloch 32124e9523 Fix check 2019-12-09 10:12:49 -07:00
Leigh McCulloch b4bc8de52a Make the go generate script better and more similar to PR 853 2019-12-09 10:12:49 -07:00
Leigh McCulloch 43bc313c8e Revert "Fix TravisCI script"
This reverts commit 7058427b48.
2019-12-09 10:12:49 -07:00
Leigh McCulloch d893331bef Fix TravisCI script 2019-12-09 10:12:49 -07:00
Leigh McCulloch 60ab6cdfeb Limit go generate to 1.12 and 1.13 2019-12-09 10:12:49 -07:00
Leigh McCulloch 8e7131e0bd Always enable modules for generation 2019-12-09 10:12:49 -07:00
Leigh McCulloch 8a3895e82f Update README with statement about Go versions 2019-12-09 10:12:49 -07:00
Leigh McCulloch 1a61f16b44 Add Go 1.12 and 1.13 to TravisCI 2019-12-09 10:12:49 -07:00
Leigh McCulloch da3134049a Remove guard from TravisCI 2019-12-09 10:12:49 -07:00
Leigh McCulloch 7c5ac23b81 Make _codegen its own module so that its dependencies do not pollute the root modules dependency graph 2019-12-09 10:12:49 -07:00
Leigh McCulloch 711f4df360 Fix codegen for Go Modules
Fix two issues with codegen that cause it not to work with Go Modules.

1. When parsing the code files the package SrcRoot and ImportPath were
being joined which assumes the package is hosted inside a GOPATH src
directory. Using those two fields is unnecessary because a package also
knows what directory it lives in, which is in the Dir field. This change
switches to using the Dir field.

2. When checking the types in the source files the default importer is
being used. Unfortunately the default importer has not been updated to
work with Go Modules and it only looks in the GOPATH for imports.
Luckily the Go team were convinced to backport some logic from the newer
go/packages package to go/build, and we can make use of the go/build
importer which supports Go Modules. One downside of using the go/build
importer is it is said to be slow. For this size project though it's I
don't think it's a problem.

In the future codegen should be rewritten to use go/packages once it is
included in the stdlib. go/packages currently lives at
golang.org/x/tools/go/packages. Once it is in the stdlib I believe it
will replace go/build.

Both fixes are compatible with Go 1.13, Go 1.12, Go 1.11, Go 1.10.

I've also updated the TravisCI config to run go generate, and to add the
newer versions of Go to demonstrate that this works on all versions of
Go.
2019-12-09 10:12:49 -07:00
Boyan Soubachov 7009337cb5 Remove vendor folder
* Removed vendor folder to get go generate working by default
2019-12-06 16:52:44 -07:00
Boyan Soubachov 37e2176163 Rerun go generate
* Added generated code for new assert & require tests
2019-12-03 09:34:43 -07:00
Jacek Szwec f1bd0923b8 Fix panic for Eventually functions
Fixes #805, Fixes #835
2019-11-06 15:43:47 -07:00
Boyan a88bf7aab8 PR comments 2019-11-04 07:28:29 -07:00
Boyan cabedb3967 Refactor 2019-11-04 07:28:29 -07:00
Boyan 4a312d02f2 Minor fix to Readme.md 2019-11-04 07:28:29 -07:00
Boyan d2e1501cff Remove hand editing of generated code 2019-11-04 07:28:29 -07:00
Boyan 4d8751d477 Unit test typo fix 2019-11-04 07:28:29 -07:00
Boyan db3bc60f5a Typo fix for require.NotSamef 2019-11-04 07:28:29 -07:00
Boyan 7088056203 Add NotSame
* Added NotSame test for the assert package
* Added NotSame test for the require package
* Included formatted variants of NotSame for both assert and require
2019-11-04 07:28:29 -07:00
Daniel Cormier 85f2b59c44 Add panic stack to output from NotPanics assertion failures 2019-09-04 10:35:30 -06:00
xiaolei 221dbe5ed4 fix mutex 2019-07-11 18:01:36 -06:00
xiaolei 1bb3d5a619 improve find expected call 2019-07-11 18:01:36 -06:00
Vitaly Isaev 555ebd3959 Fix panic for equality assertions for values of aliased string type 2019-07-11 17:52:58 -06:00
Nobuhiro MIKI 0224ef258e Add YAMLEq to assert that two YAML strings are equivalent 2019-07-11 17:50:32 -06:00
Grzegorz Miejski d84e815d44 Introduce Eventually assertion. 2019-07-09 13:57:54 -06:00
Miles Steele ae5876d09a change report wording, fix int stringification, fix doc 2019-07-08 11:49:27 -06:00
Kirill Smelkov 34c6fa2dc7 assert, require: Regenerate
Run `go generate ...` from top of testify tree. This brings in wappers
for Greater, GreaterOrEqual, Less, LessOrEqual and Same.
2019-03-11 10:14:05 -06:00
Gavin Cabbage 21cb1c2932 add ptr comparison func 'Same' to assert pkg 2019-02-28 14:48:09 -07:00
Sergej Zagursky 10a9f47426 Show stack trace on panic 2019-02-28 14:40:23 -07:00
apantykhin 3f658bd5ac add comparison 2019-02-19 09:07:39 -07:00
Chris Marchbanks 78018b09cd Helper is called before assert function in require.* functions 2019-02-14 17:34:13 -07:00
Tigran Saluev 2894c76efe Rename setupDone to suiteSetupDone 2019-02-14 09:28:04 -07:00
Tigran Saluev 943c6e8f43 Add test on suite setup skipping 2019-02-14 09:28:04 -07:00
Tigran Saluev af4cbaf11d Rerun build 2019-02-14 09:28:04 -07:00
Tigran Saluev 2696ec2b70 Fix tests 2019-02-14 09:28:04 -07:00
Tigran Saluev 3efd733edb Don't setup suite if no methods satisfy filter
When suite setup is long, the necessity to wait for all suite setups for testing one single method bothers a lot. Here's a little fix of that behavior.
2019-02-14 09:28:04 -07:00
Alexander Pantyukhin 363ebb24d0 Update assertions.go
fix for value.
2019-01-09 09:23:56 -07:00
apantykhin 834f27f4b7 refactor includeElement method 2019-01-09 09:23:56 -07:00
91 changed files with 10774 additions and 8007 deletions

14
.ci.gofmt.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
if [ -n "$(gofmt -l .)" ]; then
echo "Go code is not formatted:"
gofmt -d .
exit 1
fi
go generate ./...
if [ -n "$(git status -s -uno)" ]; then
echo "Go generate output does not match commit."
echo "Did you forget to run go generate ./... ?"
exit 1
fi

16
.ci.gogenerate.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# If GOMOD is defined we are running with Go Modules enabled, either
# automatically or via the GO111MODULE=on environment variable. Codegen only
# works with modules, so skip generation if modules is not in use.
if [[ -z "$(go env GOMOD)" ]]; then
echo "Skipping go generate because modules not enabled and required"
exit 0
fi
go generate ./...
if [ -n "$(git diff)" ]; then
echo "Go generate had not been run"
git diff
exit 1
fi

5
.ci.govet.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
go vet ./...

12
.ci.readme.fmt.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
# Verify that the code snippets in README.md are formatted.
# The tool https://github.com/hougesen/mdsf is used.
if [ -n "$(mdsf verify --config .mdsf.json --log-level error README.md 2>&1)" ]; then
echo "Go code in the README.md is not formatted."
echo "Did you forget to run 'mdsf format --config .mdsf.json README.md'?"
mdsf format --config .mdsf.json README.md
git diff
exit 1
fi

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Format to report a bug
title: ''
labels: bug
assignees: ''
---
<!-- If this is a question, consider using the discussion section of this repo -->
<!-- Here: https://github.com/stretchr/testify/discussions/new?category=q-a -->
## Description
<!-- A detailed description of the bug -->
## Step To Reproduce
<!-- Steps or code snippet to reproduce the behavior -->
## Expected behavior
<!-- A clear and concise description of what you expected to happen -->
## Actual behavior
<!-- What testify does -->

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Propose a new feature
title: ''
labels: enhancement
assignees: ''
---
<!-- If this is a question, consider using the discussion section of this repo -->
<!-- Here: https://github.com/stretchr/testify/discussions/new?category=q-a -->
## Description
<!-- A clear and concise description of what feature you are proposing -->
## Proposed solution
<!-- Optionally a suggested implementation -->
## Use case
<!-- What is the motivation? What workarounds have you used? -->

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily

15
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,15 @@
## Summary
<!-- High-level, one sentence summary of what this PR accomplishes -->
## Changes
<!-- * Description of change 1 -->
<!-- * Description of change 2 -->
<!-- ... -->
## Motivation
<!-- Why were the changes necessary. -->
<!-- ## Example usage (if applicable) -->
## Related issues
<!-- Put `Closes #XXXX` for each issue number this PR fixes/closes -->

41
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: All builds
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go_version:
- stable
- oldstable
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go_version }}
- run: npm install -g mdsf-cli
- run: ./.ci.gogenerate.sh
- run: ./.ci.gofmt.sh
- run: ./.ci.readme.fmt.sh
- run: ./.ci.govet.sh
- run: go test -v -race ./...
test:
runs-on: ubuntu-latest
strategy:
matrix:
go_version:
- "1.17"
- "1.18"
- "1.19"
- "1.20"
- "1.21"
- "1.22"
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go_version }}
- run: go test -v -race ./...

21
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Create release from new tag
# this flow will be run only when new tags are pushed that match our pattern
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Create GitHub release from tag
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true

6
.gitignore vendored
View File

@ -22,3 +22,9 @@ _testmain.go
*.exe
.DS_Store
# Output of "go test -c"
/assert/assert.test
/require/require.test
/suite/suite.test
/mock/mock.test

12
.mdsf.json Normal file
View File

@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/hougesen/mdsf/main/schemas/v0.8.2/mdsf.schema.json",
"format_finished_document": false,
"languages": {
"go": [
[
"gofmt",
"goimports"
]
]
}
}

View File

@ -1,7 +0,0 @@
#!/bin/bash
if [ -n "$(gofmt -l .)" ]; then
echo "Go code is not formatted:"
gofmt -d .
exit 1
fi

View File

@ -1,13 +0,0 @@
#!/bin/bash
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.[45](\..*)?$ ]]; then
exit 0
fi
go get github.com/ernesto-jimenez/gogen/imports
go generate ./...
if [ -n "$(git diff)" ]; then
echo "Go generate had not been run"
git diff
exit 1
fi

View File

@ -1,10 +0,0 @@
#!/bin/bash
cd "$(dirname $0)"
DIRS=". assert require mock _codegen"
set -e
for subdir in $DIRS; do
pushd $subdir
go vet
popd
done

View File

@ -1,19 +0,0 @@
language: go
sudo: false
matrix:
include:
- go: "1.8.x"
- go: "1.9.x"
- go: "1.10.x"
- go: "1.11.x"
env: GO111MODULE=off
- go: "1.11.x"
env: GO111MODULE=on
- go: tip
script:
- ./.travis.gogenerate.sh
- ./.travis.gofmt.sh
- ./.travis.govet.sh
- go test -v -race $(go list ./... | grep -v vendor)

50
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,50 @@
# Contributing to Testify
So you'd like to contribute to Testify? First of all, thank you! Testify is widely used, so each
contribution has a significant impact within the Golang community! Below you'll find everything you
need to know to get up to speed on the project.
## Philosophy
The Testify maintainers generally attempt to follow widely accepted practices within the Golang
community. That being said, the first priority is always to make sure that the package is useful to
the community. A few general guidelines are listed here:
*Keep it simple (whenever practical)* - Try not to expand the API unless the new surface area
provides meaningful benefits. For example, don't add functions because they might be useful to
someone, someday. Add what is useful to specific users, today.
*Ease of use is paramount* - This means good documentation and package organization. It also means
that we should try hard to use meaningful, descriptive function names, avoid breaking the API
unnecessarily, and try not to surprise the user.
*Quality isn't an afterthought* - Testify is a testing library, so it seems reasonable that we
should have a decent test suite. This is doubly important because a bug in Testify doesn't just mean
a bug in our users' code, it means a bug in our users' tests, which means a potentially unnoticed
and hard-to-find bug in our users' code.
## Pull Requests
We welcome pull requests! Please include the following in the description:
* Motivation, why your change is important or helpful
* Example usage (if applicable)
* Whether you intend to add / change behavior or fix a bug
Please be aware that the maintainers may ask for changes. This isn't a commentary on the quality of
your idea or your code. Testify is the result of many contributions from many individuals, so we
need to enforce certain practices and patterns to keep the package easy for others to understand.
Essentially, we recognize that there are often many good ways to do a given thing, but we have to
pick one and stick with it.
See `MAINTAINERS.md` for a list of users who can approve / merge your changes.
## Issues
If you find a bug or think of a useful feature you'd like to see added to Testify, the best thing
you can do is make the necessary changes and open a pull request (see above). If that isn't an
option, or if you'd like to discuss your change before you write the code, open an issue!
Please provide enough context in the issue description that other members of the community can
easily understand what it is that you'd like to see.

12
EMERITUS.md Normal file
View File

@ -0,0 +1,12 @@
# Emeritus
We would like to acknowledge previous testify maintainers and their huge contributions to our collective success:
* @matryer
* @glesica
* @ernesto-jimenez
* @mvdkleijn
* @georgelesica-wf
* @bencampbell-wf
We thank these members for their service to this community.

27
Gopkg.lock generated
View File

@ -1,27 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/stretchr/objx"
packages = ["."]
revision = "facf9a85c22f48d2f52f2380e4efce1768749a89"
version = "v0.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "448ddae4702c6aded2555faafd390c537789bb1c483f70b0431e6634f73f2090"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,16 +0,0 @@
[prune]
unused-packages = true
non-go = true
go-tests = true
[[constraint]]
name = "github.com/davecgh/go-spew"
version = "~1.1.0"
[[constraint]]
name = "github.com/pmezard/go-difflib"
version = "~1.0.0"
[[constraint]]
name = "github.com/stretchr/objx"
version = "~0.1.0"

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

17
MAINTAINERS.md Normal file
View File

@ -0,0 +1,17 @@
# Testify Maintainers
The individuals listed below are active in the project and have the ability to approve and merge
pull requests.
* @boyan-soubachov
* @dolmen
* @MovieStoreGuy
* @brackendawson
## Approvers
The individuals listed below are active in the project and have the ability to approve pull
requests.
* @arjunmahishi
* @ccoVeille

230
README.md
View File

@ -1,7 +1,11 @@
Testify - Thou Shalt Write Tests
================================
[![Build Status](https://travis-ci.org/stretchr/testify.svg)](https://travis-ci.org/stretchr/testify) [![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/testify)](https://goreportcard.com/report/github.com/stretchr/testify) [![GoDoc](https://godoc.org/github.com/stretchr/testify?status.svg)](https://godoc.org/github.com/stretchr/testify)
> [!NOTE]
> Testify is being maintained at v1, no breaking changes will be accepted in this repo.
> [See discussion about v2](https://github.com/stretchr/testify/discussions/1560).
[![Build Status](https://github.com/stretchr/testify/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/stretchr/testify/actions/workflows/main.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/testify)](https://goreportcard.com/report/github.com/stretchr/testify) [![PkgGoDev](https://pkg.go.dev/badge/github.com/stretchr/testify)](https://pkg.go.dev/github.com/stretchr/testify)
Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend.
@ -14,14 +18,12 @@ Features include:
Get started:
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
* For an introduction to writing test code in Go, see http://golang.org/doc/code.html#Testing
* Check out the API Documentation http://godoc.org/github.com/stretchr/testify
* To make your testing life easier, check out our other project, [gorc](http://github.com/stretchr/gorc)
* A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development)
* For an introduction to writing test code in Go, see https://go.dev/doc/code#Testing
* Check out the API Documentation https://pkg.go.dev/github.com/stretchr/testify
* Use [testifylint](https://github.com/Antonboom/testifylint) (via [golangci-lint](https://golangci-lint.run/)) to avoid common mistakes
* A little about [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)
[`assert`](http://godoc.org/github.com/stretchr/testify/assert "API documentation") package
[`assert`](https://pkg.go.dev/github.com/stretchr/testify/assert "API documentation") package
-------------------------------------------------------------------------------------------
The `assert` package provides some helpful methods that allow you to write better test code in Go.
@ -36,30 +38,27 @@ See it in action:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
```
@ -72,52 +71,55 @@ if you assert many times, use the below:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert := assert.New(t)
assert := assert.New(t)
// assert equality
assert.Equal(123, 123, "they should be equal")
// assert equality
assert.Equal(123, 123, "they should be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(object)
// assert for nil (good for errors)
assert.Nil(object)
// assert for not nil (good when you expect something)
if assert.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal("Something", object.Value)
}
// assert for not nil (good when you expect something)
if assert.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal("Something", object.Value)
}
}
```
[`require`](http://godoc.org/github.com/stretchr/testify/require "API documentation") package
[`require`](https://pkg.go.dev/github.com/stretchr/testify/require "API documentation") package
---------------------------------------------------------------------------------------------
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
These functions must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test.
Otherwise race conditions may occur.
See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details.
See [t.FailNow](https://pkg.go.dev/testing#T.FailNow) for details.
[`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package
[`mock`](https://pkg.go.dev/github.com/stretchr/testify/mock "API documentation") package
----------------------------------------------------------------------------------------
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
An example test function that tests a piece of code that relies on an external object `testObj`, can setup expectations (testify) and assert that they indeed happened:
An example test function that tests a piece of code that relies on an external object `testObj`, can set up expectations (testify) and assert that they indeed happened:
```go
package yours
import (
"testing"
"github.com/stretchr/testify/mock"
"testing"
"github.com/stretchr/testify/mock"
)
/*
@ -126,8 +128,8 @@ import (
// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{
mock.Mock
type MyMockedObject struct {
mock.Mock
}
// DoSomething is a method on MyMockedObject that implements some interface
@ -138,10 +140,8 @@ type MyMockedObject struct{
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
/*
@ -151,127 +151,150 @@ func (m *MyMockedObject) DoSomething(number int) (bool, error) {
// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// create an instance of our test object
testObj := new(MyMockedObject)
// setup expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// set up expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingElse is a second example of how to use our test object to
// TestSomethingWithPlaceholder is a second example of how to use our test object to
// make assertions about some target code we are testing.
// This time using a placeholder. Placeholders might be used when the
// data being passed in is normally dynamically generated and cannot be
// predicted beforehand (eg. containing hashes that are time sensitive)
func TestSomethingElse(t *testing.T) {
func TestSomethingWithPlaceholder(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)
// setup expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingElse2 is a third example that shows how you can use
// the Unset method to cleanup handlers and then add new ones.
func TestSomethingElse2(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// remove the handler now so we can add another one that takes precedence
mockCall.Unset()
// return false now instead of true
testObj.On("DoSomething", mock.Anything).Return(false, nil)
testObj.AssertExpectations(t)
}
```
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock).
For more information on how to write mock code, check out the [API documentation for the `mock` package](https://pkg.go.dev/github.com/stretchr/testify/mock).
You can use the [mockery tool](http://github.com/vektra/mockery) to autogenerate the mock code against an interface as well, making using mocks much quicker.
You can use the [mockery tool](https://vektra.github.io/mockery/latest/) to autogenerate the mock code against an interface as well, making using mocks much quicker.
[`suite`](http://godoc.org/github.com/stretchr/testify/suite "API documentation") package
[`suite`](https://pkg.go.dev/github.com/stretchr/testify/suite "API documentation") package
-----------------------------------------------------------------------------------------
> [!WARNING]
> The suite package does not support parallel tests. See [#934](https://github.com/stretchr/testify/issues/934).
The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
The `suite` package provides functionality that you might be used to from more common object-oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
An example suite is shown below:
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
suite.Run(t, new(ExampleTestSuite))
}
```
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite).
For more information on writing suites, check out the [API documentation for the `suite` package](https://pkg.go.dev/github.com/stretchr/testify/suite).
`Suite` object has assertion methods:
```go
// Basic imports
import (
"testing"
"github.com/stretchr/testify/suite"
"testing"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including assertion methods.
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
suite.Equal(suite.VariableThatShouldStartAtFive, 5)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
suite.Run(t, new(ExampleTestSuite))
}
```
@ -298,14 +321,13 @@ Import the `testify/assert` package into your code using this template:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert.True(t, true, "True is true!")
assert.True(t, true, "True is true!")
}
```
@ -321,7 +343,7 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test
Supported go versions
==================
We support the three major Go versions, which are 1.9, 1.10, and 1.11 at the moment.
We currently support the most recent major Go versions from 1.19 onward.
------
@ -330,7 +352,11 @@ Contributing
Please feel free to submit issues, fork the repository and send pull requests!
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
Code generation is used. [Look for `Code generated with`](https://github.com/search?q=repo%3Astretchr%2Ftestify%20%22Code%20generated%20with%22&type=code) at the top of some files. Run `go generate ./...` to update generated files.
We also chat on the [Gophers Slack](https://gophers.slack.com) group in the `#testify` and `#testify-dev` channels.
------

1
_codegen/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_codegen

5
_codegen/go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/stretchr/testify/_codegen
go 1.11
require github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607

2
_codegen/go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607 h1:cTavhURetDkezJCvxFggiyLeP40Mrk/TtVg2+ycw1Es=
github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607/go.mod h1:Cg4fM0vhYWOZdgM7RIOSTRNIc8/VT7CXClC3Ni86lu4=

View File

@ -16,7 +16,6 @@ import (
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"os"
"path"
@ -101,13 +100,15 @@ func parseTemplates() (*template.Template, *template.Template, error) {
return nil, nil, err
}
if *tmplFile != "" {
f, err := ioutil.ReadFile(*tmplFile)
f, err := os.ReadFile(*tmplFile)
if err != nil {
return nil, nil, err
}
funcTemplate = string(f)
}
tmpl, err := template.New("function").Parse(funcTemplate)
tmpl, err := template.New("function").Funcs(template.FuncMap{
"replace": strings.ReplaceAll,
}).Parse(funcTemplate)
if err != nil {
return nil, nil, err
}
@ -181,7 +182,7 @@ func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
files := make(map[string]*ast.File)
fileList := make([]*ast.File, len(pd.GoFiles))
for i, fname := range pd.GoFiles {
src, err := ioutil.ReadFile(path.Join(pd.SrcRoot, pd.ImportPath, fname))
src, err := os.ReadFile(path.Join(pd.Dir, fname))
if err != nil {
return nil, nil, err
}
@ -194,7 +195,7 @@ func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
}
cfg := types.Config{
Importer: importer.Default(),
Importer: importer.For("source", nil),
}
info := types.Info{
Defs: make(map[*ast.Ident]types.Object),
@ -287,7 +288,7 @@ func (f *testFunc) CommentFormat() string {
search := fmt.Sprintf("%s", f.DocInfo.Name)
replace := fmt.Sprintf("%sf", f.DocInfo.Name)
comment := strings.Replace(f.Comment(), search, replace, -1)
exp := regexp.MustCompile(replace + `\(((\(\)|[^)])+)\)`)
exp := regexp.MustCompile(replace + `\(((\(\)|[^\n])+)\)`)
return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`)
}
@ -297,10 +298,8 @@ func (f *testFunc) CommentWithoutT(receiver string) string {
return strings.Replace(f.Comment(), search, replace, -1)
}
var headerTemplate = `/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Standard header https://go.dev/s/generatedcode.
var headerTemplate = `// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package {{.Name}}

495
assert/assertion_compare.go Normal file
View File

@ -0,0 +1,495 @@
package assert
import (
"bytes"
"fmt"
"reflect"
"time"
)
// Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it.
type CompareType = compareResult
type compareResult int
const (
compareLess compareResult = iota - 1
compareEqual
compareGreater
)
var (
intType = reflect.TypeOf(int(1))
int8Type = reflect.TypeOf(int8(1))
int16Type = reflect.TypeOf(int16(1))
int32Type = reflect.TypeOf(int32(1))
int64Type = reflect.TypeOf(int64(1))
uintType = reflect.TypeOf(uint(1))
uint8Type = reflect.TypeOf(uint8(1))
uint16Type = reflect.TypeOf(uint16(1))
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
uintptrType = reflect.TypeOf(uintptr(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
stringType = reflect.TypeOf("")
timeType = reflect.TypeOf(time.Time{})
bytesType = reflect.TypeOf([]byte{})
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) {
obj1Value := reflect.ValueOf(obj1)
obj2Value := reflect.ValueOf(obj2)
// throughout this switch we try and avoid calling .Convert() if possible,
// as this has a pretty big performance impact
switch kind {
case reflect.Int:
{
intobj1, ok := obj1.(int)
if !ok {
intobj1 = obj1Value.Convert(intType).Interface().(int)
}
intobj2, ok := obj2.(int)
if !ok {
intobj2 = obj2Value.Convert(intType).Interface().(int)
}
if intobj1 > intobj2 {
return compareGreater, true
}
if intobj1 == intobj2 {
return compareEqual, true
}
if intobj1 < intobj2 {
return compareLess, true
}
}
case reflect.Int8:
{
int8obj1, ok := obj1.(int8)
if !ok {
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
}
int8obj2, ok := obj2.(int8)
if !ok {
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
}
if int8obj1 > int8obj2 {
return compareGreater, true
}
if int8obj1 == int8obj2 {
return compareEqual, true
}
if int8obj1 < int8obj2 {
return compareLess, true
}
}
case reflect.Int16:
{
int16obj1, ok := obj1.(int16)
if !ok {
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
}
int16obj2, ok := obj2.(int16)
if !ok {
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
}
if int16obj1 > int16obj2 {
return compareGreater, true
}
if int16obj1 == int16obj2 {
return compareEqual, true
}
if int16obj1 < int16obj2 {
return compareLess, true
}
}
case reflect.Int32:
{
int32obj1, ok := obj1.(int32)
if !ok {
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
}
int32obj2, ok := obj2.(int32)
if !ok {
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
}
if int32obj1 > int32obj2 {
return compareGreater, true
}
if int32obj1 == int32obj2 {
return compareEqual, true
}
if int32obj1 < int32obj2 {
return compareLess, true
}
}
case reflect.Int64:
{
int64obj1, ok := obj1.(int64)
if !ok {
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
}
int64obj2, ok := obj2.(int64)
if !ok {
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
}
if int64obj1 > int64obj2 {
return compareGreater, true
}
if int64obj1 == int64obj2 {
return compareEqual, true
}
if int64obj1 < int64obj2 {
return compareLess, true
}
}
case reflect.Uint:
{
uintobj1, ok := obj1.(uint)
if !ok {
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
}
uintobj2, ok := obj2.(uint)
if !ok {
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
}
if uintobj1 > uintobj2 {
return compareGreater, true
}
if uintobj1 == uintobj2 {
return compareEqual, true
}
if uintobj1 < uintobj2 {
return compareLess, true
}
}
case reflect.Uint8:
{
uint8obj1, ok := obj1.(uint8)
if !ok {
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj2, ok := obj2.(uint8)
if !ok {
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
}
if uint8obj1 > uint8obj2 {
return compareGreater, true
}
if uint8obj1 == uint8obj2 {
return compareEqual, true
}
if uint8obj1 < uint8obj2 {
return compareLess, true
}
}
case reflect.Uint16:
{
uint16obj1, ok := obj1.(uint16)
if !ok {
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj2, ok := obj2.(uint16)
if !ok {
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
}
if uint16obj1 > uint16obj2 {
return compareGreater, true
}
if uint16obj1 == uint16obj2 {
return compareEqual, true
}
if uint16obj1 < uint16obj2 {
return compareLess, true
}
}
case reflect.Uint32:
{
uint32obj1, ok := obj1.(uint32)
if !ok {
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj2, ok := obj2.(uint32)
if !ok {
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
}
if uint32obj1 > uint32obj2 {
return compareGreater, true
}
if uint32obj1 == uint32obj2 {
return compareEqual, true
}
if uint32obj1 < uint32obj2 {
return compareLess, true
}
}
case reflect.Uint64:
{
uint64obj1, ok := obj1.(uint64)
if !ok {
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj2, ok := obj2.(uint64)
if !ok {
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
}
if uint64obj1 > uint64obj2 {
return compareGreater, true
}
if uint64obj1 == uint64obj2 {
return compareEqual, true
}
if uint64obj1 < uint64obj2 {
return compareLess, true
}
}
case reflect.Float32:
{
float32obj1, ok := obj1.(float32)
if !ok {
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
}
float32obj2, ok := obj2.(float32)
if !ok {
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
}
if float32obj1 > float32obj2 {
return compareGreater, true
}
if float32obj1 == float32obj2 {
return compareEqual, true
}
if float32obj1 < float32obj2 {
return compareLess, true
}
}
case reflect.Float64:
{
float64obj1, ok := obj1.(float64)
if !ok {
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
}
float64obj2, ok := obj2.(float64)
if !ok {
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
}
if float64obj1 > float64obj2 {
return compareGreater, true
}
if float64obj1 == float64obj2 {
return compareEqual, true
}
if float64obj1 < float64obj2 {
return compareLess, true
}
}
case reflect.String:
{
stringobj1, ok := obj1.(string)
if !ok {
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
}
stringobj2, ok := obj2.(string)
if !ok {
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
}
if stringobj1 > stringobj2 {
return compareGreater, true
}
if stringobj1 == stringobj2 {
return compareEqual, true
}
if stringobj1 < stringobj2 {
return compareLess, true
}
}
// Check for known struct types we can check for compare results.
case reflect.Struct:
{
// All structs enter here. We're not interested in most types.
if !obj1Value.CanConvert(timeType) {
break
}
// time.Time can be compared!
timeObj1, ok := obj1.(time.Time)
if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
}
timeObj2, ok := obj2.(time.Time)
if !ok {
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
}
if timeObj1.Before(timeObj2) {
return compareLess, true
}
if timeObj1.Equal(timeObj2) {
return compareEqual, true
}
return compareGreater, true
}
case reflect.Slice:
{
// We only care about the []byte type.
if !obj1Value.CanConvert(bytesType) {
break
}
// []byte can be compared!
bytesObj1, ok := obj1.([]byte)
if !ok {
bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
}
bytesObj2, ok := obj2.([]byte)
if !ok {
bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
}
return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true
}
case reflect.Uintptr:
{
uintptrObj1, ok := obj1.(uintptr)
if !ok {
uintptrObj1 = obj1Value.Convert(uintptrType).Interface().(uintptr)
}
uintptrObj2, ok := obj2.(uintptr)
if !ok {
uintptrObj2 = obj2Value.Convert(uintptrType).Interface().(uintptr)
}
if uintptrObj1 > uintptrObj2 {
return compareGreater, true
}
if uintptrObj1 == uintptrObj2 {
return compareEqual, true
}
if uintptrObj1 < uintptrObj2 {
return compareLess, true
}
}
}
return compareEqual, false
}
// Greater asserts that the first element is greater than the second
//
// assert.Greater(t, 2, 1)
// assert.Greater(t, float64(2), float64(1))
// assert.Greater(t, "b", "a")
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// GreaterOrEqual asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqual(t, 2, 1)
// assert.GreaterOrEqual(t, 2, 2)
// assert.GreaterOrEqual(t, "b", "a")
// assert.GreaterOrEqual(t, "b", "b")
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
}
// Less asserts that the first element is less than the second
//
// assert.Less(t, 1, 2)
// assert.Less(t, float64(1), float64(2))
// assert.Less(t, "a", "b")
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
}
// LessOrEqual asserts that the first element is less than or equal to the second
//
// assert.LessOrEqual(t, 1, 2)
// assert.LessOrEqual(t, 2, 2)
// assert.LessOrEqual(t, "a", "b")
// assert.LessOrEqual(t, "b", "b")
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
failMessage := fmt.Sprintf("\"%v\" is not positive", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
failMessage := fmt.Sprintf("\"%v\" is not negative", e)
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
e1Kind := reflect.ValueOf(e1).Kind()
e2Kind := reflect.ValueOf(e2).Kind()
if e1Kind != e2Kind {
return Fail(t, "Elements should be the same type", msgAndArgs...)
}
compareResult, isComparable := compare(e1, e2, e1Kind)
if !isComparable {
return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, failMessage, msgAndArgs...)
}
return true
}
func containsValue(values []compareResult, value compareResult) bool {
for _, v := range values {
if v == value {
return true
}
}
return false
}

View File

@ -0,0 +1,472 @@
package assert
import (
"bytes"
"fmt"
"reflect"
"runtime"
"testing"
"time"
)
func TestCompare(t *testing.T) {
type customString string
type customInt int
type customInt8 int8
type customInt16 int16
type customInt32 int32
type customInt64 int64
type customUInt uint
type customUInt8 uint8
type customUInt16 uint16
type customUInt32 uint32
type customUInt64 uint64
type customFloat32 float32
type customFloat64 float64
type customUintptr uintptr
type customTime time.Time
type customBytes []byte
for _, currCase := range []struct {
less interface{}
greater interface{}
cType string
}{
{less: customString("a"), greater: customString("b"), cType: "string"},
{less: "a", greater: "b", cType: "string"},
{less: customInt(1), greater: customInt(2), cType: "int"},
{less: int(1), greater: int(2), cType: "int"},
{less: customInt8(1), greater: customInt8(2), cType: "int8"},
{less: int8(1), greater: int8(2), cType: "int8"},
{less: customInt16(1), greater: customInt16(2), cType: "int16"},
{less: int16(1), greater: int16(2), cType: "int16"},
{less: customInt32(1), greater: customInt32(2), cType: "int32"},
{less: int32(1), greater: int32(2), cType: "int32"},
{less: customInt64(1), greater: customInt64(2), cType: "int64"},
{less: int64(1), greater: int64(2), cType: "int64"},
{less: customUInt(1), greater: customUInt(2), cType: "uint"},
{less: uint8(1), greater: uint8(2), cType: "uint8"},
{less: customUInt8(1), greater: customUInt8(2), cType: "uint8"},
{less: uint16(1), greater: uint16(2), cType: "uint16"},
{less: customUInt16(1), greater: customUInt16(2), cType: "uint16"},
{less: uint32(1), greater: uint32(2), cType: "uint32"},
{less: customUInt32(1), greater: customUInt32(2), cType: "uint32"},
{less: uint64(1), greater: uint64(2), cType: "uint64"},
{less: customUInt64(1), greater: customUInt64(2), cType: "uint64"},
{less: float32(1.23), greater: float32(2.34), cType: "float32"},
{less: customFloat32(1.23), greater: customFloat32(2.23), cType: "float32"},
{less: float64(1.23), greater: float64(2.34), cType: "float64"},
{less: customFloat64(1.23), greater: customFloat64(2.34), cType: "float64"},
{less: uintptr(1), greater: uintptr(2), cType: "uintptr"},
{less: customUintptr(1), greater: customUintptr(2), cType: "uint64"},
{less: time.Now(), greater: time.Now().Add(time.Hour), cType: "time.Time"},
{less: time.Date(2024, 0, 0, 0, 0, 0, 0, time.Local), greater: time.Date(2263, 0, 0, 0, 0, 0, 0, time.Local), cType: "time.Time"},
{less: customTime(time.Now()), greater: customTime(time.Now().Add(time.Hour)), cType: "time.Time"},
{less: []byte{1, 1}, greater: []byte{1, 2}, cType: "[]byte"},
{less: customBytes([]byte{1, 1}), greater: customBytes([]byte{1, 2}), cType: "[]byte"},
} {
resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object should be comparable for type " + currCase.cType)
}
if resLess != compareLess {
t.Errorf("object less (%v) should be less than greater (%v) for type "+currCase.cType,
currCase.less, currCase.greater)
}
resGreater, isComparable := compare(currCase.greater, currCase.less, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object are comparable for type " + currCase.cType)
}
if resGreater != compareGreater {
t.Errorf("object greater should be greater than less for type " + currCase.cType)
}
resEqual, isComparable := compare(currCase.less, currCase.less, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object are comparable for type " + currCase.cType)
}
if resEqual != 0 {
t.Errorf("objects should be equal for type " + currCase.cType)
}
}
}
type outputT struct {
buf *bytes.Buffer
helpers map[string]struct{}
}
// Implements TestingT
func (t *outputT) Errorf(format string, args ...interface{}) {
s := fmt.Sprintf(format, args...)
t.buf.WriteString(s)
}
func (t *outputT) Helper() {
if t.helpers == nil {
t.helpers = make(map[string]struct{})
}
t.helpers[callerName(1)] = struct{}{}
}
// callerName gives the function name (qualified with a package path)
// for the caller after skip frames (where 0 means the current function).
func callerName(skip int) string {
// Make room for the skip PC.
var pc [1]uintptr
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
if n == 0 {
panic("testing: zero callers found")
}
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
return frame.Function
}
func TestGreater(t *testing.T) {
mockT := new(testing.T)
if !Greater(mockT, 2, 1) {
t.Error("Greater should return true")
}
if Greater(mockT, 1, 1) {
t.Error("Greater should return false")
}
if Greater(mockT, 1, 2) {
t.Error("Greater should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: "a", greater: "b", msg: `"a" is not greater than "b"`},
{less: int(1), greater: int(2), msg: `"1" is not greater than "2"`},
{less: int8(1), greater: int8(2), msg: `"1" is not greater than "2"`},
{less: int16(1), greater: int16(2), msg: `"1" is not greater than "2"`},
{less: int32(1), greater: int32(2), msg: `"1" is not greater than "2"`},
{less: int64(1), greater: int64(2), msg: `"1" is not greater than "2"`},
{less: uint8(1), greater: uint8(2), msg: `"1" is not greater than "2"`},
{less: uint16(1), greater: uint16(2), msg: `"1" is not greater than "2"`},
{less: uint32(1), greater: uint32(2), msg: `"1" is not greater than "2"`},
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than "2"`},
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than "2.34"`},
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than "2.34"`},
{less: uintptr(1), greater: uintptr(2), msg: `"1" is not greater than "2"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than "0001-01-01 01:00:00 +0000 UTC"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than "[1 2]"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Greater(out, currCase.less, currCase.greater))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Greater")
}
}
func TestGreaterOrEqual(t *testing.T) {
mockT := new(testing.T)
if !GreaterOrEqual(mockT, 2, 1) {
t.Error("GreaterOrEqual should return true")
}
if !GreaterOrEqual(mockT, 1, 1) {
t.Error("GreaterOrEqual should return true")
}
if GreaterOrEqual(mockT, 1, 2) {
t.Error("GreaterOrEqual should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: "a", greater: "b", msg: `"a" is not greater than or equal to "b"`},
{less: int(1), greater: int(2), msg: `"1" is not greater than or equal to "2"`},
{less: int8(1), greater: int8(2), msg: `"1" is not greater than or equal to "2"`},
{less: int16(1), greater: int16(2), msg: `"1" is not greater than or equal to "2"`},
{less: int32(1), greater: int32(2), msg: `"1" is not greater than or equal to "2"`},
{less: int64(1), greater: int64(2), msg: `"1" is not greater than or equal to "2"`},
{less: uint8(1), greater: uint8(2), msg: `"1" is not greater than or equal to "2"`},
{less: uint16(1), greater: uint16(2), msg: `"1" is not greater than or equal to "2"`},
{less: uint32(1), greater: uint32(2), msg: `"1" is not greater than or equal to "2"`},
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than or equal to "2"`},
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
{less: uintptr(1), greater: uintptr(2), msg: `"1" is not greater than or equal to "2"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than or equal to "0001-01-01 01:00:00 +0000 UTC"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than or equal to "[1 2]"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, GreaterOrEqual(out, currCase.less, currCase.greater))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.GreaterOrEqual")
}
}
func TestLess(t *testing.T) {
mockT := new(testing.T)
if !Less(mockT, 1, 2) {
t.Error("Less should return true")
}
if Less(mockT, 1, 1) {
t.Error("Less should return false")
}
if Less(mockT, 2, 1) {
t.Error("Less should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: "a", greater: "b", msg: `"b" is not less than "a"`},
{less: int(1), greater: int(2), msg: `"2" is not less than "1"`},
{less: int8(1), greater: int8(2), msg: `"2" is not less than "1"`},
{less: int16(1), greater: int16(2), msg: `"2" is not less than "1"`},
{less: int32(1), greater: int32(2), msg: `"2" is not less than "1"`},
{less: int64(1), greater: int64(2), msg: `"2" is not less than "1"`},
{less: uint8(1), greater: uint8(2), msg: `"2" is not less than "1"`},
{less: uint16(1), greater: uint16(2), msg: `"2" is not less than "1"`},
{less: uint32(1), greater: uint32(2), msg: `"2" is not less than "1"`},
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than "1"`},
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than "1.23"`},
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than "1.23"`},
{less: uintptr(1), greater: uintptr(2), msg: `"2" is not less than "1"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than "0001-01-01 00:00:00 +0000 UTC"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than "[1 1]"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Less(out, currCase.greater, currCase.less))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Less")
}
}
func TestLessOrEqual(t *testing.T) {
mockT := new(testing.T)
if !LessOrEqual(mockT, 1, 2) {
t.Error("LessOrEqual should return true")
}
if !LessOrEqual(mockT, 1, 1) {
t.Error("LessOrEqual should return true")
}
if LessOrEqual(mockT, 2, 1) {
t.Error("LessOrEqual should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: "a", greater: "b", msg: `"b" is not less than or equal to "a"`},
{less: int(1), greater: int(2), msg: `"2" is not less than or equal to "1"`},
{less: int8(1), greater: int8(2), msg: `"2" is not less than or equal to "1"`},
{less: int16(1), greater: int16(2), msg: `"2" is not less than or equal to "1"`},
{less: int32(1), greater: int32(2), msg: `"2" is not less than or equal to "1"`},
{less: int64(1), greater: int64(2), msg: `"2" is not less than or equal to "1"`},
{less: uint8(1), greater: uint8(2), msg: `"2" is not less than or equal to "1"`},
{less: uint16(1), greater: uint16(2), msg: `"2" is not less than or equal to "1"`},
{less: uint32(1), greater: uint32(2), msg: `"2" is not less than or equal to "1"`},
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than or equal to "1"`},
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
{less: uintptr(1), greater: uintptr(2), msg: `"2" is not less than or equal to "1"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than or equal to "0001-01-01 00:00:00 +0000 UTC"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than or equal to "[1 1]"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, LessOrEqual(out, currCase.greater, currCase.less))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.LessOrEqual")
}
}
func TestPositive(t *testing.T) {
mockT := new(testing.T)
if !Positive(mockT, 1) {
t.Error("Positive should return true")
}
if !Positive(mockT, 1.23) {
t.Error("Positive should return true")
}
if Positive(mockT, -1) {
t.Error("Positive should return false")
}
if Positive(mockT, -1.23) {
t.Error("Positive should return false")
}
// Check error report
for _, currCase := range []struct {
e interface{}
msg string
}{
{e: int(-1), msg: `"-1" is not positive`},
{e: int8(-1), msg: `"-1" is not positive`},
{e: int16(-1), msg: `"-1" is not positive`},
{e: int32(-1), msg: `"-1" is not positive`},
{e: int64(-1), msg: `"-1" is not positive`},
{e: float32(-1.23), msg: `"-1.23" is not positive`},
{e: float64(-1.23), msg: `"-1.23" is not positive`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Positive(out, currCase.e))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Positive")
}
}
func TestNegative(t *testing.T) {
mockT := new(testing.T)
if !Negative(mockT, -1) {
t.Error("Negative should return true")
}
if !Negative(mockT, -1.23) {
t.Error("Negative should return true")
}
if Negative(mockT, 1) {
t.Error("Negative should return false")
}
if Negative(mockT, 1.23) {
t.Error("Negative should return false")
}
// Check error report
for _, currCase := range []struct {
e interface{}
msg string
}{
{e: int(1), msg: `"1" is not negative`},
{e: int8(1), msg: `"1" is not negative`},
{e: int16(1), msg: `"1" is not negative`},
{e: int32(1), msg: `"1" is not negative`},
{e: int64(1), msg: `"1" is not negative`},
{e: float32(1.23), msg: `"1.23" is not negative`},
{e: float64(1.23), msg: `"1.23" is not negative`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Negative(out, currCase.e))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Negative")
}
}
func Test_compareTwoValuesDifferentValuesTypes(t *testing.T) {
mockT := new(testing.T)
for _, currCase := range []struct {
v1 interface{}
v2 interface{}
compareResult bool
}{
{v1: 123, v2: "abc"},
{v1: "abc", v2: 123456},
{v1: float64(12), v2: "123"},
{v1: "float(12)", v2: float64(1)},
} {
result := compareTwoValues(mockT, currCase.v1, currCase.v2, []compareResult{compareLess, compareEqual, compareGreater}, "testFailMessage")
False(t, result)
}
}
func Test_compareTwoValuesNotComparableValues(t *testing.T) {
mockT := new(testing.T)
type CompareStruct struct {
}
for _, currCase := range []struct {
v1 interface{}
v2 interface{}
}{
{v1: CompareStruct{}, v2: CompareStruct{}},
{v1: map[string]int{}, v2: map[string]int{}},
{v1: make([]int, 5), v2: make([]int, 5)},
} {
result := compareTwoValues(mockT, currCase.v1, currCase.v2, []compareResult{compareLess, compareEqual, compareGreater}, "testFailMessage")
False(t, result)
}
}
func Test_compareTwoValuesCorrectCompareResult(t *testing.T) {
mockT := new(testing.T)
for _, currCase := range []struct {
v1 interface{}
v2 interface{}
allowedResults []compareResult
}{
{v1: 1, v2: 2, allowedResults: []compareResult{compareLess}},
{v1: 1, v2: 2, allowedResults: []compareResult{compareLess, compareEqual}},
{v1: 2, v2: 2, allowedResults: []compareResult{compareGreater, compareEqual}},
{v1: 2, v2: 2, allowedResults: []compareResult{compareEqual}},
{v1: 2, v2: 1, allowedResults: []compareResult{compareEqual, compareGreater}},
{v1: 2, v2: 1, allowedResults: []compareResult{compareGreater}},
} {
result := compareTwoValues(mockT, currCase.v1, currCase.v2, currCase.allowedResults, "testFailMessage")
True(t, result)
}
}
func Test_containsValue(t *testing.T) {
for _, currCase := range []struct {
values []compareResult
value compareResult
result bool
}{
{values: []compareResult{compareGreater}, value: compareGreater, result: true},
{values: []compareResult{compareGreater, compareLess}, value: compareGreater, result: true},
{values: []compareResult{compareGreater, compareLess}, value: compareLess, result: true},
{values: []compareResult{compareGreater, compareLess}, value: compareEqual, result: false},
} {
result := containsValue(currCase.values, currCase.value)
Equal(t, currCase.result, result)
}
}
func TestComparingMsgAndArgsForwarding(t *testing.T) {
msgAndArgs := []interface{}{"format %s %x", "this", 0xc001}
expectedOutput := "format this c001\n"
funcs := []func(t TestingT){
func(t TestingT) { Greater(t, 1, 2, msgAndArgs...) },
func(t TestingT) { GreaterOrEqual(t, 1, 2, msgAndArgs...) },
func(t TestingT) { Less(t, 2, 1, msgAndArgs...) },
func(t TestingT) { LessOrEqual(t, 2, 1, msgAndArgs...) },
func(t TestingT) { Positive(t, 0, msgAndArgs...) },
func(t TestingT) { Negative(t, 0, msgAndArgs...) },
}
for _, f := range funcs {
out := &outputT{buf: bytes.NewBuffer(nil)}
f(out)
Contains(t, out.buf.String(), expectedOutput)
}
}

View File

@ -1,7 +1,4 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package assert
@ -22,9 +19,9 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo
// Containsf asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -32,7 +29,8 @@ func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
// DirExistsf checks whether a directory exists in the given path. It also fails
// if the path is a file rather a directory or there is an error checking whether it exists.
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -55,7 +53,7 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// assert.Emptyf(t, obj, "error message %s", "formatted")
// assert.Emptyf(t, obj, "error message %s", "formatted")
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -65,7 +63,7 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo
// Equalf asserts that two objects are equal.
//
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
@ -80,8 +78,8 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -89,10 +87,27 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertible to the larger
// type and equal.
//
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -102,10 +117,10 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if assert.Errorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedErrorf, err)
// }
// actualObj, err := SomeFunction()
// if assert.Errorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedErrorf, err)
// }
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -113,9 +128,75 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
return Error(t, err, append([]interface{}{msg}, args...)...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
// and that the error contains the specified substring.
//
// actualObj, err := SomeFunction()
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -141,7 +222,7 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}
// Falsef asserts that the specified value is false.
//
// assert.Falsef(t, myBool, "error message %s", "formatted")
// assert.Falsef(t, myBool, "error message %s", "formatted")
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -149,7 +230,8 @@ func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
return False(t, value, append([]interface{}{msg}, args...)...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
// FileExistsf checks whether a file exists in the given path. It also fails if
// the path points to a directory or there is an error when trying to check the file.
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -157,10 +239,35 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
return FileExists(t, path, append([]interface{}{msg}, args...)...)
}
// Greaterf asserts that the first element is greater than the second
//
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
@ -173,7 +280,7 @@ func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
@ -185,9 +292,9 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
// HTTPErrorf asserts that a specified handler returns an error status code.
//
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
// Returns whether the assertion was successful (true) or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -197,9 +304,9 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -207,9 +314,21 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
}
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
@ -221,7 +340,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin
// Implementsf asserts that an object is implemented by the specified interface.
//
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -231,7 +350,7 @@ func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, ms
// InDeltaf asserts that the two numerals are within delta of each other.
//
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -271,6 +390,54 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@ -281,7 +448,7 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin
// JSONEqf asserts that two JSON strings are equivalent.
//
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -292,7 +459,7 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int
// Lenf asserts that the specified object has specific length.
// Lenf also fails if the object has a type that len() not accept.
//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -300,9 +467,56 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
return Len(t, object, length, append([]interface{}{msg}, args...)...)
}
// Lessf asserts that the first element is less than the second
//
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// LessOrEqualf asserts that the first element is less than or equal to the second
//
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Negative(t, e, append([]interface{}{msg}, args...)...)
}
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Nilf asserts that the specified object is nil.
//
// assert.Nilf(t, err, "error message %s", "formatted")
// assert.Nilf(t, err, "error message %s", "formatted")
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -310,12 +524,21 @@ func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool
return Nil(t, object, append([]interface{}{msg}, args...)...)
}
// NoDirExistsf checks whether a directory does not exist in the given path.
// It fails if the path points to an existing _directory_ only.
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
}
// NoErrorf asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
// actualObj, err := SomeFunction()
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -323,12 +546,21 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
return NoError(t, err, append([]interface{}{msg}, args...)...)
}
// NoFileExistsf checks whether a file does not exist in a given path. It fails
// if the path points to an existing _file_ only.
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
}
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -336,12 +568,29 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should not match.
// This is an inverse of ElementsMatch.
//
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false
//
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true
//
// assert.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true
func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -351,7 +600,7 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{})
// NotEqualf asserts that the specified values are NOT equal.
//
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
@ -362,9 +611,47 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
//
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotErrorAsf asserts that none of the errors in err's chain matches target,
// but if so, sets target to that error value.
func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotErrorIsf asserts that none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotImplements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
// assert.NotNilf(t, err, "error message %s", "formatted")
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -374,7 +661,7 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -384,8 +671,8 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo
// NotRegexpf asserts that a specified regexp does not match a string.
//
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -393,10 +680,25 @@ func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ..
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSamef asserts that two pointers do not reference the same object.
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -414,7 +716,7 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
//
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -422,10 +724,22 @@ func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool
return Panics(t, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
// panics, and that the recovered panic value is an error that satisfies the
// EqualError comparison.
//
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -433,10 +747,21 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Positive(t, e, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -444,10 +769,24 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Samef asserts that two pointers reference the same object.
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -457,7 +796,7 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args
// Truef asserts that the specified value is true.
//
// assert.Truef(t, myBool, "error message %s", "formatted")
// assert.Truef(t, myBool, "error message %s", "formatted")
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -467,7 +806,7 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
// WithinDurationf asserts that the two times are within duration delta of each other.
//
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -475,6 +814,24 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// WithinRangef asserts that a time is within a time range (inclusive).
//
// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
}
// YAMLEqf asserts that two YAML strings are equivalent.
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Zerof asserts that i is the zero value for its type.
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {

File diff suppressed because it is too large Load Diff

81
assert/assertion_order.go Normal file
View File

@ -0,0 +1,81 @@
package assert
import (
"fmt"
"reflect"
)
// isOrdered checks that collection contains orderable elements.
func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
objKind := reflect.TypeOf(object).Kind()
if objKind != reflect.Slice && objKind != reflect.Array {
return false
}
objValue := reflect.ValueOf(object)
objLen := objValue.Len()
if objLen <= 1 {
return true
}
value := objValue.Index(0)
valueInterface := value.Interface()
firstValueKind := value.Kind()
for i := 1; i < objLen; i++ {
prevValue := value
prevValueInterface := valueInterface
value = objValue.Index(i)
valueInterface = value.Interface()
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
if !isComparable {
return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
}
}
return true
}
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
}

View File

@ -0,0 +1,203 @@
package assert
import (
"bytes"
"testing"
)
func TestIsIncreasing(t *testing.T) {
mockT := new(testing.T)
if !IsIncreasing(mockT, []int{1, 2}) {
t.Error("IsIncreasing should return true")
}
if !IsIncreasing(mockT, []int{1, 2, 3, 4, 5}) {
t.Error("IsIncreasing should return true")
}
if IsIncreasing(mockT, []int{1, 1}) {
t.Error("IsIncreasing should return false")
}
if IsIncreasing(mockT, []int{2, 1}) {
t.Error("IsIncreasing should return false")
}
// Check error report
for _, currCase := range []struct {
collection interface{}
msg string
}{
{collection: []string{"b", "a"}, msg: `"b" is not less than "a"`},
{collection: []int{2, 1}, msg: `"2" is not less than "1"`},
{collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than "1"`},
{collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than "1"`},
{collection: []int8{2, 1}, msg: `"2" is not less than "1"`},
{collection: []int16{2, 1}, msg: `"2" is not less than "1"`},
{collection: []int32{2, 1}, msg: `"2" is not less than "1"`},
{collection: []int64{2, 1}, msg: `"2" is not less than "1"`},
{collection: []uint8{2, 1}, msg: `"2" is not less than "1"`},
{collection: []uint16{2, 1}, msg: `"2" is not less than "1"`},
{collection: []uint32{2, 1}, msg: `"2" is not less than "1"`},
{collection: []uint64{2, 1}, msg: `"2" is not less than "1"`},
{collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`},
{collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, IsIncreasing(out, currCase.collection))
Contains(t, out.buf.String(), currCase.msg)
}
}
func TestIsNonIncreasing(t *testing.T) {
mockT := new(testing.T)
if !IsNonIncreasing(mockT, []int{2, 1}) {
t.Error("IsNonIncreasing should return true")
}
if !IsNonIncreasing(mockT, []int{5, 4, 4, 3, 2, 1}) {
t.Error("IsNonIncreasing should return true")
}
if !IsNonIncreasing(mockT, []int{1, 1}) {
t.Error("IsNonIncreasing should return true")
}
if IsNonIncreasing(mockT, []int{1, 2}) {
t.Error("IsNonIncreasing should return false")
}
// Check error report
for _, currCase := range []struct {
collection interface{}
msg string
}{
{collection: []string{"a", "b"}, msg: `"a" is not greater than or equal to "b"`},
{collection: []int{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int8{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int16{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int32{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []int64{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []uint8{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []uint16{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []uint32{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []uint64{1, 2}, msg: `"1" is not greater than or equal to "2"`},
{collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`},
{collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, IsNonIncreasing(out, currCase.collection))
Contains(t, out.buf.String(), currCase.msg)
}
}
func TestIsDecreasing(t *testing.T) {
mockT := new(testing.T)
if !IsDecreasing(mockT, []int{2, 1}) {
t.Error("IsDecreasing should return true")
}
if !IsDecreasing(mockT, []int{5, 4, 3, 2, 1}) {
t.Error("IsDecreasing should return true")
}
if IsDecreasing(mockT, []int{1, 1}) {
t.Error("IsDecreasing should return false")
}
if IsDecreasing(mockT, []int{1, 2}) {
t.Error("IsDecreasing should return false")
}
// Check error report
for _, currCase := range []struct {
collection interface{}
msg string
}{
{collection: []string{"a", "b"}, msg: `"a" is not greater than "b"`},
{collection: []int{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than "2"`},
{collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than "2"`},
{collection: []int8{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []int16{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []int32{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []int64{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []uint8{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []uint16{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []uint32{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []uint64{1, 2}, msg: `"1" is not greater than "2"`},
{collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`},
{collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, IsDecreasing(out, currCase.collection))
Contains(t, out.buf.String(), currCase.msg)
}
}
func TestIsNonDecreasing(t *testing.T) {
mockT := new(testing.T)
if !IsNonDecreasing(mockT, []int{1, 2}) {
t.Error("IsNonDecreasing should return true")
}
if !IsNonDecreasing(mockT, []int{1, 1, 2, 3, 4, 5}) {
t.Error("IsNonDecreasing should return true")
}
if !IsNonDecreasing(mockT, []int{1, 1}) {
t.Error("IsNonDecreasing should return false")
}
if IsNonDecreasing(mockT, []int{2, 1}) {
t.Error("IsNonDecreasing should return false")
}
// Check error report
for _, currCase := range []struct {
collection interface{}
msg string
}{
{collection: []string{"b", "a"}, msg: `"b" is not less than or equal to "a"`},
{collection: []int{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than or equal to "1"`},
{collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []int8{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []int16{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []int32{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []int64{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []uint8{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []uint16{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []uint32{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []uint64{2, 1}, msg: `"2" is not less than or equal to "1"`},
{collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`},
{collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, IsNonDecreasing(out, currCase.collection))
Contains(t, out.buf.String(), currCase.msg)
}
}
func TestOrderingMsgAndArgsForwarding(t *testing.T) {
msgAndArgs := []interface{}{"format %s %x", "this", 0xc001}
expectedOutput := "format this c001\n"
collection := []int{1, 2, 1}
funcs := []func(t TestingT){
func(t TestingT) { IsIncreasing(t, collection, msgAndArgs...) },
func(t TestingT) { IsNonIncreasing(t, collection, msgAndArgs...) },
func(t TestingT) { IsDecreasing(t, collection, msgAndArgs...) },
func(t TestingT) { IsNonDecreasing(t, collection, msgAndArgs...) },
}
for _, f := range funcs {
out := &outputT{buf: bytes.NewBuffer(nil)}
f(out)
Contains(t, out.buf.String(), expectedOutput)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,40 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using assert in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// var a string = "Hello"
// var b string = "Hello"
// func TestSomething(t *testing.T) {
//
// assert.Equal(t, a, b, "The two words should be the same.")
// var a string = "Hello"
// var b string = "Hello"
//
// }
// assert.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// if you assert many times, use the format below:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
//
// var a string = "Hello"
// var b string = "Hello"
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(a, b, "The two words should be the same.")
// }
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// Assertions
// # Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the

View File

@ -13,4 +13,4 @@ func New(t TestingT) *Assertions {
}
}
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs"

View File

@ -154,6 +154,30 @@ func TestNotEqualWrapper(t *testing.T) {
}
}
func TestNotEqualValuesWrapper(t *testing.T) {
assert := New(new(testing.T))
if !assert.NotEqualValues("Hello World", "Hello World!") {
t.Error("NotEqualValues should return true")
}
if !assert.NotEqualValues(123, 1234) {
t.Error("NotEqualValues should return true")
}
if !assert.NotEqualValues(123.5, 123.55) {
t.Error("NotEqualValues should return true")
}
if !assert.NotEqualValues([]byte("Hello World"), []byte("Hello World!")) {
t.Error("NotEqualValues should return true")
}
if !assert.NotEqualValues(nil, new(AssertionTesterConformingObject)) {
t.Error("NotEqualValues should return true")
}
if assert.NotEqualValues(10, uint(10)) {
t.Error("NotEqualValues should return false")
}
}
func TestContainsWrapper(t *testing.T) {
assert := New(new(testing.T))
@ -212,13 +236,13 @@ func TestConditionWrapper(t *testing.T) {
func TestDidPanicWrapper(t *testing.T) {
if funcDidPanic, _ := didPanic(func() {
if funcDidPanic, _, _ := didPanic(func() {
panic("Panic!")
}); !funcDidPanic {
t.Error("didPanic should return true")
}
if funcDidPanic, _ := didPanic(func() {
if funcDidPanic, _, _ := didPanic(func() {
}); funcDidPanic {
t.Error("didPanic should return false")
}
@ -291,6 +315,25 @@ func TestErrorWrapper(t *testing.T) {
}
func TestErrorContainsWrapper(t *testing.T) {
assert := New(t)
mockAssert := New(new(testing.T))
// start with a nil error
var err error
assert.False(mockAssert.ErrorContains(err, ""),
"ErrorContains should return false for nil arg")
// now set an error
err = errors.New("some error: another error")
assert.False(mockAssert.ErrorContains(err, "different error"),
"ErrorContains should return false for different error string")
assert.True(mockAssert.ErrorContains(err, "some error"),
"ErrorContains should return true")
assert.True(mockAssert.ErrorContains(err, "another error"),
"ErrorContains should return true")
}
func TestEqualErrorWrapper(t *testing.T) {
assert := New(t)
mockAssert := New(new(testing.T))
@ -503,7 +546,7 @@ func TestRegexpWrapper(t *testing.T) {
}
for _, tc := range cases {
False(t, assert.Regexp(tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
False(t, assert.Regexp(tc.rx, tc.str), "Expected %q to not match %q", tc.rx, tc.str)
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
True(t, assert.NotRegexp(tc.rx, tc.str))
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
@ -609,3 +652,101 @@ func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
t.Error("JSONEq should return false")
}
}
func TestYAMLEqWrapper_EqualYAMLString(t *testing.T) {
assert := New(new(testing.T))
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) {
t.Error("YAMLEq should return true")
}
}
func TestYAMLEqWrapper_EquivalentButNotEqual(t *testing.T) {
assert := New(new(testing.T))
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
t.Error("YAMLEq should return true")
}
}
func TestYAMLEqWrapper_HashOfArraysAndHashes(t *testing.T) {
assert := New(new(testing.T))
expected := `
numeric: 1.5
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
`
actual := `
numeric: 1.5
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
`
if !assert.YAMLEq(expected, actual) {
t.Error("YAMLEq should return true")
}
}
func TestYAMLEqWrapper_Array(t *testing.T) {
assert := New(new(testing.T))
if !assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) {
t.Error("YAMLEq should return true")
}
}
func TestYAMLEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
assert := New(new(testing.T))
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) {
t.Error("YAMLEq should return false")
}
}
func TestYAMLEqWrapper_HashesNotEquivalent(t *testing.T) {
assert := New(new(testing.T))
if assert.YAMLEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
t.Error("YAMLEq should return false")
}
}
func TestYAMLEqWrapper_ActualIsSimpleString(t *testing.T) {
assert := New(new(testing.T))
if assert.YAMLEq(`{"foo": "bar"}`, "Simple String") {
t.Error("YAMLEq should return false")
}
}
func TestYAMLEqWrapper_ExpectedIsSimpleString(t *testing.T) {
assert := New(new(testing.T))
if assert.YAMLEq("Simple String", `{"foo": "bar", "hello": "world"}`) {
t.Error("YAMLEq should return false")
}
}
func TestYAMLEqWrapper_ExpectedAndActualSimpleString(t *testing.T) {
assert := New(new(testing.T))
if !assert.YAMLEq("Simple String", "Simple String") {
t.Error("YAMLEq should return true")
}
}
func TestYAMLEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
assert := New(new(testing.T))
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`) {
t.Error("YAMLEq should return false")
}
}

View File

@ -12,7 +12,7 @@ import (
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return -1, err
}
@ -23,7 +23,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -32,13 +32,12 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
if !isSuccessCode {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isSuccessCode
@ -46,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -55,13 +54,12 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
if !isRedirectCode {
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isRedirectCode
@ -69,7 +67,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
// HTTPError asserts that a specified handler returns an error status code.
//
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -78,23 +76,47 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
return false
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isErrorCode := code >= http.StatusBadRequest
if !isErrorCode {
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isErrorCode
}
// HTTPStatusCode asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
successful := code == statuscode
if !successful {
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code), msgAndArgs...)
}
return successful
}
// HTTPBody is a helper that returns HTTP body of the response. It returns
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
if len(values) > 0 {
url += "?" + values.Encode()
}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return ""
}
@ -105,7 +127,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
@ -116,7 +138,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return contains
@ -125,7 +147,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
@ -136,7 +158,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return !contains

View File

@ -2,6 +2,7 @@ package assert
import (
"fmt"
"io"
"net/http"
"net/url"
"testing"
@ -11,6 +12,12 @@ func httpOK(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func httpReadBody(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(io.Discard, r.Body)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hello"))
}
func httpRedirect(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTemporaryRedirect)
}
@ -19,6 +26,10 @@ func httpError(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}
func httpStatusCode(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusSwitchingProtocols)
}
func TestHTTPSuccess(t *testing.T) {
assert := New(t)
@ -30,17 +41,33 @@ func TestHTTPSuccess(t *testing.T) {
assert.Equal(HTTPSuccess(mockT2, httpRedirect, "GET", "/", nil), false)
assert.True(mockT2.Failed())
mockT3 := new(testing.T)
assert.Equal(HTTPSuccess(mockT3, httpError, "GET", "/", nil), false)
mockT3 := new(mockTestingT)
assert.Equal(HTTPSuccess(
mockT3, httpError, "GET", "/", nil,
"was not expecting a failure here",
), false)
assert.True(mockT3.Failed())
assert.Contains(mockT3.errorString(), "was not expecting a failure here")
mockT4 := new(testing.T)
assert.Equal(HTTPSuccess(mockT4, httpStatusCode, "GET", "/", nil), false)
assert.True(mockT4.Failed())
mockT5 := new(testing.T)
assert.Equal(HTTPSuccess(mockT5, httpReadBody, "POST", "/", nil), true)
assert.False(mockT5.Failed())
}
func TestHTTPRedirect(t *testing.T) {
assert := New(t)
mockT1 := new(testing.T)
assert.Equal(HTTPRedirect(mockT1, httpOK, "GET", "/", nil), false)
mockT1 := new(mockTestingT)
assert.Equal(HTTPRedirect(
mockT1, httpOK, "GET", "/", nil,
"was expecting a 3xx status code. Got 200.",
), false)
assert.True(mockT1.Failed())
assert.Contains(mockT1.errorString(), "was expecting a 3xx status code. Got 200.")
mockT2 := new(testing.T)
assert.Equal(HTTPRedirect(mockT2, httpRedirect, "GET", "/", nil), true)
@ -49,6 +76,10 @@ func TestHTTPRedirect(t *testing.T) {
mockT3 := new(testing.T)
assert.Equal(HTTPRedirect(mockT3, httpError, "GET", "/", nil), false)
assert.True(mockT3.Failed())
mockT4 := new(testing.T)
assert.Equal(HTTPRedirect(mockT4, httpStatusCode, "GET", "/", nil), false)
assert.True(mockT4.Failed())
}
func TestHTTPError(t *testing.T) {
@ -58,13 +89,45 @@ func TestHTTPError(t *testing.T) {
assert.Equal(HTTPError(mockT1, httpOK, "GET", "/", nil), false)
assert.True(mockT1.Failed())
mockT2 := new(testing.T)
assert.Equal(HTTPError(mockT2, httpRedirect, "GET", "/", nil), false)
mockT2 := new(mockTestingT)
assert.Equal(HTTPError(
mockT2, httpRedirect, "GET", "/", nil,
"Expected this request to error out. But it didn't",
), false)
assert.True(mockT2.Failed())
assert.Contains(mockT2.errorString(), "Expected this request to error out. But it didn't")
mockT3 := new(testing.T)
assert.Equal(HTTPError(mockT3, httpError, "GET", "/", nil), true)
assert.False(mockT3.Failed())
mockT4 := new(testing.T)
assert.Equal(HTTPError(mockT4, httpStatusCode, "GET", "/", nil), false)
assert.True(mockT4.Failed())
}
func TestHTTPStatusCode(t *testing.T) {
assert := New(t)
mockT1 := new(testing.T)
assert.Equal(HTTPStatusCode(mockT1, httpOK, "GET", "/", nil, http.StatusSwitchingProtocols), false)
assert.True(mockT1.Failed())
mockT2 := new(testing.T)
assert.Equal(HTTPStatusCode(mockT2, httpRedirect, "GET", "/", nil, http.StatusSwitchingProtocols), false)
assert.True(mockT2.Failed())
mockT3 := new(mockTestingT)
assert.Equal(HTTPStatusCode(
mockT3, httpError, "GET", "/", nil, http.StatusSwitchingProtocols,
"Expected the status code to be %d", http.StatusSwitchingProtocols,
), false)
assert.True(mockT3.Failed())
assert.Contains(mockT3.errorString(), "Expected the status code to be 101")
mockT4 := new(testing.T)
assert.Equal(HTTPStatusCode(mockT4, httpStatusCode, "GET", "/", nil, http.StatusSwitchingProtocols), true)
assert.False(mockT4.Failed())
}
func TestHTTPStatusesWrapper(t *testing.T) {
@ -86,7 +149,7 @@ func TestHTTPStatusesWrapper(t *testing.T) {
func httpHelloName(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
w.Write([]byte(fmt.Sprintf("Hello, %s!", name)))
_, _ = fmt.Fprintf(w, "Hello, %s!", name)
}
func TestHTTPRequestWithNoParams(t *testing.T) {
@ -120,15 +183,21 @@ func TestHTTPRequestWithParams(t *testing.T) {
func TestHttpBody(t *testing.T) {
assert := New(t)
mockT := new(testing.T)
mockT := new(mockTestingT)
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
assert.False(HTTPBodyNotContains(
mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World",
"Expected the request body to not contain 'World'. But it did.",
))
assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
assert.Contains(mockT.errorString(), "Expected the request body to not contain 'World'. But it did.")
assert.True(HTTPBodyContains(mockT, httpReadBody, "GET", "/", nil, "hello"))
}
func TestHttpBodyWrappers(t *testing.T) {
@ -142,5 +211,4 @@ func TestHttpBodyWrappers(t *testing.T) {
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
}

View File

@ -0,0 +1,4 @@
// This package exists just to isolate tests that reference the [unsafe] package.
//
// The tests in this package are totally safe.
package unsafetests

View File

@ -0,0 +1,34 @@
package unsafetests_test
import (
"fmt"
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
type ignoreTestingT struct{}
var _ assert.TestingT = ignoreTestingT{}
func (ignoreTestingT) Helper() {}
func (ignoreTestingT) Errorf(format string, args ...interface{}) {
// Run the formatting, but ignore the result
msg := fmt.Sprintf(format, args...)
_ = msg
}
func TestUnsafePointers(t *testing.T) {
var ignore ignoreTestingT
assert.True(t, assert.Nil(t, unsafe.Pointer(nil), "unsafe.Pointer(nil) is nil"))
assert.False(t, assert.NotNil(ignore, unsafe.Pointer(nil), "unsafe.Pointer(nil) is nil"))
assert.True(t, assert.Nil(t, unsafe.Pointer((*int)(nil)), "unsafe.Pointer((*int)(nil)) is nil"))
assert.False(t, assert.NotNil(ignore, unsafe.Pointer((*int)(nil)), "unsafe.Pointer((*int)(nil)) is nil"))
assert.False(t, assert.Nil(ignore, unsafe.Pointer(new(int)), "unsafe.Pointer(new(int)) is NOT nil"))
assert.True(t, assert.NotNil(t, unsafe.Pointer(new(int)), "unsafe.Pointer(new(int)) is NOT nil"))
}

View File

@ -0,0 +1,25 @@
//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default
// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default
// Package yaml is an implementation of YAML functions that calls a pluggable implementation.
//
// This implementation is selected with the testify_yaml_custom build tag.
//
// go test -tags testify_yaml_custom
//
// This implementation can be used at build time to replace the default implementation
// to avoid linking with [gopkg.in/yaml.v3].
//
// In your test package:
//
// import assertYaml "github.com/stretchr/testify/assert/yaml"
//
// func init() {
// assertYaml.Unmarshal = func (in []byte, out interface{}) error {
// // ...
// return nil
// }
// }
package yaml
var Unmarshal func(in []byte, out interface{}) error

View File

@ -0,0 +1,37 @@
//go:build !testify_yaml_fail && !testify_yaml_custom
// +build !testify_yaml_fail,!testify_yaml_custom
// Package yaml is just an indirection to handle YAML deserialization.
//
// This package is just an indirection that allows the builder to override the
// indirection with an alternative implementation of this package that uses
// another implementation of YAML deserialization. This allows to not either not
// use YAML deserialization at all, or to use another implementation than
// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]).
//
// Alternative implementations are selected using build tags:
//
// - testify_yaml_fail: [Unmarshal] always fails with an error
// - testify_yaml_custom: [Unmarshal] is a variable. Caller must initialize it
// before calling any of [github.com/stretchr/testify/assert.YAMLEq] or
// [github.com/stretchr/testify/assert.YAMLEqf].
//
// Usage:
//
// go test -tags testify_yaml_fail
//
// You can check with "go list" which implementation is linked:
//
// go list -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
// go list -tags testify_yaml_fail -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
// go list -tags testify_yaml_custom -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
//
// [PR #1120]: https://github.com/stretchr/testify/pull/1120
package yaml
import goyaml "gopkg.in/yaml.v3"
// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal].
func Unmarshal(in []byte, out interface{}) error {
return goyaml.Unmarshal(in, out)
}

18
assert/yaml/yaml_fail.go Normal file
View File

@ -0,0 +1,18 @@
//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default
// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default
// Package yaml is an implementation of YAML functions that always fail.
//
// This implementation can be used at build time to replace the default implementation
// to avoid linking with [gopkg.in/yaml.v3]:
//
// go test -tags testify_yaml_fail
package yaml
import "errors"
var errNotImplemented = errors.New("YAML functions are not available (see https://pkg.go.dev/github.com/stretchr/testify/assert/yaml)")
func Unmarshal([]byte, interface{}) error {
return errNotImplemented
}

17
doc.go
View File

@ -4,19 +4,12 @@
//
// The assert package provides a comprehensive set of assertion functions that tie in to the Go testing system.
//
// The http package contains tools to make it easier to test http activity using the Go testing system.
//
// The mock package provides a system by which it is possible to mock your objects and verify calls are happening as expected.
//
// The suite package provides a basic structure for using structs as testing suites, and methods on those structs as tests. It includes setup/teardown functionality in the way of interfaces.
//
// A [golangci-lint] compatible linter for testify is available called [testifylint].
//
// [golangci-lint]: https://golangci-lint.run/
// [testifylint]: https://github.com/Antonboom/testifylint
package testify
// blank imports help docs.
import (
// assert package
_ "github.com/stretchr/testify/assert"
// http package
_ "github.com/stretchr/testify/http"
// mock package
_ "github.com/stretchr/testify/mock"
)

13
go.mod
View File

@ -1,7 +1,16 @@
module github.com/stretchr/testify
// This should match the minimum supported version that is tested in
// .github/workflows/main.yml
go 1.17
require (
github.com/davecgh/go-spew v1.1.0
github.com/davecgh/go-spew v1.1.1
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/objx v0.1.0
github.com/stretchr/objx v0.5.2
gopkg.in/yaml.v3 v3.0.1
)
// Break dependency cycle with objx.
// See https://github.com/stretchr/objx/pull/140
exclude github.com/stretchr/testify v1.8.2

16
go.sum
View File

@ -1,6 +1,18 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,2 +1,2 @@
// Package http DEPRECATED USE net/http/httptest
// Deprecated: Use [net/http/httptest] instead.
package http

View File

@ -4,7 +4,7 @@ import (
"net/http"
)
// TestResponseWriter DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
// Deprecated: Use [net/http/httptest] instead.
type TestResponseWriter struct {
// StatusCode is the last int written by the call to WriteHeader(int)
@ -17,7 +17,7 @@ type TestResponseWriter struct {
header http.Header
}
// Header DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
// Deprecated: Use [net/http/httptest] instead.
func (rw *TestResponseWriter) Header() http.Header {
if rw.header == nil {
@ -27,7 +27,7 @@ func (rw *TestResponseWriter) Header() http.Header {
return rw.header
}
// Write DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
// Deprecated: Use [net/http/httptest] instead.
func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
// assume 200 success if no header has been set
@ -36,14 +36,14 @@ func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
}
// add these bytes to the output string
rw.Output = rw.Output + string(bytes)
rw.Output += string(bytes)
// return normal values
return 0, nil
}
// WriteHeader DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
// Deprecated: Use [net/http/httptest] instead.
func (rw *TestResponseWriter) WriteHeader(i int) {
rw.StatusCode = i
}

View File

@ -1,16 +1,17 @@
package http
import (
"github.com/stretchr/testify/mock"
"net/http"
"github.com/stretchr/testify/mock"
)
// TestRoundTripper DEPRECATED USE net/http/httptest
// Deprecated: Use [net/http/httptest] instead.
type TestRoundTripper struct {
mock.Mock
}
// RoundTrip DEPRECATED USE net/http/httptest
// Deprecated: Use [net/http/httptest] instead.
func (t *TestRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
args := t.Called(req)
return args.Get(0).(*http.Response), args.Error(1)

View File

@ -1,17 +1,17 @@
// Package mock provides a system by which it is possible to mock your objects
// and verify calls are happening as expected.
//
// Example Usage
// # Example Usage
//
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
// embedded into a test object as shown below:
//
// type MyTestObject struct {
// // add a Mock object instance
// mock.Mock
// type MyTestObject struct {
// // add a Mock object instance
// mock.Mock
//
// // other fields go here as normal
// }
// // other fields go here as normal
// }
//
// When implementing the methods of an interface, you wire your functions up
// to call the Mock.Called(args...) method, and return the appropriate values.
@ -19,25 +19,25 @@
// For example, to mock a method that saves the name and age of a person and returns
// the year of their birth or an error, you might write this:
//
// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
// args := o.Called(firstname, lastname, age)
// return args.Int(0), args.Error(1)
// }
// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
// args := o.Called(firstname, lastname, age)
// return args.Int(0), args.Error(1)
// }
//
// The Int, Error and Bool methods are examples of strongly typed getters that take the argument
// index position. Given this argument list:
//
// (12, true, "Something")
// (12, true, "Something")
//
// You could read them out strongly typed like this:
//
// args.Int(0)
// args.Bool(1)
// args.String(2)
// args.Int(0)
// args.Bool(1)
// args.String(2)
//
// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion:
//
// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
//
// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those
// cases you should check for nil first.

View File

@ -3,6 +3,7 @@ package mock
import (
"errors"
"fmt"
"path"
"reflect"
"regexp"
"runtime"
@ -13,9 +14,13 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/objx"
"github.com/stretchr/testify/assert"
)
// regex for GCCGO functions
var gccgoRE = regexp.MustCompile(`\.pN\d+_`)
// TestingT is an interface wrapper around *testing.T
type TestingT interface {
Logf(format string, args ...interface{})
@ -65,18 +70,27 @@ type Call struct {
// reference. It's useful when mocking methods such as unmarshalers or
// decoders.
RunFn func(Arguments)
// PanicMsg holds msg to be used to mock panic on the function call
// if the PanicMsg is set to a non nil string the function call will panic
// irrespective of other settings
PanicMsg *string
// Calls which must be satisfied before this call can be
requires []*Call
}
func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call {
func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments Arguments, returnArguments Arguments) *Call {
return &Call{
Parent: parent,
Method: methodName,
Arguments: methodArguments,
ReturnArguments: make([]interface{}, 0),
ReturnArguments: returnArguments,
callerInfo: callerInfo,
Repeatability: 0,
WaitFor: nil,
RunFn: nil,
PanicMsg: nil,
}
}
@ -90,7 +104,7 @@ func (c *Call) unlock() {
// Return specifies the return arguments for the expectation.
//
// Mock.On("DoSomething").Return(errors.New("failed"))
// Mock.On("DoSomething").Return(errors.New("failed"))
func (c *Call) Return(returnArguments ...interface{}) *Call {
c.lock()
defer c.unlock()
@ -100,24 +114,36 @@ func (c *Call) Return(returnArguments ...interface{}) *Call {
return c
}
// Once indicates that that the mock should only return the value once.
// Panic specifies if the function call should fail and the panic message
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
// Mock.On("DoSomething").Panic("test panic")
func (c *Call) Panic(msg string) *Call {
c.lock()
defer c.unlock()
c.PanicMsg = &msg
return c
}
// Once indicates that the mock should only return the value once.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
func (c *Call) Once() *Call {
return c.Times(1)
}
// Twice indicates that that the mock should only return the value twice.
// Twice indicates that the mock should only return the value twice.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
func (c *Call) Twice() *Call {
return c.Times(2)
}
// Times indicates that that the mock should only return the indicated number
// Times indicates that the mock should only return the indicated number
// of times.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
func (c *Call) Times(i int) *Call {
c.lock()
defer c.unlock()
@ -128,7 +154,7 @@ func (c *Call) Times(i int) *Call {
// WaitUntil sets the channel that will block the mock's return until its closed
// or a message is received.
//
// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second))
// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second))
func (c *Call) WaitUntil(w <-chan time.Time) *Call {
c.lock()
defer c.unlock()
@ -138,7 +164,7 @@ func (c *Call) WaitUntil(w <-chan time.Time) *Call {
// After sets how long to block until the call returns
//
// Mock.On("MyMethod", arg1, arg2).After(time.Second)
// Mock.On("MyMethod", arg1, arg2).After(time.Second)
func (c *Call) After(d time.Duration) *Call {
c.lock()
defer c.unlock()
@ -147,13 +173,13 @@ func (c *Call) After(d time.Duration) *Call {
}
// Run sets a handler to be called before returning. It can be used when
// mocking a method such as unmarshalers that takes a pointer to a struct and
// mocking a method (such as an unmarshaler) that takes a pointer to a struct and
// sets properties in such struct
//
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) {
// arg := args.Get(0).(*map[string]interface{})
// arg["foo"] = "bar"
// })
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) {
// arg := args.Get(0).(*map[string]interface{})
// arg["foo"] = "bar"
// })
func (c *Call) Run(fn func(args Arguments)) *Call {
c.lock()
defer c.unlock()
@ -173,14 +199,101 @@ func (c *Call) Maybe() *Call {
// On chains a new expectation description onto the mocked interface. This
// allows syntax like.
//
// Mock.
// On("MyMethod", 1).Return(nil).
// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error"))
// Mock.
// On("MyMethod", 1).Return(nil).
// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error"))
//
//go:noinline
func (c *Call) On(methodName string, arguments ...interface{}) *Call {
return c.Parent.On(methodName, arguments...)
}
// Unset removes all mock handlers that satisfy the call instance arguments from being
// called. Only supported on call instances with static input arguments.
//
// For example, the only handler remaining after the following would be "MyMethod(2, 2)":
//
// Mock.
// On("MyMethod", 2, 2).Return(0).
// On("MyMethod", 3, 3).Return(0).
// On("MyMethod", Anything, Anything).Return(0)
// Mock.On("MyMethod", 3, 3).Unset()
func (c *Call) Unset() *Call {
var unlockOnce sync.Once
for _, arg := range c.Arguments {
if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg))
}
}
c.lock()
defer unlockOnce.Do(c.unlock)
foundMatchingCall := false
// in-place filter slice for calls to be removed - iterate from 0'th to last skipping unnecessary ones
var index int // write index
for _, call := range c.Parent.ExpectedCalls {
if call.Method == c.Method {
_, diffCount := call.Arguments.Diff(c.Arguments)
if diffCount == 0 {
foundMatchingCall = true
// Remove from ExpectedCalls - just skip it
continue
}
}
c.Parent.ExpectedCalls[index] = call
index++
}
// trim slice up to last copied index
c.Parent.ExpectedCalls = c.Parent.ExpectedCalls[:index]
if !foundMatchingCall {
unlockOnce.Do(c.unlock)
c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n",
callString(c.Method, c.Arguments, true),
)
}
return c
}
// NotBefore indicates that the mock should only be called after the referenced
// calls have been called as expected. The referenced calls may be from the
// same mock instance and/or other mock instances.
//
// Mock.On("Do").Return(nil).NotBefore(
// Mock.On("Init").Return(nil)
// )
func (c *Call) NotBefore(calls ...*Call) *Call {
c.lock()
defer c.unlock()
for _, call := range calls {
if call.Parent == nil {
panic("not before calls must be created with Mock.On()")
}
}
c.requires = append(c.requires, calls...)
return c
}
// InOrder defines the order in which the calls should be made
//
// For example:
//
// InOrder(
// Mock.On("init").Return(nil),
// Mock.On("Do").Return(nil),
// )
func InOrder(calls ...*Call) {
for i := 1; i < len(calls); i++ {
calls[i].NotBefore(calls[i-1])
}
}
// Mock is the workhorse used to track activity on another object.
// For an example of its usage, refer to the "Example Usage" section at the top
// of this document.
@ -203,10 +316,17 @@ type Mock struct {
mutex sync.Mutex
}
// String provides a %v format string for Mock.
// Note: this is used implicitly by Arguments.Diff if a Mock is passed.
// It exists because go's default %v formatting traverses the struct
// without acquiring the mutex, which is detected by go test -race.
func (m *Mock) String() string {
return fmt.Sprintf("%[1]T<%[1]p>", m)
}
// TestData holds any data that might be useful for testing. Testify ignores
// this data completely allowing you to do whatever you like with it.
func (m *Mock) TestData() objx.Map {
if m.testData == nil {
m.testData = make(objx.Map)
}
@ -218,7 +338,10 @@ func (m *Mock) TestData() objx.Map {
Setting expectations
*/
// Test sets the test struct variable of the mock object
// Test sets the [TestingT] on which errors will be reported, otherwise errors
// will cause a panic.
// Test should not be called on an object that is going to be used in a
// goroutine other than the one running the test function.
func (m *Mock) Test(t TestingT) {
m.mutex.Lock()
defer m.mutex.Unlock()
@ -242,7 +365,7 @@ func (m *Mock) fail(format string, args ...interface{}) {
// On starts a description of an expectation of the specified method
// being called.
//
// Mock.On("MyMethod", arg1, arg2)
// Mock.On("MyMethod", arg1, arg2)
func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
for _, arg := range arguments {
if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
@ -252,7 +375,8 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
m.mutex.Lock()
defer m.mutex.Unlock()
c := newCall(m, methodName, assert.CallerInfo(), arguments...)
c := newCall(m, methodName, assert.CallerInfo(), arguments, make([]interface{}, 0))
m.ExpectedCalls = append(m.ExpectedCalls, c)
return c
}
@ -262,46 +386,80 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
// */
func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) {
for i, call := range m.ExpectedCalls {
if call.Method == method && call.Repeatability > -1 {
var expectedCall *Call
for i, call := range m.ExpectedCalls {
if call.Method == method {
_, diffCount := call.Arguments.Diff(arguments)
if diffCount == 0 {
return i, call
expectedCall = call
if call.Repeatability > -1 {
return i, call
}
}
}
}
return -1, nil
return -1, expectedCall
}
type matchCandidate struct {
call *Call
mismatch string
diffCount int
}
func (c matchCandidate) isBetterMatchThan(other matchCandidate) bool {
if c.call == nil {
return false
}
if other.call == nil {
return true
}
if c.diffCount > other.diffCount {
return false
}
if c.diffCount < other.diffCount {
return true
}
if c.call.Repeatability > 0 && other.call.Repeatability <= 0 {
return true
}
return false
}
func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) {
var diffCount int
var closestCall *Call
var err string
var bestMatch matchCandidate
for _, call := range m.expectedCalls() {
if call.Method == method {
errInfo, tempDiffCount := call.Arguments.Diff(arguments)
if tempDiffCount < diffCount || diffCount == 0 {
diffCount = tempDiffCount
closestCall = call
err = errInfo
tempCandidate := matchCandidate{
call: call,
mismatch: errInfo,
diffCount: tempDiffCount,
}
if tempCandidate.isBetterMatchThan(bestMatch) {
bestMatch = tempCandidate
}
}
}
return closestCall, err
return bestMatch.call, bestMatch.mismatch
}
func callString(method string, arguments Arguments, includeArgumentValues bool) string {
var argValsString string
if includeArgumentValues {
var argVals []string
for argIndex, arg := range arguments {
if _, ok := arg.(*FunctionalOptionsArgument); ok {
argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg))
continue
}
argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg))
}
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
@ -321,13 +479,12 @@ func (m *Mock) Called(arguments ...interface{}) Arguments {
panic("Couldn't get the caller information")
}
functionPath := runtime.FuncForPC(pc).Name()
//Next four lines are required to use GCCGO function naming conventions.
//For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock
//uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree
//With GCCGO we need to remove interface information starting from pN<dd>.
re := regexp.MustCompile("\\.pN\\d+_")
if re.MatchString(functionPath) {
functionPath = re.Split(functionPath, -1)[0]
// Next four lines are required to use GCCGO function naming conventions.
// For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock
// uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree
// With GCCGO we need to remove interface information starting from pN<dd>.
if gccgoRE.MatchString(functionPath) {
functionPath = gccgoRE.Split(functionPath, -1)[0]
}
parts := strings.Split(functionPath, ".")
functionName := parts[len(parts)-1]
@ -340,29 +497,53 @@ func (m *Mock) Called(arguments ...interface{}) Arguments {
// If Call.WaitFor is set, blocks until the channel is closed or receives a message.
func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments {
m.mutex.Lock()
//TODO: could combine expected and closes in single loop
// TODO: could combine expected and closes in single loop
found, call := m.findExpectedCall(methodName, arguments...)
if found < 0 {
// expected call found, but it has already been called with repeatable times
if call != nil {
m.mutex.Unlock()
m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(%#v).Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo())
}
// we have to fail here - because we don't know what to do
// as the return arguments. This is because:
//
// a) this is a totally unexpected call to this method,
// b) the arguments are not what was expected, or
// c) the developer has forgotten to add an accompanying On...Return pair.
closestCall, mismatch := m.findClosestCall(methodName, arguments...)
m.mutex.Unlock()
if closestCall != nil {
m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s",
m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s\nat: %s\n",
callString(methodName, arguments, true),
callString(methodName, closestCall.Arguments, true),
diffArguments(closestCall.Arguments, arguments),
strings.TrimSpace(mismatch),
assert.CallerInfo(),
)
} else {
m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo())
m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(%#v).Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo())
}
}
for _, requirement := range call.requires {
if satisfied, _ := requirement.Parent.checkExpectation(requirement); !satisfied {
m.mutex.Unlock()
m.fail("mock: Unexpected Method Call\n-----------------------------\n\n%s\n\nMust not be called before%s:\n\n%s",
callString(call.Method, call.Arguments, true),
func() (s string) {
if requirement.totalCalls > 0 {
s = " another call of"
}
if call.Parent != requirement.Parent {
s += " method from another mock instance"
}
return
}(),
callString(requirement.Method, requirement.Arguments, true),
)
}
}
@ -374,7 +555,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
call.totalCalls++
// add the call
m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...))
m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments, call.ReturnArguments))
m.mutex.Unlock()
// block if specified
@ -384,6 +565,13 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
time.Sleep(call.waitTime)
}
m.mutex.Lock()
panicMsg := call.PanicMsg
m.mutex.Unlock()
if panicMsg != nil {
panic(*panicMsg)
}
m.mutex.Lock()
runFn := call.RunFn
m.mutex.Unlock()
@ -403,7 +591,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
Assertions
*/
type assertExpectationser interface {
type assertExpectationiser interface {
AssertExpectations(TestingT) bool
}
@ -416,11 +604,11 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
h.Helper()
}
for _, obj := range testObjects {
if m, ok := obj.(Mock); ok {
if m, ok := obj.(*Mock); ok {
t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)")
obj = &m
obj = m
}
m := obj.(assertExpectationser)
m := obj.(assertExpectationiser)
if !m.AssertExpectations(t) {
t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m))
return false
@ -432,37 +620,42 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
// AssertExpectations asserts that everything specified with On and Return was
// in fact called as expected. Calls may have occurred in any order.
func (m *Mock) AssertExpectations(t TestingT) bool {
if s, ok := t.(interface{ Skipped() bool }); ok && s.Skipped() {
return true
}
if h, ok := t.(tHelper); ok {
h.Helper()
}
m.mutex.Lock()
defer m.mutex.Unlock()
var somethingMissing bool
var failedExpectations int
// iterate through each expectation
expectedCalls := m.expectedCalls()
for _, expectedCall := range expectedCalls {
if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 {
somethingMissing = true
satisfied, reason := m.checkExpectation(expectedCall)
if !satisfied {
failedExpectations++
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
} else {
if expectedCall.Repeatability > 0 {
somethingMissing = true
failedExpectations++
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
} else {
t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String())
}
t.Logf(reason)
}
}
if somethingMissing {
if failedExpectations != 0 {
t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo())
}
return !somethingMissing
return failedExpectations == 0
}
func (m *Mock) checkExpectation(call *Call) (bool, string) {
if !call.optional && !m.methodWasCalled(call.Method, call.Arguments) && call.totalCalls == 0 {
return false, fmt.Sprintf("FAIL:\t%s(%s)\n\t\tat: %s", call.Method, call.Arguments.String(), call.callerInfo)
}
if call.Repeatability > 0 {
return false, fmt.Sprintf("FAIL:\t%s(%s)\n\t\tat: %s", call.Method, call.Arguments.String(), call.callerInfo)
}
return true, fmt.Sprintf("PASS:\t%s(%s)", call.Method, call.Arguments.String())
}
// AssertNumberOfCalls asserts that the method was called expectedCalls times.
@ -519,6 +712,45 @@ func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...inter
return true
}
// IsMethodCallable checking that the method can be called
// If the method was called more than `Repeatability` return false
func (m *Mock) IsMethodCallable(t TestingT, methodName string, arguments ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
m.mutex.Lock()
defer m.mutex.Unlock()
for _, v := range m.ExpectedCalls {
if v.Method != methodName {
continue
}
if len(arguments) != len(v.Arguments) {
continue
}
if v.Repeatability < v.totalCalls {
continue
}
if isArgsEqual(v.Arguments, arguments) {
return true
}
}
return false
}
// isArgsEqual compares arguments
func isArgsEqual(expected Arguments, args []interface{}) bool {
if len(expected) != len(args) {
return false
}
for i, v := range args {
if !reflect.DeepEqual(expected[i], v) {
return false
}
}
return true
}
func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
for _, call := range m.calls() {
if call.Method == methodName {
@ -557,17 +789,80 @@ const (
Anything = "mock.Anything"
)
// AnythingOfTypeArgument is a string that contains the type of an argument
// for use when type checking. Used in Diff and Assert.
type AnythingOfTypeArgument string
// AnythingOfTypeArgument contains the type of an argument
// for use when type checking. Used in [Arguments.Diff] and [Arguments.Assert].
//
// Deprecated: this is an implementation detail that must not be used. Use the [AnythingOfType] constructor instead, example:
//
// m.On("Do", mock.AnythingOfType("string"))
//
// All explicit type declarations can be replaced with interface{} as is expected by [Mock.On], example:
//
// func anyString interface{} {
// return mock.AnythingOfType("string")
// }
type AnythingOfTypeArgument = anythingOfTypeArgument
// AnythingOfType returns an AnythingOfTypeArgument object containing the
// name of the type to check for. Used in Diff and Assert.
// anythingOfTypeArgument is a string that contains the type of an argument
// for use when type checking. Used in Diff and Assert.
type anythingOfTypeArgument string
// AnythingOfType returns a special value containing the
// name of the type to check for. The type name will be matched against the type name returned by [reflect.Type.String].
//
// Used in Diff and Assert.
//
// For example:
// Assert(t, AnythingOfType("string"), AnythingOfType("int"))
//
// args.Assert(t, AnythingOfType("string"), AnythingOfType("int"))
func AnythingOfType(t string) AnythingOfTypeArgument {
return AnythingOfTypeArgument(t)
return anythingOfTypeArgument(t)
}
// IsTypeArgument is a struct that contains the type of an argument
// for use when type checking. This is an alternative to [AnythingOfType].
// Used in [Arguments.Diff] and [Arguments.Assert].
type IsTypeArgument struct {
t reflect.Type
}
// IsType returns an IsTypeArgument object containing the type to check for.
// You can provide a zero-value of the type to check. This is an
// alternative to [AnythingOfType]. Used in [Arguments.Diff] and [Arguments.Assert].
//
// For example:
//
// args.Assert(t, IsType(""), IsType(0))
func IsType(t interface{}) *IsTypeArgument {
return &IsTypeArgument{t: reflect.TypeOf(t)}
}
// FunctionalOptionsArgument contains a list of functional options arguments
// expected for use when matching a list of arguments.
type FunctionalOptionsArgument struct {
values []interface{}
}
// String returns the string representation of FunctionalOptionsArgument
func (f *FunctionalOptionsArgument) String() string {
var name string
if len(f.values) > 0 {
name = "[]" + reflect.TypeOf(f.values[0]).String()
}
return strings.Replace(fmt.Sprintf("%#v", f.values), "[]interface {}", name, 1)
}
// FunctionalOptions returns an [FunctionalOptionsArgument] object containing
// the expected functional-options to check for.
//
// For example:
//
// args.Assert(t, FunctionalOptions(foo.Opt1("strValue"), foo.Opt2(613)))
func FunctionalOptions(values ...interface{}) *FunctionalOptionsArgument {
return &FunctionalOptionsArgument{
values: values,
}
}
// argumentMatcher performs custom argument matching, returning whether or
@ -604,7 +899,7 @@ func (f argumentMatcher) Matches(argument interface{}) bool {
}
func (f argumentMatcher) String() string {
return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name())
return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).String())
}
// MatchedBy can be used to match a mock call based on only certain properties
@ -613,10 +908,11 @@ func (f argumentMatcher) String() string {
// and false otherwise.
//
// Example:
// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" }))
//
// |fn|, must be a function accepting a single argument (of the expected type)
// which returns a bool. If |fn| doesn't match the required signature,
// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" }))
//
// fn must be a function accepting a single argument (of the expected type)
// which returns a bool. If fn doesn't match the required signature,
// MatchedBy() panics.
func MatchedBy(fn interface{}) argumentMatcher {
fnType := reflect.TypeOf(fn)
@ -657,12 +953,12 @@ func (args Arguments) Is(objects ...interface{}) bool {
//
// Returns the diff string and number of differences found.
func (args Arguments) Diff(objects []interface{}) (string, int) {
//TODO: could return string as error and nil for No difference
// TODO: could return string as error and nil for No difference
var output = "\n"
output := "\n"
var differences int
var maxArgCount = len(args)
maxArgCount := len(args)
if len(objects) > maxArgCount {
maxArgCount = len(objects)
}
@ -688,32 +984,66 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
}
if matcher, ok := expected.(argumentMatcher); ok {
if matcher.Matches(actual) {
var matches bool
func() {
defer func() {
if r := recover(); r != nil {
actualFmt = fmt.Sprintf("panic in argument matcher: %v", r)
}
}()
matches = matcher.Matches(actual)
}()
if matches {
output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher)
} else {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher)
}
} else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() {
// type checking
if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt)
}
} else {
switch expected := expected.(type) {
case anythingOfTypeArgument:
// type checking
if reflect.TypeOf(actual).Name() != string(expected) && reflect.TypeOf(actual).String() != string(expected) {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt)
}
case *IsTypeArgument:
actualT := reflect.TypeOf(actual)
if actualT != expected.t {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected.t.Name(), actualT.Name(), actualFmt)
}
case *FunctionalOptionsArgument:
var name string
if len(expected.values) > 0 {
name = "[]" + reflect.TypeOf(expected.values[0]).String()
}
// normal checking
const tName = "[]interface{}"
if name != reflect.TypeOf(actual).String() && len(expected.values) != 0 {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt)
} else {
if ef, af := assertOpts(expected.values, actual); ef == "" && af == "" {
// match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef)
}
}
if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
// match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt)
default:
if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
// match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt)
}
}
}
@ -724,7 +1054,6 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
}
return output, differences
}
// Assert compares the arguments with the specified objects and fails if
@ -746,7 +1075,6 @@ func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
t.Errorf("%sArguments do not match.", assert.CallerInfo())
return false
}
// String gets the argument at the specified index. Panics if there is no argument, or
@ -755,17 +1083,16 @@ func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
// If no index is provided, String() returns a complete string representation
// of the arguments.
func (args Arguments) String(indexOrNil ...int) string {
if len(indexOrNil) == 0 {
// normal String() method - return a string representation of the args
var argsStr []string
for _, arg := range args {
argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg)))
argsStr = append(argsStr, fmt.Sprintf("%T", arg)) // handles nil nicely
}
return strings.Join(argsStr, ",")
} else if len(indexOrNil) == 1 {
// Index has been specified - get the argument at that index
var index = indexOrNil[0]
index := indexOrNil[0]
var s string
var ok bool
if s, ok = args.Get(index).(string); !ok {
@ -775,7 +1102,6 @@ func (args Arguments) String(indexOrNil ...int) string {
}
panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil)))
}
// Int gets the argument at the specified index. Panics if there is no argument, or
@ -799,7 +1125,7 @@ func (args Arguments) Error(index int) error {
return nil
}
if s, ok = obj.(error); !ok {
panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, obj))
}
return s
}
@ -884,3 +1210,89 @@ var spewConfig = spew.ConfigState{
type tHelper interface {
Helper()
}
func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) {
expectedOpts := reflect.ValueOf(expected)
actualOpts := reflect.ValueOf(actual)
var expectedFuncs []*runtime.Func
var expectedNames []string
for i := 0; i < expectedOpts.Len(); i++ {
f := runtimeFunc(expectedOpts.Index(i).Interface())
expectedFuncs = append(expectedFuncs, f)
expectedNames = append(expectedNames, funcName(f))
}
var actualFuncs []*runtime.Func
var actualNames []string
for i := 0; i < actualOpts.Len(); i++ {
f := runtimeFunc(actualOpts.Index(i).Interface())
actualFuncs = append(actualFuncs, f)
actualNames = append(actualNames, funcName(f))
}
if expectedOpts.Len() != actualOpts.Len() {
expectedFmt = fmt.Sprintf("%v", expectedNames)
actualFmt = fmt.Sprintf("%v", actualNames)
return
}
for i := 0; i < expectedOpts.Len(); i++ {
if !isFuncSame(expectedFuncs[i], actualFuncs[i]) {
expectedFmt = expectedNames[i]
actualFmt = actualNames[i]
return
}
expectedOpt := expectedOpts.Index(i).Interface()
actualOpt := actualOpts.Index(i).Interface()
ot := reflect.TypeOf(expectedOpt)
var expectedValues []reflect.Value
var actualValues []reflect.Value
if ot.NumIn() == 0 {
return
}
for i := 0; i < ot.NumIn(); i++ {
vt := ot.In(i).Elem()
expectedValues = append(expectedValues, reflect.New(vt))
actualValues = append(actualValues, reflect.New(vt))
}
reflect.ValueOf(expectedOpt).Call(expectedValues)
reflect.ValueOf(actualOpt).Call(actualValues)
for i := 0; i < ot.NumIn(); i++ {
if expectedArg, actualArg := expectedValues[i].Interface(), actualValues[i].Interface(); !assert.ObjectsAreEqual(expectedArg, actualArg) {
expectedFmt = fmt.Sprintf("%s(%T) -> %#v", expectedNames[i], expectedArg, expectedArg)
actualFmt = fmt.Sprintf("%s(%T) -> %#v", expectedNames[i], actualArg, actualArg)
return
}
}
}
return "", ""
}
func runtimeFunc(opt interface{}) *runtime.Func {
return runtime.FuncForPC(reflect.ValueOf(opt).Pointer())
}
func funcName(f *runtime.Func) string {
name := f.Name()
trimmed := strings.TrimSuffix(path.Base(name), path.Ext(name))
splitted := strings.Split(trimmed, ".")
if len(splitted) == 0 {
return trimmed
}
return splitted[len(splitted)-1]
}
func isFuncSame(f1, f2 *runtime.Func) bool {
f1File, f1Loc := f1.FileLine(f1.Entry())
f2File, f2Loc := f2.FileLine(f2.Entry())
return f1File == f2File && f1Loc == f2Loc
}

View File

@ -32,6 +32,40 @@ func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) {
return args.Int(0), errors.New("Whoops")
}
type options struct {
num int
str string
}
type OptionFn func(*options)
func OpNum(n int) OptionFn {
return func(o *options) {
o.num = n
}
}
func OpStr(s string) OptionFn {
return func(o *options) {
o.str = s
}
}
func OpBytes(b []byte) OptionFn {
return func(m *options) {
m.str = string(b)
}
}
func (i *TestExampleImplementation) TheExampleMethodFunctionalOptions(x string, opts ...OptionFn) error {
args := i.Called(x, opts)
return args.Error(0)
}
func TheExampleMethodFunctionalOptionsIndirect(i *TestExampleImplementation) {
i.TheExampleMethodFunctionalOptions("test", OpNum(1), OpStr("foo"))
}
//go:noinline
func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) {
i.Called(yesorno)
@ -113,7 +147,7 @@ func (m *MockTestingT) Errorf(string, ...interface{}) {
// the execution stops.
// When expecting this method, the call that invokes it should use the following code:
//
// assert.PanicsWithValue(t, mockTestingTFailNowCalled, func() {...})
// assert.PanicsWithValue(t, mockTestingTFailNowCalled, func() {...})
func (m *MockTestingT) FailNow() {
m.failNowCount++
@ -151,7 +185,7 @@ func Test_Mock_Chained_On(t *testing.T) {
var mockedService = new(TestExampleImplementation)
// determine our current line number so we can assert the expected calls callerInfo properly
_, _, line, _ := runtime.Caller(0)
_, filename, line, _ := runtime.Caller(0)
mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(0).
@ -164,14 +198,14 @@ func Test_Mock_Chained_On(t *testing.T) {
Method: "TheExampleMethod",
Arguments: []interface{}{1, 2, 3},
ReturnArguments: []interface{}{0},
callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+2)},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+2)},
},
{
Parent: &mockedService.Mock,
Method: "TheExampleMethod3",
Arguments: []interface{}{AnythingOfType("*mock.ExampleType")},
ReturnArguments: []interface{}{nil},
callerInfo: []string{fmt.Sprintf("mock_test.go:%d", line+4)},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+4)},
},
}
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
@ -233,6 +267,32 @@ func Test_Mock_On_WithIntArgMatcher(t *testing.T) {
})
}
func Test_Mock_On_WithArgMatcherThatPanics(t *testing.T) {
var mockedService TestExampleImplementation
mockedService.On("TheExampleMethod2", MatchedBy(func(_ interface{}) bool {
panic("try to lock mockedService")
})).Return()
defer func() {
assertedExpectations := make(chan struct{})
go func() {
tt := new(testing.T)
mockedService.AssertExpectations(tt)
close(assertedExpectations)
}()
select {
case <-assertedExpectations:
case <-time.After(time.Second):
t.Fatal("AssertExpectations() deadlocked, did the panic leave mockedService locked?")
}
}()
assert.Panics(t, func() {
mockedService.TheExampleMethod2(false)
})
}
func TestMock_WithTest(t *testing.T) {
var (
mockedService TestExampleImplementation
@ -462,6 +522,161 @@ func Test_Mock_On_WithFuncTypeArg(t *testing.T) {
})
}
func Test_Mock_Unset(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
call := mockedService.
On("TheExampleMethodFuncType", "argA").
Return("blah")
found, foundCall := mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
require.NotEqual(t, -1, found)
require.Equal(t, foundCall, call)
call.Unset()
found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
require.Equal(t, -1, found)
var expectedCall *Call
require.Equal(t, expectedCall, foundCall)
fn := func(string) error { return nil }
assert.Panics(t, func() {
mockedService.TheExampleMethodFuncType(fn)
})
}
// Since every time you call On it creates a new object
// the last time you call Unset it will only unset the last call
func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
// determine our current line number so we can assert the expected calls callerInfo properly
_, filename, line, _ := runtime.Caller(0)
mockedService.
On("TheExampleMethod1", 1, 1).
Return(0).
On("TheExampleMethod2", 2, 2).
On("TheExampleMethod3", 3, 3, 3).
Return(nil).
Unset()
expectedCalls := []*Call{
{
Parent: &mockedService.Mock,
Method: "TheExampleMethod1",
Arguments: []interface{}{1, 1},
ReturnArguments: []interface{}{0},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+2)},
},
{
Parent: &mockedService.Mock,
Method: "TheExampleMethod2",
Arguments: []interface{}{2, 2},
ReturnArguments: []interface{}{},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+4)},
},
}
assert.Equal(t, 2, len(expectedCalls))
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
}
func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
mock1 := mockedService.
On("TheExampleMethod1", 1, 1).
Return(1)
assert.Equal(t, 1, len(mockedService.ExpectedCalls))
mock1.Unset()
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
assert.Panics(t, func() {
mock1.Unset()
})
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
}
func Test_Mock_UnsetByOnMethodSpec(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
mock1 := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(0, nil)
assert.Equal(t, 1, len(mockedService.ExpectedCalls))
mock1.On("TheExampleMethod", 1, 2, 3).
Return(0, nil).Unset()
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
assert.Panics(t, func() {
mock1.Unset()
})
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
}
func Test_Mock_UnsetByOnMethodSpecAmongOthers(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
_, filename, line, _ := runtime.Caller(0)
mock1 := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(0, nil).
On("TheExampleMethodVariadic", 1, 2, 3, 4, 5).Once().
Return(nil)
mock1.
On("TheExampleMethodFuncType", Anything).
Return(nil)
assert.Equal(t, 3, len(mockedService.ExpectedCalls))
mock1.On("TheExampleMethod", 1, 2, 3).
Return(0, nil).Unset()
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
expectedCalls := []*Call{
{
Parent: &mockedService.Mock,
Method: "TheExampleMethodVariadic",
Repeatability: 1,
Arguments: []interface{}{1, 2, 3, 4, 5},
ReturnArguments: []interface{}{nil},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+4)},
},
{
Parent: &mockedService.Mock,
Method: "TheExampleMethodFuncType",
Arguments: []interface{}{Anything},
ReturnArguments: []interface{}{nil},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+7)},
},
}
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
}
func Test_Mock_Unset_WithFuncPanics(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
mock1 := mockedService.On("TheExampleMethod", 1)
mock1.Arguments = append(mock1.Arguments, func(string) error { return nil })
assert.Panics(t, func() {
mock1.Unset()
})
}
func Test_Mock_Return(t *testing.T) {
// make a test impl object
@ -486,6 +701,29 @@ func Test_Mock_Return(t *testing.T) {
assert.Nil(t, call.WaitFor)
}
func Test_Mock_Panic(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
c := mockedService.
On("TheExampleMethod", "A", "B", true).
Panic("panic message for example method")
require.Equal(t, []*Call{c}, mockedService.ExpectedCalls)
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 0, call.Repeatability)
assert.Equal(t, 0, call.Repeatability)
assert.Equal(t, "panic message for example method", *call.PanicMsg)
assert.Nil(t, call.WaitFor)
}
func Test_Mock_Return_WaitUntil(t *testing.T) {
// make a test impl object
@ -690,6 +928,245 @@ func Test_Mock_Return_Nothing(t *testing.T) {
assert.Equal(t, 0, len(call.ReturnArguments))
}
func Test_Mock_Return_NotBefore_In_Order(t *testing.T) {
var mockedService = new(TestExampleImplementation)
b := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil)
c := mockedService.
On("TheExampleMethod2", true).
Return().
NotBefore(b)
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
require.NotPanics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod2(true)
})
}
func Test_Mock_Return_InOrder_Uses_NotBefore(t *testing.T) {
var mockedService = new(TestExampleImplementation)
InOrder(
mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil),
mockedService.
On("TheExampleMethod2", true).
Return(),
)
require.NotPanics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod2(true)
})
}
func Test_Mock_Return_NotBefore_Out_Of_Order(t *testing.T) {
var mockedService = new(TestExampleImplementation)
b := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil).Twice()
c := mockedService.
On("TheExampleMethod2", true).
Return().
NotBefore(b)
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
expectedPanicString := `mock: Unexpected Method Call
-----------------------------
TheExampleMethod2(bool)
0: true
Must not be called before:
TheExampleMethod(int,int,int)
0: 1
1: 2
2: 3`
require.PanicsWithValue(t, expectedPanicString, func() {
mockedService.TheExampleMethod2(true)
})
}
func Test_Mock_Return_InOrder_Uses_NotBefore_Out_Of_Order(t *testing.T) {
var mockedService = new(TestExampleImplementation)
InOrder(
mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil).Twice(),
mockedService.
On("TheExampleMethod2", true).
Return(),
)
expectedPanicString := `mock: Unexpected Method Call
-----------------------------
TheExampleMethod2(bool)
0: true
Must not be called before:
TheExampleMethod(int,int,int)
0: 1
1: 2
2: 3`
require.PanicsWithValue(t, expectedPanicString, func() {
mockedService.TheExampleMethod2(true)
})
}
func Test_Mock_Return_NotBefore_Not_Enough_Times(t *testing.T) {
var mockedService = new(TestExampleImplementation)
b := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil).Twice()
c := mockedService.
On("TheExampleMethod2", true).
Return().
NotBefore(b)
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
require.NotPanics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
expectedPanicString := `mock: Unexpected Method Call
-----------------------------
TheExampleMethod2(bool)
0: true
Must not be called before another call of:
TheExampleMethod(int,int,int)
0: 1
1: 2
2: 3`
require.PanicsWithValue(t, expectedPanicString, func() {
mockedService.TheExampleMethod2(true)
})
}
func Test_Mock_Return_NotBefore_Different_Mock_In_Order(t *testing.T) {
var (
mockedService1 = new(TestExampleImplementation)
mockedService2 = new(TestExampleImplementation)
)
b := mockedService1.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil)
c := mockedService2.
On("TheExampleMethod2", true).
Return().
NotBefore(b)
require.Equal(t, []*Call{c}, mockedService2.ExpectedCalls)
require.NotPanics(t, func() {
mockedService1.TheExampleMethod(1, 2, 3)
})
require.NotPanics(t, func() {
mockedService2.TheExampleMethod2(true)
})
}
func Test_Mock_Return_NotBefore_Different_Mock_Out_Of_Order(t *testing.T) {
var (
mockedService1 = new(TestExampleImplementation)
mockedService2 = new(TestExampleImplementation)
)
b := mockedService1.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil)
c := mockedService2.
On("TheExampleMethod2", true).
Return().
NotBefore(b)
require.Equal(t, []*Call{c}, mockedService2.ExpectedCalls)
expectedPanicString := `mock: Unexpected Method Call
-----------------------------
TheExampleMethod2(bool)
0: true
Must not be called before method from another mock instance:
TheExampleMethod(int,int,int)
0: 1
1: 2
2: 3`
require.PanicsWithValue(t, expectedPanicString, func() {
mockedService2.TheExampleMethod2(true)
})
}
func Test_Mock_Return_NotBefore_In_Order_With_Non_Dependant(t *testing.T) {
var mockedService = new(TestExampleImplementation)
a := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(4, nil)
b := mockedService.
On("TheExampleMethod", 4, 5, 6).
Return(4, nil)
c := mockedService.
On("TheExampleMethod2", true).
Return().
NotBefore(a, b)
d := mockedService.
On("TheExampleMethod7", []bool{}).Return(nil)
require.Equal(t, []*Call{a, b, c, d}, mockedService.ExpectedCalls)
require.NotPanics(t, func() {
mockedService.TheExampleMethod7([]bool{})
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod7([]bool{})
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod(4, 5, 6)
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod7([]bool{})
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod2(true)
})
require.NotPanics(t, func() {
mockedService.TheExampleMethod7([]bool{})
})
}
func Test_Mock_Return_NotBefore_Orphan_Call(t *testing.T) {
var mockedService = new(TestExampleImplementation)
require.PanicsWithValue(t, "not before calls must be created with Mock.On()", func() {
mockedService.
On("TheExampleMethod2", true).
Return().
NotBefore(&Call{Method: "Not", Arguments: Arguments{"how", "it's"}, ReturnArguments: Arguments{"done"}})
})
}
func Test_Mock_findExpectedCall(t *testing.T) {
m := new(Mock)
@ -740,11 +1217,22 @@ func Test_Mock_findExpectedCall_Respects_Repeatability(t *testing.T) {
}
}
c = m.On("Once", 1).Return("one").Once()
c.Repeatability = -1
f, c = m.findExpectedCall("Once", 1)
if assert.Equal(t, -1, f) {
if assert.NotNil(t, c) {
assert.Equal(t, "Once", c.Method)
assert.Equal(t, 1, c.Arguments[0])
assert.Equal(t, "one", c.ReturnArguments[0])
}
}
}
func Test_callString(t *testing.T) {
assert.Equal(t, `Method(int,bool,string)`, callString("Method", []interface{}{1, true, "something"}, false))
assert.Equal(t, `Method(<nil>)`, callString("Method", []interface{}{nil}, false))
}
@ -779,7 +1267,7 @@ func Test_Mock_Called_blocks(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.Mock.On("asyncCall", 1, 2, 3).Return(5, "6", true).After(2 * time.Millisecond)
mockedService.Mock.On("asyncCall", 1, 2, 3).Return(5, "6", true).After(20 * time.Millisecond)
ch := make(chan Arguments)
@ -788,7 +1276,7 @@ func Test_Mock_Called_blocks(t *testing.T) {
select {
case <-ch:
t.Fatal("should have waited")
case <-time.After(1 * time.Millisecond):
case <-time.After(10 * time.Millisecond):
}
returnArguments := <-ch
@ -881,6 +1369,7 @@ func Test_AssertExpectationsForObjects_Helper(t *testing.T) {
var mockedService1 = new(TestExampleImplementation)
var mockedService2 = new(TestExampleImplementation)
var mockedService3 = new(TestExampleImplementation)
var mockedService4 = new(TestExampleImplementation) // No expectations does not cause a panic
mockedService1.On("Test_AssertExpectationsForObjects_Helper", 1).Return()
mockedService2.On("Test_AssertExpectationsForObjects_Helper", 2).Return()
@ -890,8 +1379,8 @@ func Test_AssertExpectationsForObjects_Helper(t *testing.T) {
mockedService2.Called(2)
mockedService3.Called(3)
assert.True(t, AssertExpectationsForObjects(t, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock))
assert.True(t, AssertExpectationsForObjects(t, mockedService1, mockedService2, mockedService3))
assert.True(t, AssertExpectationsForObjects(t, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock, &mockedService4.Mock))
assert.True(t, AssertExpectationsForObjects(t, mockedService1, mockedService2, mockedService3, mockedService4))
}
@ -1010,6 +1499,85 @@ func Test_Mock_AssertExpectationsCustomType(t *testing.T) {
}
func Test_Mock_AssertExpectationsFunctionalOptionsType(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpNum(1), OpStr("foo"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test", OpNum(1), OpStr("foo"))
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Empty(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions()).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test")
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Indirectly(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpNum(1), OpStr("foo"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
TheExampleMethodFunctionalOptionsIndirect(mockedService)
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Diff_Func(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpStr("this"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
assert.Panics(t, func() {
mockedService.TheExampleMethodFunctionalOptions("test", OpBytes([]byte("this")))
})
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Diff_Arg(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpStr("this"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
assert.Panics(t, func() {
mockedService.TheExampleMethodFunctionalOptions("test", OpStr("that"))
})
}
func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
var mockedService = new(TestExampleImplementation)
@ -1031,6 +1599,16 @@ func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
}
func Test_Mock_AssertExpectations_Skipped_Test(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertExpectations_Skipped_Test", 1, 2, 3).Return(5, 6, 7)
defer mockedService.AssertExpectations(t)
t.Skip("skipping test to ensure AssertExpectations does not fail")
}
func Test_Mock_TwoCallsWithDifferentArguments(t *testing.T) {
var mockedService = new(TestExampleImplementation)
@ -1133,6 +1711,38 @@ func Test_Mock_AssertNotCalled(t *testing.T) {
}
func Test_Mock_IsMethodCallable(t *testing.T) {
var mockedService = new(TestExampleImplementation)
arg := []Call{{Repeatability: 1}, {Repeatability: 2}}
arg2 := []Call{{Repeatability: 1}, {Repeatability: 1}}
arg3 := []Call{{Repeatability: 1}, {Repeatability: 1}}
mockedService.On("Test_Mock_IsMethodCallable", arg2).Return(true).Twice()
assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg))
assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg3))
mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
}
func TestIsArgsEqual(t *testing.T) {
var expected = Arguments{5, 3, 4, 6, 7, 2}
// Copy elements 1 to 5
args := append(([]interface{})(nil), expected[1:]...)
args[2] = expected[1]
assert.False(t, isArgsEqual(expected, args))
// Clone
arr := append(([]interface{})(nil), expected...)
assert.True(t, isArgsEqual(expected, arr))
}
func Test_Mock_AssertOptional(t *testing.T) {
// Optional called
var ms1 = new(TestExampleImplementation)
@ -1159,7 +1769,7 @@ func Test_Mock_AssertOptional(t *testing.T) {
}
/*
Arguments helper methods
Arguments helper methods
*/
func Test_Arguments_Get(t *testing.T) {
@ -1247,6 +1857,24 @@ func Test_Arguments_Diff_WithAnythingOfTypeArgument_Failing(t *testing.T) {
}
func Test_Arguments_Diff_WithIsTypeArgument(t *testing.T) {
var args = Arguments([]interface{}{"string", IsType(0), true})
var count int
_, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 0, count)
}
func Test_Arguments_Diff_WithIsTypeArgument_Failing(t *testing.T) {
var args = Arguments([]interface{}{"string", IsType(""), true})
var count int
var diff string
diff, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 1, count)
assert.Contains(t, diff, `string != type int - (int=123)`)
}
func Test_Arguments_Diff_WithArgMatcher(t *testing.T) {
matchFn := func(a int) bool {
return a == 123
@ -1262,6 +1890,7 @@ func Test_Arguments_Diff_WithArgMatcher(t *testing.T) {
assert.Contains(t, diff, `(bool=false) not matched by func(int) bool`)
diff, count = args.Diff([]interface{}{"string", 123, false})
assert.Equal(t, 1, count)
assert.Contains(t, diff, `(int=123) matched by func(int) bool`)
diff, count = args.Diff([]interface{}{"string", 123, true})
@ -1356,6 +1985,14 @@ func Test_MockMethodCalled(t *testing.T) {
m.AssertExpectations(t)
}
func Test_MockMethodCalled_Panic(t *testing.T) {
m := new(Mock)
m.On("foo", "hello").Panic("world panics")
require.PanicsWithValue(t, "world panics", func() { m.MethodCalled("foo", "hello") })
m.AssertExpectations(t)
}
// Test to validate fix for racy concurrent call access in MethodCalled()
func Test_MockReturnAndCalledConcurrent(t *testing.T) {
iterations := 1000
@ -1386,6 +2023,10 @@ func (s *timer) GetTime(i int) string {
return s.Called(i).Get(0).(string)
}
func (s *timer) GetTimes(times []int) string {
return s.Called(times).Get(0).(string)
}
type tCustomLogger struct {
*testing.T
logs []string
@ -1434,7 +2075,7 @@ func TestAfterTotalWaitTimeWhileExecution(t *testing.T) {
end := time.Now()
elapsedTime := end.Sub(start)
assert.True(t, elapsedTime > waitMs, fmt.Sprintf("Total elapsed time:%v should be atleast greater than %v", elapsedTime, waitMs))
assert.True(t, elapsedTime > waitMs, fmt.Sprintf("Total elapsed time:%v should be at least greater than %v", elapsedTime, waitMs))
assert.Equal(t, total, len(results))
for i := range results {
assert.Equal(t, fmt.Sprintf("Time%d", i), results[i], "Return value of method should be same")
@ -1445,7 +2086,7 @@ func TestArgumentMatcherToPrintMismatch(t *testing.T) {
defer func() {
if r := recover(); r != nil {
matchingExp := regexp.MustCompile(
`\s+mock: Unexpected Method Call\s+-*\s+GetTime\(int\)\s+0: 1\s+The closest call I have is:\s+GetTime\(mock.argumentMatcher\)\s+0: mock.argumentMatcher\{.*?\}\s+Diff:.*\(int=1\) not matched by func\(int\) bool`)
`\s+mock: Unexpected Method Call\s+-*\s+GetTime\(int\)\s+0: 1\s+The closest call I have is:\s+GetTime\(mock.argumentMatcher\)\s+0: mock.argumentMatcher\{.*?\}\s+Diff:.*\(int=1\) not matched by func\(int\) bool\nat: \[[^\]]+mock\/mock_test.go`)
assert.Regexp(t, matchingExp, r)
}
}()
@ -1458,10 +2099,27 @@ func TestArgumentMatcherToPrintMismatch(t *testing.T) {
m.AssertExpectations(t)
}
func TestArgumentMatcherToPrintMismatchWithReferenceType(t *testing.T) {
defer func() {
if r := recover(); r != nil {
matchingExp := regexp.MustCompile(
`\s+mock: Unexpected Method Call\s+-*\s+GetTimes\(\[\]int\)\s+0: \[\]int\{1\}\s+The closest call I have is:\s+GetTimes\(mock.argumentMatcher\)\s+0: mock.argumentMatcher\{.*?\}\s+Diff:.*\(\[\]int=\[1\]\) not matched by func\(\[\]int\) bool\nat: \[[^\]]+mock\/mock_test.go`)
assert.Regexp(t, matchingExp, r)
}
}()
m := new(timer)
m.On("GetTimes", MatchedBy(func(_ []int) bool { return false })).Return("SomeTime").Once()
res := m.GetTimes([]int{1})
require.Equal(t, "SomeTime", res)
m.AssertExpectations(t)
}
func TestClosestCallMismatchedArgumentInformationShowsTheClosest(t *testing.T) {
defer func() {
if r := recover(); r != nil {
matchingExp := regexp.MustCompile(unexpectedCallRegex(`TheExampleMethod(int,int,int)`, `0: 1\s+1: 1\s+2: 2`, `0: 1\s+1: 1\s+2: 1`, `0: PASS: \(int=1\) == \(int=1\)\s+1: PASS: \(int=1\) == \(int=1\)\s+2: FAIL: \(int=2\) != \(int=1\)`))
matchingExp := regexp.MustCompile(unexpectedCallRegex(`TheExampleMethod(int,int,int)`, `0: 1\s+1: 1\s+2: 2`, `0: 1\s+1: 1\s+2: 1`, `Diff: 0: PASS: \(int=1\) == \(int=1\)\s+1: PASS: \(int=1\) == \(int=1\)\s+2: FAIL: \(int=2\) != \(int=1\)`))
assert.Regexp(t, matchingExp, r)
}
}()
@ -1473,10 +2131,46 @@ func TestClosestCallMismatchedArgumentInformationShowsTheClosest(t *testing.T) {
m.TheExampleMethod(1, 1, 2)
}
func TestClosestCallFavorsFirstMock(t *testing.T) {
defer func() {
if r := recover(); r != nil {
diffRegExp := `Difference found in argument 0:\s+--- Expected\s+\+\+\+ Actual\s+@@ -2,4 \+2,4 @@\s+\(bool\) true,\s+- \(bool\) true,\s+- \(bool\) true\s+\+ \(bool\) false,\s+\+ \(bool\) false\s+}\s+Diff: 0: FAIL: \(\[\]bool=\[(true\s?|false\s?){3}]\) != \(\[\]bool=\[(true\s?|false\s?){3}\]\)`
matchingExp := regexp.MustCompile(unexpectedCallRegex(`TheExampleMethod7([]bool)`, `0: \[\]bool{true, false, false}`, `0: \[\]bool{true, true, true}`, diffRegExp))
assert.Regexp(t, matchingExp, r)
}
}()
m := new(TestExampleImplementation)
m.On("TheExampleMethod7", []bool{true, true, true}).Return(nil).Once()
m.On("TheExampleMethod7", []bool{false, false, false}).Return(nil).Once()
m.TheExampleMethod7([]bool{true, false, false})
}
func TestClosestCallUsesRepeatabilityToFindClosest(t *testing.T) {
defer func() {
if r := recover(); r != nil {
diffRegExp := `Difference found in argument 0:\s+--- Expected\s+\+\+\+ Actual\s+@@ -1,4 \+1,4 @@\s+\(\[\]bool\) \(len=3\) {\s+- \(bool\) false,\s+- \(bool\) false,\s+\+ \(bool\) true,\s+\+ \(bool\) true,\s+\(bool\) false\s+Diff: 0: FAIL: \(\[\]bool=\[(true\s?|false\s?){3}]\) != \(\[\]bool=\[(true\s?|false\s?){3}\]\)`
matchingExp := regexp.MustCompile(unexpectedCallRegex(`TheExampleMethod7([]bool)`, `0: \[\]bool{true, true, false}`, `0: \[\]bool{false, false, false}`, diffRegExp))
assert.Regexp(t, matchingExp, r)
}
}()
m := new(TestExampleImplementation)
m.On("TheExampleMethod7", []bool{true, true, true}).Return(nil).Once()
m.On("TheExampleMethod7", []bool{false, false, false}).Return(nil).Once()
m.TheExampleMethod7([]bool{true, true, true})
// Since the first mocked call has already been used, it now has no repeatability,
// thus the second mock should be shown as the closest match
m.TheExampleMethod7([]bool{true, true, false})
}
func TestClosestCallMismatchedArgumentValueInformation(t *testing.T) {
defer func() {
if r := recover(); r != nil {
matchingExp := regexp.MustCompile(unexpectedCallRegex(`GetTime(int)`, "0: 1", "0: 999", `0: FAIL: \(int=1\) != \(int=999\)`))
matchingExp := regexp.MustCompile(unexpectedCallRegex(`GetTime(int)`, "0: 1", "0: 999", `Diff: 0: FAIL: \(int=1\) != \(int=999\)`))
assert.Regexp(t, matchingExp, r)
}
}()
@ -1487,9 +2181,33 @@ func TestClosestCallMismatchedArgumentValueInformation(t *testing.T) {
_ = m.GetTime(1)
}
func Test_isBetterMatchThanReturnsFalseIfCandidateCallIsNil(t *testing.T) {
assert.False(t, matchCandidate{}.isBetterMatchThan(matchCandidate{}))
}
func Test_isBetterMatchThanReturnsTrueIfOtherCandidateCallIsNil(t *testing.T) {
assert.True(t, matchCandidate{call: &Call{}}.isBetterMatchThan(matchCandidate{}))
}
func Test_isBetterMatchThanReturnsFalseIfDiffCountIsGreaterThanOther(t *testing.T) {
assert.False(t, matchCandidate{call: &Call{}, diffCount: 2}.isBetterMatchThan(matchCandidate{call: &Call{}, diffCount: 1}))
}
func Test_isBetterMatchThanReturnsTrueIfDiffCountIsLessThanOther(t *testing.T) {
assert.True(t, matchCandidate{call: &Call{}, diffCount: 1}.isBetterMatchThan(matchCandidate{call: &Call{}, diffCount: 2}))
}
func Test_isBetterMatchThanReturnsTrueIfRepeatabilityIsGreaterThanOther(t *testing.T) {
assert.True(t, matchCandidate{call: &Call{Repeatability: 1}, diffCount: 1}.isBetterMatchThan(matchCandidate{call: &Call{Repeatability: -1}, diffCount: 1}))
}
func Test_isBetterMatchThanReturnsFalseIfRepeatabilityIsLessThanOrEqualToOther(t *testing.T) {
assert.False(t, matchCandidate{call: &Call{Repeatability: 1}, diffCount: 1}.isBetterMatchThan(matchCandidate{call: &Call{Repeatability: 1}, diffCount: 1}))
}
func unexpectedCallRegex(method, calledArg, expectedArg, diff string) string {
rMethod := regexp.QuoteMeta(method)
return fmt.Sprintf(`\s+mock: Unexpected Method Call\s+-*\s+%s\s+%s\s+The closest call I have is:\s+%s\s+%s\s+Diff: %s`,
return fmt.Sprintf(`\s+mock: Unexpected Method Call\s+-*\s+%s\s+%s\s+The closest call I have is:\s+%s\s+%s\s+%s\nat: \[[^\]]+mock\/mock_test.go`,
rMethod, calledArg, rMethod, expectedArg, diff)
}
@ -1497,3 +2215,40 @@ func unexpectedCallRegex(method, calledArg, expectedArg, diff string) string {
func ConcurrencyTestMethod(m *Mock) {
m.Called()
}
func TestConcurrentArgumentRead(t *testing.T) {
methodUnderTest := func(c caller, u user) {
go u.Use(c)
c.Call()
}
c := &mockCaller{}
defer c.AssertExpectations(t)
u := &mockUser{}
defer u.AssertExpectations(t)
done := make(chan struct{})
c.On("Call").Return().Once()
u.On("Use", c).Return().Once().Run(func(args Arguments) { close(done) })
methodUnderTest(c, u)
<-done // wait until Use is called or assertions will fail
}
type caller interface {
Call()
}
type mockCaller struct{ Mock }
func (m *mockCaller) Call() { m.Called() }
type user interface {
Use(caller)
}
type mockUser struct{ Mock }
func (m *mockUser) Use(c caller) { m.Called(c) }

View File

@ -1,8 +1,9 @@
package testify
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestImports(t *testing.T) {

View File

@ -1,27 +1,30 @@
// Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using require in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// func TestSomething(t *testing.T) {
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// var a string = "Hello"
// var b string = "Hello"
// func TestSomething(t *testing.T) {
//
// require.Equal(t, a, b, "The two words should be the same.")
// var a string = "Hello"
// var b string = "Hello"
//
// }
// require.Equal(t, a, b, "The two words should be the same.")
//
// Assertions
// }
//
// # Assertions
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.
// A consequence of this is that it must be called from the goroutine running
// the test function, not from other goroutines created during the test.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.

View File

@ -13,4 +13,4 @@ func New(t TestingT) *Assertions {
}
}
//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs"

View File

@ -196,6 +196,18 @@ func TestErrorWrapper(t *testing.T) {
}
}
func TestErrorContainsWrapper(t *testing.T) {
require := New(t)
require.ErrorContains(errors.New("some error: another error"), "some error")
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.ErrorContains(errors.New("some error: another error"), "different error")
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestEqualErrorWrapper(t *testing.T) {
require := New(t)
require.EqualError(errors.New("some error"), "some error")
@ -383,3 +395,129 @@ func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
t.Error("Check should fail")
}
}
func TestYAMLEqWrapper_EqualYAMLString(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEqWrapper_EquivalentButNotEqual(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEqWrapper_HashOfArraysAndHashes(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
expected := `
numeric: 1.5
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
`
actual := `
numeric: 1.5
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
`
mockRequire.YAMLEq(expected, actual)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEqWrapper_Array(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEqWrapper_HashesNotEquivalent(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEqWrapper_ActualIsSimpleString(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`{"foo": "bar"}`, "Simple String")
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEqWrapper_ExpectedIsSimpleString(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq("Simple String", `{"foo": "bar", "hello": "world"}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEqWrapper_ExpectedAndActualSimpleString(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq("Simple String", "Simple String")
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
mockT := new(MockT)
mockRequire := New(mockT)
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`)
if !mockT.Failed {
t.Error("Check should fail")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{{.Comment}}
{{ replace .Comment "assert." "require."}}
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
if h, ok := t.(tHelper); ok { h.Helper() }
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
t.FailNow()
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ type TestingT interface {
FailNow()
}
type tHelper interface {
type tHelper = interface {
Helper()
}
@ -26,4 +26,4 @@ type BoolAssertionFunc func(TestingT, bool, ...interface{})
// for table driven tests.
type ErrorAssertionFunc func(TestingT, error, ...interface{})
//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs"

View File

@ -5,6 +5,8 @@ import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// AssertionTesterInterface defines an interface to be used for testing assertion methods
@ -208,6 +210,17 @@ func TestError(t *testing.T) {
}
}
func TestErrorContains(t *testing.T) {
ErrorContains(t, errors.New("some error: another error"), "some error")
mockT := new(MockT)
ErrorContains(mockT, errors.New("some error"), "different error")
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestEqualError(t *testing.T) {
EqualError(t, errors.New("some error"), "some error")
@ -369,6 +382,111 @@ func TestJSONEq_ArraysOfDifferentOrder(t *testing.T) {
}
}
func TestYAMLEq_EqualYAMLString(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEq_EquivalentButNotEqual(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEq_HashOfArraysAndHashes(t *testing.T) {
mockT := new(MockT)
expected := `
numeric: 1.5
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
`
actual := `
numeric: 1.5
hash:
nested: hash
nested_slice: [this, is, nested]
string: "foo"
array:
- foo: bar
- 1
- "string"
- ["nested", "array", 5.5]
`
YAMLEq(mockT, expected, actual)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEq_Array(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`)
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEq_HashAndArrayNotEquivalent(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEq_HashesNotEquivalent(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEq_ActualIsSimpleString(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `{"foo": "bar"}`, "Simple String")
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEq_ExpectedIsSimpleString(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, "Simple String", `{"foo": "bar", "hello": "world"}`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func TestYAMLEq_ExpectedAndActualSimpleString(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, "Simple String", "Simple String")
if mockT.Failed {
t.Error("Check should pass")
}
}
func TestYAMLEq_ArraysOfDifferentOrder(t *testing.T) {
mockT := new(MockT)
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`)
if !mockT.Failed {
t.Error("Check should fail")
}
}
func ExampleComparisonAssertionFunc() {
t := &testing.T{} // provided by test
@ -416,6 +534,7 @@ func TestComparisonAssertionFunc(t *testing.T) {
{"equalValues", t, t, EqualValues},
{"exactly", t, t, Exactly},
{"notEqual", t, nil, NotEqual},
{"NotEqualValues", t, nil, NotEqualValues},
{"notContains", []int{1, 2, 3}, 4, NotContains},
{"subset", []int{1, 2, 3, 4}, []int{2, 3}, Subset},
{"notSubset", []int{1, 2, 3, 4}, []int{0, 3}, NotSubset},
@ -564,3 +683,30 @@ func TestErrorAssertionFunc(t *testing.T) {
})
}
}
func TestEventuallyWithTFalse(t *testing.T) {
mockT := new(MockT)
condition := func(collect *assert.CollectT) {
True(collect, false)
}
EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)
True(t, mockT.Failed, "Check should fail")
}
func TestEventuallyWithTTrue(t *testing.T) {
mockT := new(MockT)
counter := 0
condition := func(collect *assert.CollectT) {
defer func() {
counter += 1
}()
True(collect, counter == 1)
}
EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)
False(t, mockT.Failed, "Check should pass")
Equal(t, 2, counter, "Condition is expected to be called 2 times")
}

View File

@ -5,6 +5,8 @@
// or individual tests (depending on which interface(s) you
// implement).
//
// The suite package does not support parallel tests. See [issue 934].
//
// A testing suite is usually built by first extending the built-in
// suite functionality from suite.Suite in testify. Alternatively,
// you could reproduce that logic on your own if you wanted (you
@ -29,37 +31,40 @@
// Suite object has assertion methods.
//
// A crude example:
// // Basic imports
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/suite"
// )
//
// // Define the suite, and absorb the built-in basic suite
// // functionality from testify - including a T() method which
// // returns the current testing context
// type ExampleTestSuite struct {
// suite.Suite
// VariableThatShouldStartAtFive int
// }
// // Basic imports
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/suite"
// )
//
// // Make sure that VariableThatShouldStartAtFive is set to five
// // before each test
// func (suite *ExampleTestSuite) SetupTest() {
// suite.VariableThatShouldStartAtFive = 5
// }
// // Define the suite, and absorb the built-in basic suite
// // functionality from testify - including a T() method which
// // returns the current testing context
// type ExampleTestSuite struct {
// suite.Suite
// VariableThatShouldStartAtFive int
// }
//
// // All methods that begin with "Test" are run as tests within a
// // suite.
// func (suite *ExampleTestSuite) TestExample() {
// assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
// suite.Equal(5, suite.VariableThatShouldStartAtFive)
// }
// // Make sure that VariableThatShouldStartAtFive is set to five
// // before each test
// func (suite *ExampleTestSuite) SetupTest() {
// suite.VariableThatShouldStartAtFive = 5
// }
//
// // In order for 'go test' to run this suite, we need to create
// // a normal test function and pass our suite to suite.Run
// func TestExampleTestSuite(t *testing.T) {
// suite.Run(t, new(ExampleTestSuite))
// }
// // All methods that begin with "Test" are run as tests within a
// // suite.
// func (suite *ExampleTestSuite) TestExample() {
// assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
// suite.Equal(5, suite.VariableThatShouldStartAtFive)
// }
//
// // In order for 'go test' to run this suite, we need to create
// // a normal test function and pass our suite to suite.Run
// func TestExampleTestSuite(t *testing.T) {
// suite.Run(t, new(ExampleTestSuite))
// }
//
// [issue 934]: https://github.com/stretchr/testify/issues/934
package suite

View File

@ -7,6 +7,7 @@ import "testing"
type TestingSuite interface {
T() *testing.T
SetT(*testing.T)
SetS(suite TestingSuite)
}
// SetupAllSuite has a SetupSuite method, which will run before the
@ -44,3 +45,22 @@ type BeforeTest interface {
type AfterTest interface {
AfterTest(suiteName, testName string)
}
// WithStats implements HandleStats, a function that will be executed
// when a test suite is finished. The stats contain information about
// the execution of that suite and its tests.
type WithStats interface {
HandleStats(suiteName string, stats *SuiteInformation)
}
// SetupSubTest has a SetupSubTest method, which will run before each
// subtest in the suite.
type SetupSubTest interface {
SetupSubTest()
}
// TearDownSubTest has a TearDownSubTest method, which will run after
// each subtest in the suite have been run.
type TearDownSubTest interface {
TearDownSubTest()
}

46
suite/stats.go Normal file
View File

@ -0,0 +1,46 @@
package suite
import "time"
// SuiteInformation stats stores stats for the whole suite execution.
type SuiteInformation struct {
Start, End time.Time
TestStats map[string]*TestInformation
}
// TestInformation stores information about the execution of each test.
type TestInformation struct {
TestName string
Start, End time.Time
Passed bool
}
func newSuiteInformation() *SuiteInformation {
testStats := make(map[string]*TestInformation)
return &SuiteInformation{
TestStats: testStats,
}
}
func (s SuiteInformation) start(testName string) {
s.TestStats[testName] = &TestInformation{
TestName: testName,
Start: time.Now(),
}
}
func (s SuiteInformation) end(testName string, passed bool) {
s.TestStats[testName].End = time.Now()
s.TestStats[testName].Passed = passed
}
func (s SuiteInformation) Passed() bool {
for _, stats := range s.TestStats {
if !stats.Passed {
return false
}
}
return true
}

29
suite/stats_test.go Normal file
View File

@ -0,0 +1,29 @@
package suite
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPassedReturnsTrueWhenAllTestsPass(t *testing.T) {
sinfo := newSuiteInformation()
sinfo.TestStats = map[string]*TestInformation{
"Test1": {TestName: "Test1", Passed: true},
"Test2": {TestName: "Test2", Passed: true},
"Test3": {TestName: "Test3", Passed: true},
}
assert.True(t, sinfo.Passed())
}
func TestPassedReturnsFalseWhenSomeTestFails(t *testing.T) {
sinfo := newSuiteInformation()
sinfo.TestStats = map[string]*TestInformation{
"Test1": {TestName: "Test1", Passed: true},
"Test2": {TestName: "Test2", Passed: false},
"Test3": {TestName: "Test3", Passed: true},
}
assert.False(t, sinfo.Passed())
}

View File

@ -6,7 +6,10 @@ import (
"os"
"reflect"
"regexp"
"runtime/debug"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -19,26 +22,43 @@ var matchMethod = flag.String("testify.m", "", "regular expression to select tes
// retrieving the current *testing.T context.
type Suite struct {
*assert.Assertions
mu sync.RWMutex
require *require.Assertions
t *testing.T
// Parent suite to have access to the implemented methods of parent struct
s TestingSuite
}
// T retrieves the current *testing.T context.
func (suite *Suite) T() *testing.T {
suite.mu.RLock()
defer suite.mu.RUnlock()
return suite.t
}
// SetT sets the current *testing.T context.
func (suite *Suite) SetT(t *testing.T) {
suite.mu.Lock()
defer suite.mu.Unlock()
suite.t = t
suite.Assertions = assert.New(t)
suite.require = require.New(t)
}
// SetS needs to set the current test suite as parent
// to get access to the parent methods
func (suite *Suite) SetS(s TestingSuite) {
suite.s = s
}
// Require returns a require context for suite.
func (suite *Suite) Require() *require.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.require == nil {
suite.require = require.New(suite.T())
panic("'Require' must not be called before 'Run' or 'SetT'")
}
return suite.require
}
@ -49,16 +69,24 @@ func (suite *Suite) Require() *require.Assertions {
// assert.Assertions with require.Assertions), this method is provided so you
// can call `suite.Assert().NoError()`.
func (suite *Suite) Assert() *assert.Assertions {
suite.mu.Lock()
defer suite.mu.Unlock()
if suite.Assertions == nil {
suite.Assertions = assert.New(suite.T())
panic("'Assert' must not be called before 'Run' or 'SetT'")
}
return suite.Assertions
}
func failOnPanic(t *testing.T) {
func recoverAndFailOnPanic(t *testing.T) {
t.Helper()
r := recover()
failOnPanic(t, r)
}
func failOnPanic(t *testing.T, r interface{}) {
t.Helper()
if r != nil {
t.Errorf("test panicked: %v", r)
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
t.FailNow()
}
}
@ -69,9 +97,21 @@ func failOnPanic(t *testing.T) {
// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
func (suite *Suite) Run(name string, subtest func()) bool {
oldT := suite.T()
defer suite.SetT(oldT)
return oldT.Run(name, func(t *testing.T) {
suite.SetT(t)
defer suite.SetT(oldT)
defer recoverAndFailOnPanic(t)
if setupSubTest, ok := suite.s.(SetupSubTest); ok {
setupSubTest.SetupSubTest()
}
if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
defer tearDownSubTest.TearDownSubTest()
}
subtest()
})
}
@ -79,60 +119,122 @@ func (suite *Suite) Run(name string, subtest func()) bool {
// Run takes a testing suite and runs all of the tests attached
// to it.
func Run(t *testing.T, suite TestingSuite) {
defer recoverAndFailOnPanic(t)
suite.SetT(t)
defer failOnPanic(t)
suite.SetS(suite)
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
setupAllSuite.SetupSuite()
var suiteSetupDone bool
var stats *SuiteInformation
if _, ok := suite.(WithStats); ok {
stats = newSuiteInformation()
}
defer func() {
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
tearDownAllSuite.TearDownSuite()
}
}()
methodFinder := reflect.TypeOf(suite)
tests := []testing.InternalTest{}
for index := 0; index < methodFinder.NumMethod(); index++ {
method := methodFinder.Method(index)
methodFinder := reflect.TypeOf(suite)
suiteName := methodFinder.Elem().Name()
for i := 0; i < methodFinder.NumMethod(); i++ {
method := methodFinder.Method(i)
ok, err := methodFilter(method.Name)
if err != nil {
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
os.Exit(1)
}
if ok {
test := testing.InternalTest{
Name: method.Name,
F: func(t *testing.T) {
parentT := suite.T()
suite.SetT(t)
defer failOnPanic(t)
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
setupTestSuite.SetupTest()
}
if beforeTestSuite, ok := suite.(BeforeTest); ok {
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
}
defer func() {
if afterTestSuite, ok := suite.(AfterTest); ok {
afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name)
}
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
tearDownTestSuite.TearDownTest()
}
suite.SetT(parentT)
}()
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
},
}
tests = append(tests, test)
if !ok {
continue
}
if !suiteSetupDone {
if stats != nil {
stats.Start = time.Now()
}
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
setupAllSuite.SetupSuite()
}
suiteSetupDone = true
}
test := testing.InternalTest{
Name: method.Name,
F: func(t *testing.T) {
parentT := suite.T()
suite.SetT(t)
defer recoverAndFailOnPanic(t)
defer func() {
t.Helper()
r := recover()
if stats != nil {
passed := !t.Failed() && r == nil
stats.end(method.Name, passed)
}
if afterTestSuite, ok := suite.(AfterTest); ok {
afterTestSuite.AfterTest(suiteName, method.Name)
}
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
tearDownTestSuite.TearDownTest()
}
suite.SetT(parentT)
failOnPanic(t, r)
}()
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
setupTestSuite.SetupTest()
}
if beforeTestSuite, ok := suite.(BeforeTest); ok {
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
}
if stats != nil {
stats.start(method.Name)
}
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
},
}
tests = append(tests, test)
}
if suiteSetupDone {
defer func() {
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
tearDownAllSuite.TearDownSuite()
}
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
stats.End = time.Now()
suiteWithStats.HandleStats(suiteName, stats)
}
}()
}
runTests(t, tests)
}
// Filtering method according to set regular expression
// specified command-line argument -m
func methodFilter(name string) (bool, error) {
if ok, _ := regexp.MatchString("^Test", name); !ok {
return false, nil
}
return regexp.MatchString(*matchMethod, name)
}
func runTests(t testing.TB, tests []testing.InternalTest) {
if len(tests) == 0 {
t.Log("warning: no tests to run")
return
}
r, ok := t.(runner)
if !ok { // backwards compatibility with Go 1.6 and below
if !testing.RunTests(allTestsFilter, tests) {
@ -146,15 +248,6 @@ func runTests(t testing.TB, tests []testing.InternalTest) {
}
}
// Filtering method according to set regular expression
// specified command-line argument -m
func methodFilter(name string) (bool, error) {
if ok, _ := regexp.MatchString("^Test", name); !ok {
return false, nil
}
return regexp.MatchString(*matchMethod, name)
}
type runner interface {
Run(name string, f func(t *testing.T)) bool
}

View File

@ -1,9 +1,14 @@
package suite
import (
"bytes"
"errors"
"io/ioutil"
"flag"
"io"
"math/rand"
"os"
"os/exec"
"strings"
"testing"
"time"
@ -16,20 +21,20 @@ import (
type SuiteRequireTwice struct{ Suite }
// TestSuiteRequireTwice checks for regressions of issue #149 where
// suite.requirements was not initialised in suite.SetT()
// suite.requirements was not initialized in suite.SetT()
// A regression would result on these tests panicking rather than failing.
func TestSuiteRequireTwice(t *testing.T) {
ok := testing.RunTests(
allTestsFilter,
[]testing.InternalTest{{
Name: "TestSuiteRequireTwice",
Name: t.Name() + "/SuiteRequireTwice",
F: func(t *testing.T) {
suite := new(SuiteRequireTwice)
Run(t, suite)
},
}},
)
assert.Equal(t, false, ok)
assert.False(t, ok)
}
func (s *SuiteRequireTwice) TestRequireOne() {
@ -99,31 +104,31 @@ func TestSuiteRecoverPanic(t *testing.T) {
ok := true
panickingTests := []testing.InternalTest{
{
Name: "TestPanicInSetupSuite",
Name: t.Name() + "/InSetupSuite",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInSetupSuite: true}) },
},
{
Name: "TestPanicInSetupTest",
Name: t.Name() + "/InSetupTest",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInSetupTest: true}) },
},
{
Name: "TestPanicInBeforeTest",
Name: t.Name() + "InBeforeTest",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInBeforeTest: true}) },
},
{
Name: "TestPanicInTest",
Name: t.Name() + "/InTest",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTest: true}) },
},
{
Name: "TestPanicInAfterTest",
Name: t.Name() + "/InAfterTest",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInAfterTest: true}) },
},
{
Name: "TestPanicInTearDownTest",
Name: t.Name() + "/InTearDownTest",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTearDownTest: true}) },
},
{
Name: "TestPanicInTearDownSuite",
Name: t.Name() + "/InTearDownSuite",
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTearDownSuite: true}) },
},
}
@ -146,14 +151,19 @@ type SuiteTester struct {
Suite
// Keep counts of how many times each method is run.
SetupSuiteRunCount int
TearDownSuiteRunCount int
SetupTestRunCount int
TearDownTestRunCount int
TestOneRunCount int
TestTwoRunCount int
TestSubtestRunCount int
NonTestMethodRunCount int
SetupSuiteRunCount int
TearDownSuiteRunCount int
SetupTestRunCount int
TearDownTestRunCount int
TestOneRunCount int
TestTwoRunCount int
TestSubtestRunCount int
NonTestMethodRunCount int
SetupSubTestRunCount int
TearDownSubTestRunCount int
SetupSubTestNames []string
TearDownSubTestNames []string
SuiteNameBefore []string
TestNameBefore []string
@ -165,15 +175,6 @@ type SuiteTester struct {
TimeAfter []time.Time
}
type SuiteSkipTester struct {
// Include our basic suite logic.
Suite
// Keep counts of how many times each method is run.
SetupSuiteRunCount int
TearDownSuiteRunCount int
}
// The SetupSuite method will be run by testify once, at the very
// start of the testing suite, before any tests are run.
func (suite *SuiteTester) SetupSuite() {
@ -192,21 +193,12 @@ func (suite *SuiteTester) AfterTest(suiteName, testName string) {
suite.TimeAfter = append(suite.TimeAfter, time.Now())
}
func (suite *SuiteSkipTester) SetupSuite() {
suite.SetupSuiteRunCount++
suite.T().Skip()
}
// The TearDownSuite method will be run by testify once, at the very
// end of the testing suite, after all tests have been run.
func (suite *SuiteTester) TearDownSuite() {
suite.TearDownSuiteRunCount++
}
func (suite *SuiteSkipTester) TearDownSuite() {
suite.TearDownSuiteRunCount++
}
// The SetupTest method will be run before every test in the suite.
func (suite *SuiteTester) SetupTest() {
suite.SetupTestRunCount++
@ -268,6 +260,40 @@ func (suite *SuiteTester) TestSubtest() {
}
}
func (suite *SuiteTester) TearDownSubTest() {
suite.TearDownSubTestNames = append(suite.TearDownSubTestNames, suite.T().Name())
suite.TearDownSubTestRunCount++
}
func (suite *SuiteTester) SetupSubTest() {
suite.SetupSubTestNames = append(suite.SetupSubTestNames, suite.T().Name())
suite.SetupSubTestRunCount++
}
type SuiteSkipTester struct {
// Include our basic suite logic.
Suite
// Keep counts of how many times each method is run.
SetupSuiteRunCount int
TearDownSuiteRunCount int
}
func (suite *SuiteSkipTester) SetupSuite() {
suite.SetupSuiteRunCount++
suite.T().Skip()
}
func (suite *SuiteSkipTester) TestNothing() {
// SetupSuite is only called when at least one test satisfies
// test filter. For this suite to be set up (and then tore down)
// it is necessary to add at least one test method.
}
func (suite *SuiteSkipTester) TearDownSuite() {
suite.TearDownSuiteRunCount++
}
// TestRunSuite will be run by the 'go test' command, so within it, we
// can run our suite using the Run(*testing.T, TestingSuite) function.
func TestRunSuite(t *testing.T) {
@ -280,13 +306,13 @@ func TestRunSuite(t *testing.T) {
// The suite was only run once, so the SetupSuite and TearDownSuite
// methods should have each been run only once.
assert.Equal(t, suiteTester.SetupSuiteRunCount, 1)
assert.Equal(t, suiteTester.TearDownSuiteRunCount, 1)
assert.Equal(t, 1, suiteTester.SetupSuiteRunCount)
assert.Equal(t, 1, suiteTester.TearDownSuiteRunCount)
assert.Equal(t, len(suiteTester.SuiteNameAfter), 4)
assert.Equal(t, len(suiteTester.SuiteNameBefore), 4)
assert.Equal(t, len(suiteTester.TestNameAfter), 4)
assert.Equal(t, len(suiteTester.TestNameBefore), 4)
assert.Len(t, suiteTester.SuiteNameAfter, 4)
assert.Len(t, suiteTester.SuiteNameBefore, 4)
assert.Len(t, suiteTester.TestNameAfter, 4)
assert.Len(t, suiteTester.TestNameBefore, 4)
assert.Contains(t, suiteTester.TestNameAfter, "TestOne")
assert.Contains(t, suiteTester.TestNameAfter, "TestTwo")
@ -298,6 +324,12 @@ func TestRunSuite(t *testing.T) {
assert.Contains(t, suiteTester.TestNameBefore, "TestSkip")
assert.Contains(t, suiteTester.TestNameBefore, "TestSubtest")
assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuite/TestSubtest/first")
assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuite/TestSubtest/second")
assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuite/TestSubtest/first")
assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuite/TestSubtest/second")
for _, suiteName := range suiteTester.SuiteNameAfter {
assert.Equal(t, "SuiteTester", suiteName)
}
@ -317,17 +349,20 @@ func TestRunSuite(t *testing.T) {
// There are four test methods (TestOne, TestTwo, TestSkip, and TestSubtest), so
// the SetupTest and TearDownTest methods (which should be run once for
// each test) should have been run four times.
assert.Equal(t, suiteTester.SetupTestRunCount, 4)
assert.Equal(t, suiteTester.TearDownTestRunCount, 4)
assert.Equal(t, 4, suiteTester.SetupTestRunCount)
assert.Equal(t, 4, suiteTester.TearDownTestRunCount)
// Each test should have been run once.
assert.Equal(t, suiteTester.TestOneRunCount, 1)
assert.Equal(t, suiteTester.TestTwoRunCount, 1)
assert.Equal(t, suiteTester.TestSubtestRunCount, 1)
assert.Equal(t, 1, suiteTester.TestOneRunCount)
assert.Equal(t, 1, suiteTester.TestTwoRunCount)
assert.Equal(t, 1, suiteTester.TestSubtestRunCount)
assert.Equal(t, 2, suiteTester.TearDownSubTestRunCount)
assert.Equal(t, 2, suiteTester.SetupSubTestRunCount)
// Methods that don't match the test method identifier shouldn't
// have been run at all.
assert.Equal(t, suiteTester.NonTestMethodRunCount, 0)
assert.Equal(t, 0, suiteTester.NonTestMethodRunCount)
suiteSkipTester := new(SuiteSkipTester)
Run(t, suiteSkipTester)
@ -335,11 +370,38 @@ func TestRunSuite(t *testing.T) {
// The suite was only run once, so the SetupSuite and TearDownSuite
// methods should have each been run only once, even though SetupSuite
// called Skip()
assert.Equal(t, suiteSkipTester.SetupSuiteRunCount, 1)
assert.Equal(t, suiteSkipTester.TearDownSuiteRunCount, 1)
assert.Equal(t, 1, suiteSkipTester.SetupSuiteRunCount)
assert.Equal(t, 1, suiteSkipTester.TearDownSuiteRunCount)
}
// This suite has no Test... methods. It's setup and teardown must be skipped.
type SuiteSetupSkipTester struct {
Suite
setUp bool
toreDown bool
}
func (s *SuiteSetupSkipTester) SetupSuite() {
s.setUp = true
}
func (s *SuiteSetupSkipTester) NonTestMethod() {
}
func (s *SuiteSetupSkipTester) TearDownSuite() {
s.toreDown = true
}
func TestSkippingSuiteSetup(t *testing.T) {
suiteTester := new(SuiteSetupSkipTester)
Run(t, suiteTester)
assert.False(t, suiteTester.setUp)
assert.False(t, suiteTester.toreDown)
}
func TestSuiteGetters(t *testing.T) {
suite := new(SuiteTester)
suite.SetT(t)
@ -378,7 +440,7 @@ func (sc *StdoutCapture) StopCapture() (string, error) {
}
os.Stdout.Close()
os.Stdout = sc.oldStdout
bytes, err := ioutil.ReadAll(sc.readPipe)
bytes, err := io.ReadAll(sc.readPipe)
if err != nil {
return "", err
}
@ -389,7 +451,7 @@ func TestSuiteLogging(t *testing.T) {
suiteLoggingTester := new(SuiteLoggingTester)
capture := StdoutCapture{}
internalTest := testing.InternalTest{
Name: "SomeTest",
Name: t.Name() + "/SuiteLoggingTester",
F: func(subT *testing.T) {
Run(subT, suiteLoggingTester)
},
@ -410,3 +472,277 @@ func TestSuiteLogging(t *testing.T) {
assert.NotContains(t, output, "TESTLOGPASS")
}
}
type CallOrderSuite struct {
Suite
callOrder []string
}
func (s *CallOrderSuite) call(method string) {
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
s.callOrder = append(s.callOrder, method)
}
func TestSuiteCallOrder(t *testing.T) {
Run(t, new(CallOrderSuite))
}
func (s *CallOrderSuite) SetupSuite() {
s.call("SetupSuite")
}
func (s *CallOrderSuite) TearDownSuite() {
s.call("TearDownSuite")
assert.Equal(s.T(), "SetupSuite;SetupTest;Test A;SetupSubTest;SubTest A1;TearDownSubTest;SetupSubTest;SubTest A2;TearDownSubTest;TearDownTest;SetupTest;Test B;SetupSubTest;SubTest B1;TearDownSubTest;SetupSubTest;SubTest B2;TearDownSubTest;TearDownTest;TearDownSuite", strings.Join(s.callOrder, ";"))
}
func (s *CallOrderSuite) SetupTest() {
s.call("SetupTest")
}
func (s *CallOrderSuite) TearDownTest() {
s.call("TearDownTest")
}
func (s *CallOrderSuite) SetupSubTest() {
s.call("SetupSubTest")
}
func (s *CallOrderSuite) TearDownSubTest() {
s.call("TearDownSubTest")
}
func (s *CallOrderSuite) Test_A() {
s.call("Test A")
s.Run("SubTest A1", func() {
s.call("SubTest A1")
})
s.Run("SubTest A2", func() {
s.call("SubTest A2")
})
}
func (s *CallOrderSuite) Test_B() {
s.call("Test B")
s.Run("SubTest B1", func() {
s.call("SubTest B1")
})
s.Run("SubTest B2", func() {
s.call("SubTest B2")
})
}
type suiteWithStats struct {
Suite
wasCalled bool
stats *SuiteInformation
}
func (s *suiteWithStats) HandleStats(suiteName string, stats *SuiteInformation) {
s.wasCalled = true
s.stats = stats
}
func (s *suiteWithStats) TestSomething() {
s.Equal(1, 1)
}
func (s *suiteWithStats) TestPanic() {
panic("oops")
}
func TestSuiteWithStats(t *testing.T) {
suiteWithStats := new(suiteWithStats)
suiteSuccess := testing.RunTests(allTestsFilter, []testing.InternalTest{
{
Name: t.Name() + "/suiteWithStats",
F: func(t *testing.T) {
Run(t, suiteWithStats)
},
},
})
require.False(t, suiteSuccess, "suiteWithStats should report test failure because of panic in TestPanic")
assert.True(t, suiteWithStats.wasCalled)
assert.NotZero(t, suiteWithStats.stats.Start)
assert.NotZero(t, suiteWithStats.stats.End)
assert.False(t, suiteWithStats.stats.Passed())
testStats := suiteWithStats.stats.TestStats
assert.NotZero(t, testStats["TestSomething"].Start)
assert.NotZero(t, testStats["TestSomething"].End)
assert.True(t, testStats["TestSomething"].Passed)
assert.NotZero(t, testStats["TestPanic"].Start)
assert.NotZero(t, testStats["TestPanic"].End)
assert.False(t, testStats["TestPanic"].Passed)
}
// FailfastSuite will test the behavior when running with the failfast flag
// It logs calls in the callOrder slice which we then use to assert the correct calls were made
type FailfastSuite struct {
Suite
callOrder []string
}
func (s *FailfastSuite) call(method string) {
s.callOrder = append(s.callOrder, method)
}
func TestFailfastSuite(t *testing.T) {
// This test suite is run twice. Once normally and once with the -failfast flag by TestFailfastSuiteFailFastOn
// If you need to debug it run this test directly with the failfast flag set on/off as you need
failFast := flag.Lookup("test.failfast").Value.(flag.Getter).Get().(bool)
s := new(FailfastSuite)
ok := testing.RunTests(
allTestsFilter,
[]testing.InternalTest{{
Name: t.Name() + "/FailfastSuite",
F: func(t *testing.T) {
Run(t, s)
},
}},
)
assert.False(t, ok)
var expect []string
if failFast {
// Test A Fails and because we are running with failfast Test B never runs and we proceed straight to TearDownSuite
expect = []string{"SetupSuite", "SetupTest", "Test A Fails", "TearDownTest", "TearDownSuite"}
} else {
// Test A Fails and because we are running without failfast we continue and run Test B and then proceed to TearDownSuite
expect = []string{"SetupSuite", "SetupTest", "Test A Fails", "TearDownTest", "SetupTest", "Test B Passes", "TearDownTest", "TearDownSuite"}
}
callOrderAssert(t, expect, s.callOrder)
}
type tHelper interface {
Helper()
}
// callOrderAssert is a help with confirms that asserts that expect
// matches one or more times in callOrder. This makes it compatible
// with go test flag -count=X where X > 1.
func callOrderAssert(t *testing.T, expect, callOrder []string) {
var ti interface{} = t
if h, ok := ti.(tHelper); ok {
h.Helper()
}
callCount := len(callOrder)
expectCount := len(expect)
if callCount > expectCount && callCount%expectCount == 0 {
// Command line flag -count=X where X > 1.
for len(callOrder) >= expectCount {
assert.Equal(t, expect, callOrder[:expectCount])
callOrder = callOrder[expectCount:]
}
return
}
assert.Equal(t, expect, callOrder)
}
func TestFailfastSuiteFailFastOn(t *testing.T) {
// To test this with failfast on (and isolated from other intended test failures in our test suite) we launch it in its own process
cmd := exec.Command("go", "test", "-v", "-race", "-run", "TestFailfastSuite", "-failfast")
var out bytes.Buffer
cmd.Stdout = &out
t.Log("Running go test -v -race -run TestFailfastSuite -failfast")
err := cmd.Run()
t.Log(out.String())
if err != nil {
t.Log(err)
t.Fail()
}
}
func (s *FailfastSuite) SetupSuite() {
s.call("SetupSuite")
}
func (s *FailfastSuite) TearDownSuite() {
s.call("TearDownSuite")
}
func (s *FailfastSuite) SetupTest() {
s.call("SetupTest")
}
func (s *FailfastSuite) TearDownTest() {
s.call("TearDownTest")
}
func (s *FailfastSuite) Test_A_Fails() {
s.call("Test A Fails")
s.T().Error("Test A meant to fail")
}
func (s *FailfastSuite) Test_B_Passes() {
s.call("Test B Passes")
s.Require().True(true)
}
type subtestPanicSuite struct {
Suite
inTearDownSuite bool
inTearDownTest bool
inTearDownSubTest bool
}
func (s *subtestPanicSuite) TearDownSuite() {
s.inTearDownSuite = true
}
func (s *subtestPanicSuite) TearDownTest() {
s.inTearDownTest = true
}
func (s *subtestPanicSuite) TearDownSubTest() {
s.inTearDownSubTest = true
}
func (s *subtestPanicSuite) TestSubtestPanic() {
ok := s.Run("subtest", func() {
panic("panic")
})
s.False(ok, "subtest failure is expected")
}
func TestSubtestPanic(t *testing.T) {
suite := new(subtestPanicSuite)
ok := testing.RunTests(
allTestsFilter,
[]testing.InternalTest{{
Name: t.Name() + "/subtestPanicSuite",
F: func(t *testing.T) {
Run(t, suite)
},
}},
)
assert.False(t, ok, "TestSubtestPanic/subtest should make the testsuite fail")
assert.True(t, suite.inTearDownSubTest)
assert.True(t, suite.inTearDownTest)
assert.True(t, suite.inTearDownSuite)
}
type unInitializedSuite struct {
Suite
}
// TestUnInitializedSuites asserts the behavior of the suite methods when the
// suite is not initialized
func TestUnInitializedSuites(t *testing.T) {
t.Run("should panic on Require", func(t *testing.T) {
suite := new(unInitializedSuite)
assert.Panics(t, func() {
suite.Require().True(true)
})
})
t.Run("should panic on Assert", func(t *testing.T) {
suite := new(unInitializedSuite)
assert.Panics(t, func() {
suite.Assert().True(true)
})
})
}

View File

@ -1,15 +0,0 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,152 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build !js,!appengine,!safe,!disableunsafe
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

View File

@ -1,38 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@ -1,341 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@ -1,211 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound == true:
d.w.Write(nilAngleBytes)
case cycleFound == true:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@ -1,419 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound == true:
f.fs.Write(nilAngleBytes)
case cycleFound == true:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,772 +0,0 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
ws := func(s string) error {
_, err := buf.WriteString(s)
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := ws(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := ws("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
ws := func(s string) {
_, err := buf.WriteString(s)
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
}
first, last := g[0], g[len(g)-1]
ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

View File

@ -1,22 +0,0 @@
The MIT License
Copyright (c) 2014 Stretchr, Inc.
Copyright (c) 2017-2018 objx contributors
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.

View File

@ -1,171 +0,0 @@
package objx
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// arrayAccesRegexString is the regex used to extract the array number
// from the access path
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
// arrayAccesRegex is the compiled arrayAccesRegexString
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
// Get gets the value using the specified selector and
// returns it inside a new Obj object.
//
// If it cannot find the value, Get will return a nil
// value inside an instance of Obj.
//
// Get can only operate directly on map[string]interface{} and []interface.
//
// Example
//
// To access the title of the third chapter of the second book, do:
//
// o.Get("books[1].chapters[2].title")
func (m Map) Get(selector string) *Value {
rawObj := access(m, selector, nil, false, false)
return &Value{data: rawObj}
}
// Set sets the value using the specified selector and
// returns the object on which Set was called.
//
// Set can only operate directly on map[string]interface{} and []interface
//
// Example
//
// To set the title of the third chapter of the second book, do:
//
// o.Set("books[1].chapters[2].title","Time to Go")
func (m Map) Set(selector string, value interface{}) Map {
access(m, selector, value, true, false)
return m
}
// access accesses the object using the selector and performs the
// appropriate action.
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
switch selector.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if array, ok := current.([]interface{}); ok {
index := intFromInterface(selector)
if index >= len(array) {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
return nil
}
return array[index]
}
return nil
case string:
selStr := selector.(string)
selSegs := strings.SplitN(selStr, PathSeparator, 2)
thisSel := selSegs[0]
index := -1
var err error
if strings.Contains(thisSel, "[") {
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
if len(arrayMatches) > 0 {
// Get the key into the map
thisSel = arrayMatches[1]
// Get the index into the array at the key
index, err = strconv.Atoi(arrayMatches[2])
if err != nil {
// This should never happen. If it does, something has gone
// seriously wrong. Panic.
panic("objx: Array index is not an integer. Must use array[int].")
}
}
}
if curMap, ok := current.(Map); ok {
current = map[string]interface{}(curMap)
}
// get the object in question
switch current.(type) {
case map[string]interface{}:
curMSI := current.(map[string]interface{})
if len(selSegs) <= 1 && isSet {
curMSI[thisSel] = value
return nil
}
current = curMSI[thisSel]
default:
current = nil
}
if current == nil && panics {
panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
}
// do we need to access the item of an array?
if index > -1 {
if array, ok := current.([]interface{}); ok {
if index < len(array) {
current = array[index]
} else {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
current = nil
}
}
}
if len(selSegs) > 1 {
current = access(current, selSegs[1], value, isSet, panics)
}
}
return current
}
// intFromInterface converts an interface object to the largest
// representation of an unsigned integer using a type switch and
// assertions
func intFromInterface(selector interface{}) int {
var value int
switch selector.(type) {
case int:
value = selector.(int)
case int8:
value = int(selector.(int8))
case int16:
value = int(selector.(int16))
case int32:
value = int(selector.(int32))
case int64:
value = int(selector.(int64))
case uint:
value = int(selector.(uint))
case uint8:
value = int(selector.(uint8))
case uint16:
value = int(selector.(uint16))
case uint32:
value = int(selector.(uint32))
case uint64:
value = int(selector.(uint64))
default:
panic("objx: array access argument is not an integer type (this should never happen)")
}
return value
}

View File

@ -1,13 +0,0 @@
package objx
const (
// PathSeparator is the character used to separate the elements
// of the keypath.
//
// For example, `location.address.city`
PathSeparator string = "."
// SignatureSeparator is the character that is used to
// separate the Base64 string from the security signature.
SignatureSeparator = "_"
)

View File

@ -1,108 +0,0 @@
package objx
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// JSON converts the contained object to a JSON string
// representation
func (m Map) JSON() (string, error) {
result, err := json.Marshal(m)
if err != nil {
err = errors.New("objx: JSON encode failed with: " + err.Error())
}
return string(result), err
}
// MustJSON converts the contained object to a JSON string
// representation and panics if there is an error
func (m Map) MustJSON() string {
result, err := m.JSON()
if err != nil {
panic(err.Error())
}
return result
}
// Base64 converts the contained object to a Base64 string
// representation of the JSON string representation
func (m Map) Base64() (string, error) {
var buf bytes.Buffer
jsonData, err := m.JSON()
if err != nil {
return "", err
}
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
_, err = encoder.Write([]byte(jsonData))
if err != nil {
return "", err
}
_ = encoder.Close()
return buf.String(), nil
}
// MustBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and panics
// if there is an error
func (m Map) MustBase64() string {
result, err := m.Base64()
if err != nil {
panic(err.Error())
}
return result
}
// SignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key.
func (m Map) SignedBase64(key string) (string, error) {
base64, err := m.Base64()
if err != nil {
return "", err
}
sig := HashWithKey(base64, key)
return base64 + SignatureSeparator + sig, nil
}
// MustSignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key and panics if there is an error
func (m Map) MustSignedBase64(key string) string {
result, err := m.SignedBase64(key)
if err != nil {
panic(err.Error())
}
return result
}
/*
URL Query
------------------------------------------------
*/
// URLValues creates a url.Values object from an Obj. This
// function requires that the wrapped object be a map[string]interface{}
func (m Map) URLValues() url.Values {
vals := make(url.Values)
for k, v := range m {
//TODO: can this be done without sprintf?
vals.Set(k, fmt.Sprintf("%v", v))
}
return vals
}
// URLQuery gets an encoded URL query representing the given
// Obj. This function requires that the wrapped object be a
// map[string]interface{}
func (m Map) URLQuery() (string, error) {
return m.URLValues().Encode(), nil
}

View File

@ -1,66 +0,0 @@
/*
Objx - Go package for dealing with maps, slices, JSON and other data.
Overview
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
a powerful `Get` method (among others) that allows you to easily and quickly get
access to data within the map, without having to worry too much about type assertions,
missing data, default values etc.
Pattern
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy.
Call one of the `objx.` functions to create your `objx.Map` to get going:
m, err := objx.FromJSON(json)
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
the rest will be optimistic and try to figure things out without panicking.
Use `Get` to access the value you're interested in. You can use dot and array
notation too:
m.Get("places[0].latlng")
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
if m.Get("code").IsStr() { // Your code... }
Or you can just assume the type, and use one of the strong type methods to extract the real value:
m.Get("code").Int()
If there's no value there (or if it's the wrong type) then a default value will be returned,
or you can be explicit about the default value.
Get("code").Int(-1)
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating,
manipulating and selecting that data. You can find out more by exploring the index below.
Reading data
A simple example of how to use Objx:
// Use MustFromJSON to make an objx.Map from some JSON
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
// Get the details
name := m.Get("name").Str()
age := m.Get("age").Int()
// Get their nickname (or use their name if they don't have one)
nickname := m.Get("nickname").Str(name)
Ranging
Since `objx.Map` is a `map[string]interface{}` you can treat it as such.
For example, to `range` the data, do what you would expect:
m := objx.MustFromJSON(json)
for key, value := range m {
// Your code...
}
*/
package objx

View File

@ -1,193 +0,0 @@
package objx
import (
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"net/url"
"strings"
)
// MSIConvertable is an interface that defines methods for converting your
// custom types to a map[string]interface{} representation.
type MSIConvertable interface {
// MSI gets a map[string]interface{} (msi) representing the
// object.
MSI() map[string]interface{}
}
// Map provides extended functionality for working with
// untyped data, in particular map[string]interface (msi).
type Map map[string]interface{}
// Value returns the internal value instance
func (m Map) Value() *Value {
return &Value{data: m}
}
// Nil represents a nil Map.
var Nil = New(nil)
// New creates a new Map containing the map[string]interface{} in the data argument.
// If the data argument is not a map[string]interface, New attempts to call the
// MSI() method on the MSIConvertable interface to create one.
func New(data interface{}) Map {
if _, ok := data.(map[string]interface{}); !ok {
if converter, ok := data.(MSIConvertable); ok {
data = converter.MSI()
} else {
return nil
}
}
return Map(data.(map[string]interface{}))
}
// MSI creates a map[string]interface{} and puts it inside a new Map.
//
// The arguments follow a key, value pattern.
//
// Panics
//
// Panics if any key argument is non-string or if there are an odd number of arguments.
//
// Example
//
// To easily create Maps:
//
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
//
// // creates an Map equivalent to
// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}})
func MSI(keyAndValuePairs ...interface{}) Map {
newMap := make(map[string]interface{})
keyAndValuePairsLen := len(keyAndValuePairs)
if keyAndValuePairsLen%2 != 0 {
panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.")
}
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
key := keyAndValuePairs[i]
value := keyAndValuePairs[i+1]
// make sure the key is a string
keyString, keyStringOK := key.(string)
if !keyStringOK {
panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.")
}
newMap[keyString] = value
}
return New(newMap)
}
// ****** Conversion Constructors
// MustFromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Panics if the JSON is invalid.
func MustFromJSON(jsonString string) Map {
o, err := FromJSON(jsonString)
if err != nil {
panic("objx: MustFromJSON failed with error: " + err.Error())
}
return o
}
// FromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Returns an error if the JSON is invalid.
func FromJSON(jsonString string) (Map, error) {
var data interface{}
err := json.Unmarshal([]byte(jsonString), &data)
if err != nil {
return Nil, err
}
return New(data), nil
}
// FromBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by Base64
func FromBase64(base64String string) (Map, error) {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
decoded, err := ioutil.ReadAll(decoder)
if err != nil {
return nil, err
}
return FromJSON(string(decoded))
}
// MustFromBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromBase64(base64String string) Map {
result, err := FromBase64(base64String)
if err != nil {
panic("objx: MustFromBase64 failed with error: " + err.Error())
}
return result
}
// FromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by SignedBase64
func FromSignedBase64(base64String, key string) (Map, error) {
parts := strings.Split(base64String, SignatureSeparator)
if len(parts) != 2 {
return nil, errors.New("objx: Signed base64 string is malformed")
}
sig := HashWithKey(parts[0], key)
if parts[1] != sig {
return nil, errors.New("objx: Signature for base64 data does not match")
}
return FromBase64(parts[0])
}
// MustFromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromSignedBase64(base64String, key string) Map {
result, err := FromSignedBase64(base64String, key)
if err != nil {
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
}
return result
}
// FromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
func FromURLQuery(query string) (Map, error) {
vals, err := url.ParseQuery(query)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
for k, vals := range vals {
m[k] = vals[0]
}
return New(m), nil
}
// MustFromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
//
// Panics if it encounters an error
func MustFromURLQuery(query string) Map {
o, err := FromURLQuery(query)
if err != nil {
panic("objx: MustFromURLQuery failed with error: " + err.Error())
}
return o
}

View File

@ -1,74 +0,0 @@
package objx
// Exclude returns a new Map with the keys in the specified []string
// excluded.
func (m Map) Exclude(exclude []string) Map {
excluded := make(Map)
for k, v := range m {
var shouldInclude = true
for _, toExclude := range exclude {
if k == toExclude {
shouldInclude = false
break
}
}
if shouldInclude {
excluded[k] = v
}
}
return excluded
}
// Copy creates a shallow copy of the Obj.
func (m Map) Copy() Map {
copied := make(map[string]interface{})
for k, v := range m {
copied[k] = v
}
return New(copied)
}
// Merge blends the specified map with a copy of this map and returns the result.
//
// Keys that appear in both will be selected from the specified map.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) Merge(merge Map) Map {
return m.Copy().MergeHere(merge)
}
// MergeHere blends the specified map with this map and returns the current map.
//
// Keys that appear in both will be selected from the specified map. The original map
// will be modified. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) MergeHere(merge Map) Map {
for k, v := range merge {
m[k] = v
}
return m
}
// Transform builds a new Obj giving the transformer a chance
// to change the keys and values as it goes. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
newMap := make(map[string]interface{})
for k, v := range m {
modifiedKey, modifiedVal := transformer(k, v)
newMap[modifiedKey] = modifiedVal
}
return New(newMap)
}
// TransformKeys builds a new map using the specified key mapping.
//
// Unspecified keys will be unaltered.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) TransformKeys(mapping map[string]string) Map {
return m.Transform(func(key string, value interface{}) (string, interface{}) {
if newKey, ok := mapping[key]; ok {
return newKey, value
}
return key, value
})
}

View File

@ -1,17 +0,0 @@
package objx
import (
"crypto/sha1"
"encoding/hex"
)
// HashWithKey hashes the specified string using the security
// key.
func HashWithKey(data, key string) string {
hash := sha1.New()
_, err := hash.Write([]byte(data + ":" + key))
if err != nil {
return ""
}
return hex.EncodeToString(hash.Sum(nil))
}

View File

@ -1,17 +0,0 @@
package objx
// Has gets whether there is something at the specified selector
// or not.
//
// If m is nil, Has will always return false.
func (m Map) Has(selector string) bool {
if m == nil {
return false
}
return !m.Get(selector).IsNil()
}
// IsNil gets whether the data is nil or not.
func (v *Value) IsNil() bool {
return v == nil || v.data == nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
package objx
import (
"fmt"
"strconv"
)
// Value provides methods for extracting interface{} data in various
// types.
type Value struct {
// data contains the raw data being managed by this Value
data interface{}
}
// Data returns the raw data contained by this Value
func (v *Value) Data() interface{} {
return v.data
}
// String returns the value always as a string
func (v *Value) String() string {
switch {
case v.IsStr():
return v.Str()
case v.IsBool():
return strconv.FormatBool(v.Bool())
case v.IsFloat32():
return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32)
case v.IsFloat64():
return strconv.FormatFloat(v.Float64(), 'f', -1, 64)
case v.IsInt():
return strconv.FormatInt(int64(v.Int()), 10)
case v.IsInt():
return strconv.FormatInt(int64(v.Int()), 10)
case v.IsInt8():
return strconv.FormatInt(int64(v.Int8()), 10)
case v.IsInt16():
return strconv.FormatInt(int64(v.Int16()), 10)
case v.IsInt32():
return strconv.FormatInt(int64(v.Int32()), 10)
case v.IsInt64():
return strconv.FormatInt(v.Int64(), 10)
case v.IsUint():
return strconv.FormatUint(uint64(v.Uint()), 10)
case v.IsUint8():
return strconv.FormatUint(uint64(v.Uint8()), 10)
case v.IsUint16():
return strconv.FormatUint(uint64(v.Uint16()), 10)
case v.IsUint32():
return strconv.FormatUint(uint64(v.Uint32()), 10)
case v.IsUint64():
return strconv.FormatUint(v.Uint64(), 10)
}
return fmt.Sprintf("%#v", v.Data())
}