Skip to content

Commit

Permalink
Contains rework to handle maps, arrays and slices
Browse files Browse the repository at this point in the history
Signed-off-by: Maxime Soulé <btik-git@scoubidou.com>
  • Loading branch information
maxatome committed Jun 22, 2018
1 parent a9cff18 commit f07c478
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 53 deletions.
6 changes: 3 additions & 3 deletions cmp_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,17 @@ func CmpCode(t TestingT, got interface{}, fn interface{}, args ...interface{}) b

// CmpContains is a shortcut for:
//
// CmpDeeply(t, got, Contains(expected), args...)
// CmpDeeply(t, got, Contains(expectedValue), args...)
//
// Returns true if the test is OK, false if it fails.
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. 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.
func CmpContains(t TestingT, got interface{}, expected string, args ...interface{}) bool {
func CmpContains(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool {
t.Helper()
return CmpDeeply(t, got, Contains(expected), args...)
return CmpDeeply(t, got, Contains(expectedValue), args...)
}

// CmpEmpty is a shortcut for:
Expand Down
6 changes: 3 additions & 3 deletions t.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,17 @@ func (t *T) Code(got interface{}, fn interface{}, args ...interface{}) bool {

// Contains is a shortcut for:
//
// t.CmpDeeply(got, Contains(expected), args...)
// t.CmpDeeply(got, Contains(expectedValue), args...)
//
// Returns true if the test is OK, false if it fails.
//
// "args..." are optional and allow to name the test. This name is
// logged as is in case of failure. 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.
func (t *T) Contains(got interface{}, expected string, args ...interface{}) bool {
func (t *T) Contains(got interface{}, expectedValue interface{}, args ...interface{}) bool {
t.Helper()
return t.CmpDeeply(got, Contains(expected), args...)
return t.CmpDeeply(got, Contains(expectedValue), args...)
}

// Empty is a shortcut for:
Expand Down
107 changes: 107 additions & 0 deletions td_contains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2018, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

package testdeep

import (
"reflect"
"strings"
)

type tdContains struct {
Base
expectedValue reflect.Value
}

var _ TestDeep = &tdContains{}

// Contains operator allows to check the presence of a string (or
// convertible), error or fmt.Stringer interface (error interface is
// tested before fmt.Stringer.)
//
// type Foobar string
// CmpDeeply(t, Foobar("foobar"), Contains("ooba")) // succeeds
//
// err := errors.New("error!")
// CmpDeeply(t, err, Contains("ror")) // succeeds
//
// bstr := bytes.NewBufferString("fmt.Stringer!")
// CmpDeeply(t, bstr, Contains("String")) // succeeds
func Contains(expectedValue interface{}) TestDeep {
return &tdContains{
Base: NewBase(3),
expectedValue: reflect.ValueOf(expectedValue),
}
}

func (t *tdContains) doesNotContains(ctx Context, got interface{}) *Error {
if ctx.booleanError {
return booleanError
}
return ctx.CollectError(&Error{
Message: "does not contain",
Got: got,
Expected: t,
})
}

func (t *tdContains) Match(ctx Context, got reflect.Value) *Error {
switch got.Kind() {
// case reflect.Ptr:
case reflect.Array, reflect.Slice:
for index := got.Len() - 1; index >= 0; index-- {
if deepValueEqualOK(got.Index(index), t.expectedValue) {
return nil
}
}
return t.doesNotContains(ctx, got)

case reflect.Map:
for _, vkey := range got.MapKeys() {
if deepValueEqualOK(got.MapIndex(vkey), t.expectedValue) {
return nil
}
}
return t.doesNotContains(ctx, got)
}

switch expectedKind := t.expectedValue.Kind(); expectedKind {
case reflect.String, reflect.Int32, reflect.Uint8: // string, rune & byte
str, err := getString(ctx, got)
if err != nil {
return err
}

switch expectedKind {
case reflect.String:
if strings.Contains(str, t.expectedValue.String()) {
return nil
}
case reflect.Int32:
if strings.ContainsRune(str, rune(t.expectedValue.Int())) {
return nil
}
default: // = case reflect.Uint8:
if strings.IndexByte(str, byte(t.expectedValue.Uint())) >= 0 {
return nil
}
}
return t.doesNotContains(ctx, str)
}

if ctx.booleanError {
return booleanError
}
return ctx.CollectError(&Error{
Message: "cannot check contains",
Got: got,
Expected: t.expectedValue,
})
}

func (t *tdContains) String() string {
return "Contains(" + toString(t.expectedValue) + ")"
}
47 changes: 0 additions & 47 deletions td_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,50 +188,3 @@ func (s *tdHasSuffix) Match(ctx Context, got reflect.Value) *Error {
func (s *tdHasSuffix) String() string {
return "HasSuffix(" + toString(s.expected) + ")"
}

type tdContains struct {
tdStringBase
}

var _ TestDeep = &tdContains{}

// Contains operator allows to check the presence of a string (or
// convertible), error or fmt.Stringer interface (error interface is
// tested before fmt.Stringer.)
//
// type Foobar string
// CmpDeeply(t, Foobar("foobar"), Contains("ooba")) // succeeds
//
// err := errors.New("error!")
// CmpDeeply(t, err, Contains("ror")) // succeeds
//
// bstr := bytes.NewBufferString("fmt.Stringer!")
// CmpDeeply(t, bstr, Contains("String")) // succeeds
func Contains(expected string) TestDeep {
return &tdContains{
tdStringBase: newStringBase(expected),
}
}

func (s *tdContains) Match(ctx Context, got reflect.Value) *Error {
str, err := getString(ctx, got)
if err != nil {
return err
}

if strings.Contains(str, s.expected) {
return nil
}
if ctx.booleanError {
return booleanError
}
return ctx.CollectError(&Error{
Message: "does not contain",
Got: str,
Expected: s,
})
}

func (s *tdContains) String() string {
return "Contains(" + toString(s.expected) + ")"
}

0 comments on commit f07c478

Please sign in to comment.