Skip to content

Commit

Permalink
Add MatchCap matcher
Browse files Browse the repository at this point in the history
Works like MatchLen but on `cap` and `Cap() int`.
  • Loading branch information
rgalanakis committed Jul 3, 2024
1 parent 29dc194 commit 639d06b
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
9 changes: 9 additions & 0 deletions golangal.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,19 @@ func AtKey(key interface{}, m interface{}) gomega.OmegaMatcher {
// MatchLen matches the length of a collection against a matcher.
// It's like HaveLen, but allows a dynamic length.
// HaveLen(2) would be equivalent to MatchLen(Equal(2)).
// MatchLen also works with any type with a Len() int method.
func MatchLen(m interface{}) gomega.OmegaMatcher {
return &matchers.MatchLenMatcher{Matcher: internal.CoerceToMatcher(m)}
}

// MatchCap matches the length of a collection against a matcher.
// It's like HaveCap, but allows a dynamic length.
// HaveCap(2) would be equivalent to MatchCap(Equal(2)).
// MatchCap also works with any type with a Cap() int method.
func MatchCap(m interface{}) gomega.OmegaMatcher {
return &matchers.MatchCapMatcher{Matcher: internal.CoerceToMatcher(m)}
}

// MatchField matches the value of the field named name on the actual struct.
// If m is a gomega matcher, it is matched against the field value.
// Otherwise, test against field equality.
Expand Down
55 changes: 55 additions & 0 deletions matchers/match_cap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package matchers

import (
"fmt"
"reflect"

"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)

type MatchCapMatcher struct {
Matcher types.GomegaMatcher
}

func (matcher *MatchCapMatcher) Match(actual interface{}) (bool, error) {
capacity, ok := capOf(actual)
if !ok {
return false, fmt.Errorf("MatchCap matcher expects a string/array/map/channel/slice, or type with a Cap() int method. Got:\n%s",
format.Object(actual, 1))
}
return matcher.Matcher.Match(capacity)
}

func (matcher *MatchCapMatcher) FailureMessage(actual interface{}) (message string) {
capacity, _ := capOf(actual)
return fmt.Sprintf("Expected capacity of\n%s\nto match, but failed with\n%s",
format.Object(actual, 1),
format.IndentString(matcher.Matcher.FailureMessage(capacity), 1))
}

func (matcher *MatchCapMatcher) NegatedFailureMessage(actual interface{}) (message string) {
capacity, _ := capOf(actual)
return fmt.Sprintf("Expected capacity of\n%s\nnot to match, but did with\n%s",
format.Object(actual, 1),
format.IndentString(matcher.Matcher.NegatedFailureMessage(capacity), 1))
}

func capOf(a interface{}) (int, bool) {
if a == nil {
return 0, false
}
if capper, ok := a.(hasCap); ok {
return capper.Cap(), true
}
switch reflect.TypeOf(a).Kind() {
case reflect.Array, reflect.Chan, reflect.Slice:
return reflect.ValueOf(a).Cap(), true
default:
return 0, false
}
}

type hasCap interface {
Cap() int
}
67 changes: 67 additions & 0 deletions matchers/match_cap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package matchers_test

import (
"bytes"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/rgalanakis/golangal"
)

var _ = Describe("MatchCap matcher", func() {
slice := make([]int, 1, 3)

It("matches a matcher against the capacity of an object", func() {
Expect(slice).To(MatchCap(BeNumerically(">", 1)))
Expect(slice).ToNot(MatchCap(Equal(1)))
})

It("fails if the capacity of the object does not match the matcher", func() {
success, err := MatchCap(Equal(1)).Match(slice)
Expect(success).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
msg := MatchCap(Equal(1)).FailureMessage(slice)
Expect(msg).To(HaveSuffix(`Expected capacity of
<[]int | len:1, cap:3>: [0]
to match, but failed with
Expected
<int>: 3
to equal
<int>: 1`))
})

It("fails if the capacity of the object matches the matcher but should not (negated)", func() {
success, err := MatchCap(Equal(3)).Match(slice)
Expect(success).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
msg := MatchCap(Equal(3)).NegatedFailureMessage(slice)
Expect(msg).To(HaveSuffix(`Expected capacity of
<[]int | len:1, cap:3>: [0]
not to match, but did with
Expected
<int>: 3
not to equal
<int>: 3`))
})

It("matches a matcher against the capacity of an object with a Cap method", func() {
Expect(CustomCap{3}).To(MatchCap(3))
Expect(slice).ToNot(MatchCap(Equal(1)))
Expect(bytes.NewBufferString("abc")).To(MatchCap(8))
Expect(bytes.NewBufferString("abc")).ToNot(MatchCap(7))
})

It("errors if the type is not a slice/string/array and is not a hasCap", func() {
success, err := MatchCap(Equal(1)).Match(5)
Expect(success).To(BeFalse())
Expect(err).To(MatchError(`MatchCap matcher expects a string/array/map/channel/slice, or type with a Cap() int method. Got:
<int>: 5`))
})
})

type CustomCap struct {
cap int
}

func (cl CustomCap) Cap() int {
return cl.cap
}

0 comments on commit 639d06b

Please sign in to comment.