Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grep, First & Last operators #216

Merged
merged 5 commits into from
Sep 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
if: matrix.full-tests
run: |
curl -sL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh |
sh -s -- -b $HOME/go/bin v1.48.0
sh -s -- -b $HOME/go/bin v1.49.0
echo $PATH
$HOME/go/bin/golangci-lint run --max-issues-per-linter 0 \
--max-same-issues 0 \
Expand Down Expand Up @@ -96,7 +96,7 @@ jobs:
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go install github.com/mattn/goveralls@v0.0.9
go install github.com/mattn/goveralls@v0.0.11
go install github.com/wadey/gocovmerge@latest
gocovmerge coverage.out \
coverage-safe.out \
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`ContainsKey`]: https://go-testdeep.zetta.rocks/operators/containskey/
[`Delay`]: https://go-testdeep.zetta.rocks/operators/delay/
[`Empty`]: https://go-testdeep.zetta.rocks/operators/empty/
[`First`]: https://go-testdeep.zetta.rocks/operators/first/
[`Grep`]: https://go-testdeep.zetta.rocks/operators/grep/
[`Gt`]: https://go-testdeep.zetta.rocks/operators/gt/
[`Gte`]: https://go-testdeep.zetta.rocks/operators/gte/
[`HasPrefix`]: https://go-testdeep.zetta.rocks/operators/hasprefix/
Expand All @@ -314,6 +316,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`JSON`]: https://go-testdeep.zetta.rocks/operators/json/
[`JSONPointer`]: https://go-testdeep.zetta.rocks/operators/jsonpointer/
[`Keys`]: https://go-testdeep.zetta.rocks/operators/keys/
[`Last`]: https://go-testdeep.zetta.rocks/operators/last/
[`Lax`]: https://go-testdeep.zetta.rocks/operators/lax/
[`Len`]: https://go-testdeep.zetta.rocks/operators/len/
[`Lt`]: https://go-testdeep.zetta.rocks/operators/lt/
Expand Down Expand Up @@ -367,6 +370,8 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`CmpContains`]: https://go-testdeep.zetta.rocks/operators/contains/#cmpcontains-shortcut
[`CmpContainsKey`]: https://go-testdeep.zetta.rocks/operators/containskey/#cmpcontainskey-shortcut
[`CmpEmpty`]: https://go-testdeep.zetta.rocks/operators/empty/#cmpempty-shortcut
[`CmpFirst`]: https://go-testdeep.zetta.rocks/operators/first/#cmpfirst-shortcut
[`CmpGrep`]: https://go-testdeep.zetta.rocks/operators/grep/#cmpgrep-shortcut
[`CmpGt`]: https://go-testdeep.zetta.rocks/operators/gt/#cmpgt-shortcut
[`CmpGte`]: https://go-testdeep.zetta.rocks/operators/gte/#cmpgte-shortcut
[`CmpHasPrefix`]: https://go-testdeep.zetta.rocks/operators/hasprefix/#cmphasprefix-shortcut
Expand All @@ -375,6 +380,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`CmpJSON`]: https://go-testdeep.zetta.rocks/operators/json/#cmpjson-shortcut
[`CmpJSONPointer`]: https://go-testdeep.zetta.rocks/operators/jsonpointer/#cmpjsonpointer-shortcut
[`CmpKeys`]: https://go-testdeep.zetta.rocks/operators/keys/#cmpkeys-shortcut
[`CmpLast`]: https://go-testdeep.zetta.rocks/operators/last/#cmplast-shortcut
[`CmpLax`]: https://go-testdeep.zetta.rocks/operators/lax/#cmplax-shortcut
[`CmpLen`]: https://go-testdeep.zetta.rocks/operators/len/#cmplen-shortcut
[`CmpLt`]: https://go-testdeep.zetta.rocks/operators/lt/#cmplt-shortcut
Expand Down Expand Up @@ -427,6 +433,8 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`T.Contains`]: https://go-testdeep.zetta.rocks/operators/contains/#tcontains-shortcut
[`T.ContainsKey`]: https://go-testdeep.zetta.rocks/operators/containskey/#tcontainskey-shortcut
[`T.Empty`]: https://go-testdeep.zetta.rocks/operators/empty/#tempty-shortcut
[`T.First`]: https://go-testdeep.zetta.rocks/operators/first/#tfirst-shortcut
[`T.Grep`]: https://go-testdeep.zetta.rocks/operators/grep/#tgrep-shortcut
[`T.Gt`]: https://go-testdeep.zetta.rocks/operators/gt/#tgt-shortcut
[`T.Gte`]: https://go-testdeep.zetta.rocks/operators/gte/#tgte-shortcut
[`T.HasPrefix`]: https://go-testdeep.zetta.rocks/operators/hasprefix/#thasprefix-shortcut
Expand All @@ -435,6 +443,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
[`T.JSON`]: https://go-testdeep.zetta.rocks/operators/json/#tjson-shortcut
[`T.JSONPointer`]: https://go-testdeep.zetta.rocks/operators/jsonpointer/#tjsonpointer-shortcut
[`T.Keys`]: https://go-testdeep.zetta.rocks/operators/keys/#tkeys-shortcut
[`T.Last`]: https://go-testdeep.zetta.rocks/operators/last/#tlast-shortcut
[`T.CmpLax`]: https://go-testdeep.zetta.rocks/operators/lax/#tcmplax-shortcut
[`T.Len`]: https://go-testdeep.zetta.rocks/operators/len/#tlen-shortcut
[`T.Lt`]: https://go-testdeep.zetta.rocks/operators/lt/#tlt-shortcut
Expand Down
4 changes: 2 additions & 2 deletions internal/anchors/types_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020, Maxime Soulé
// Copyright (c) 2020-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand All @@ -18,7 +18,7 @@ func TestAddAnchorableStructType(t *testing.T) {
defer func() { anchors.AnchorableTypes = oldAnchorableTypes }()

