Skip to content

Commit

Permalink
Adds support for MatchXML
Browse files Browse the repository at this point in the history
Actual and expected can not be pretty printed in the test output because
there are issues in the encoding/xml package related to deplicated
namespaces. See golang/go#7535

Because Go has no support for collecting all XML attributes before 1.8
(see [here](golang/go@c1a1328)),
the two following XMLs will be equal for 1.7 and before:

<person gender="female">

<person gender="male">
  • Loading branch information
aubm committed Apr 30, 2017
1 parent 9b8c753 commit a6b7b3d
Show file tree
Hide file tree
Showing 18 changed files with 525 additions and 0 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ language: go
go:
- 1.6
- 1.7
- 1.8

install:
- go get -v ./...
Expand Down
14 changes: 14 additions & 0 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,20 @@ func MatchJSON(json interface{}) types.GomegaMatcher {
}
}

//MatchXML succeeds if actual is a string or stringer of XML that matches
//the expected XML. The XMLs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
//The comparison of attribute values is not supported for go1.7 and before.
//For go1.7, the two following XMLs are equal:
//<person gender="female">
//
//<person gender="male">
func MatchXML(xml interface{}) types.GomegaMatcher {
return &matchers.MatchXMLMatcher{
XMLToMatch: xml,
}
}

//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.
Expand Down
58 changes: 58 additions & 0 deletions matchers/match_xml_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package matchers

import (
"encoding/xml"
"fmt"
"reflect"

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

type MatchXMLMatcher struct {
XMLToMatch interface{}
}

func (matcher *MatchXMLMatcher) Match(actual interface{}) (success bool, err error) {
actualString, expectedString, err := matcher.formattedPrint(actual)
if err != nil {
return false, err
}

aval := &xmlNode{}
eval := &xmlNode{}

if err := xml.Unmarshal([]byte(actualString), aval); err != nil {
return false, fmt.Errorf("Actual '%s' should be valid XML, but it is not.\nUnderlying error:%s", actualString, err)
}
if err := xml.Unmarshal([]byte(expectedString), eval); err != nil {
return false, fmt.Errorf("Expected '%s' should be valid XML, but it is not.\nUnderlying error:%s", expectedString, err)
}

aval.Clean()
eval.Clean()

return reflect.DeepEqual(aval, eval), nil
}

func (matcher *MatchXMLMatcher) FailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nto match XML of\n%s", actualString, expectedString)
}

func (matcher *MatchXMLMatcher) NegatedFailureMessage(actual interface{}) (message string) {
actualString, expectedString, _ := matcher.formattedPrint(actual)
return fmt.Sprintf("Expected\n%s\nnot to match XML of\n%s", actualString, expectedString)
}

func (matcher *MatchXMLMatcher) formattedPrint(actual interface{}) (actualString, expectedString string, err error) {
var ok bool
actualString, ok = toString(actual)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
}
expectedString, ok = toString(matcher.XMLToMatch)
if !ok {
return "", "", fmt.Errorf("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n%s", format.Object(matcher.XMLToMatch, 1))
}
return actualString, expectedString, nil
}
22 changes: 22 additions & 0 deletions matchers/match_xml_matcher_go1.8_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// +build go1.8

package matchers_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("MatchXMLMatcher Go 1.8", func() {

var (
sample_09 = readFileContents("test_data/xml/sample_09.xml")
sample_10 = readFileContents("test_data/xml/sample_10.xml")
)

Context("When passed stringifiables", func() {
It("should succeed if the XML matches", func() {
Ω(sample_09).ShouldNot(MatchXML(sample_10)) // same structures with different attribute values
})
})
})
87 changes: 87 additions & 0 deletions matchers/match_xml_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// +build !go1.8

package matchers_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/matchers"
)

var _ = Describe("MatchXMLMatcher", func() {

var (
sample_01 = readFileContents("test_data/xml/sample_01.xml")
sample_02 = readFileContents("test_data/xml/sample_02.xml")
sample_03 = readFileContents("test_data/xml/sample_03.xml")
sample_04 = readFileContents("test_data/xml/sample_04.xml")
sample_05 = readFileContents("test_data/xml/sample_05.xml")
sample_06 = readFileContents("test_data/xml/sample_06.xml")
sample_07 = readFileContents("test_data/xml/sample_07.xml")
sample_08 = readFileContents("test_data/xml/sample_08.xml")
)

Context("When passed stringifiables", func() {
It("should succeed if the XML matches", func() {
Ω(sample_01).Should(MatchXML(sample_01)) // same XML
Ω(sample_01).Should(MatchXML(sample_02)) // same XML with blank lines
Ω(sample_01).Should(MatchXML(sample_03)) // same XML with different formatting
Ω(sample_01).ShouldNot(MatchXML(sample_04)) // same structures with different values
Ω(sample_01).ShouldNot(MatchXML(sample_05)) // different structures
Ω(sample_06).ShouldNot(MatchXML(sample_07)) // same xml names with different namespaces
Ω(sample_07).ShouldNot(MatchXML(sample_08)) // same structures with different values
})

It("should work with byte arrays", func() {
Ω([]byte(sample_01)).Should(MatchXML([]byte(sample_01)))
Ω([]byte(sample_01)).Should(MatchXML(sample_01))
Ω(sample_01).Should(MatchXML([]byte(sample_01)))
})
})

Context("when the expected is not valid XML", func() {
It("should error and explain why", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(`oops`)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("Actual 'oops' should be valid XML"))
})
})

Context("when the actual is not valid XML", func() {
It("should error and explain why", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: `oops`}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("Expected 'oops' should be valid XML"))
})
})

Context("when the expected is neither a string nor a stringer nor a byte array", func() {
It("should error", func() {
success, err := (&MatchXMLMatcher{XMLToMatch: 2}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got expected:\n <int>: 2"))

success, err = (&MatchXMLMatcher{XMLToMatch: nil}).Match(sample_01)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher 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 := (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(2)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <int>: 2"))

success, err = (&MatchXMLMatcher{XMLToMatch: sample_01}).Match(nil)
Ω(success).Should(BeFalse())
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("MatchXMLMatcher matcher requires a string, stringer, or []byte. Got actual:\n <nil>: nil"))
})
})
})
20 changes: 20 additions & 0 deletions matchers/matcher_tests_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package matchers_test

import (
"fmt"
"io/ioutil"
"os"
"testing"

. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -28,3 +31,20 @@ func Test(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Gomega Matchers")
}

func readFileContents(filePath string) []byte {
f := openFile(filePath)
b, err := ioutil.ReadAll(f)
if err != nil {
panic(fmt.Errorf("failed to read file contents: %v", err))
}
return b
}

func openFile(filePath string) *os.File {
f, err := os.Open(filePath)
if err != nil {
panic(fmt.Errorf("failed to open file: %v", err))
}
return f
}
6 changes: 6 additions & 0 deletions matchers/test_data/xml/sample_01.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
9 changes: 9 additions & 0 deletions matchers/test_data/xml/sample_02.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

1 change: 1 addition & 0 deletions matchers/test_data/xml/sample_03.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
6 changes: 6 additions & 0 deletions matchers/test_data/xml/sample_04.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<note>
<to>Tove</to>
<from>John</from>
<heading>Doe</heading>
<body>Don't forget me this weekend!</body>
</note>
Loading

0 comments on commit a6b7b3d

Please sign in to comment.