Merge remote-tracking branch 'origin/pr/163'
diff --git a/.gitignore b/.gitignore
index 5514532..720c13c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.DS_Store
*.test
.
+.idea
+gomega.iml
diff --git a/.travis.yml b/.travis.yml
index 79780ec..8ed1e44 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,8 @@
language: go
go:
- - 1.4
- 1.5
+ - 1.6.2
+ - stable
install:
- go get -v ./...
diff --git a/format/format_test.go b/format/format_test.go
index fd926f5..59517fe 100644
--- a/format/format_test.go
+++ b/format/format_test.go
@@ -2,11 +2,11 @@
import (
"fmt"
- "strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
+ "strings"
)
//recursive struct
diff --git a/gbytes/say_matcher_test.go b/gbytes/say_matcher_test.go
index d0ddf1f..63fb3b3 100644
--- a/gbytes/say_matcher_test.go
+++ b/gbytes/say_matcher_test.go
@@ -1,8 +1,8 @@
package gbytes_test
import (
- "time"
. "github.com/onsi/gomega/gbytes"
+ "time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
diff --git a/gexec/build.go b/gexec/build.go
index 3e9bf9f..220c8c4 100644
--- a/gexec/build.go
+++ b/gexec/build.go
@@ -9,9 +9,13 @@
"path"
"path/filepath"
"runtime"
+ "sync"
)
-var tmpDir string
+var (
+ mu sync.Mutex
+ tmpDir string
+)
/*
Build uses go build to compile the package at packagePath. The resulting binary is saved off in a temporary directory.
@@ -60,13 +64,18 @@
gexec. In Ginkgo this is typically done in an AfterSuite callback.
*/
func CleanupBuildArtifacts() {
+ mu.Lock()
+ defer mu.Unlock()
if tmpDir != "" {
os.RemoveAll(tmpDir)
+ tmpDir = ""
}
}
func temporaryDirectory() (string, error) {
var err error
+ mu.Lock()
+ defer mu.Unlock()
if tmpDir == "" {
tmpDir, err = ioutil.TempDir("", "gexec_artifacts")
if err != nil {
diff --git a/gexec/build_test.go b/gexec/build_test.go
new file mode 100644
index 0000000..7bd62fe
--- /dev/null
+++ b/gexec/build_test.go
@@ -0,0 +1,37 @@
+package gexec_test
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe(".Build", func() {
+ var packagePath = "./_fixture/firefly"
+
+ Context("when there have been previous calls to Build", func() {
+ BeforeEach(func() {
+ _, err := gexec.Build(packagePath)
+ Ω(err).ShouldNot(HaveOccurred())
+ })
+
+ It("compiles the specified package", func() {
+ compiledPath, err := gexec.Build(packagePath)
+ Ω(err).ShouldNot(HaveOccurred())
+ Ω(compiledPath).Should(BeAnExistingFile())
+ })
+
+ Context("and CleanupBuildArtifacts has been called", func() {
+ BeforeEach(func() {
+ gexec.CleanupBuildArtifacts()
+ })
+
+ It("compiles the specified package", func() {
+ var err error
+ fireflyPath, err = gexec.Build(packagePath)
+ Ω(err).ShouldNot(HaveOccurred())
+ Ω(fireflyPath).Should(BeAnExistingFile())
+ })
+ })
+ })
+})
diff --git a/gexec/exit_matcher_test.go b/gexec/exit_matcher_test.go
index 9f18e2d..79615dd 100644
--- a/gexec/exit_matcher_test.go
+++ b/gexec/exit_matcher_test.go
@@ -1,9 +1,9 @@
package gexec_test
import (
+ . "github.com/onsi/gomega/gexec"
"os/exec"
"time"
- . "github.com/onsi/gomega/gexec"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
diff --git a/ghttp/test_server.go b/ghttp/test_server.go
index fde65be..093550d 100644
--- a/ghttp/test_server.go
+++ b/ghttp/test_server.go
@@ -202,7 +202,9 @@
server := s.HTTPTestServer
s.HTTPTestServer = nil
- server.Close()
+ if server != nil {
+ server.Close()
+ }
}
//ServeHTTP() makes Server an http.Handler
@@ -223,7 +225,7 @@
//If the handler panics GHTTP will silently succeed. This is bad™.
//To catch this case we need to fail the test if the handler has panicked.
- //However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an asswertion failed)
+ //However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an assertion failed)
//then we shouldn't double-report the error as this will confuse people.
//So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure
@@ -349,6 +351,17 @@
return s.requestHandlers[index]
}
+func (s *Server) Reset() {
+ s.writeLock.Lock()
+ defer s.writeLock.Unlock()
+
+ s.HTTPTestServer.CloseClientConnections()
+ s.calls = 0
+ s.receivedRequests = nil
+ s.requestHandlers = nil
+ s.routedHandlers = nil
+}
+
//WrapHandler combines the passed in handler with the handler registered at the passed in index.
//This is useful, for example, when a server has been set up in a shared context but must be tweaked
//for a particular test.
diff --git a/ghttp/test_server_test.go b/ghttp/test_server_test.go
index 292b51d..88b3246 100644
--- a/ghttp/test_server_test.go
+++ b/ghttp/test_server_test.go
@@ -2,6 +2,7 @@
import (
"bytes"
+ "io"
"io/ioutil"
"net/http"
"net/url"
@@ -31,29 +32,56 @@
s.Close()
})
+ Describe("Resetting the server", func() {
+ BeforeEach(func() {
+ s.RouteToHandler("GET", "/", func(w http.ResponseWriter, req *http.Request) {})
+ s.AppendHandlers(func(w http.ResponseWriter, req *http.Request) {})
+ http.Get(s.URL() + "/")
+
+ Ω(s.ReceivedRequests()).Should(HaveLen(1))
+ })
+
+ It("clears all handlers and call counts", func() {
+ s.Reset()
+ Ω(s.ReceivedRequests()).Should(HaveLen(0))
+ Ω(func() { s.GetHandler(0) }).Should(Panic())
+ })
+ })
+
Describe("closing client connections", func() {
It("closes", func() {
- s.AppendHandlers(
+ s.RouteToHandler("GET", "/",
func(w http.ResponseWriter, req *http.Request) {
- w.Write([]byte("hello"))
- },
- func(w http.ResponseWriter, req *http.Request) {
- s.CloseClientConnections()
+ io.WriteString(w, req.RemoteAddr)
},
)
-
- resp, err := http.Get(s.URL())
+ client := http.Client{Transport: &http.Transport{DisableKeepAlives: true}}
+ resp, err := client.Get(s.URL())
Ω(err).ShouldNot(HaveOccurred())
Ω(resp.StatusCode).Should(Equal(200))
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
Ω(err).ShouldNot(HaveOccurred())
- Ω(body).Should(Equal([]byte("hello")))
- resp, err = http.Get(s.URL())
- Ω(err).Should(HaveOccurred())
- Ω(resp).Should(BeNil())
+ s.CloseClientConnections()
+
+ resp, err = client.Get(s.URL())
+ Ω(err).ShouldNot(HaveOccurred())
+ Ω(resp.StatusCode).Should(Equal(200))
+
+ body2, err := ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ Ω(err).ShouldNot(HaveOccurred())
+
+ Ω(body2).ShouldNot(Equal(body))
+ })
+ })
+
+ Describe("closing server mulitple times", func() {
+ It("should not fail", func() {
+ s.Close()
+ Ω(s.Close).ShouldNot(Panic())
})
})
diff --git a/matchers.go b/matchers.go
index b6110c4..0c30aa1 100644
--- a/matchers.go
+++ b/matchers.go
@@ -26,6 +26,15 @@
}
}
+//BeIdenticalTo uses the == operator to compare actual with expected.
+//BeIdenticalTo is strict about types when performing comparisons.
+//It is an error for both actual and expected to be nil. Use BeNil() instead.
+func BeIdenticalTo(expected interface{}) types.GomegaMatcher {
+ return &matchers.BeIdenticalToMatcher{
+ Expected: expected,
+ }
+}
+
//BeNil succeeds if actual is nil
func BeNil() types.GomegaMatcher {
return &matchers.BeNilMatcher{}
@@ -205,6 +214,15 @@
}
}
+//MatchYAML succeeds if actual is a string or stringer of YAML that matches
+//the expected YAML. The YAML's are decoded and the resulting objects are compared via
+//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
+func MatchYAML(yaml interface{}) types.GomegaMatcher {
+ return &matchers.MatchYAMLMatcher{
+ YAMLToMatch: yaml,
+ }
+}
+
//BeEmpty succeeds if actual is empty. Actual must be of type string, array, map, chan, or slice.
func BeEmpty() types.GomegaMatcher {
return &matchers.BeEmptyMatcher{}
@@ -217,6 +235,13 @@
}
}
+//HaveCap succeeds if actual has the passed-in capacity. Actual must be of type array, chan, or slice.
+func HaveCap(count int) types.GomegaMatcher {
+ return &matchers.HaveCapMatcher{
+ Count: count,
+ }
+}
+
//BeZero succeeds if actual is the zero value for its type or if actual is nil.
func BeZero() types.GomegaMatcher {
return &matchers.BeZeroMatcher{}
diff --git a/matchers/be_identical_to.go b/matchers/be_identical_to.go
new file mode 100644
index 0000000..fdcda4d
--- /dev/null
+++ b/matchers/be_identical_to.go
@@ -0,0 +1,37 @@
+package matchers
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/onsi/gomega/format"
+)
+
+type BeIdenticalToMatcher struct {
+ Expected interface{}
+}
+
+func (matcher *BeIdenticalToMatcher) Match(actual interface{}) (success bool, matchErr error) {
+ if actual == nil && matcher.Expected == nil {
+ return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
+ }
+
+ defer func() {
+ if r := recover(); r != nil {
+ if _, ok := r.(runtime.Error); ok {
+ success = false
+ matchErr = nil
+ }
+ }
+ }()
+
+ return actual == matcher.Expected, nil
+}
+
+func (matcher *BeIdenticalToMatcher) FailureMessage(actual interface{}) string {
+ return format.Message(actual, "to be identical to", matcher.Expected)
+}
+
+func (matcher *BeIdenticalToMatcher) NegatedFailureMessage(actual interface{}) string {
+ return format.Message(actual, "not to be identical to", matcher.Expected)
+}
diff --git a/matchers/be_identical_to_test.go b/matchers/be_identical_to_test.go
new file mode 100644
index 0000000..8b90a1a
--- /dev/null
+++ b/matchers/be_identical_to_test.go
@@ -0,0 +1,61 @@
+package matchers_test
+
+import (
+ "errors"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/matchers"
+)
+
+var _ = Describe("BeIdenticalTo", func() {
+ Context("when asserting that nil equals nil", func() {
+ It("should error", func() {
+ success, err := (&BeIdenticalToMatcher{Expected: nil}).Match(nil)
+
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(HaveOccurred())
+ })
+ })
+
+ It("should treat the same pointer to a struct as identical", func() {
+ mySpecialStruct := myCustomType{}
+ Ω(&mySpecialStruct).Should(BeIdenticalTo(&mySpecialStruct))
+ Ω(&myCustomType{}).ShouldNot(BeIdenticalTo(&mySpecialStruct))
+ })
+
+ It("should be strict about types", func() {
+ Ω(5).ShouldNot(BeIdenticalTo("5"))
+ Ω(5).ShouldNot(BeIdenticalTo(5.0))
+ Ω(5).ShouldNot(BeIdenticalTo(3))
+ })
+
+ It("should treat primtives as identical", func() {
+ Ω("5").Should(BeIdenticalTo("5"))
+ Ω("5").ShouldNot(BeIdenticalTo("55"))
+
+ Ω(5.55).Should(BeIdenticalTo(5.55))
+ Ω(5.55).ShouldNot(BeIdenticalTo(6.66))
+
+ Ω(5).Should(BeIdenticalTo(5))
+ Ω(5).ShouldNot(BeIdenticalTo(55))
+ })
+
+ It("should treat the same pointers to a slice as identical", func() {
+ mySlice := []int{1, 2}
+ Ω(&mySlice).Should(BeIdenticalTo(&mySlice))
+ Ω(&mySlice).ShouldNot(BeIdenticalTo(&[]int{1, 2}))
+ })
+
+ It("should treat the same pointers to a map as identical", func() {
+ myMap := map[string]string{"a": "b", "c": "d"}
+ Ω(&myMap).Should(BeIdenticalTo(&myMap))
+ Ω(myMap).ShouldNot(BeIdenticalTo(map[string]string{"a": "b", "c": "d"}))
+ })
+
+ It("should treat the same pointers to an error as identical", func() {
+ myError := errors.New("foo")
+ Ω(&myError).Should(BeIdenticalTo(&myError))
+ Ω(errors.New("foo")).ShouldNot(BeIdenticalTo(errors.New("bar")))
+ })
+})
diff --git a/matchers/be_sent_matcher_test.go b/matchers/be_sent_matcher_test.go
index 381c2b4..205d71f 100644
--- a/matchers/be_sent_matcher_test.go
+++ b/matchers/be_sent_matcher_test.go
@@ -1,8 +1,8 @@
package matchers_test
import (
- "time"
. "github.com/onsi/gomega/matchers"
+ "time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
diff --git a/matchers/consist_of_test.go b/matchers/consist_of_test.go
index 0b230e3..dcd1afe 100644
--- a/matchers/consist_of_test.go
+++ b/matchers/consist_of_test.go
@@ -54,10 +54,10 @@
Ω([]string{"foo", "bar", "baz"}).ShouldNot(ConsistOf("foo", MatchRegexp("^ba"), MatchRegexp("turducken")))
})
- It("should not depend on the order of the matchers", func() {
+ It("should not depend on the order of the matchers", func() {
Ω([][]int{[]int{1, 2}, []int{2}}).Should(ConsistOf(ContainElement(1), ContainElement(2)))
Ω([][]int{[]int{1, 2}, []int{2}}).Should(ConsistOf(ContainElement(2), ContainElement(1)))
- })
+ })
Context("when a matcher errors", func() {
It("should soldier on", func() {
diff --git a/matchers/have_cap_matcher.go b/matchers/have_cap_matcher.go
new file mode 100644
index 0000000..7ace93d
--- /dev/null
+++ b/matchers/have_cap_matcher.go
@@ -0,0 +1,28 @@
+package matchers
+
+import (
+ "fmt"
+
+ "github.com/onsi/gomega/format"
+)
+
+type HaveCapMatcher struct {
+ Count int
+}
+
+func (matcher *HaveCapMatcher) Match(actual interface{}) (success bool, err error) {
+ length, ok := capOf(actual)
+ if !ok {
+ return false, fmt.Errorf("HaveCap matcher expects a array/channel/slice. Got:\n%s", format.Object(actual, 1))
+ }
+
+ return length == matcher.Count, nil
+}
+
+func (matcher *HaveCapMatcher) FailureMessage(actual interface{}) (message string) {
+ return fmt.Sprintf("Expected\n%s\nto have capacity %d", format.Object(actual, 1), matcher.Count)
+}
+
+func (matcher *HaveCapMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+ return fmt.Sprintf("Expected\n%s\nnot to have capacity %d", format.Object(actual, 1), matcher.Count)
+}
diff --git a/matchers/have_cap_matcher_test.go b/matchers/have_cap_matcher_test.go
new file mode 100644
index 0000000..a92a177
--- /dev/null
+++ b/matchers/have_cap_matcher_test.go
@@ -0,0 +1,50 @@
+package matchers_test
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/matchers"
+)
+
+var _ = Describe("HaveCap", func() {
+ Context("when passed a supported type", func() {
+ It("should do the right thing", func() {
+ Ω([0]int{}).Should(HaveCap(0))
+ Ω([2]int{1}).Should(HaveCap(2))
+
+ Ω([]int{}).Should(HaveCap(0))
+ Ω([]int{1, 2, 3, 4, 5}[:2]).Should(HaveCap(5))
+ Ω(make([]int, 0, 5)).Should(HaveCap(5))
+
+ c := make(chan bool, 3)
+ Ω(c).Should(HaveCap(3))
+ c <- true
+ c <- true
+ Ω(c).Should(HaveCap(3))
+
+ Ω(make(chan bool)).Should(HaveCap(0))
+ })
+ })
+
+ Context("when passed a correctly typed nil", func() {
+ It("should operate succesfully on the passed in value", func() {
+ var nilSlice []int
+ Ω(nilSlice).Should(HaveCap(0))
+
+ var nilChan chan int
+ Ω(nilChan).Should(HaveCap(0))
+ })
+ })
+
+ Context("when passed an unsupported type", func() {
+ It("should error", func() {
+ success, err := (&HaveCapMatcher{Count: 0}).Match(0)
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(HaveOccurred())
+
+ success, err = (&HaveCapMatcher{Count: 0}).Match(nil)
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(HaveOccurred())
+ })
+ })
+})
diff --git a/matchers/have_occurred_matcher.go b/matchers/have_occurred_matcher.go
index cdc1d54..ebdd717 100644
--- a/matchers/have_occurred_matcher.go
+++ b/matchers/have_occurred_matcher.go
@@ -10,15 +10,18 @@
}
func (matcher *HaveOccurredMatcher) Match(actual interface{}) (success bool, err error) {
- if isNil(actual) {
+ // is purely nil?
+ if actual == nil {
return false, nil
}
- if isError(actual) {
- return true, nil
+ // must be an 'error' type
+ if !isError(actual) {
+ return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
}
- return false, fmt.Errorf("Expected an error. Got:\n%s", format.Object(actual, 1))
+ // must be non-nil (or a pointer to a non-nil)
+ return !isNil(actual), nil
}
func (matcher *HaveOccurredMatcher) FailureMessage(actual interface{}) (message string) {
diff --git a/matchers/have_occurred_matcher_test.go b/matchers/have_occurred_matcher_test.go
index 0fc35a9..009e23e 100644
--- a/matchers/have_occurred_matcher_test.go
+++ b/matchers/have_occurred_matcher_test.go
@@ -34,6 +34,18 @@
Ω(err).Should(HaveOccurred())
})
+ It("doesn't support non-error type", func() {
+ success, err := (&HaveOccurredMatcher{}).Match(AnyType{})
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(MatchError("Expected an error-type. Got:\n <matchers_test.AnyType>: {}"))
+ })
+
+ It("doesn't support non-error pointer type", func() {
+ success, err := (&HaveOccurredMatcher{}).Match(&AnyType{})
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(MatchError(MatchRegexp(`Expected an error-type. Got:\n <*matchers_test.AnyType | 0x[[:xdigit:]]+>: {}`)))
+ })
+
It("should succeed with pointer types that conform to error interface", func() {
err := &CustomErr{"ohai"}
Ω(err).Should(HaveOccurred())
diff --git a/matchers/have_suffix_matcher.go b/matchers/have_suffix_matcher.go
index eb1b284..afc78fc 100644
--- a/matchers/have_suffix_matcher.go
+++ b/matchers/have_suffix_matcher.go
@@ -16,7 +16,7 @@
return false, fmt.Errorf("HaveSuffix matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
}
suffix := matcher.suffix()
- return len(actualString) >= len(suffix) && actualString[len(actualString) - len(suffix):] == suffix, nil
+ return len(actualString) >= len(suffix) && actualString[len(actualString)-len(suffix):] == suffix, nil
}
func (matcher *HaveSuffixMatcher) suffix() string {
diff --git a/matchers/match_json_matcher.go b/matchers/match_json_matcher.go
index efc5e15..e61978a 100644
--- a/matchers/match_json_matcher.go
+++ b/matchers/match_json_matcher.go
@@ -4,8 +4,9 @@
"bytes"
"encoding/json"
"fmt"
- "github.com/onsi/gomega/format"
"reflect"
+
+ "github.com/onsi/gomega/format"
)
type MatchJSONMatcher struct {
@@ -39,22 +40,24 @@
}
func (matcher *MatchJSONMatcher) prettyPrint(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
- actualString, aok := toString(actual)
- expectedString, eok := toString(matcher.JSONToMatch)
-
- if !(aok && eok) {
- return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string or stringer. Got:\n%s", format.Object(actual, 1))
+ actualString, ok := toString(actual)
+ if !ok {
+ return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
+ }
+ expectedString, ok := toString(matcher.JSONToMatch)
+ if !ok {
+ return "", "", fmt.Errorf("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.JSONToMatch, 1))
}
abuf := new(bytes.Buffer)
ebuf := new(bytes.Buffer)
if err := json.Indent(abuf, []byte(actualString), "", " "); err != nil {
- return "", "", err
+ return "", "", fmt.Errorf("Actual '%s' should be valid JSON, but it is not.\nUnderlying error:%s", actualString, err)
}
if err := json.Indent(ebuf, []byte(expectedString), "", " "); err != nil {
- return "", "", err
+ return "", "", fmt.Errorf("Expected '%s' should be valid JSON, but it is not.\nUnderlying error:%s", expectedString, err)
}
return abuf.String(), ebuf.String(), nil
diff --git a/matchers/match_json_matcher_test.go b/matchers/match_json_matcher_test.go
index c1924ba..755c4ad 100644
--- a/matchers/match_json_matcher_test.go
+++ b/matchers/match_json_matcher_test.go
@@ -25,35 +25,49 @@
})
})
- Context("when either side is not valid JSON", func() {
- It("should error", func() {
- success, err := (&MatchJSONMatcher{JSONToMatch: `oops`}).Match(`{}`)
+ Context("when the expected is not valid JSON", func() {
+ It("should error and explain why", func() {
+ success, err := (&MatchJSONMatcher{JSONToMatch: `{}`}).Match(`oops`)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
-
- success, err = (&MatchJSONMatcher{JSONToMatch: `{}`}).Match(`oops`)
- Ω(success).Should(BeFalse())
- Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("Actual 'oops' should be valid JSON"))
})
})
- Context("when either side is neither a string nor a stringer", func() {
- It("should error", func() {
- success, err := (&MatchJSONMatcher{JSONToMatch: "{}"}).Match(2)
+ Context("when the actual is not valid JSON", func() {
+ It("should error and explain why", func() {
+ success, err := (&MatchJSONMatcher{JSONToMatch: `oops`}).Match(`{}`)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("Expected 'oops' should be valid JSON"))
+ })
+ })
- success, err = (&MatchJSONMatcher{JSONToMatch: 2}).Match("{}")
+ Context("when the expected is neither a string nor a stringer nor a byte array", func() {
+ It("should error", func() {
+ success, err := (&MatchJSONMatcher{JSONToMatch: 2}).Match("{}")
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got expected:\n <int>: 2"))
success, err = (&MatchJSONMatcher{JSONToMatch: nil}).Match("{}")
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got expected:\n <nil>: nil"))
+ })
+ })
- success, err = (&MatchJSONMatcher{JSONToMatch: 2}).Match(nil)
+ Context("when the actual is neither a string nor a stringer nor a byte array", func() {
+ It("should error", func() {
+ success, err := (&MatchJSONMatcher{JSONToMatch: "{}"}).Match(2)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got actual:\n <int>: 2"))
+
+ success, err = (&MatchJSONMatcher{JSONToMatch: "{}"}).Match(nil)
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(HaveOccurred())
+ Ω(err.Error()).Should(ContainSubstring("MatchJSONMatcher matcher requires a string, stringer, or []byte. Got actual:\n <nil>: nil"))
})
})
})
diff --git a/matchers/match_yaml_matcher.go b/matchers/match_yaml_matcher.go
new file mode 100644
index 0000000..69fb51a
--- /dev/null
+++ b/matchers/match_yaml_matcher.go
@@ -0,0 +1,74 @@
+package matchers
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/onsi/gomega/format"
+ "gopkg.in/yaml.v2"
+)
+
+type MatchYAMLMatcher struct {
+ YAMLToMatch interface{}
+}
+
+func (matcher *MatchYAMLMatcher) Match(actual interface{}) (success bool, err error) {
+ actualString, expectedString, err := matcher.toStrings(actual)
+ if err != nil {
+ return false, err
+ }
+
+ var aval interface{}
+ var eval interface{}
+
+ if err := yaml.Unmarshal([]byte(actualString), &aval); err != nil {
+ return false, fmt.Errorf("Actual '%s' should be valid YAML, but it is not.\nUnderlying error:%s", actualString, err)
+ }
+ if err := yaml.Unmarshal([]byte(expectedString), &eval); err != nil {
+ return false, fmt.Errorf("Expected '%s' should be valid YAML, but it is not.\nUnderlying error:%s", expectedString, err)
+ }
+
+ return reflect.DeepEqual(aval, eval), nil
+}
+
+func (matcher *MatchYAMLMatcher) FailureMessage(actual interface{}) (message string) {
+ actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
+ return format.Message(actualString, "to match YAML of", expectedString)
+}
+
+func (matcher *MatchYAMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
+ actualString, expectedString, _ := matcher.toNormalisedStrings(actual)
+ return format.Message(actualString, "not to match YAML of", expectedString)
+}
+
+func (matcher *MatchYAMLMatcher) toNormalisedStrings(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
+ actualString, expectedString, err := matcher.toStrings(actual)
+ return normalise(actualString), normalise(expectedString), err
+}
+
+func normalise(input string) string {
+ var val interface{}
+ err := yaml.Unmarshal([]byte(input), &val)
+ if err != nil {
+ panic(err) // guarded by Match
+ }
+ output, err := yaml.Marshal(val)
+ if err != nil {
+ panic(err) // guarded by Unmarshal
+ }
+ return strings.TrimSpace(string(output))
+}
+
+func (matcher *MatchYAMLMatcher) toStrings(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
+ actualString, ok := toString(actual)
+ if !ok {
+ return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
+ }
+ expectedString, ok := toString(matcher.YAMLToMatch)
+ if !ok {
+ return "", "", fmt.Errorf("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.YAMLToMatch, 1))
+ }
+
+ return actualString, expectedString, nil
+}
diff --git a/matchers/match_yaml_matcher_test.go b/matchers/match_yaml_matcher_test.go
new file mode 100644
index 0000000..8e63de1
--- /dev/null
+++ b/matchers/match_yaml_matcher_test.go
@@ -0,0 +1,94 @@
+package matchers_test
+
+import (
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+
+ . "github.com/onsi/gomega/matchers"
+)
+
+var _ = Describe("MatchYAMLMatcher", func() {
+ Context("When passed stringifiables", func() {
+ It("should succeed if the YAML matches", func() {
+ Expect("---").Should(MatchYAML(""))
+ Expect("a: 1").Should(MatchYAML(`{"a":1}`))
+ Expect("a: 1\nb: 2").Should(MatchYAML(`{"b":2, "a":1}`))
+ })
+
+ It("should explain if the YAML does not match when it should", func() {
+ message := (&MatchYAMLMatcher{YAMLToMatch: "a: 1"}).FailureMessage("b: 2")
+ Expect(message).To(MatchRegexp(`Expected\s+<string>: b: 2\s+to match YAML of\s+<string>: a: 1`))
+ })
+
+ It("should normalise the expected and actual when explaining if the YAML does not match when it should", func() {
+ message := (&MatchYAMLMatcher{YAMLToMatch: "a: 'one'"}).FailureMessage("{b: two}")
+ Expect(message).To(MatchRegexp(`Expected\s+<string>: b: two\s+to match YAML of\s+<string>: a: one`))
+ })
+
+ It("should explain if the YAML matches when it should not", func() {
+ message := (&MatchYAMLMatcher{YAMLToMatch: "a: 1"}).NegatedFailureMessage("a: 1")
+ Expect(message).To(MatchRegexp(`Expected\s+<string>: a: 1\s+not to match YAML of\s+<string>: a: 1`))
+ })
+
+ It("should normalise the expected and actual when explaining if the YAML matches when it should not", func() {
+ message := (&MatchYAMLMatcher{YAMLToMatch: "a: 'one'"}).NegatedFailureMessage("{a: one}")
+ Expect(message).To(MatchRegexp(`Expected\s+<string>: a: one\s+not to match YAML of\s+<string>: a: one`))
+ })
+
+ It("should fail if the YAML does not match", func() {
+ Expect("a: 1").ShouldNot(MatchYAML(`{"b":2, "a":1}`))
+ })
+
+ It("should work with byte arrays", func() {
+ Expect([]byte("a: 1")).Should(MatchYAML([]byte("a: 1")))
+ Expect("a: 1").Should(MatchYAML([]byte("a: 1")))
+ Expect([]byte("a: 1")).Should(MatchYAML("a: 1"))
+ })
+ })
+
+ Context("when the expected is not valid YAML", func() {
+ It("should error and explain why", func() {
+ success, err := (&MatchYAMLMatcher{YAMLToMatch: ""}).Match("good:\nbad")
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("Actual 'good:\nbad' should be valid YAML"))
+ })
+ })
+
+ Context("when the actual is not valid YAML", func() {
+ It("should error and explain why", func() {
+ success, err := (&MatchYAMLMatcher{YAMLToMatch: "good:\nbad"}).Match("")
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("Expected 'good:\nbad' should be valid YAML"))
+ })
+ })
+
+ Context("when the expected is neither a string nor a stringer nor a byte array", func() {
+ It("should error", func() {
+ success, err := (&MatchYAMLMatcher{YAMLToMatch: 2}).Match("")
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n <int>: 2"))
+
+ success, err = (&MatchYAMLMatcher{YAMLToMatch: nil}).Match("")
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n <nil>: nil"))
+ })
+ })
+
+ Context("when the actual is neither a string nor a stringer nor a byte array", func() {
+ It("should error", func() {
+ success, err := (&MatchYAMLMatcher{YAMLToMatch: ""}).Match(2)
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <int>: 2"))
+
+ success, err = (&MatchYAMLMatcher{YAMLToMatch: ""}).Match(nil)
+ Expect(success).Should(BeFalse())
+ Expect(err).Should(HaveOccurred())
+ Expect(err.Error()).Should(ContainSubstring("MatchYAMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <nil>: nil"))
+ })
+ })
+})
diff --git a/matchers/succeed_matcher.go b/matchers/succeed_matcher.go
index f7dd853..721ed55 100644
--- a/matchers/succeed_matcher.go
+++ b/matchers/succeed_matcher.go
@@ -10,15 +10,18 @@
}
func (matcher *SucceedMatcher) Match(actual interface{}) (success bool, err error) {
+ // is purely nil?
if actual == nil {
return true, nil
}
- if isError(actual) {
- return false, nil
+ // must be an 'error' type
+ if !isError(actual) {
+ return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
}
- return false, fmt.Errorf("Expected an error-type. Got:\n%s", format.Object(actual, 1))
+ // must be nil (or a pointer to a nil)
+ return isNil(actual), nil
}
func (matcher *SucceedMatcher) FailureMessage(actual interface{}) (message string) {
diff --git a/matchers/succeed_matcher_test.go b/matchers/succeed_matcher_test.go
index 3562e70..6b62c8b 100644
--- a/matchers/succeed_matcher_test.go
+++ b/matchers/succeed_matcher_test.go
@@ -34,6 +34,29 @@
It("should not if passed a non-error", func() {
success, err := (&SucceedMatcher{}).Match(Invalid())
Ω(success).Should(BeFalse())
- Ω(err).Should(HaveOccurred())
+ Ω(err).Should(MatchError("Expected an error-type. Got:\n <*matchers_test.AnyType | 0x0>: nil"))
})
+
+ It("doesn't support non-error type", func() {
+ success, err := (&SucceedMatcher{}).Match(AnyType{})
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(MatchError("Expected an error-type. Got:\n <matchers_test.AnyType>: {}"))
+ })
+
+ It("doesn't support non-error pointer type", func() {
+ success, err := (&SucceedMatcher{}).Match(&AnyType{})
+ Ω(success).Should(BeFalse())
+ Ω(err).Should(MatchError(MatchRegexp(`Expected an error-type. Got:\n <*matchers_test.AnyType | 0x[[:xdigit:]]+>: {}`)))
+ })
+
+ It("should not succeed with pointer types that conform to error interface", func() {
+ err := &CustomErr{"ohai"}
+ Ω(err).ShouldNot(Succeed())
+ })
+
+ It("should succeed with nil pointers to types that conform to error interface", func() {
+ var err *CustomErr = nil
+ Ω(err).Should(Succeed())
+ })
+
})
diff --git a/matchers/support/goraph/util/util.go b/matchers/support/goraph/util/util.go
index a24cd27..d76a1ee 100644
--- a/matchers/support/goraph/util/util.go
+++ b/matchers/support/goraph/util/util.go
@@ -3,5 +3,5 @@
import "math"
func Odd(n int) bool {
- return math.Mod(float64(n), 2.0) == 1.0
-}
+ return math.Mod(float64(n), 2.0) == 1.0
+}
diff --git a/matchers/type_support.go b/matchers/type_support.go
index ef9b448..04020f0 100644
--- a/matchers/type_support.go
+++ b/matchers/type_support.go
@@ -150,6 +150,17 @@
return 0, false
}
}
+func capOf(a interface{}) (int, bool) {
+ if a == nil {
+ return 0, false
+ }
+ switch reflect.TypeOf(a).Kind() {
+ case reflect.Array, reflect.Chan, reflect.Slice:
+ return reflect.ValueOf(a).Cap(), true
+ default:
+ return 0, false
+ }
+}
func isNil(a interface{}) bool {
if a == nil {