Merge pull request #150 from tinygrasshopper/build-fix
Fixing build on travis
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/ghttp/test_server.go b/ghttp/test_server.go
index f5f537e..2f76bb2 100644
--- a/ghttp/test_server.go
+++ b/ghttp/test_server.go
@@ -223,7 +223,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
diff --git a/matchers.go b/matchers.go
index 12d2b96..0c30aa1 100644
--- a/matchers.go
+++ b/matchers.go
@@ -214,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{}
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"))
+ })
+ })
+})