type ok struct{ index int }
type notComparable struct{ s []int } //nolint: structcheck,unused
type notComparable struct{ s []int } //nolint: unused

// Usage error cases
for i, fn := range []any{
Expand Down
24 changes: 2 additions & 22 deletions internal/ctxerr/op_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"bytes"
"fmt"
"reflect"
"strings"

"github.com/maxatome/go-testdeep/internal/types"
)
Expand Down Expand Up @@ -73,33 +72,14 @@ func OpBad(op, s string, args ...any) *Error {
}
}

func kindType(got reflect.Value) (gotKind string) {
if !got.IsValid() {
return "nil"
}

nptr := 0
typ := got.Type()
for typ.Kind() == reflect.Ptr {
nptr++
typ = typ.Elem()
}
gotKind = strings.Repeat("*", nptr) + typ.Kind().String()
gotType := got.Type().String()
if gotKind != gotType {
gotKind += " (" + gotType + " type)"
}
return
}

// BadKind returns a “bad kind” [*Error], saying got kind does not
// match kind(s) listed in okKinds. It is the caller responsibility to
// check the kinds compatibility. got can be invalid, in this case it
// is displayed as nil.
func BadKind(got reflect.Value, okKinds string) *Error {
return &Error{
Message: "bad kind",
Got: types.RawString(kindType(got)),
Got: types.RawString(types.KindType(got)),
Expected: types.RawString(okKinds),
}
}
Expand All @@ -111,7 +91,7 @@ func BadKind(got reflect.Value, okKinds string) *Error {
func NilPointer(got reflect.Value, expected string) *Error {
return &Error{
Message: "nil pointer",
Got: types.RawString("nil " + kindType(got)),
Got: types.RawString("nil " + types.KindType(got)),
Expected: types.RawString(expected),
}
}
7 changes: 4 additions & 3 deletions internal/dark/copy_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018, Maxime Soulé
// Copyright (c) 2018-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand All @@ -15,7 +15,8 @@ import (
)

