From aca1890ec1b2e3a58411e4cb1d7b9324e704a20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= Date: Thu, 7 Mar 2024 11:59:12 +0100 Subject: [PATCH 1/9] mock: document more alternatives to deprecated AnythingOfTypeArgument --- mock/mock.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/mock/mock.go b/mock/mock.go index eca55f6a1..7abce13d1 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -766,7 +766,25 @@ const ( // AnythingOfTypeArgument contains the type of an argument // for use when type checking. Used in Diff and Assert. // -// Deprecated: this is an implementation detail that must not be used. Use [AnythingOfType] instead. +// Deprecated: this is an implementation detail that must not be used. Use the [AnythingOfType] constructor instead. +// +// If AnythingOfTypeArgument is used as a function return value type you can usually replace +// the function with a variable. Example: +// +// func anyString() mock.AnythingOfTypeArgument { +// return mock.AnythingOfType("string") +// } +// +// Can be replaced by: +// +// func anyString() interface{} { +// return mock.IsType("") +// } +// +// It could even be replaced with a variable: +// +// var anyString = mock.AnythingOfType("string") +// var anyString = mock.IsType("") // alternative type AnythingOfTypeArgument = anythingOfTypeArgument // anythingOfTypeArgument is a string that contains the type of an argument From d3dbb19355185612379252b72db50d1c67af9f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= Date: Tue, 19 Mar 2024 21:35:26 +0100 Subject: [PATCH 2/9] 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 } --- assert/assertions.go | 4 +++- assert/yaml/yaml_custom.go | 25 +++++++++++++++++++++++++ assert/yaml/yaml_default.go | 37 +++++++++++++++++++++++++++++++++++++ assert/yaml/yaml_fail.go | 18 ++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 assert/yaml/yaml_custom.go create mode 100644 assert/yaml/yaml_default.go create mode 100644 assert/yaml/yaml_fail.go diff --git a/assert/assertions.go b/assert/assertions.go index 8109ef2a8..7c6398df0 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -19,7 +19,9 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" - "gopkg.in/yaml.v3" + + // Wrapper around gopkg.in/yaml.v3 + "github.com/stretchr/testify/assert/yaml" ) //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" diff --git a/assert/yaml/yaml_custom.go b/assert/yaml/yaml_custom.go new file mode 100644 index 000000000..bc32dc96c --- /dev/null +++ b/assert/yaml/yaml_custom.go @@ -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.Unmarshall = func (in []byte, out interface{}) error { +// // ... +// return nil +// } +// } +package yaml + +var Unmarshal func(in []byte, out interface{}) error diff --git a/assert/yaml/yaml_default.go b/assert/yaml/yaml_default.go new file mode 100644 index 000000000..9a92eed67 --- /dev/null +++ b/assert/yaml/yaml_default.go @@ -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 implemantation 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) +} diff --git a/assert/yaml/yaml_fail.go b/assert/yaml/yaml_fail.go new file mode 100644 index 000000000..e78f7dfe6 --- /dev/null +++ b/assert/yaml/yaml_fail.go @@ -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 +} From 4ec7678c611459e936827bd38005466b835f34c7 Mon Sep 17 00:00:00 2001 From: Jonathan Crowther Date: Fri, 12 Apr 2024 14:19:10 -0400 Subject: [PATCH 3/9] Correct the EventuallyWithT and EventuallyWithTf example --- assert/assertion_format.go | 2 +- assert/assertion_forward.go | 4 ++-- assert/assertions.go | 2 +- require/require.go | 4 ++-- require/require_forward.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 3ddab109a..9397c8170 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -186,7 +186,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick // 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") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 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() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index a84e09bd4..d2065beda 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -336,7 +336,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -361,7 +361,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor // a.EventuallyWithTf(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") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index 8109ef2a8..a291b1fe6 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1955,7 +1955,7 @@ func (*CollectT) Copy(TestingT) { // assert.EventuallyWithT(t, func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require.go b/require/require.go index 506a82f80..5d9bc7caf 100644 --- a/require/require.go +++ b/require/require.go @@ -418,7 +418,7 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t // assert.EventuallyWithT(t, func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -446,7 +446,7 @@ func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitF // 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") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index eee8310a5..6fed4cb03 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -337,7 +337,7 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti // a.EventuallyWithT(func(c *assert.CollectT) { // // add assertions as needed; any assertion failure will fail the current tick // assert.True(c, externalValue, "expected 'externalValue' to be true") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -362,7 +362,7 @@ func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), w // a.EventuallyWithTf(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") -// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() From a9e6121b1c054b960da5a6f3ba2c74fe51bdb9b1 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Thu, 11 Apr 2024 14:32:29 -0700 Subject: [PATCH 4/9] 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. --- assert/assertions.go | 10 ++++++++-- assert/assertions_test.go | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index a291b1fe6..eb800c1a9 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1615,7 +1615,6 @@ func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...in // matchRegexp return true if a specified regexp matches a string. func matchRegexp(rx interface{}, str interface{}) bool { - var r *regexp.Regexp if rr, ok := rx.(*regexp.Regexp); ok { r = rr @@ -1623,7 +1622,14 @@ func matchRegexp(rx interface{}, str interface{}) bool { r = regexp.MustCompile(fmt.Sprint(rx)) } - return (r.FindStringIndex(fmt.Sprint(str)) != nil) + switch v := str.(type) { + case []byte: + return r.Match(v) + case string: + return r.MatchString(v) + default: + return r.MatchString(fmt.Sprint(v)) + } } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 0ae36cd92..fa68fe210 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1972,13 +1972,16 @@ func TestRegexp(t *testing.T) { }{ {"^start", "start of the line"}, {"end$", "in the end"}, + {"end$", "in the end"}, {"[0-9]{3}[.-]?[0-9]{2}[.-]?[0-9]{2}", "My phone number is 650.12.34"}, } for _, tc := range cases { True(t, Regexp(mockT, tc.rx, tc.str)) True(t, Regexp(mockT, regexp.MustCompile(tc.rx), tc.str)) + True(t, Regexp(mockT, regexp.MustCompile(tc.rx), []byte(tc.str))) False(t, NotRegexp(mockT, tc.rx, tc.str)) + False(t, NotRegexp(mockT, tc.rx, []byte(tc.str))) False(t, NotRegexp(mockT, regexp.MustCompile(tc.rx), tc.str)) } @@ -1993,7 +1996,9 @@ func TestRegexp(t *testing.T) { for _, tc := range cases { False(t, Regexp(mockT, tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str) False(t, Regexp(mockT, regexp.MustCompile(tc.rx), tc.str)) + False(t, Regexp(mockT, regexp.MustCompile(tc.rx), []byte(tc.str))) True(t, NotRegexp(mockT, tc.rx, tc.str)) + True(t, NotRegexp(mockT, tc.rx, []byte(tc.str))) True(t, NotRegexp(mockT, regexp.MustCompile(tc.rx), tc.str)) } } From e33bd6fdd1e5e20802cd264db2ab1e363bad69bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:37:40 +0000 Subject: [PATCH 5/9] 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] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21ffe56a3..08adf73d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,6 @@ jobs: uses: actions/checkout@v4 - name: Create GitHub release from tag - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: generate_release_notes: true From 8c324a0bbd418741163c17c7219a5f68295ed1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= Date: Tue, 23 Apr 2024 15:23:21 +0200 Subject: [PATCH 6/9] mock: simpler deprecation doc for AnythingOfTypeArgument Co-authored-by: Bracken --- mock/mock.go | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/mock/mock.go b/mock/mock.go index 7abce13d1..84ffcdc63 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -766,25 +766,15 @@ const ( // AnythingOfTypeArgument contains the type of an argument // for use when type checking. Used in Diff and Assert. // -// Deprecated: this is an implementation detail that must not be used. Use the [AnythingOfType] constructor instead. +// Deprecated: this is an implementation detail that must not be used. Use the [AnythingOfType] constructor instead, example: // -// If AnythingOfTypeArgument is used as a function return value type you can usually replace -// the function with a variable. Example: +// m.On("Do", mock.AnythingOfType("string")) // -// func anyString() mock.AnythingOfTypeArgument { -// return mock.AnythingOfType("string") -// } -// -// Can be replaced by: +// All explicit type declarations can be replaced with interface{} as is expected by [Mock.On], example: // -// func anyString() interface{} { -// return mock.IsType("") +// func anyString interface{} { +// return mock.AnythingOfType("string") // } -// -// It could even be replaced with a variable: -// -// var anyString = mock.AnythingOfType("string") -// var anyString = mock.IsType("") // alternative type AnythingOfTypeArgument = anythingOfTypeArgument // anythingOfTypeArgument is a string that contains the type of an argument From 32766084e47ab272ffaf0fcaa7c74be5882954bd Mon Sep 17 00:00:00 2001 From: Bracken Dawson Date: Mon, 29 Apr 2024 13:49:40 +0100 Subject: [PATCH 7/9] Correctly document EqualValues behavior --- assert/assertion_format.go | 4 ++-- assert/assertion_forward.go | 8 ++++---- assert/assertions.go | 4 ++-- require/require.go | 8 ++++---- require/require_forward.go | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 9397c8170..c40f423c9 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -104,8 +104,8 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// 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 { diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index d2065beda..185e02674 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -186,8 +186,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface return EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { @@ -197,8 +197,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn return EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { diff --git a/assert/assertions.go b/assert/assertions.go index a291b1fe6..21f5fb740 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -576,8 +576,8 @@ func truncatingFormat(data interface{}) string { return value } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { diff --git a/require/require.go b/require/require.go index 5d9bc7caf..9290bcd34 100644 --- a/require/require.go +++ b/require/require.go @@ -232,8 +232,8 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, t.FailNow() } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // assert.EqualValues(t, uint32(123), int32(123)) func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { @@ -246,8 +246,8 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg t.FailNow() } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// 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{}) { diff --git a/require/require_forward.go b/require/require_forward.go index 6fed4cb03..f8302a1b8 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -187,8 +187,8 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface EqualExportedValuesf(a.t, expected, actual, msg, args...) } -// EqualValues asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValues(uint32(123), int32(123)) func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { @@ -198,8 +198,8 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn EqualValues(a.t, expected, actual, msgAndArgs...) } -// EqualValuesf asserts that two objects are equal or convertible to the same types -// and equal. +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. // // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { From 7af3ed34c25997a502b39d9882c3269012ee904d Mon Sep 17 00:00:00 2001 From: "hendry.wiranto" Date: Wed, 22 May 2024 01:03:16 +0700 Subject: [PATCH 8/9] feat: new assertion NotElementsMatch --- assert/assertion_format.go | 17 ++++++++++++++ assert/assertion_forward.go | 34 +++++++++++++++++++++++++++ assert/assertions.go | 33 ++++++++++++++++++++++++++ assert/assertions_test.go | 46 +++++++++++++++++++++++++++++++++++++ require/require.go | 40 ++++++++++++++++++++++++++++++++ require/require_forward.go | 34 +++++++++++++++++++++++++++ 6 files changed, 204 insertions(+) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index c40f423c9..ad395233c 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -568,6 +568,23 @@ 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. // diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 185e02674..d90c65da9 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -1128,6 +1128,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin return NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch 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. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// 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. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // diff --git a/assert/assertions.go b/assert/assertions.go index 16d21e716..52487f063 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1176,6 +1176,39 @@ func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) stri return msg.String() } +// NotElementsMatch 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.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// assert.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return Fail(t, "A and B are the same", msgAndArgs) + } + + if !isList(t, listA, msgAndArgs...) { + return Fail(t, "A is not a list", msgAndArgs...) + } + if !isList(t, listB, msgAndArgs...) { + return Fail(t, "B is not a list", msgAndArgs...) + } + + extraA, extraB := diffLists(listA, listB) + if len(extraA) == 0 && len(extraB) == 0 { + return Fail(t, "A and B are the same", msgAndArgs) + } + + return true +} + // Condition uses a Comparison to assert a complex condition. func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/assert/assertions_test.go b/assert/assertions_test.go index fa68fe210..8b8772713 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1319,6 +1319,52 @@ func TestDiffLists(t *testing.T) { } } +func TestNotElementsMatch(t *testing.T) { + mockT := new(testing.T) + + cases := []struct { + expected interface{} + actual interface{} + result bool + }{ + // not mathing + {[]int{1}, []int{}, true}, + {[]int{}, []int{2}, true}, + {[]int{1}, []int{2}, true}, + {[]int{1}, []int{1, 1}, true}, + {[]int{1, 2}, []int{3, 4}, true}, + {[]int{3, 4}, []int{1, 2}, true}, + {[]int{1, 1, 2, 3}, []int{1, 2, 3}, true}, + {[]string{"hello"}, []string{"world"}, true}, + {[]string{"hello", "hello"}, []string{"world", "world"}, true}, + {[3]string{"hello", "hello", "hello"}, [3]string{"world", "world", "world"}, true}, + + // matching + {nil, nil, false}, + {[]int{}, nil, false}, + {[]int{}, []int{}, false}, + {[]int{1}, []int{1}, false}, + {[]int{1, 1}, []int{1, 1}, false}, + {[]int{1, 2}, []int{2, 1}, false}, + {[2]int{1, 2}, [2]int{2, 1}, false}, + {[]int{1, 1, 2}, []int{1, 2, 1}, false}, + {[]string{"hello", "world"}, []string{"world", "hello"}, false}, + {[]string{"hello", "hello"}, []string{"hello", "hello"}, false}, + {[]string{"hello", "hello", "world"}, []string{"hello", "world", "hello"}, false}, + {[3]string{"hello", "hello", "world"}, [3]string{"hello", "world", "hello"}, false}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("NotElementsMatch(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { + res := NotElementsMatch(mockT, c.actual, c.expected) + + if res != c.result { + t.Errorf("NotElementsMatch(%#v, %#v) should return %v", c.actual, c.expected, c.result) + } + }) + } +} + func TestCondition(t *testing.T) { mockT := new(testing.T) diff --git a/require/require.go b/require/require.go index 9290bcd34..59b87c8e3 100644 --- a/require/require.go +++ b/require/require.go @@ -1429,6 +1429,46 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a t.FailNow() } +// NotElementsMatch 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.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// assert.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// 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{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // diff --git a/require/require_forward.go b/require/require_forward.go index f8302a1b8..389de9abe 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -1129,6 +1129,40 @@ func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg strin NotContainsf(a.t, s, contains, msg, args...) } +// NotElementsMatch 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. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// 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. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatchf(a.t, listA, listB, msg, args...) +} + // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either // a slice or a channel with len == 0. // From cb4e70cf8d9d1a87d1cb4580c1d53bdaab991ad0 Mon Sep 17 00:00:00 2001 From: "hendry.wiranto" Date: Tue, 28 May 2024 21:35:41 +0700 Subject: [PATCH 9/9] review: match fail msg with params --- assert/assertions.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assert/assertions.go b/assert/assertions.go index 52487f063..7f16cb82f 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1191,19 +1191,19 @@ func NotElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interf h.Helper() } if isEmpty(listA) && isEmpty(listB) { - return Fail(t, "A and B are the same", msgAndArgs) + return Fail(t, "listA and listB contain the same elements", msgAndArgs) } if !isList(t, listA, msgAndArgs...) { - return Fail(t, "A is not a list", msgAndArgs...) + return Fail(t, "listA is not a list type", msgAndArgs...) } if !isList(t, listB, msgAndArgs...) { - return Fail(t, "B is not a list", msgAndArgs...) + return Fail(t, "listB is not a list type", msgAndArgs...) } extraA, extraB := diffLists(listA, listB) if len(extraA) == 0 && len(extraB) == 0 { - return Fail(t, "A and B are the same", msgAndArgs) + return Fail(t, "listA and listB contain the same elements", msgAndArgs) } return true