diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad6a567..53e7dff 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,7 +11,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
- version: v1.42
+ version: v1.43
env:
VERBOSE: "true"
diff --git a/Makefile b/Makefile
index 15b676b..e6248a7 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ endef
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc))
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt))
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports))
-$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42))
+$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod))
.PHONY: tools
diff --git a/README.md b/README.md
index 9f68dcd..e44fa8a 100644
--- a/README.md
+++ b/README.md
@@ -70,4 +70,4 @@ for documentation and examples.
## License
-[MIT](https://github.com/jimeh/go-golden/blob/master/LICENSE)
+[MIT](https://github.com/jimeh/go-golden/blob/main/LICENSE)
diff --git a/assert.go b/assert.go
index b18033d..f2ca44d 100644
--- a/assert.go
+++ b/assert.go
@@ -1,26 +1,16 @@
package golden
-import (
- "bytes"
- "encoding/json"
- "encoding/xml"
- "io"
- "testing"
-
- "gopkg.in/yaml.v3"
-)
-
-var globalAssert = NewAssert()
+var defaultAsserter = NewAsserter()
// AssertJSONMarshaling asserts that the given "v" value JSON marshals to an
// expected value fetched from a golden file on disk, and then verifies that the
// marshaled result produces a value that is equal to "v" when unmarshaled.
//
// Used for objects that do NOT change when they are marshaled and unmarshaled.
-func AssertJSONMarshaling(t *testing.T, v interface{}) {
+func AssertJSONMarshaling(t TestingT, v interface{}) {
t.Helper()
- globalAssert.JSONMarshaling(t, v)
+ defaultAsserter.JSONMarshaling(t, v)
}
// AssertJSONMarshalingP asserts that the given "v" value JSON marshals to an
@@ -28,10 +18,10 @@ func AssertJSONMarshaling(t *testing.T, v interface{}) {
// marshaled result produces a value that is equal to "want" when unmarshaled.
//
// Used for objects that change when they are marshaled and unmarshaled.
-func AssertJSONMarshalingP(t *testing.T, v, want interface{}) {
+func AssertJSONMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
- globalAssert.JSONMarshalingP(t, v, want)
+ defaultAsserter.JSONMarshalingP(t, v, want)
}
// AssertXMLMarshaling asserts that the given "v" value XML marshals to an
@@ -39,10 +29,10 @@ func AssertJSONMarshalingP(t *testing.T, v, want interface{}) {
// marshaled result produces a value that is equal to "v" when unmarshaled.
//
// Used for objects that do NOT change when they are marshaled and unmarshaled.
-func AssertXMLMarshaling(t *testing.T, v interface{}) {
+func AssertXMLMarshaling(t TestingT, v interface{}) {
t.Helper()
- globalAssert.XMLMarshaling(t, v)
+ defaultAsserter.XMLMarshaling(t, v)
}
// AssertXMLMarshalingP asserts that the given "v" value XML marshals to an
@@ -50,10 +40,10 @@ func AssertXMLMarshaling(t *testing.T, v interface{}) {
// marshaled result produces a value that is equal to "want" when unmarshaled.
//
// Used for objects that change when they are marshaled and unmarshaled.
-func AssertXMLMarshalingP(t *testing.T, v, want interface{}) {
+func AssertXMLMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
- globalAssert.XMLMarshalingP(t, v, want)
+ defaultAsserter.XMLMarshalingP(t, v, want)
}
// AssertYAMLMarshaling asserts that the given "v" value YAML marshals to an
@@ -61,10 +51,10 @@ func AssertXMLMarshalingP(t *testing.T, v, want interface{}) {
// marshaled result produces a value that is equal to "v" when unmarshaled.
//
// Used for objects that do NOT change when they are marshaled and unmarshaled.
-func AssertYAMLMarshaling(t *testing.T, v interface{}) {
+func AssertYAMLMarshaling(t TestingT, v interface{}) {
t.Helper()
- globalAssert.YAMLMarshaling(t, v)
+ defaultAsserter.YAMLMarshaling(t, v)
}
// AssertYAMLMarshalingP asserts that the given "v" value YAML marshals to an
@@ -72,236 +62,8 @@ func AssertYAMLMarshaling(t *testing.T, v interface{}) {
// marshaled result produces a value that is equal to "want" when unmarshaled.
//
// Used for objects that change when they are marshaled and unmarshaled.
-func AssertYAMLMarshalingP(t *testing.T, v, want interface{}) {
+func AssertYAMLMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
- globalAssert.YAMLMarshalingP(t, v, want)
-}
-
-// Assert exposes a series of JSON, YAML, and XML marshaling assertion helpers.
-type Assert interface {
- // JSONMarshaling asserts that the given "v" value JSON marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "v" when
- // unmarshaled.
- //
- // Used for objects that do NOT change when they are marshaled and
- // unmarshaled.
- JSONMarshaling(t *testing.T, v interface{})
-
- // JSONMarshalingP asserts that the given "v" value JSON marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "want" when
- // unmarshaled.
- //
- // Used for objects that change when they are marshaled and unmarshaled.
- JSONMarshalingP(t *testing.T, v interface{}, want interface{})
-
- // XMLMarshaling asserts that the given "v" value XML marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "v" when
- // unmarshaled.
- //
- // Used for objects that do NOT change when they are marshaled and
- // unmarshaled.
- XMLMarshaling(t *testing.T, v interface{})
-
- // XMLMarshalingP asserts that the given "v" value XML marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "want" when
- // unmarshaled.
- //
- // Used for objects that change when they are marshaled and unmarshaled.
- XMLMarshalingP(t *testing.T, v interface{}, want interface{})
-
- // YAMLMarshaling asserts that the given "v" value YAML marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "v" when
- // unmarshaled.
- //
- // Used for objects that do NOT change when they are marshaled and
- // unmarshaled.
- YAMLMarshaling(t *testing.T, v interface{})
-
- // YAMLMarshalingP asserts that the given "v" value YAML marshals to an
- // expected value fetched from a golden file on disk, and then verifies that
- // the marshaled result produces a value that is equal to "want" when
- // unmarshaled.
- //
- // Used for objects that change when they are marshaled and unmarshaled.
- YAMLMarshalingP(t *testing.T, v interface{}, want interface{})
-}
-
-type AssertOption interface {
- apply(*asserter)
-}
-
-type assertOptionFunc func(*asserter)
-
-func (fn assertOptionFunc) apply(c *asserter) {
- fn(c)
-}
-
-// WithGolden allows setting a custom *Golden instance when calling NewAssert().
-func WithGolden(golden *Golden) AssertOption {
- return assertOptionFunc(func(a *asserter) {
- a.golden = golden
- })
-}
-
-// WithNormalizedLineBreaks allows turning off line-break normalization which
-// replaces Windows' CRLF (\r\n) and Mac Classic CR (\r) line breaks with Unix's
-// LF (\n) line breaks.
-func WithNormalizedLineBreaks(value bool) AssertOption {
- return assertOptionFunc(func(a *asserter) {
- a.normalizeLineBreaks = value
- })
-}
-
-// NewAssert returns a new Assert which exposes a number of marshaling assertion
-// helpers for JSON, YAML and XML.
-//
-// The default encoders all specify indentation of two spaces, essentially
-// enforcing pretty formatting for JSON and XML.
-//
-// The default decoders for JSON and YAML prohibit unknown fields which are not
-// present on the provided struct.
-func NewAssert(options ...AssertOption) Assert {
- a := &asserter{
- golden: globalGolden,
- normalizeLineBreaks: true,
- }
-
- for _, opt := range options {
- opt.apply(a)
- }
-
- a.JSONAsserter = NewMarshalAsserter(
- a.golden, "JSON",
- newJSONEncoder, newJSONDecoder,
- a.normalizeLineBreaks,
- )
- a.XMLAsserter = NewMarshalAsserter(
- a.golden, "XML",
- newXMLEncoder, newXMLDecoder,
- a.normalizeLineBreaks,
- )
- a.YAMLAsserter = NewMarshalAsserter(
- a.golden, "YAML",
- newYAMLEncoder, newYAMLDecoder,
- a.normalizeLineBreaks,
- )
-
- return a
-}
-
-// asserter implements the Assert interface.
-type asserter struct {
- golden *Golden
- normalizeLineBreaks bool
-
- JSONAsserter *MarshalAsserter
- XMLAsserter *MarshalAsserter
- YAMLAsserter *MarshalAsserter
-}
-
-func (s *asserter) JSONMarshaling(t *testing.T, v interface{}) {
- t.Helper()
-
- s.JSONAsserter.Marshaling(t, v)
-}
-
-func (s *asserter) JSONMarshalingP(
- t *testing.T,
- v interface{},
- want interface{},
-) {
- t.Helper()
-
- s.JSONAsserter.MarshalingP(t, v, want)
-}
-
-func (s *asserter) XMLMarshaling(t *testing.T, v interface{}) {
- t.Helper()
-
- s.XMLAsserter.Marshaling(t, v)
-}
-
-func (s *asserter) XMLMarshalingP(t *testing.T, v, want interface{}) {
- t.Helper()
-
- s.XMLAsserter.MarshalingP(t, v, want)
-}
-
-func (s *asserter) YAMLMarshaling(t *testing.T, v interface{}) {
- t.Helper()
-
- s.YAMLAsserter.Marshaling(t, v)
-}
-
-func (s *asserter) YAMLMarshalingP(t *testing.T, v, want interface{}) {
- t.Helper()
-
- s.YAMLAsserter.MarshalingP(t, v, want)
-}
-
-// newJSONEncoder is the default JSONEncoderFunc used by Assert. It returns a
-// *json.Encoder which is set to indent with two spaces.
-func newJSONEncoder(w io.Writer) MarshalEncoder {
- enc := json.NewEncoder(w)
- enc.SetIndent("", " ")
-
- return enc
-}
-
-// newJSONDecoder is the default JSONDecoderFunc used by Assert. It returns a
-// *json.Decoder which disallows unknown fields.
-func newJSONDecoder(r io.Reader) MarshalDecoder {
- dec := json.NewDecoder(r)
- dec.DisallowUnknownFields()
-
- return dec
-}
-
-// newXMLEncoder is the default XMLEncoderFunc used by Assert. It returns a
-// *xml.Encoder which is set to indent with two spaces.
-func newXMLEncoder(w io.Writer) MarshalEncoder {
- enc := xml.NewEncoder(w)
- enc.Indent("", " ")
-
- return enc
-}
-
-// newXMLDecoder is the default XMLDecoderFunc used by Assert.
-func newXMLDecoder(r io.Reader) MarshalDecoder {
- return xml.NewDecoder(r)
-}
-
-// newYAMLEncoder is the default YAMLEncoderFunc used by Assert. It returns a
-// *yaml.Encoder which is set to indent with two spaces.
-func newYAMLEncoder(w io.Writer) MarshalEncoder {
- enc := yaml.NewEncoder(w)
- enc.SetIndent(2)
-
- return enc
-}
-
-// newYAMLDecoder is the default YAMLDecoderFunc used by Assert. It returns a
-// *yaml.Decoder which disallows unknown fields.
-func newYAMLDecoder(r io.Reader) MarshalDecoder {
- dec := yaml.NewDecoder(r)
- dec.KnownFields(true)
-
- return dec
-}
-
-// normalizeLineBreaks replaces Windows CRLF (\r\n) and Classic MacOS CR (\r)
-// line-breaks with Unix LF (\n) line breaks.
-func normalizeLineBreaks(data []byte) []byte {
- // Replace Windows CRLF (\r\n) with Unix LF (\n)
- result := bytes.ReplaceAll(data, []byte{13, 10}, []byte{10})
- // Replace Classic MacOS CR (\r) with Unix LF (\n)
- result = bytes.ReplaceAll(result, []byte{13}, []byte{10})
-
- return result
+ defaultAsserter.YAMLMarshalingP(t, v, want)
}
diff --git a/assert_test.go b/assert_test.go
index c88f7a6..dd336a0 100644
--- a/assert_test.go
+++ b/assert_test.go
@@ -8,6 +8,10 @@ import (
"testing"
"time"
+ "github.com/jimeh/go-golden/marshal"
+ "github.com/jimeh/go-golden/unmarshal"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
@@ -15,29 +19,30 @@ import (
// Helpers
//
-type Author struct {
+type author struct {
FirstName string `json:"first_name" yaml:"first_name" xml:"first_name"`
LastName string `json:"last_name" yaml:"last_name" xml:"last_name"`
}
-type Book struct {
+type book struct {
ID string `json:"id" yaml:"id" xml:"id"`
Title string `json:"title" yaml:"title" xml:"title"`
- Author *Author `json:"author,omitempty" yaml:"author,omitempty" xml:"author,omitempty"`
+ Author *author `json:"author,omitempty" yaml:"author,omitempty" xml:"author,omitempty"`
Year int `json:"year,omitempty" yaml:"year,omitempty" xml:"year,omitempty"`
}
-type Article struct {
+type article struct {
ID string `json:"id" yaml:"id" xml:"id"`
Title string `json:"title" yaml:"title" xml:"title"`
- Author *Author `json:"author" yaml:"author" xml:"author"`
+ Author *author `json:"author" yaml:"author" xml:"author"`
Date *time.Time `json:"date,omitempty" yaml:"date,omitempty" xml:"date,omitempty"`
Rank int `json:"-" yaml:"-" xml:"-"`
order int
}
-type Comic struct {
+// comic is used for testing custom marshal/unmarshal functions on a type.
+type comic struct {
ID string
Name string
Issue string
@@ -50,11 +55,11 @@ type xmlComic struct {
Issue string `xml:"issue,attr"`
}
-func (s *Comic) MarshalJSON() ([]byte, error) {
+func (s *comic) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`{"%s":"%s=%s"}`, s.ID, s.Name, s.Issue)), nil
}
-func (s *Comic) UnmarshalJSON(data []byte) error {
+func (s *comic) UnmarshalJSON(data []byte) error {
m := regexp.MustCompile(`^{\s*"(.*?)":\s*"(.*?)=(.*)"\s*}$`)
matches := m.FindSubmatch(bytes.TrimSpace(data))
if matches == nil {
@@ -68,11 +73,11 @@ func (s *Comic) UnmarshalJSON(data []byte) error {
return nil
}
-func (s *Comic) MarshalYAML() (interface{}, error) {
+func (s *comic) MarshalYAML() (interface{}, error) {
return map[string]map[string]string{s.ID: {s.Name: s.Issue}}, nil
}
-func (s *Comic) UnmarshalYAML(value *yaml.Node) error {
+func (s *comic) UnmarshalYAML(value *yaml.Node) error {
// Horribly hacky code, but it works and specifically only needs to extract
// these specific three values.
if len(value.Content) == 2 {
@@ -86,18 +91,18 @@ func (s *Comic) UnmarshalYAML(value *yaml.Node) error {
return nil
}
-func (s *Comic) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+func (s *comic) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(
&xmlComic{ID: s.ID, Name: s.Name, Issue: s.Issue},
start,
)
}
-func (s *Comic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+func (s *comic) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
x := &xmlComic{}
_ = d.DecodeElement(x, &start)
- v := Comic{ID: x.ID, Name: x.Name, Issue: x.Issue}
+ v := comic{ID: x.ID, Name: x.Name, Issue: x.Issue}
*s = v
@@ -142,21 +147,21 @@ var marhalingTestCases = []struct {
},
{
name: "empty struct",
- v: &Book{},
+ v: &book{},
},
{
name: "partial struct",
- v: &Book{
+ v: &book{
ID: "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
Title: "The Traveler",
},
},
{
name: "full struct",
- v: &Book{
+ v: &book{
ID: "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
Title: "The Traveler",
- Author: &Author{
+ Author: &author{
FirstName: "John",
LastName: "Twelve Hawks",
},
@@ -165,7 +170,7 @@ var marhalingTestCases = []struct {
},
{
name: "custom marshaling",
- v: &Comic{
+ v: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
@@ -204,26 +209,26 @@ var marshalingPTestCases = []struct {
},
{
name: "empty struct",
- v: &Article{},
- want: &Article{},
+ v: &article{},
+ want: &article{},
},
{
name: "partial struct",
- v: &Book{
+ v: &book{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
},
- want: &Book{
+ want: &book{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
},
},
{
name: "full struct",
- v: &Article{
+ v: &article{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
- Author: &Author{
+ Author: &author{
FirstName: "Doc",
LastName: "Brown",
},
@@ -231,10 +236,10 @@ var marshalingPTestCases = []struct {
Rank: 8,
order: 16,
},
- want: &Article{
+ want: &article{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
- Author: &Author{
+ Author: &author{
FirstName: "Doc",
LastName: "Brown",
},
@@ -243,13 +248,13 @@ var marshalingPTestCases = []struct {
},
{
name: "custom marshaling",
- v: &Comic{
+ v: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
Ignored: "don't pay attention to this :)",
},
- want: &Comic{
+ want: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
@@ -261,6 +266,190 @@ var marshalingPTestCases = []struct {
// Tests
//
+func TestWithGolden(t *testing.T) {
+ type args struct {
+ golden Golden
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ name: "nil",
+ args: args{golden: nil},
+ },
+ {
+ name: "non-nil",
+ args: args{golden: New(WithSuffix(".my-custom-golden"))},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ original := New(WithSuffix(".original-golden"))
+ o := &asserterOptions{golden: original}
+
+ fn := WithGolden(tt.args.golden)
+ fn.apply(o)
+
+ if tt.args.golden == nil {
+ assert.Equal(t, original, o.golden)
+ } else {
+ assert.Equal(t, tt.args.golden, o.golden)
+ }
+ })
+ }
+}
+
+func TestNormalizedLineBreaks(t *testing.T) {
+ type args struct {
+ value bool
+ }
+ tests := []struct {
+ name string
+ args args
+ }{
+ {
+ name: "true",
+ args: args{value: true},
+ },
+ {
+ name: "false",
+ args: args{value: false},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ o := &asserterOptions{normalizeLineBreaks: !tt.args.value}
+
+ fn := WithNormalizedLineBreaks(tt.args.value)
+ fn.apply(o)
+
+ assert.Equal(t, tt.args.value, o.normalizeLineBreaks)
+ })
+ }
+}
+
+func TestNewAssert(t *testing.T) {
+ otherGolden := New(WithSuffix(".other-golden"))
+
+ type args struct {
+ options []AsserterOption
+ }
+
+ tests := []struct {
+ name string
+ args args
+ want *asserter
+ }{
+ {
+ name: "no options",
+ args: args{options: []AsserterOption{}},
+ want: &asserter{
+ json: NewMarshalingAsserter(
+ defaultGolden, "JSON",
+ marshal.JSON, unmarshal.JSON,
+ true,
+ ),
+ xml: NewMarshalingAsserter(
+ defaultGolden, "XML",
+ marshal.XML, unmarshal.XML,
+ true,
+ ),
+ yaml: NewMarshalingAsserter(
+ defaultGolden, "YAML",
+ marshal.YAML, unmarshal.YAML,
+ true,
+ ),
+ },
+ },
+ {
+ name: "WithGlobal option",
+ args: args{
+ options: []AsserterOption{
+ WithGolden(otherGolden),
+ },
+ },
+ want: &asserter{
+ json: NewMarshalingAsserter(
+ otherGolden, "JSON",
+ marshal.JSON, unmarshal.JSON,
+ true,
+ ),
+ xml: NewMarshalingAsserter(
+ otherGolden, "XML",
+ marshal.XML, unmarshal.XML,
+ true,
+ ),
+ yaml: NewMarshalingAsserter(
+ otherGolden, "YAML",
+ marshal.YAML, unmarshal.YAML,
+ true,
+ ),
+ },
+ },
+ {
+ name: "WithNormalizedLineBreaks option",
+ args: args{
+ options: []AsserterOption{
+ WithNormalizedLineBreaks(false),
+ },
+ },
+ want: &asserter{
+ json: NewMarshalingAsserter(
+ defaultGolden, "JSON",
+ marshal.JSON, unmarshal.JSON,
+ false,
+ ),
+ xml: NewMarshalingAsserter(
+ defaultGolden, "XML",
+ marshal.XML, unmarshal.XML,
+ false,
+ ),
+ yaml: NewMarshalingAsserter(
+ defaultGolden, "YAML",
+ marshal.YAML, unmarshal.YAML,
+ false,
+ ),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ a := NewAsserter(tt.args.options...)
+ assert.Implements(t, (*Asserter)(nil), a)
+
+ got, ok := a.(*asserter)
+ require.True(
+ t, ok, "failed to type assert return value to a *asserter",
+ )
+
+ assert.Equal(t, tt.want.json.Golden, got.json.Golden)
+ assert.Equal(t, tt.want.json.Format, got.json.Format)
+ assert.Equal(t,
+ tt.want.json.NormalizeLineBreaks, got.json.NormalizeLineBreaks,
+ )
+
+ assert.Equal(t, tt.want.xml.Golden, got.xml.Golden)
+ assert.Equal(t, tt.want.xml.Format, got.xml.Format)
+ assert.Equal(t,
+ tt.want.xml.NormalizeLineBreaks, got.xml.NormalizeLineBreaks,
+ )
+
+ assert.Equal(t, tt.want.yaml.Golden, got.yaml.Golden)
+ assert.Equal(t, tt.want.yaml.Format, got.yaml.Format)
+ assert.Equal(t,
+ tt.want.yaml.NormalizeLineBreaks, got.yaml.NormalizeLineBreaks,
+ )
+ })
+ }
+}
+
+func Test_asserter(t *testing.T) {
+ a := &asserter{}
+
+ assert.Implements(t, (*Asserter)(nil), a)
+}
+
func TestAssertJSONMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
@@ -312,7 +501,7 @@ func TestAssertYAMLMarshalingP(t *testing.T) {
func TestAssert_JSONMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.JSONMarshaling(t, tt.v)
})
@@ -322,7 +511,7 @@ func TestAssert_JSONMarshaling(t *testing.T) {
func TestAssert_JSONMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.JSONMarshalingP(t, tt.v, tt.want)
})
@@ -332,7 +521,7 @@ func TestAssert_JSONMarshalingP(t *testing.T) {
func TestAssert_XMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.XMLMarshaling(t, tt.v)
})
@@ -342,7 +531,7 @@ func TestAssert_XMLMarshaling(t *testing.T) {
func TestAssert_XMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.XMLMarshalingP(t, tt.v, tt.want)
})
@@ -352,7 +541,7 @@ func TestAssert_XMLMarshalingP(t *testing.T) {
func TestAssert_YAMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.YAMLMarshaling(t, tt.v)
})
@@ -362,7 +551,7 @@ func TestAssert_YAMLMarshaling(t *testing.T) {
func TestAssert_YAMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
- assert := NewAssert()
+ assert := NewAsserter()
assert.YAMLMarshalingP(t, tt.v, tt.want)
})
diff --git a/asserter.go b/asserter.go
new file mode 100644
index 0000000..2d5a721
--- /dev/null
+++ b/asserter.go
@@ -0,0 +1,178 @@
+package golden
+
+import (
+ "github.com/jimeh/go-golden/marshal"
+ "github.com/jimeh/go-golden/unmarshal"
+)
+
+// Asserter exposes a series of JSON, YAML, and XML marshaling assertion
+// helpers.
+type Asserter interface {
+ // JSONMarshaling asserts that the given "v" value JSON marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "v" when
+ // unmarshaled.
+ //
+ // Used for objects that do NOT change when they are marshaled and
+ // unmarshaled.
+ JSONMarshaling(t TestingT, v interface{})
+
+ // JSONMarshalingP asserts that the given "v" value JSON marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "want" when
+ // unmarshaled.
+ //
+ // Used for objects that change when they are marshaled and unmarshaled.
+ JSONMarshalingP(t TestingT, v interface{}, want interface{})
+
+ // XMLMarshaling asserts that the given "v" value XML marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "v" when
+ // unmarshaled.
+ //
+ // Used for objects that do NOT change when they are marshaled and
+ // unmarshaled.
+ XMLMarshaling(t TestingT, v interface{})
+
+ // XMLMarshalingP asserts that the given "v" value XML marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "want" when
+ // unmarshaled.
+ //
+ // Used for objects that change when they are marshaled and unmarshaled.
+ XMLMarshalingP(t TestingT, v interface{}, want interface{})
+
+ // YAMLMarshaling asserts that the given "v" value YAML marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "v" when
+ // unmarshaled.
+ //
+ // Used for objects that do NOT change when they are marshaled and
+ // unmarshaled.
+ YAMLMarshaling(t TestingT, v interface{})
+
+ // YAMLMarshalingP asserts that the given "v" value YAML marshals to an
+ // expected value fetched from a golden file on disk, and then verifies that
+ // the marshaled result produces a value that is equal to "want" when
+ // unmarshaled.
+ //
+ // Used for objects that change when they are marshaled and unmarshaled.
+ YAMLMarshalingP(t TestingT, v interface{}, want interface{})
+}
+
+// NewAsserter returns a new Asserter which exposes a number of marshaling
+// assertion helpers for JSON, YAML and XML.
+//
+// The default encoders all specify indentation of two spaces, essentially
+// enforcing pretty formatting for JSON and XML.
+//
+// The default decoders for JSON and YAML prohibit unknown fields which are not
+// present on the provided struct.
+func NewAsserter(options ...AsserterOption) Asserter {
+ o := &asserterOptions{
+ golden: defaultGolden,
+ normalizeLineBreaks: true,
+ }
+
+ for _, opt := range options {
+ opt.apply(o)
+ }
+
+ return &asserter{
+ json: NewMarshalingAsserter(
+ o.golden, "JSON",
+ marshal.JSON, unmarshal.JSON,
+ o.normalizeLineBreaks,
+ ),
+ xml: NewMarshalingAsserter(
+ o.golden, "XML",
+ marshal.XML, unmarshal.XML,
+ o.normalizeLineBreaks,
+ ),
+ yaml: NewMarshalingAsserter(
+ o.golden, "YAML",
+ marshal.YAML, unmarshal.YAML,
+ o.normalizeLineBreaks,
+ ),
+ }
+}
+
+type asserterOptions struct {
+ golden Golden
+ normalizeLineBreaks bool
+}
+
+type AsserterOption interface {
+ apply(*asserterOptions)
+}
+
+type asserterOptionFunc func(*asserterOptions)
+
+func (fn asserterOptionFunc) apply(c *asserterOptions) {
+ fn(c)
+}
+
+// WithGolden allows setting a custom *Golden instance when calling NewAssert().
+func WithGolden(golden Golden) AsserterOption {
+ return asserterOptionFunc(func(a *asserterOptions) {
+ if golden != nil {
+ a.golden = golden
+ }
+ })
+}
+
+// WithNormalizedLineBreaks allows turning off line-break normalization which
+// replaces Windows' CRLF (\r\n) and Mac Classic CR (\r) line breaks with Unix's
+// LF (\n) line breaks.
+func WithNormalizedLineBreaks(value bool) AsserterOption {
+ return asserterOptionFunc(func(a *asserterOptions) {
+ a.normalizeLineBreaks = value
+ })
+}
+
+// asserter implements the Assert interface.
+type asserter struct {
+ json *MarshalingAsserter
+ xml *MarshalingAsserter
+ yaml *MarshalingAsserter
+}
+
+func (s *asserter) JSONMarshaling(t TestingT, v interface{}) {
+ t.Helper()
+
+ s.json.Marshaling(t, v)
+}
+
+func (s *asserter) JSONMarshalingP(
+ t TestingT,
+ v interface{},
+ want interface{},
+) {
+ t.Helper()
+
+ s.json.MarshalingP(t, v, want)
+}
+
+func (s *asserter) XMLMarshaling(t TestingT, v interface{}) {
+ t.Helper()
+
+ s.xml.Marshaling(t, v)
+}
+
+func (s *asserter) XMLMarshalingP(t TestingT, v, want interface{}) {
+ t.Helper()
+
+ s.xml.MarshalingP(t, v, want)
+}
+
+func (s *asserter) YAMLMarshaling(t TestingT, v interface{}) {
+ t.Helper()
+
+ s.yaml.Marshaling(t, v)
+}
+
+func (s *asserter) YAMLMarshalingP(t TestingT, v, want interface{}) {
+ t.Helper()
+
+ s.yaml.MarshalingP(t, v, want)
+}
diff --git a/go.mod b/go.mod
index 61c8115..24beb97 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,8 @@ go 1.15
require (
github.com/jimeh/envctl v0.1.0
+ github.com/jimeh/go-mocktesting v0.1.0
+ github.com/spf13/afero v1.6.0
github.com/stretchr/testify v1.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
diff --git a/go.sum b/go.sum
index 078e210..33c0728 100644
--- a/go.sum
+++ b/go.sum
@@ -2,14 +2,32 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jimeh/envctl v0.1.0 h1:KTv3D+pi5M4/PgFVE/W8ssWqiZP3pDJ8Cga50L+1avo=
github.com/jimeh/envctl v0.1.0/go.mod h1:aM27ffBbO1yUBKUzgJGCUorS4z+wyh+qhQe1ruxXZZo=
+github.com/jimeh/go-mocktesting v0.1.0 h1:y0tLABo3V4i9io7m6TiXdXbU3IVMjtPvWkr+A0+aLTM=
+github.com/jimeh/go-mocktesting v0.1.0/go.mod h1:xnekQ6yP/ull2ewkOp1CbgH7Dym7nbKa/t96XWrIiH8=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/golden.go b/golden.go
index 40e62a6..15da3a0 100644
--- a/golden.go
+++ b/golden.go
@@ -1,6 +1,6 @@
// Package golden is yet another package for working with *.golden test files,
// with a focus on simplicity through it's default behavior, and marshaling
-// assertion helpers to validate JSON/YAML/XML format of structs.
+// assertion helpers to validate the JSON, YAML, and XML formats of structs.
//
// Golden file names are based on the name of the test function and any subtest
// names by calling t.Name(). File names are sanitized to ensure they're
@@ -61,7 +61,8 @@
//
// Golden Usage
//
-// Typical usage would look something like this:
+// If you need manual control over golden file reading and writing, this is a
+// typical example:
//
// func TestExampleMyStruct(t *testing.T) {
// got, err := json.Marshal(&MyStruct{Foo: "Bar"})
@@ -176,30 +177,37 @@
package golden
import (
- "io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
+
+ "github.com/jimeh/go-golden/sanitize"
+ "github.com/spf13/afero"
)
-const (
- DefaultDirMode = 0o755
- DefaultFileMode = 0o644
- DefaultSuffix = ".golden"
- DefaultDirname = "testdata"
-)
+// TestingT is a interface describing a sub-set of methods of *testing.T which
+// golden uses.
+type TestingT interface {
+ Error(args ...interface{})
+ Errorf(format string, args ...interface{})
+ FailNow()
+ Fatal(args ...interface{})
+ Fatalf(format string, args ...interface{})
+ Helper()
+ Log(args ...interface{})
+ Logf(format string, args ...interface{})
+ Name() string
+}
-var DefaultUpdateFunc = EnvUpdateFunc
-
-var globalGolden = New()
+var defaultGolden = New()
// File returns the filename of the golden file for the given *testing.T
// instance as determined by t.Name().
func File(t *testing.T) string {
t.Helper()
- return globalGolden.File(t)
+ return defaultGolden.File(t)
}
// Get returns the content of the golden file for the given *testing.T instance
@@ -208,7 +216,7 @@ func File(t *testing.T) string {
func Get(t *testing.T) []byte {
t.Helper()
- return globalGolden.Get(t)
+ return defaultGolden.Get(t)
}
// Set writes given data to the golden file for the given *testing.T instance as
@@ -217,7 +225,7 @@ func Get(t *testing.T) []byte {
func Set(t *testing.T, data []byte) {
t.Helper()
- globalGolden.Set(t, data)
+ defaultGolden.Set(t, data)
}
// FileP returns the filename of the specifically named golden file for the
@@ -225,7 +233,7 @@ func Set(t *testing.T, data []byte) {
func FileP(t *testing.T, name string) string {
t.Helper()
- return globalGolden.FileP(t, name)
+ return defaultGolden.FileP(t, name)
}
// GetP returns the content of the specifically named golden file belonging
@@ -237,7 +245,7 @@ func FileP(t *testing.T, name string) string {
func GetP(t *testing.T, name string) []byte {
t.Helper()
- return globalGolden.GetP(t, name)
+ return defaultGolden.GetP(t, name)
}
// SetP writes given data of the specifically named golden file belonging to
@@ -249,7 +257,7 @@ func GetP(t *testing.T, name string) []byte {
func SetP(t *testing.T, name string, data []byte) {
t.Helper()
- globalGolden.SetP(t, name, data)
+ defaultGolden.SetP(t, name, data)
}
// Update returns true when golden is set to update golden files. Should be used
@@ -259,104 +267,228 @@ func SetP(t *testing.T, name string, data []byte) {
// environment variable is set to a truthy value. To customize create a custom
// *Golden instance with New() and set a new UpdateFunc value.
func Update() bool {
- return globalGolden.Update()
+ return defaultGolden.Update()
}
// Golden handles all interactions with golden files. The top-level package
-// functions all just proxy through to a default global *Golden instance.
-type Golden struct {
- // DirMode determines the file system permissions of any folders created to
- // hold golden files.
- DirMode os.FileMode
+// functions proxy through to a default global Golden instance.
+type Golden interface {
+ // File returns the filename of the golden file for the given testing.TB
+ // instance as determined by t.Name().
+ File(t TestingT) string
- // FileMode determines the file system permissions of any created or updated
- // golden files written to disk.
- FileMode os.FileMode
+ // Get returns the content of the golden file for the given TestingT
+ // instance as determined by t.Name(). If no golden file can be found/read,
+ // it will fail the test by calling t.Fatal().
+ Get(t TestingT) []byte
- // Suffix determines the filename suffix for all golden files. Typically
- // this should be ".golden", but can be changed here if needed.
- Suffix string
+ // Set writes given data to the golden file for the given TestingT
+ // instance as determined by t.Name(). If writing fails it will fail the
+ // test by calling t.Fatal() with error details.
+ Set(t TestingT, data []byte)
- // Dirname is the name of the top-level directory at the root of the package
- // which holds all golden files. Typically this should "testdata", but can
- // be changed here if needed.
- Dirname string
+ // FileP returns the filename of the specifically named golden file for the
+ // given TestingT instance as determined by t.Name().
+ FileP(t TestingT, name string) string
- // UpdateFunc is used to determine if golden files should be updated or
- // not. Its boolean return value is returned by Update().
- UpdateFunc UpdateFunc
+ // GetP returns the content of the specifically named golden file belonging
+ // to the given TestingT instance as determined by t.Name(). If no golden
+ // file can be found/read, it will fail the test with t.Fatal().
+ //
+ // This is very similar to Get(), but it allows multiple different golden
+ // files to be used within the same one TestingT instance.
+ GetP(t TestingT, name string) []byte
+
+ // SetP writes given data of the specifically named golden file belonging to
+ // the given TestingT instance as determined by t.Name(). If writing fails
+ // it will fail the test with t.Fatal() detailing the error.
+ //
+ // This is very similar to Set(), but it allows multiple different golden
+ // files to be used within the same one TestingT instance.
+ SetP(t TestingT, name string, data []byte)
+
+ // Update returns true when golden is set to update golden files. Should be
+ // used to determine if golden.Set() or golden.SetP() should be called or
+ // not.
+ //
+ // Default behavior uses EnvUpdateFunc() to check if the "GOLDEN_UPDATE"
+ // environment variable is set to a truthy value. To customize set a new
+ // UpdateFunc value on *Golden.
+ Update() bool
}
-// New returns a new *Golden instance with default values correctly
-// populated. This is ideally how you should create a custom *Golden, and then
-// modify the relevant fields as you see fit.
-func New() *Golden {
- return &Golden{
- DirMode: DefaultDirMode,
- FileMode: DefaultFileMode,
- Suffix: DefaultSuffix,
- Dirname: DefaultDirname,
- UpdateFunc: DefaultUpdateFunc,
+// New returns a new Golden instance. Used to create custom Golden instances.
+// See the the various Option functions for details of what can be customized.
+func New(options ...Option) Golden {
+ g := &golden{
+ dirMode: 0o755,
+ fileMode: 0o644,
+ suffix: ".golden",
+ dirname: "testdata",
+ updateFunc: EnvUpdateFunc,
+ fs: afero.NewOsFs(),
+ logOnWrite: true,
}
+
+ for _, opt := range options {
+ opt.apply(g)
+ }
+
+ return g
}
-// File returns the filename of the golden file for the given *testing.T
-// instance as determined by t.Name().
-func (s *Golden) File(t *testing.T) string {
+type Option interface {
+ apply(*golden)
+}
+
+type optionFunc func(*golden)
+
+func (fn optionFunc) apply(g *golden) {
+ fn(g)
+}
+
+// WithDirMode sets the file system permissions used for any folders created to
+// hold golden files.
+//
+// When this option is not provided, the default value is 0o755.
+func WithDirMode(mode os.FileMode) Option {
+ return optionFunc(func(g *golden) {
+ g.dirMode = mode
+ })
+}
+
+// WithFileMode sets the file system permissions used for any created or updated
+// golden files written to.
+//
+// When this option is not provided, the default value is 0o644.
+func WithFileMode(mode os.FileMode) Option {
+ return optionFunc(func(g *golden) {
+ g.fileMode = mode
+ })
+}
+
+// WithSuffix sets the filename suffix used for all golden files.
+//
+// When this option is not provided, the default value is ".golden".
+func WithSuffix(suffix string) Option {
+ return optionFunc(func(g *golden) {
+ g.suffix = suffix
+ })
+}
+
+// WithDirname sets the name of the top-level directory used to hold golden
+// files.
+//
+// When this option is not provided, the default value is "testdata".
+func WithDirname(name string) Option {
+ return optionFunc(func(g *golden) {
+ g.dirname = name
+ })
+}
+
+// WithUpdateFunc sets the function used to determine if golden files should be
+// updated or not. Essentially the provided UpdateFunc is called by Update().
+//
+// When this option is not provided, the default value is EnvUpdateFunc.
+func WithUpdateFunc(fn UpdateFunc) Option {
+ return optionFunc(func(g *golden) {
+ g.updateFunc = fn
+ })
+}
+
+// WithFs sets s afero.Fs instance which is used to read/write all golden files.
+//
+// When this option is not provided, the default value is afero.NewOsFs().
+func WithFs(fs afero.Fs) Option {
+ return optionFunc(func(g *golden) {
+ g.fs = fs
+ })
+}
+
+// WithSilentWrites silences the "golden: writing [...]" log messages whenever
+// set functions write a golden file to disk.
+func WithSilentWrites() Option {
+ return optionFunc(func(g *golden) {
+ g.logOnWrite = false
+ })
+}
+
+// golden is the underlying struct that implements the Golden interface.
+type golden struct {
+ // dirMode determines the file system permissions of any folders created to
+ // hold golden files.
+ dirMode os.FileMode
+
+ // fileMode determines the file system permissions of any created or updated
+ // golden files written to disk.
+ fileMode os.FileMode
+
+ // suffix determines the filename suffix for all golden files. Typically
+ // this should be ".golden", but can be changed here if needed.
+ suffix string
+
+ // dirname is the name of the top-level directory at the root of the package
+ // which holds all golden files. Typically this should be "testdata", but
+ // can be changed here if needed.
+ dirname string
+
+ // updateFunc is used to determine if golden files should be updated or
+ // not. Its boolean return value is returned by Update().
+ updateFunc UpdateFunc
+
+ // fs is used for all file system operations. This enables providing custom
+ // afero.fs instances which can be useful for testing purposes.
+ fs afero.Fs
+
+ // logOnWrite determines if a message is logged with t.Logf when a golden
+ // file is written to with either of the set methods.
+ logOnWrite bool
+}
+
+// Ensure golden satisfies Golden interface.
+var _ Golden = &golden{}
+
+func (s *golden) File(t TestingT) string {
+ t.Helper()
+
return s.file(t, "")
}
-// Get returns the content of the golden file for the given *testing.T instance
-// as determined by t.Name(). If no golden file can be found/read, it will fail
-// the test by calling t.Fatal().
-func (s *Golden) Get(t *testing.T) []byte {
+func (s *golden) Get(t TestingT) []byte {
+ t.Helper()
+
return s.get(t, "")
}
-// Set writes given data to the golden file for the given *testing.T instance as
-// determined by t.Name(). If writing fails it will fail the test by calling
-// t.Fatal() with error details.
-func (s *Golden) Set(t *testing.T, data []byte) {
+func (s *golden) Set(t TestingT, data []byte) {
+ t.Helper()
+
s.set(t, "", data)
}
-// FileP returns the filename of the specifically named golden file for the
-// given *testing.T instance as determined by t.Name().
-func (s *Golden) FileP(t *testing.T, name string) string {
- if name == "" {
- if t != nil {
- t.Fatal("golden: name cannot be empty")
- }
+func (s *golden) FileP(t TestingT, name string) string {
+ t.Helper()
- return ""
+ if name == "" {
+ t.Fatalf("golden: test name cannot be empty")
}
return s.file(t, name)
}
-// GetP returns the content of the specifically named golden file belonging
-// to the given *testing.T instance as determined by t.Name(). If no golden file
-// can be found/read, it will fail the test with t.Fatal().
-//
-// This is very similar to Get(), but it allows multiple different golden files
-// to be used within the same one *testing.T instance.
-func (s *Golden) GetP(t *testing.T, name string) []byte {
+func (s *golden) GetP(t TestingT, name string) []byte {
+ t.Helper()
+
if name == "" {
t.Fatal("golden: name cannot be empty")
-
- return nil
}
return s.get(t, name)
}
-// SetP writes given data of the specifically named golden file belonging to
-// the given *testing.T instance as determined by t.Name(). If writing fails it
-// will fail the test with t.Fatal() detailing the error.
-//
-// This is very similar to Set(), but it allows multiple different golden files
-// to be used within the same one *testing.T instance.
-func (s *Golden) SetP(t *testing.T, name string, data []byte) {
+func (s *golden) SetP(t TestingT, name string, data []byte) {
+ t.Helper()
+
if name == "" {
t.Fatal("golden: name cannot be empty")
}
@@ -364,65 +496,65 @@ func (s *Golden) SetP(t *testing.T, name string, data []byte) {
s.set(t, name, data)
}
-func (s *Golden) file(t *testing.T, name string) string {
- if t.Name() == "" {
- t.Fatalf("golden: could not determine filename for: %+v", t)
+func (s *golden) file(t TestingT, name string) string {
+ t.Helper()
- return ""
+ if t.Name() == "" {
+ t.Fatalf(
+ "golden: could not determine filename for given %T instance", t,
+ )
}
- base := []string{s.Dirname, filepath.FromSlash(t.Name())}
+ base := []string{s.dirname, filepath.FromSlash(t.Name())}
if name != "" {
base = append(base, name)
}
- f := filepath.Clean(filepath.Join(base...) + s.Suffix)
+ f := filepath.Clean(filepath.Join(base...) + s.suffix)
dirty := strings.Split(f, string(os.PathSeparator))
clean := make([]string, 0, len(dirty))
for _, s := range dirty {
- clean = append(clean, sanitizeFilename(s))
+ clean = append(clean, sanitize.Filename(s))
}
return strings.Join(clean, string(os.PathSeparator))
}
-func (s *Golden) get(t *testing.T, name string) []byte {
+func (s *golden) get(t TestingT, name string) []byte {
+ t.Helper()
+
f := s.file(t, name)
- b, err := ioutil.ReadFile(f)
+ b, err := afero.ReadFile(s.fs, f)
if err != nil {
- t.Fatalf("golden: failed reading %s: %s", f, err.Error())
+ t.Fatalf("golden: %s", err.Error())
}
return b
}
-func (s *Golden) set(t *testing.T, name string, data []byte) {
+func (s *golden) set(t TestingT, name string, data []byte) {
+ t.Helper()
+
f := s.file(t, name)
dir := filepath.Dir(f)
- t.Logf("golden: writing .golden file: %s", f)
-
- err := os.MkdirAll(dir, s.DirMode)
- if err != nil {
- t.Fatalf("golden: failed to create directory: %s", err.Error())
-
- return
+ if s.logOnWrite {
+ t.Logf("golden: writing golden file: %s", f)
}
- err = ioutil.WriteFile(f, data, s.FileMode)
+ err := s.fs.MkdirAll(dir, s.dirMode)
+ if err != nil {
+ t.Fatalf("golden: failed to create directory: %s", err.Error())
+ }
+
+ err = afero.WriteFile(s.fs, f, data, s.fileMode)
if err != nil {
t.Fatalf("golden: filed to write file: %s", err.Error())
}
}
-// Update returns true when golden is set to update golden files. Should be used
-// to determine if golden.Set() or golden.SetP() should be called or not.
-//
-// Default behavior uses EnvUpdateFunc() to check if the "GOLDEN_UPDATE"
-// environment variable is set to a truthy value. To customize set a new
-// UpdateFunc value on *Golden.
-func (s *Golden) Update() bool {
- return s.UpdateFunc()
+func (s *golden) Update() bool {
+ return s.updateFunc()
}
diff --git a/golden_test.go b/golden_test.go
index a077a32..567cb76 100644
--- a/golden_test.go
+++ b/golden_test.go
@@ -4,9 +4,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "reflect"
+ "runtime"
"testing"
"github.com/jimeh/envctl"
+ "github.com/jimeh/go-mocktesting"
+ "github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -122,6 +126,7 @@ func TestGet(t *testing.T) {
func TestSet(t *testing.T) {
t.Cleanup(func() {
+ t.Log("cleaning up golden files")
err := os.RemoveAll(filepath.Join("testdata", "TestSet"))
require.NoError(t, err)
err = os.Remove(filepath.Join("testdata", "TestSet.golden"))
@@ -344,6 +349,7 @@ func TestGetP(t *testing.T) {
func TestSetP(t *testing.T) {
t.Cleanup(func() {
+ t.Log("cleaning up golden files")
err := os.RemoveAll(filepath.Join("testdata", "TestSetP"))
require.NoError(t, err)
})
@@ -441,3 +447,623 @@ func TestUpdate(t *testing.T) {
})
}
}
+
+func TestNew(t *testing.T) {
+ myUpdateFunc := func() bool { return false }
+
+ type args struct {
+ options []Option
+ }
+ tests := []struct {
+ name string
+ args args
+ want *golden
+ }{
+ {
+ name: "no options",
+ args: args{options: nil},
+ want: &golden{
+ dirMode: 0o755,
+ fileMode: 0o644,
+ suffix: ".golden",
+ dirname: "testdata",
+ updateFunc: EnvUpdateFunc,
+ fs: afero.NewOsFs(),
+ logOnWrite: true,
+ },
+ },
+ {
+ name: "all options",
+ args: args{
+ options: []Option{
+ WithDirMode(0o777),
+ WithFileMode(0o666),
+ WithSuffix(".gold"),
+ WithDirname("goldstuff"),
+ WithUpdateFunc(myUpdateFunc),
+ WithFs(afero.NewMemMapFs()),
+ WithSilentWrites(),
+ },
+ },
+ want: &golden{
+ dirMode: 0o777,
+ fileMode: 0o666,
+ suffix: ".gold",
+ dirname: "goldstuff",
+ updateFunc: myUpdateFunc,
+ fs: afero.NewMemMapFs(),
+ logOnWrite: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ g := New(tt.args.options...)
+ got, ok := g.(*golden)
+ require.True(t, ok, "New did not returns a *golden instance")
+
+ gotUpdateFunc := runtime.FuncForPC(
+ reflect.ValueOf(got.updateFunc).Pointer(),
+ ).Name()
+ wantUpdateFunc := runtime.FuncForPC(
+ reflect.ValueOf(tt.want.updateFunc).Pointer(),
+ ).Name()
+
+ assert.Equal(t, tt.want.dirMode, got.dirMode)
+ assert.Equal(t, tt.want.fileMode, got.fileMode)
+ assert.Equal(t, tt.want.suffix, got.suffix)
+ assert.Equal(t, tt.want.dirname, got.dirname)
+ assert.Equal(t, tt.want.logOnWrite, got.logOnWrite)
+ assert.Equal(t, wantUpdateFunc, gotUpdateFunc)
+ assert.IsType(t, tt.want.fs, got.fs)
+ })
+ }
+}
+
+func Test_golden_File(t *testing.T) {
+ type fields struct {
+ suffix *string
+ dirname *string
+ }
+ tests := []struct {
+ name string
+ testName string
+ fields fields
+ want string
+ wantAborted bool
+ wantFailCount int
+ wantTestOutput []string
+ }{
+ {
+ name: "top-level",
+ testName: "TestFooBar",
+ want: filepath.Join("testdata", "TestFooBar.golden"),
+ },
+ {
+ name: "sub-test",
+ testName: "TestFooBar/it_is_here",
+ want: filepath.Join(
+ "testdata", "TestFooBar", "it_is_here.golden",
+ ),
+ },
+ {
+ name: "blank test name",
+ testName: "",
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: could not determine filename for given " +
+ "*mocktesting.T instance\n",
+ },
+ },
+ {
+ name: "custom dirname",
+ testName: "TestFozBar",
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ },
+ want: filepath.Join("goldenfiles", "TestFozBar.golden"),
+ },
+ {
+ name: "custom suffix",
+ testName: "TestFozBaz",
+ fields: fields{
+ suffix: stringPtr(".goldfile"),
+ },
+ want: filepath.Join("testdata", "TestFozBaz.goldfile"),
+ },
+ {
+ name: "custom dirname and suffix",
+ testName: "TestFozBar",
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ suffix: stringPtr(".goldfile"),
+ },
+ want: filepath.Join("goldenfiles", "TestFozBar.goldfile"),
+ },
+ {
+ name: "invalid chars in test name",
+ testName: `TestFooBar/foo?<>:*|"bar`,
+ want: filepath.Join(
+ "testdata", "TestFooBar", "foo_______bar.golden",
+ ),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if tt.fields.suffix == nil {
+ tt.fields.suffix = stringPtr(".golden")
+ }
+ if tt.fields.dirname == nil {
+ tt.fields.dirname = stringPtr("testdata")
+ }
+
+ g := &golden{
+ suffix: *tt.fields.suffix,
+ dirname: *tt.fields.dirname,
+ }
+
+ mt := mocktesting.NewT(tt.testName)
+
+ var got string
+ mocktesting.Go(func() {
+ got = g.File(mt)
+ })
+
+ assert.Equal(t, tt.want, got)
+ assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
+ assert.Equal(t,
+ tt.wantFailCount, mt.FailedCount(), "failed count",
+ )
+ assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
+ })
+ }
+}
+
+func Test_golden_Get(t *testing.T) {
+ type fields struct {
+ suffix *string
+ dirname *string
+ }
+ tests := []struct {
+ name string
+ testName string
+ fields fields
+ files map[string][]byte
+ want []byte
+ wantAborted bool
+ wantFailCount int
+ wantTestOutput []string
+ }{
+ {
+ name: "file exists",
+ testName: "TestFooBar",
+ files: map[string][]byte{
+ filepath.Join("testdata", "TestFooBar.golden"): []byte(
+ "foo: bar\nhello: world",
+ ),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "file is missing",
+ testName: "TestFooBar",
+ files: map[string][]byte{},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: open " + filepath.Join(
+ "testdata", "TestFooBar.golden",
+ ) + ": file does not exist\n",
+ },
+ },
+ {
+ name: "sub-test file exists",
+ testName: "TestFooBar/it_is_here",
+ files: map[string][]byte{
+ filepath.Join(
+ "testdata", "TestFooBar", "it_is_here.golden",
+ ): []byte("this is really here ^_^\n"),
+ },
+ want: []byte("this is really here ^_^\n"),
+ },
+ {
+ name: "sub-test file is missing",
+ testName: "TestFooBar/not_really_here",
+ files: map[string][]byte{},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: open " + filepath.Join(
+ "testdata", "TestFooBar", "not_really_here.golden",
+ ) + ": file does not exist\n",
+ },
+ },
+ {
+ name: "blank test name",
+ testName: "",
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: could not determine filename for given " +
+ "*mocktesting.T instance\n",
+ },
+ },
+ {
+ name: "custom dirname",
+ testName: "TestFozBar",
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ },
+ files: map[string][]byte{
+ filepath.Join("goldenfiles", "TestFozBar.golden"): []byte(
+ "foo: bar\nhello: world",
+ ),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "custom suffix",
+ testName: "TestFozBaz",
+ fields: fields{
+ suffix: stringPtr(".goldfile"),
+ },
+ files: map[string][]byte{
+ filepath.Join("testdata", "TestFozBaz.goldfile"): []byte(
+ "foo: bar\nhello: world",
+ ),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "custom dirname and suffix",
+ testName: "TestFozBar",
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ suffix: stringPtr(".goldfile"),
+ },
+ files: map[string][]byte{
+ filepath.Join("goldenfiles", "TestFozBar.goldfile"): []byte(
+ "foo: bar\nhello: world",
+ ),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "invalid chars in test name",
+ testName: `TestFooBar/foo?<>:*|"bar`,
+ files: map[string][]byte{
+ filepath.Join(
+ "testdata", "TestFooBar", "foo_______bar.golden",
+ ): []byte("foo: bar\nhello: world"),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+ for f, b := range tt.files {
+ _ = afero.WriteFile(fs, f, b, 0o644)
+ }
+
+ if tt.fields.suffix == nil {
+ tt.fields.suffix = stringPtr(".golden")
+ }
+ if tt.fields.dirname == nil {
+ tt.fields.dirname = stringPtr("testdata")
+ }
+
+ g := &golden{
+ suffix: *tt.fields.suffix,
+ dirname: *tt.fields.dirname,
+ fs: fs,
+ }
+
+ mt := mocktesting.NewT(tt.testName)
+
+ var got []byte
+ mocktesting.Go(func() {
+ got = g.Get(mt)
+ })
+
+ assert.Equal(t, tt.want, got)
+ assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
+ assert.Equal(t,
+ tt.wantFailCount, mt.FailedCount(), "failed count",
+ )
+ assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
+ })
+ }
+}
+
+func Test_golden_FileP(t *testing.T) {
+ type args struct {
+ name string
+ }
+ type fields struct {
+ suffix *string
+ dirname *string
+ }
+ tests := []struct {
+ name string
+ testName string
+ args args
+ fields fields
+ want string
+ wantAborted bool
+ wantFailCount int
+ wantTestOutput []string
+ }{
+ {
+ name: "top-level",
+ testName: "TestFooBar",
+ args: args{name: "yaml"},
+ want: filepath.Join("testdata", "TestFooBar", "yaml.golden"),
+ },
+ {
+ name: "sub-test",
+ testName: "TestFooBar/it_is_here",
+ args: args{name: "json"},
+ want: filepath.Join(
+ "testdata", "TestFooBar", "it_is_here", "json.golden",
+ ),
+ },
+ {
+ name: "blank test name",
+ testName: "",
+ args: args{name: "json"},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: could not determine filename for given " +
+ "*mocktesting.T instance\n",
+ },
+ },
+ {
+ name: "custom dirname",
+ testName: "TestFozBar",
+ args: args{name: "xml"},
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ },
+ want: filepath.Join("goldenfiles", "TestFozBar", "xml.golden"),
+ },
+ {
+ name: "custom suffix",
+ testName: "TestFozBaz",
+ args: args{name: "toml"},
+ fields: fields{
+ suffix: stringPtr(".goldfile"),
+ },
+ want: filepath.Join("testdata", "TestFozBaz", "toml.goldfile"),
+ },
+ {
+ name: "custom dirname and suffix",
+ testName: "TestFozBar",
+ args: args{name: "json"},
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ suffix: stringPtr(".goldfile"),
+ },
+ want: filepath.Join("goldenfiles", "TestFozBar", "json.goldfile"),
+ },
+ {
+ name: "invalid chars in test name",
+ testName: `TestFooBar/foo?<>:*|"bar`,
+ args: args{name: "yml"},
+ want: filepath.Join(
+ "testdata", "TestFooBar", "foo_______bar", "yml.golden",
+ ),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if tt.fields.suffix == nil {
+ tt.fields.suffix = stringPtr(".golden")
+ }
+ if tt.fields.dirname == nil {
+ tt.fields.dirname = stringPtr("testdata")
+ }
+
+ g := &golden{
+ suffix: *tt.fields.suffix,
+ dirname: *tt.fields.dirname,
+ }
+
+ mt := mocktesting.NewT(tt.testName)
+
+ var got string
+ mocktesting.Go(func() {
+ got = g.FileP(mt, tt.args.name)
+ })
+
+ assert.Equal(t, tt.want, got)
+ assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
+ assert.Equal(t,
+ tt.wantFailCount, mt.FailedCount(), "failed count",
+ )
+ assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
+ })
+ }
+}
+
+func Test_golden_GetP(t *testing.T) {
+ type args struct {
+ name string
+ }
+ type fields struct {
+ suffix *string
+ dirname *string
+ }
+ tests := []struct {
+ name string
+ testName string
+ args args
+ fields fields
+ files map[string][]byte
+ want []byte
+ wantAborted bool
+ wantFailCount int
+ wantTestOutput []string
+ }{
+ {
+ name: "file exists",
+ testName: "TestFooBar",
+ args: args{name: "yaml"},
+ files: map[string][]byte{
+ filepath.Join("testdata", "TestFooBar", "yaml.golden"): []byte(
+ "foo: bar\nhello: world",
+ ),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "file is missing",
+ testName: "TestFooBar",
+ args: args{name: "yaml"},
+ files: map[string][]byte{},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: open " + filepath.Join(
+ "testdata", "TestFooBar", "yaml.golden",
+ ) + ": file does not exist\n",
+ },
+ },
+ {
+ name: "sub-test file exists",
+ testName: "TestFooBar/it_is_here",
+ args: args{name: "plain"},
+ files: map[string][]byte{
+ filepath.Join(
+ "testdata", "TestFooBar", "it_is_here", "plain.golden",
+ ): []byte("this is really here ^_^\n"),
+ },
+ want: []byte("this is really here ^_^\n"),
+ },
+ {
+ name: "sub-test file is missing",
+ testName: "TestFooBar/not_really_here",
+ args: args{name: "plain"},
+ files: map[string][]byte{},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: open " + filepath.Join(
+ "testdata", "TestFooBar", "not_really_here", "plain.golden",
+ ) + ": file does not exist\n",
+ },
+ },
+ {
+ name: "blank test name",
+ testName: "",
+ args: args{name: "plain"},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: could not determine filename for given " +
+ "*mocktesting.T instance\n",
+ },
+ },
+ {
+ name: "blank name",
+ testName: "TestFooBar",
+ args: args{name: ""},
+ wantAborted: true,
+ wantFailCount: 1,
+ wantTestOutput: []string{
+ "golden: name cannot be empty\n",
+ },
+ },
+ {
+ name: "custom dirname",
+ testName: "TestFozBar",
+ args: args{name: "yaml"},
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ },
+ files: map[string][]byte{
+ filepath.Join(
+ "goldenfiles", "TestFozBar", "yaml.golden",
+ ): []byte("foo: bar\nhello: world"),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "custom suffix",
+ testName: "TestFozBaz",
+ args: args{name: "yaml"},
+ fields: fields{
+ suffix: stringPtr(".goldfile"),
+ },
+ files: map[string][]byte{
+ filepath.Join(
+ "testdata", "TestFozBaz", "yaml.goldfile",
+ ): []byte("foo: bar\nhello: world"),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "custom dirname and suffix",
+ testName: "TestFozBar",
+ args: args{name: "yaml"},
+ fields: fields{
+ dirname: stringPtr("goldenfiles"),
+ suffix: stringPtr(".goldfile"),
+ },
+ files: map[string][]byte{
+ filepath.Join(
+ "goldenfiles", "TestFozBar", "yaml.goldfile",
+ ): []byte("foo: bar\nhello: world"),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ {
+ name: "invalid chars in test name",
+ testName: `TestFooBar/foo?<>:*|"bar`,
+ args: args{name: "trash"},
+ files: map[string][]byte{
+ filepath.Join(
+ "testdata", "TestFooBar", "foo_______bar", "trash.golden",
+ ): []byte("foo: bar\nhello: world"),
+ },
+ want: []byte("foo: bar\nhello: world"),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+ for f, b := range tt.files {
+ _ = afero.WriteFile(fs, f, b, 0o644)
+ }
+
+ if tt.fields.suffix == nil {
+ tt.fields.suffix = stringPtr(".golden")
+ }
+ if tt.fields.dirname == nil {
+ tt.fields.dirname = stringPtr("testdata")
+ }
+
+ g := &golden{
+ suffix: *tt.fields.suffix,
+ dirname: *tt.fields.dirname,
+ fs: fs,
+ }
+
+ mt := mocktesting.NewT(tt.testName)
+
+ var got []byte
+ mocktesting.Go(func() {
+ got = g.GetP(mt, tt.args.name)
+ })
+
+ assert.Equal(t, tt.want, got)
+ assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
+ assert.Equal(t,
+ tt.wantFailCount, mt.FailedCount(), "failed count",
+ )
+ assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
+ })
+ }
+}
diff --git a/marshal/marshal.go b/marshal/marshal.go
new file mode 100644
index 0000000..322f194
--- /dev/null
+++ b/marshal/marshal.go
@@ -0,0 +1,45 @@
+package marshal
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+
+ "gopkg.in/yaml.v3"
+)
+
+// JSON returns the JSON encoding of v. Returned JSON is intended by two spaces
+// (pretty formatted), and is not HTML escaped.
+func JSON(v interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ enc := json.NewEncoder(&buf)
+ enc.SetIndent("", " ")
+ enc.SetEscapeHTML(false)
+
+ err := enc.Encode(v)
+
+ return buf.Bytes(), err
+}
+
+// XML returns the XML encoding of v. Returned XML is intended by two spaces
+// (pretty formatted).
+func XML(v interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ enc := xml.NewEncoder(&buf)
+ enc.Indent("", " ")
+
+ err := enc.Encode(v)
+
+ return buf.Bytes(), err
+}
+
+// YAML returns the YAML encoding of v. Returned YAML is intended by two spaces.
+func YAML(v interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ enc := yaml.NewEncoder(&buf)
+ enc.SetIndent(2)
+
+ err := enc.Encode(v)
+
+ return buf.Bytes(), err
+}
diff --git a/marshal/marshal_test.go b/marshal/marshal_test.go
new file mode 100644
index 0000000..09a3d62
--- /dev/null
+++ b/marshal/marshal_test.go
@@ -0,0 +1,318 @@
+package marshal_test
+
+import (
+ "testing"
+
+ "github.com/jimeh/go-golden/marshal"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type book struct {
+ Title string `json:"title" yaml:"title" xml:"title"`
+ Author string `json:"author,omitempty" yaml:"author,omitempty" xml:"author,omitempty"`
+ Price int `json:"price" yaml:"price" xml:"price"`
+}
+
+type shoe struct {
+ Make string `json:"make" yaml:"make" xml:"make"`
+ Model string `json:"model,omitempty" yaml:"model,omitempty" xml:"model,omitempty"`
+ Size int `json:"size" yaml:"size" xml:"size"`
+}
+
+func TestJSON(t *testing.T) {
+ type args struct {
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ wantErr string
+ wantErrIs error
+ }{
+ {
+ name: "nil",
+ args: args{v: nil},
+ want: []byte("null\n"),
+ },
+ {
+ name: "empty struct (1)",
+ args: args{v: &book{}},
+ want: []byte(`{
+ "title": "",
+ "price": 0
+}
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{v: &shoe{}},
+ want: []byte(`{
+ "make": "",
+ "size": 0
+}
+`,
+ ),
+ },
+ {
+ name: "full struct (1)",
+ args: args{
+ v: &book{
+ Title: "a",
+ Author: "b",
+ Price: 499,
+ },
+ },
+ want: []byte(`{
+ "title": "a",
+ "author": "b",
+ "price": 499
+}
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{
+ v: &shoe{
+ Make: "a",
+ Model: "b",
+ Size: 42,
+ },
+ },
+ want: []byte(`{
+ "make": "a",
+ "model": "b",
+ "size": 42
+}
+`,
+ ),
+ },
+ {
+ name: "channel",
+ args: args{
+ v: make(chan int),
+ },
+ wantErr: "json: unsupported type: chan int",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := marshal.JSON(tt.args.v)
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ }
+
+ assert.Equal(t, string(tt.want), string(got))
+ })
+ }
+}
+
+func TestYAML(t *testing.T) {
+ type args struct {
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ wantErr string
+ wantErrIs error
+ wantPanic interface{}
+ }{
+ {
+ name: "nil",
+ args: args{v: nil},
+ want: []byte("null\n"),
+ },
+ {
+ name: "empty struct (1)",
+ args: args{v: &book{}},
+ want: []byte(`title: ""
+price: 0
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{v: &shoe{}},
+ want: []byte(`make: ""
+size: 0
+`,
+ ),
+ },
+ {
+ name: "full struct (1)",
+ args: args{
+ v: &book{
+ Title: "a",
+ Author: "b",
+ Price: 499,
+ },
+ },
+ want: []byte(`title: a
+author: b
+price: 499
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{
+ v: &shoe{
+ Make: "a",
+ Model: "b",
+ Size: 42,
+ },
+ },
+ want: []byte(`make: a
+model: b
+size: 42
+`,
+ ),
+ },
+ {
+ name: "channel",
+ args: args{
+ v: make(chan int),
+ },
+ wantPanic: "cannot marshal type: chan int",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ f := func() (got []byte, err error, p interface{}) {
+ defer func() { p = recover() }()
+ got, err = marshal.YAML(tt.args.v)
+
+ return
+ }
+
+ got, err, p := f()
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantPanic != nil {
+ assert.Equal(t, tt.wantPanic, p)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ }
+
+ assert.Equal(t, string(tt.want), string(got))
+ })
+ }
+}
+
+func TestXML(t *testing.T) {
+ type args struct {
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ wantErr string
+ wantErrIs error
+ }{
+ {
+ name: "nil",
+ args: args{v: nil},
+ want: []byte(""),
+ },
+ {
+ name: "empty struct (1)",
+ args: args{v: &book{}},
+ want: []byte(`
+
+ 0
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{v: &shoe{}},
+ want: []byte(`
+
+ 0
+`,
+ ),
+ },
+ {
+ name: "full struct (1)",
+ args: args{
+ v: &book{
+ Title: "a",
+ Author: "b",
+ Price: 499,
+ },
+ },
+ want: []byte(`
+ a
+ b
+ 499
+`,
+ ),
+ },
+ {
+ name: "empty struct (2)",
+ args: args{
+ v: &shoe{
+ Make: "a",
+ Model: "b",
+ Size: 42,
+ },
+ },
+ want: []byte(`
+ a
+ b
+ 42
+`,
+ ),
+ },
+ {
+ name: "channel",
+ args: args{
+ v: make(chan int),
+ },
+ wantErr: "xml: unsupported type: chan int",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := marshal.XML(tt.args.v)
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ }
+
+ assert.Equal(t, string(tt.want), string(got))
+ })
+ }
+}
diff --git a/marshal_asserter.go b/marshaling_asserter.go
similarity index 52%
rename from marshal_asserter.go
rename to marshaling_asserter.go
index f5ae6b2..4adb940 100644
--- a/marshal_asserter.go
+++ b/marshaling_asserter.go
@@ -1,38 +1,24 @@
package golden
import (
- "bytes"
- "io"
"reflect"
"strings"
- "testing"
+ "github.com/jimeh/go-golden/sanitize"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type (
- NewEncoderFunc func(w io.Writer) MarshalEncoder
- NewDecoderFunc func(r io.Reader) MarshalDecoder
+ MarshalFunc func(interface{}) ([]byte, error)
+ UnmarshalFunc func([]byte, interface{}) error
)
-type MarshalEncoder interface {
- Encode(v interface{}) error
-}
-
-type MarshalDecoder interface {
- Decode(v interface{}) error
-}
-
-// MarshalAsserter allows building custom marshaling asserters, but providing
-// functions which returns new encoder and decoders for the format to be
-// asserted.
-//
-// All the AssertMarshaling helper functions uses MarshalAsserter under
-// the hood.
-type MarshalAsserter struct {
+// MarshalingAsserter allows building marshaling asserters by providing
+// functions which marshal and unmarshal objects.
+type MarshalingAsserter struct {
// Golden is the *Golden instance used to read/write golden files.
- Golden *Golden
+ Golden Golden
// Name of the format the MarshalAsserter handles.
Format string
@@ -43,39 +29,34 @@ type MarshalAsserter struct {
// default.
GoldName string
- // NewEncoderFunc is the function used to create a new encoder for
- // marshaling objects.
- NewEncoderFunc NewEncoderFunc
+ // MarshalFunc is the function used to marshal given objects.
+ MarshalFunc MarshalFunc
- // NewDecoderFunc is the function used to create a new decoder for
- // unmarshaling objects.
- NewDecoderFunc NewDecoderFunc
+ // UnmarshalFunc is the function used to unmarshal given objects.
+ UnmarshalFunc UnmarshalFunc
- // NormalizeLineBreaks determines if Windows' CRLF (\r\n) and Mac Classic CR
- // (\r) line breaks are replaced with Unix's LF (\n) line breaks. This
- // ensure marshaling assertions works cross platform.
+ // NormalizeLineBreaks determines if Windows' CRLF (\r\n) and MacOS Classic
+ // CR (\r) line breaks are replaced with Unix's LF (\n) line breaks. This
+ // ensures marshaling assertions work across different platforms.
NormalizeLineBreaks bool
}
-func NewMarshalAsserter(
- golden *Golden,
+// New returns a new MarshalingAsserter.
+func NewMarshalingAsserter(
+ golden Golden,
format string,
- newEncoderFunc NewEncoderFunc,
- newDecoderFunc NewDecoderFunc,
+ marshalFunc MarshalFunc,
+ unmarshalFunc UnmarshalFunc,
normalizeLineBreaks bool,
-) *MarshalAsserter {
- if golden == nil {
- golden = globalGolden
- }
+) *MarshalingAsserter {
+ goldName := "marshaled_" + strings.ToLower(sanitize.Filename(format))
- goldName := "marshaled_" + strings.ToLower(sanitizeFilename(format))
-
- return &MarshalAsserter{
+ return &MarshalingAsserter{
Golden: golden,
Format: format,
GoldName: goldName,
- NewEncoderFunc: newEncoderFunc,
- NewDecoderFunc: newDecoderFunc,
+ MarshalFunc: marshalFunc,
+ UnmarshalFunc: unmarshalFunc,
NormalizeLineBreaks: normalizeLineBreaks,
}
}
@@ -86,7 +67,7 @@ func NewMarshalAsserter(
// unmarshaled.
//
// Used for objects that do NOT change when they are marshaled and unmarshaled.
-func (s *MarshalAsserter) Marshaling(t *testing.T, v interface{}) {
+func (s *MarshalingAsserter) Marshaling(t TestingT, v interface{}) {
t.Helper()
s.MarshalingP(t, v, v)
@@ -98,8 +79,8 @@ func (s *MarshalAsserter) Marshaling(t *testing.T, v interface{}) {
// when unmarshaled.
//
// Used for objects that change when they are marshaled and unmarshaled.
-func (s *MarshalAsserter) MarshalingP(
- t *testing.T,
+func (s *MarshalingAsserter) MarshalingP(
+ t TestingT,
v interface{},
want interface{},
) {
@@ -107,18 +88,17 @@ func (s *MarshalAsserter) MarshalingP(
if reflect.ValueOf(want).Kind() != reflect.Ptr {
require.FailNowf(t,
- "only pointer types can be asserted",
+ "golden: only pointer types can be asserted",
"%T is not a pointer type", want,
)
}
- var buf bytes.Buffer
- err := s.NewEncoderFunc(&buf).Encode(v)
- require.NoErrorf(t, err, "failed to %s marshal %T: %+v", s.Format, v, v)
-
- marshaled := buf.Bytes()
+ marshaled, err := s.MarshalFunc(v)
+ require.NoErrorf(t,
+ err, "golden: failed to %s marshal %T: %+v", s.Format, v, v,
+ )
if s.NormalizeLineBreaks {
- marshaled = normalizeLineBreaks(marshaled)
+ marshaled = sanitize.LineBreaks(marshaled)
}
if s.Golden.Update() {
@@ -127,7 +107,7 @@ func (s *MarshalAsserter) MarshalingP(
gold := s.Golden.GetP(t, s.GoldName)
if s.NormalizeLineBreaks {
- gold = normalizeLineBreaks(gold)
+ gold = sanitize.LineBreaks(gold)
}
switch strings.ToLower(s.Format) {
@@ -140,12 +120,14 @@ func (s *MarshalAsserter) MarshalingP(
}
got := reflect.New(reflect.TypeOf(want).Elem()).Interface()
- err = s.NewDecoderFunc(bytes.NewBuffer(gold)).Decode(got)
+ err = s.UnmarshalFunc(gold, got)
+
+ f := s.Golden.FileP(t, s.GoldName)
require.NoErrorf(t, err,
- "failed to %s unmarshal %T from %s",
- s.Format, got, s.Golden.FileP(t, s.GoldName),
+ "golden: failed to %s unmarshal %T from %s", s.Format, got, f,
)
- assert.Equal(t, want, got,
- "unmarshaling from golden file does not match expected object",
+ assert.Equalf(t, want, got,
+ "golden: unmarshaling from golden file does not match "+
+ "expected object; golden file: %s", f,
)
}
diff --git a/marshaling_asserter_test.go b/marshaling_asserter_test.go
new file mode 100644
index 0000000..f2bf18e
--- /dev/null
+++ b/marshaling_asserter_test.go
@@ -0,0 +1,98 @@
+package golden
+
+import (
+ "testing"
+
+ "github.com/jimeh/go-golden/marshal"
+ "github.com/jimeh/go-golden/unmarshal"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewMarshalingAsserter(t *testing.T) {
+ type args struct {
+ golden Golden
+ format string
+ marshalFunc MarshalFunc
+ unmarshalFunc UnmarshalFunc
+ normalizeLineBreaks bool
+ }
+ tests := []struct {
+ name string
+ args args
+ want *MarshalingAsserter
+ }{
+ {
+ name: "json",
+ args: args{
+ nil,
+ "JSON",
+ marshal.JSON,
+ unmarshal.JSON,
+ true,
+ },
+ want: &MarshalingAsserter{
+ Golden: nil,
+ Format: "JSON",
+ GoldName: "marshaled_json",
+ MarshalFunc: marshal.JSON,
+ UnmarshalFunc: unmarshal.JSON,
+ NormalizeLineBreaks: true,
+ },
+ },
+ {
+ name: "xml",
+ args: args{
+ nil,
+ "XML",
+ marshal.XML,
+ unmarshal.XML,
+ true,
+ },
+ want: &MarshalingAsserter{
+ Golden: nil,
+ Format: "XML",
+ GoldName: "marshaled_xml",
+ MarshalFunc: marshal.XML,
+ UnmarshalFunc: unmarshal.XML,
+ NormalizeLineBreaks: true,
+ },
+ },
+ {
+ name: "yaml",
+ args: args{
+ nil,
+ "YAML",
+ marshal.YAML,
+ unmarshal.YAML,
+ true,
+ },
+ want: &MarshalingAsserter{
+ Golden: nil,
+ Format: "YAML",
+ GoldName: "marshaled_yaml",
+ MarshalFunc: marshal.YAML,
+ UnmarshalFunc: unmarshal.YAML,
+ NormalizeLineBreaks: true,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := NewMarshalingAsserter(
+ tt.args.golden,
+ tt.args.format,
+ tt.args.marshalFunc,
+ tt.args.unmarshalFunc,
+ tt.args.normalizeLineBreaks,
+ )
+
+ assert.Equal(t, tt.want.Golden, got.Golden)
+ assert.Equal(t, tt.want.Format, got.Format)
+ assert.Equal(t, tt.want.GoldName, got.GoldName)
+ assert.Equal(t,
+ tt.want.NormalizeLineBreaks,
+ got.NormalizeLineBreaks,
+ )
+ })
+ }
+}
diff --git a/sanitize.go b/sanitize/filename.go
similarity index 92%
rename from sanitize.go
rename to sanitize/filename.go
index c77d741..4b1ce7d 100644
--- a/sanitize.go
+++ b/sanitize/filename.go
@@ -1,4 +1,4 @@
-package golden
+package sanitize
import (
"regexp"
@@ -15,7 +15,7 @@ var (
)
)
-func sanitizeFilename(name string) string {
+func Filename(name string) string {
if reservedNames.MatchString(name) || winReserved.MatchString(name) {
var b []byte
for i := 0; i < len(name); i++ {
diff --git a/sanitize_test.go b/sanitize/filename_test.go
similarity index 95%
rename from sanitize_test.go
rename to sanitize/filename_test.go
index c98b93b..a917b26 100644
--- a/sanitize_test.go
+++ b/sanitize/filename_test.go
@@ -1,12 +1,13 @@
-package golden
+package sanitize_test
import (
"testing"
+ "github.com/jimeh/go-golden/sanitize"
"github.com/stretchr/testify/assert"
)
-func Test_sanitizeFilename(t *testing.T) {
+func TestFilename(t *testing.T) {
tests := []struct {
name string
filename string
@@ -69,6 +70,7 @@ func Test_sanitizeFilename(t *testing.T) {
filename: "foobar.golden .. .. .. ",
want: "foobar.golden",
},
+ // Protected Windows filenames.
{name: "con", filename: "con", want: "___"},
{name: "prn", filename: "prn", want: "___"},
{name: "aux", filename: "aux", want: "___"},
@@ -116,7 +118,7 @@ func Test_sanitizeFilename(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := sanitizeFilename(tt.filename)
+ got := sanitize.Filename(tt.filename)
assert.Equal(t, tt.want, got)
})
diff --git a/sanitize/line_breaks.go b/sanitize/line_breaks.go
new file mode 100644
index 0000000..2d5db86
--- /dev/null
+++ b/sanitize/line_breaks.go
@@ -0,0 +1,21 @@
+package sanitize
+
+import "bytes"
+
+var (
+ lf = []byte{10}
+ cr = []byte{13}
+ crlf = []byte{13, 10}
+)
+
+// LineBreaks replaces Windows CRLF (\r\n) and MacOS Classic CR (\r)
+// line-breaks with Unix LF (\n) line breaks.
+func LineBreaks(data []byte) []byte {
+ // Replace Windows CRLF (\r\n) with Unix LF (\n)
+ result := bytes.ReplaceAll(data, crlf, lf)
+
+ // Replace Classic MacOS CR (\r) with Unix LF (\n)
+ result = bytes.ReplaceAll(result, cr, lf)
+
+ return result
+}
diff --git a/sanitize/line_breaks_test.go b/sanitize/line_breaks_test.go
new file mode 100644
index 0000000..31aaa0b
--- /dev/null
+++ b/sanitize/line_breaks_test.go
@@ -0,0 +1,67 @@
+package sanitize_test
+
+import (
+ "testing"
+
+ "github.com/jimeh/go-golden/sanitize"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLineBreaks(t *testing.T) {
+ type args struct {
+ data []byte
+ }
+ tests := []struct {
+ name string
+ args args
+ want []byte
+ }{
+ {
+ name: "nil",
+ args: args{data: nil},
+ want: nil,
+ },
+ {
+ name: "empty",
+ args: args{data: []byte{}},
+ want: nil,
+ },
+ {
+ name: "no line breaks",
+ args: args{data: []byte("hello world")},
+ want: []byte("hello world"),
+ },
+ {
+ name: "UNIX line breaks",
+ args: args{data: []byte("hello\nworld\nhow are you?")},
+ want: []byte("hello\nworld\nhow are you?"),
+ },
+ {
+ name: "Windows line breaks",
+ args: args{data: []byte("hello\r\nworld\r\nhow are you?")},
+ want: []byte("hello\nworld\nhow are you?"),
+ },
+ {
+ name: "MacOS Classic line breaks",
+ args: args{data: []byte("hello\rworld\rhow are you?")},
+ want: []byte("hello\nworld\nhow are you?"),
+ },
+ {
+ name: "Windows and MacOS Classic line breaks",
+ args: args{data: []byte("hello\r\nworld\rhow are you?")},
+ want: []byte("hello\nworld\nhow are you?"),
+ },
+ {
+ name: "Windows, MacOS Classic, and UNIX line breaks",
+ args: args{data: []byte("hello\r\nworld\rhow are you?\nGood!")},
+ want: []byte("hello\nworld\nhow are you?\nGood!"),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := sanitize.LineBreaks(tt.args.data)
+
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
diff --git a/testdata/TestAssertXMLMarshaling/custom_marshaling/marshaled_xml.golden b/testdata/TestAssertXMLMarshaling/custom_marshaling/marshaled_xml.golden
index d1cdc64..3bf37bf 100644
--- a/testdata/TestAssertXMLMarshaling/custom_marshaling/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshaling/custom_marshaling/marshaled_xml.golden
@@ -1 +1 @@
-Hello World!
\ No newline at end of file
+Hello World!
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshaling/empty_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshaling/empty_struct/marshaled_xml.golden
index 6f87e3c..556f3ee 100644
--- a/testdata/TestAssertXMLMarshaling/empty_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshaling/empty_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshaling/full_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshaling/full_struct/marshaled_xml.golden
index 6defdfd..615ca53 100644
--- a/testdata/TestAssertXMLMarshaling/full_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshaling/full_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
cfda163c-d5c1-44a2-909b-5d2ce3a31979
The Traveler
@@ -6,4 +6,4 @@
Twelve Hawks
2005
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshaling/partial_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshaling/partial_struct/marshaled_xml.golden
index a4e50fb..9219250 100644
--- a/testdata/TestAssertXMLMarshaling/partial_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshaling/partial_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
cfda163c-d5c1-44a2-909b-5d2ce3a31979
The Traveler
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshalingP/custom_marshaling/marshaled_xml.golden b/testdata/TestAssertXMLMarshalingP/custom_marshaling/marshaled_xml.golden
index d1cdc64..3bf37bf 100644
--- a/testdata/TestAssertXMLMarshalingP/custom_marshaling/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshalingP/custom_marshaling/marshaled_xml.golden
@@ -1 +1 @@
-Hello World!
\ No newline at end of file
+Hello World!
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshalingP/empty_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshalingP/empty_struct/marshaled_xml.golden
index 2f055d7..64f793c 100644
--- a/testdata/TestAssertXMLMarshalingP/empty_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshalingP/empty_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshalingP/full_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshalingP/full_struct/marshaled_xml.golden
index 085f862..74af7c9 100644
--- a/testdata/TestAssertXMLMarshalingP/full_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshalingP/full_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
10eec54d-e30a-4428-be18-01095d889126
Time Travel
@@ -6,4 +6,4 @@
Brown
2021-10-27T22:30:34Z
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssertXMLMarshalingP/partial_struct/marshaled_xml.golden b/testdata/TestAssertXMLMarshalingP/partial_struct/marshaled_xml.golden
index 3cfa28b..c2b9496 100644
--- a/testdata/TestAssertXMLMarshalingP/partial_struct/marshaled_xml.golden
+++ b/testdata/TestAssertXMLMarshalingP/partial_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
10eec54d-e30a-4428-be18-01095d889126
Time Travel
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshaling/custom_marshaling/marshaled_xml.golden b/testdata/TestAssert_XMLMarshaling/custom_marshaling/marshaled_xml.golden
index d1cdc64..3bf37bf 100644
--- a/testdata/TestAssert_XMLMarshaling/custom_marshaling/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshaling/custom_marshaling/marshaled_xml.golden
@@ -1 +1 @@
-Hello World!
\ No newline at end of file
+Hello World!
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshaling/empty_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshaling/empty_struct/marshaled_xml.golden
index 6f87e3c..556f3ee 100644
--- a/testdata/TestAssert_XMLMarshaling/empty_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshaling/empty_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshaling/full_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshaling/full_struct/marshaled_xml.golden
index 6defdfd..615ca53 100644
--- a/testdata/TestAssert_XMLMarshaling/full_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshaling/full_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
cfda163c-d5c1-44a2-909b-5d2ce3a31979
The Traveler
@@ -6,4 +6,4 @@
Twelve Hawks
2005
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshaling/partial_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshaling/partial_struct/marshaled_xml.golden
index a4e50fb..9219250 100644
--- a/testdata/TestAssert_XMLMarshaling/partial_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshaling/partial_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
cfda163c-d5c1-44a2-909b-5d2ce3a31979
The Traveler
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshalingP/custom_marshaling/marshaled_xml.golden b/testdata/TestAssert_XMLMarshalingP/custom_marshaling/marshaled_xml.golden
index d1cdc64..3bf37bf 100644
--- a/testdata/TestAssert_XMLMarshalingP/custom_marshaling/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshalingP/custom_marshaling/marshaled_xml.golden
@@ -1 +1 @@
-Hello World!
\ No newline at end of file
+Hello World!
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshalingP/empty_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshalingP/empty_struct/marshaled_xml.golden
index 2f055d7..64f793c 100644
--- a/testdata/TestAssert_XMLMarshalingP/empty_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshalingP/empty_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshalingP/full_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshalingP/full_struct/marshaled_xml.golden
index 085f862..74af7c9 100644
--- a/testdata/TestAssert_XMLMarshalingP/full_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshalingP/full_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
10eec54d-e30a-4428-be18-01095d889126
Time Travel
@@ -6,4 +6,4 @@
Brown
2021-10-27T22:30:34Z
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/testdata/TestAssert_XMLMarshalingP/partial_struct/marshaled_xml.golden b/testdata/TestAssert_XMLMarshalingP/partial_struct/marshaled_xml.golden
index 3cfa28b..c2b9496 100644
--- a/testdata/TestAssert_XMLMarshalingP/partial_struct/marshaled_xml.golden
+++ b/testdata/TestAssert_XMLMarshalingP/partial_struct/marshaled_xml.golden
@@ -1,4 +1,4 @@
-
+
10eec54d-e30a-4428-be18-01095d889126
Time Travel
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/unmarshal/unmarshal.go b/unmarshal/unmarshal.go
new file mode 100644
index 0000000..3505f06
--- /dev/null
+++ b/unmarshal/unmarshal.go
@@ -0,0 +1,35 @@
+package unmarshal
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+
+ "gopkg.in/yaml.v3"
+)
+
+// JSON parses the JSON-encoded data and stores the result in the value pointed
+// to by v. Unknown fields in the JSON data is not allowed.
+func JSON(data []byte, v interface{}) error {
+ dec := json.NewDecoder(bytes.NewReader(data))
+ dec.DisallowUnknownFields()
+
+ return dec.Decode(v)
+}
+
+// XML parses the XML-encoded data and stores the result in the value pointed
+// to by v.
+func XML(data []byte, v interface{}) error {
+ dec := xml.NewDecoder(bytes.NewReader(data))
+
+ return dec.Decode(v)
+}
+
+// YAML parses the YAML-encoded data and stores the result in the value pointed
+// to by v. Unknown fields in the YAML data is not allowed.
+func YAML(data []byte, v interface{}) error {
+ dec := yaml.NewDecoder(bytes.NewReader(data))
+ dec.KnownFields(true)
+
+ return dec.Decode(v)
+}
diff --git a/unmarshal/unmarshal_test.go b/unmarshal/unmarshal_test.go
new file mode 100644
index 0000000..8cefac7
--- /dev/null
+++ b/unmarshal/unmarshal_test.go
@@ -0,0 +1,431 @@
+package unmarshal_test
+
+import (
+ "io"
+ "testing"
+
+ "github.com/jimeh/go-golden/unmarshal"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type book struct {
+ Title string `json:"title" yaml:"title" xml:"title"`
+ Author string `json:"author" yaml:"author" xml:"author"`
+ Price int `json:"price" yaml:"price" xml:"price"`
+}
+
+type shoe struct {
+ Make string `json:"make" yaml:"make" xml:"make"`
+ Model string `json:"model" yaml:"model" xml:"model"`
+ Size int `json:"size" yaml:"size" xml:"size"`
+}
+
+func TestJSON(t *testing.T) {
+ type args struct {
+ data []byte
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want interface{}
+ wantErr string
+ wantErrIs error
+ }{
+ {
+ name: "nil",
+ args: args{data: nil, v: nil},
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "empty string (1)",
+ args: args{
+ data: []byte(""),
+ v: &book{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "empty string (2)",
+ args: args{
+ data: []byte(""),
+ v: &shoe{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "no fields (1)",
+ args: args{
+ data: []byte("{}"),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "no fields (2)",
+ args: args{
+ data: []byte("{}"),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "empty fields (1)",
+ args: args{
+ data: []byte(`{"title":"","author":"","price":0}`),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "empty fields (2)",
+ args: args{
+ data: []byte(`{"make":"","model":"","size":0}`),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "populated fields (1)",
+ args: args{
+ data: []byte(`{"title":"a","author":"b","price":499}`),
+ v: &book{},
+ },
+ want: &book{Title: "a", Author: "b", Price: 499},
+ },
+ {
+ name: "populated fields (2)",
+ args: args{
+ data: []byte(`{"Make":"a","model":"b","size":42}`),
+ v: &shoe{},
+ },
+ want: &shoe{Make: "a", Model: "b", Size: 42},
+ },
+ {
+ name: "unknown field (1)",
+ args: args{
+ data: []byte(`{"title":"a","summary":"b","price":499}`),
+ v: &book{},
+ },
+ wantErr: `json: unknown field "summary"`,
+ },
+ {
+ name: "unknown field (2)",
+ args: args{
+ data: []byte(`{"make":"a","inventory":"b","size":42}`),
+ v: &shoe{},
+ },
+ wantErr: `json: unknown field "inventory"`,
+ },
+ {
+ name: "to channel",
+ args: args{
+ data: []byte(`{"make":"a","model":"b","size":42}`),
+ v: make(chan int),
+ },
+ wantErr: `json: Unmarshal(non-pointer chan int)`,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := unmarshal.JSON(tt.args.data, tt.args.v)
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ assert.Equal(t, tt.want, tt.args.v)
+ }
+ })
+ }
+}
+
+func TestYAML(t *testing.T) {
+ type args struct {
+ data []byte
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want interface{}
+ wantErr string
+ wantErrIs error
+ }{
+ {
+ name: "nil",
+ args: args{data: nil, v: nil},
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "empty string (1)",
+ args: args{
+ data: []byte(""),
+ v: &book{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "empty string (2)",
+ args: args{
+ data: []byte(""),
+ v: &shoe{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "no fields (1)",
+ args: args{
+ data: []byte("{}"),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "no fields (2)",
+ args: args{
+ data: []byte("{}"),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "empty fields (1)",
+ args: args{
+ data: []byte("title:\nauthor:\nprice: 0"),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "empty fields (2)",
+ args: args{
+ data: []byte("make:\nmodel:\nsize: 0"),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "populated fields (1)",
+ args: args{
+ data: []byte("title: a\nauthor: b\nprice: 499"),
+ v: &book{},
+ },
+ want: &book{Title: "a", Author: "b", Price: 499},
+ },
+ {
+ name: "populated fields (2)",
+ args: args{
+ data: []byte("make: a\nmodel: b\nsize: 42"),
+ v: &shoe{},
+ },
+ want: &shoe{Make: "a", Model: "b", Size: 42},
+ },
+ {
+ name: "unknown field (1)",
+ args: args{
+ data: []byte("title: a\nsummary: b\nprice: 499"),
+ v: &book{},
+ },
+ wantErr: "yaml: unmarshal errors:\n " +
+ "line 2: field summary not found in type unmarshal_test.book",
+ },
+ {
+ name: "unknown field (2)",
+ args: args{
+ data: []byte("make: a\ninventory: b\nsize: 42"),
+ v: &shoe{},
+ },
+ wantErr: "yaml: unmarshal errors:\n " +
+ "line 2: field inventory not found in type unmarshal_test.shoe",
+ },
+ {
+ name: "to channel",
+ args: args{
+ data: []byte("make: a\nmodel: b\nsize: 42"),
+ v: make(chan int),
+ },
+ wantErr: "yaml: unmarshal errors:\n " +
+ "line 1: cannot unmarshal !!map into chan int",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := unmarshal.YAML(tt.args.data, tt.args.v)
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ assert.Equal(t, tt.want, tt.args.v)
+ }
+ })
+ }
+}
+
+func TestXML(t *testing.T) {
+ type args struct {
+ data []byte
+ v interface{}
+ }
+ tests := []struct {
+ name string
+ args args
+ want interface{}
+ wantErr string
+ wantErrIs error
+ }{
+ {
+ name: "nil",
+ args: args{data: nil, v: nil},
+ wantErr: "non-pointer passed to Unmarshal",
+ },
+ {
+ name: "empty string (1)",
+ args: args{
+ data: []byte(""),
+ v: &book{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "empty string (2)",
+ args: args{
+ data: []byte(""),
+ v: &shoe{},
+ },
+ wantErrIs: io.EOF,
+ },
+ {
+ name: "no fields (1)",
+ args: args{
+ data: []byte(""),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "no fields (2)",
+ args: args{
+ data: []byte(""),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "empty fields (1)",
+ args: args{
+ data: []byte("" +
+ "" +
+ "" +
+ "" +
+ ""),
+ v: &book{},
+ },
+ want: &book{},
+ },
+ {
+ name: "empty fields (2)",
+ args: args{
+ data: []byte("" +
+ "" +
+ "" +
+ "" +
+ ""),
+ v: &shoe{},
+ },
+ want: &shoe{},
+ },
+ {
+ name: "populated fields (1)",
+ args: args{
+ data: []byte("" +
+ "a" +
+ "b" +
+ "499" +
+ ""),
+ v: &book{},
+ },
+ want: &book{Title: "a", Author: "b", Price: 499},
+ },
+ {
+ name: "populated fields (2)",
+ args: args{
+ data: []byte("" +
+ "a" +
+ "b" +
+ "42" +
+ ""),
+ v: &shoe{},
+ },
+ want: &shoe{Make: "a", Model: "b", Size: 42},
+ },
+ {
+ name: "unknown field (1)",
+ args: args{
+ data: []byte("" +
+ "a" +
+ "b" +
+ "499" +
+ ""),
+ v: &book{},
+ },
+ want: &book{Title: "a", Author: "", Price: 499},
+ },
+ {
+ name: "unknown field (2)",
+ args: args{
+ data: []byte("" +
+ "a" +
+ "b" +
+ "42" +
+ ""),
+ v: &shoe{},
+ },
+ want: &shoe{Make: "a", Model: "", Size: 42},
+ },
+ {
+ name: "to channel",
+ args: args{
+ data: []byte("" +
+ "a" +
+ "b" +
+ "42" +
+ ""),
+ v: make(chan int),
+ },
+ wantErr: "non-pointer passed to Unmarshal",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := unmarshal.XML(tt.args.data, tt.args.v)
+
+ if tt.wantErr != "" {
+ assert.EqualError(t, err, tt.wantErr)
+ }
+
+ if tt.wantErrIs != nil {
+ assert.ErrorIs(t, err, tt.wantErrIs)
+ }
+
+ if tt.wantErr == "" && tt.wantErrIs == nil {
+ require.NoError(t, err)
+ assert.Equal(t, tt.want, tt.args.v)
+ }
+ })
+ }
+}
diff --git a/update_test.go b/update_test.go
index 26937a7..0523e8a 100644
--- a/update_test.go
+++ b/update_test.go
@@ -41,56 +41,171 @@ var envUpdateFuncTestCases = []struct {
env: map[string]string{"GOLDEN_UPDATE": "y"},
want: true,
},
+ {
+ name: "GOLDEN_UPDATE set to Y",
+ env: map[string]string{"GOLDEN_UPDATE": "Y"},
+ want: true,
+ },
{
name: "GOLDEN_UPDATE set to n",
env: map[string]string{"GOLDEN_UPDATE": "n"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to N",
+ env: map[string]string{"GOLDEN_UPDATE": "N"},
+ want: false,
+ },
{
name: "GOLDEN_UPDATE set to t",
env: map[string]string{"GOLDEN_UPDATE": "t"},
want: true,
},
+ {
+ name: "GOLDEN_UPDATE set to T",
+ env: map[string]string{"GOLDEN_UPDATE": "T"},
+ want: true,
+ },
{
name: "GOLDEN_UPDATE set to f",
env: map[string]string{"GOLDEN_UPDATE": "f"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to F",
+ env: map[string]string{"GOLDEN_UPDATE": "F"},
+ want: false,
+ },
{
name: "GOLDEN_UPDATE set to yes",
env: map[string]string{"GOLDEN_UPDATE": "yes"},
want: true,
},
+ {
+ name: "GOLDEN_UPDATE set to Yes",
+ env: map[string]string{"GOLDEN_UPDATE": "Yes"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to YeS",
+ env: map[string]string{"GOLDEN_UPDATE": "YeS"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to YES",
+ env: map[string]string{"GOLDEN_UPDATE": "YES"},
+ want: true,
+ },
{
name: "GOLDEN_UPDATE set to no",
env: map[string]string{"GOLDEN_UPDATE": "no"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to No",
+ env: map[string]string{"GOLDEN_UPDATE": "No"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to nO",
+ env: map[string]string{"GOLDEN_UPDATE": "nO"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to NO",
+ env: map[string]string{"GOLDEN_UPDATE": "NO"},
+ want: false,
+ },
{
name: "GOLDEN_UPDATE set to on",
env: map[string]string{"GOLDEN_UPDATE": "on"},
want: true,
},
+ {
+ name: "GOLDEN_UPDATE set to oN",
+ env: map[string]string{"GOLDEN_UPDATE": "oN"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to On",
+ env: map[string]string{"GOLDEN_UPDATE": "On"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to ON",
+ env: map[string]string{"GOLDEN_UPDATE": "ON"},
+ want: true,
+ },
{
name: "GOLDEN_UPDATE set to off",
env: map[string]string{"GOLDEN_UPDATE": "off"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to Off",
+ env: map[string]string{"GOLDEN_UPDATE": "Off"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to oFF",
+ env: map[string]string{"GOLDEN_UPDATE": "oFF"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to OFF",
+ env: map[string]string{"GOLDEN_UPDATE": "OFF"},
+ want: false,
+ },
{
name: "GOLDEN_UPDATE set to true",
env: map[string]string{"GOLDEN_UPDATE": "true"},
want: true,
},
+ {
+ name: "GOLDEN_UPDATE set to True",
+ env: map[string]string{"GOLDEN_UPDATE": "True"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to TruE",
+ env: map[string]string{"GOLDEN_UPDATE": "TruE"},
+ want: true,
+ },
+ {
+ name: "GOLDEN_UPDATE set to TRUE",
+ env: map[string]string{"GOLDEN_UPDATE": "TRUE"},
+ want: true,
+ },
{
name: "GOLDEN_UPDATE set to false",
env: map[string]string{"GOLDEN_UPDATE": "false"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to False",
+ env: map[string]string{"GOLDEN_UPDATE": "False"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to FaLsE",
+ env: map[string]string{"GOLDEN_UPDATE": "FaLsE"},
+ want: false,
+ },
+ {
+ name: "GOLDEN_UPDATE set to FALSE",
+ env: map[string]string{"GOLDEN_UPDATE": "FALSE"},
+ want: false,
+ },
{
name: "GOLDEN_UPDATE set to foobarnopebbq",
env: map[string]string{"GOLDEN_UPDATE": "foobarnopebbq"},
want: false,
},
+ {
+ name: "GOLDEN_UPDATE set to FOOBARNOPEBBQ",
+ env: map[string]string{"GOLDEN_UPDATE": "FOOBARNOPEBBQ"},
+ want: false,
+ },
}
func TestEnvUpdateFunc(t *testing.T) {