func checkFieldValueOK(t *testing.T,
s reflect.Value, fieldName string, value any) {
s reflect.Value, fieldName string, value any,
) {
t.Helper()

testName := "field " + fieldName
Expand Down Expand Up @@ -51,7 +52,7 @@ func TestCopyValue(t *testing.T) {
}

type SubPrivate struct {
private int //nolint: unused,megacheck,staticcheck,structcheck
private int //nolint: unused,megacheck,staticcheck
}

type Private struct {
Expand Down
29 changes: 26 additions & 3 deletions internal/types/reflect.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020, Maxime Soulé
// Copyright (c) 2020-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand All @@ -10,6 +10,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
)

Expand Down Expand Up @@ -57,13 +58,13 @@ func IsTypeOrConvertible(v reflect.Value, target reflect.Type) (bool, bool) {
return false, false
}

// IsConvertible returns true if v if convertible to target type,
// IsConvertible returns true if v is convertible to target type,
// false otherwise.
//
// It handles go 1.17 slice to array pointer convertibility.
func IsConvertible(v reflect.Value, target reflect.Type) bool {
if v.Type().ConvertibleTo(target) {
// Since go 1.17, a slice can be convertible to a pointer of an
// Since go 1.17, a slice can be convertible to a pointer to an
// array, but Convert() may still panic if the slice length is lesser
// than array pointed one
if v.Kind() != reflect.Slice ||
Expand All @@ -75,3 +76,25 @@ func IsConvertible(v reflect.Value, target reflect.Type) bool {
}
return false
}

// KindType returns the kind of val as a string. If the kind is
// [reflect.Ptr], a "*" is used as prefix of kind of
// val.Type().Elem(), and so on. If the final kind differs from
// val.Type(), the type is appended inside parenthesis.
func KindType(val reflect.Value) string {
if !val.IsValid() {
return "nil"
}

nptr := 0
typ := val.Type()
for typ.Kind() == reflect.Ptr {
nptr++
typ = typ.Elem()
}
kind := strings.Repeat("*", nptr) + typ.Kind().String()
if typ := val.Type().String(); kind != typ {
kind += " (" + typ + " type)"
}
return kind
}
24 changes: 23 additions & 1 deletion internal/types/reflect_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2021, Maxime Soulé
// Copyright (c) 2020-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
Expand Down Expand Up @@ -56,3 +56,25 @@ func TestIsTypeOrConvertible(t *testing.T) {
test.IsFalse(t, ok)
test.IsFalse(t, convertible)
}

func TestKindType(t *testing.T) {
for _, tc := range []struct {
val any
expected string
}{
{nil, "nil"},
{42, "int"},
{(*int)(nil), "*int"},
{(*[]int)(nil), "*slice (*[]int type)"},
{(***int)(nil), "***int"},
} {
vval := reflect.ValueOf(tc.val)
name := "nil"
if tc.val != nil {
name = vval.Type().String()
}
t.Run(name, func(t *testing.T) {
test.EqualStr(t, types.KindType(vval), tc.expected)
})
}
}
2 changes: 1 addition & 1 deletion internal/util/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func TestTypeFullName(t *testing.T) {
b bool
}{})), "struct { s struct { a []int }; b bool }")

type anon struct{ a []int } //nolint: structcheck,unused
type anon struct{ a []int } //nolint: unused
test.EqualStr(t, util.TypeFullName(reflect.TypeOf(struct {
anon
b bool
Expand Down
68 changes: 67 additions & 1 deletion td/cmp_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"time"
)

// allOperators lists the 63 operators.
// allOperators lists the 66 operators.
// nil means not usable in JSON().
var allOperators = map[string]any{
"All": All,
Expand All @@ -28,6 +28,8 @@ var allOperators = map[string]any{
"ContainsKey": ContainsKey,
"Delay": nil,
"Empty": Empty,
"First": First,
"Grep": Grep,
"Gt": Gt,
"Gte": Gte,
"HasPrefix": HasPrefix,
Expand All @@ -37,6 +39,7 @@ var allOperators = map[string]any{
"JSON": nil,
"JSONPointer": JSONPointer,
"Keys": Keys,
"Last": Last,
"Lax": nil,
"Len": Len,
"Lt": Lt,
Expand Down Expand Up @@ -315,6 +318,48 @@ func CmpEmpty(t TestingT, got any, args ...any) bool {
return Cmp(t, got, Empty(), args...)
}

// CmpFirst is a shortcut for:
//
// td.Cmp(t, got, td.First(filter, expectedValue), args...)
//
// See [First] for details.
//
// Returns true if the test is OK, false if it fails.
//
// If t is a [*T] then its Config field is inherited.
//
// args... are optional and allow to name the test. This name is
// used in case of failure to qualify the test. If len(args) > 1 and
// the first item of args is a string and contains a '%' rune then
// [fmt.Fprintf] is used to compose the name, else args are passed to
// [fmt.Fprint]. Do not forget it is the name of the test, not the
// reason of a potential failure.
func CmpFirst(t TestingT, got, filter, expectedValue any, args ...any) bool {
t.Helper()
return Cmp(t, got, First(filter, expectedValue), args...)
}

// CmpGrep is a shortcut for:
//
// td.Cmp(t, got, td.Grep(filter, expectedValue), args...)
//
// See [Grep] for details.
//
// Returns true if the test is OK, false if it fails.
//
// If t is a [*T] then its Config field is inherited.
//
// args... are optional and allow to name the test. This name is
// used in case of failure to qualify the test. If len(args) > 1 and
// the first item of args is a string and contains a '%' rune then
// [fmt.Fprintf] is used to compose the name, else args are passed to
// [fmt.Fprint]. Do not forget it is the name of the test, not the
// reason of a potential failure.
func CmpGrep(t TestingT, got, filter, expectedValue any, args ...any) bool {
t.Helper()
return Cmp(t, got, Grep(filter, expectedValue), args...)
}

// CmpGt is a shortcut for:
//
// td.Cmp(t, got, td.Gt(minExpectedValue), args...)
Expand Down Expand Up @@ -483,6 +528,27 @@ func CmpKeys(t TestingT, got, val any, args ...any) bool {
return Cmp(t, got, Keys(val), args...)
}

// CmpLast is a shortcut for:
//
// td.Cmp(t, got, td.Last(filter, expectedValue), args...)
//
// See [Last] for details.
//
// Returns true if the test is OK, false if it fails.
//
// If t is a [*T] then its Config field is inherited.
//
// args... are optional and allow to name the test. This name is
// used in case of failure to qualify the test. If len(args) > 1 and
// the first item of args is a string and contains a '%' rune then
// [fmt.Fprintf] is used to compose the name, else args are passed to
// [fmt.Fprint]. Do not forget it is the name of the test, not the
// reason of a potential failure.
func CmpLast(t TestingT, got, filter, expectedValue any, args ...any) bool {
t.Helper()
return Cmp(t, got, Last(filter, expectedValue), args...)
}

// CmpLax is a shortcut for:
//
// td.Cmp(t, got, td.Lax(expectedValue), args...)
Expand Down
2 changes: 1 addition & 1 deletion td/equal_unsafe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

// Map, unsafe access is mandatory here.
func TestEqualMapUnsafe(t *testing.T) {
type key struct{ k string } //nolint: structcheck
type key struct{ k string }
type A struct{ x map[key]struct{} }

checkError(t, A{x: map[key]struct{}{{k: "z"}: {}}},
Expand Down
Loading