12 Commits

Author SHA1 Message Date
jimehbot[bot]
e17b04717f chore(main): release 0.2.1 (#15)
Co-authored-by: jimehbot[bot] <132453784+jimehbot[bot]@users.noreply.github.com>
2025-04-05 12:34:00 +01:00
550ba17fb0 feat(options): add optional Options arguments to New() function (#14)
Enables simpler creation of custom *Golden instances, as you can just pass in the custom values to New(), rather than modifying fields after increating the Golden instance.
2025-04-05 12:28:57 +01:00
jimehbot[bot]
8a88e5174b chore(main): release 0.2.0 (#8)
Co-authored-by: jimehbot[bot] <132453784+jimehbot[bot]@users.noreply.github.com>
2025-03-24 13:19:39 +00:00
bc86ba7a6d feat(do): add new Do function to simplify golden usage (#11)
Add new Do function whihc ecapsulates the common pattern of writing to
golden file if the update mechnism evaluates to true, followed by
immediately reading the file. Compresses four lines into one.
2025-03-24 13:17:17 +00:00
62e8344ff3 feat(interface): use TestingT interface intead of *testing.T in function signatures (#13) 2025-03-24 13:04:34 +00:00
e46dc124ff feat(defaults): expose a Default *Golden instance (#9)
Also change the Default* constants to variables so they can be modified
as well.
2025-03-24 12:54:29 +00:00
6817ec6101 feat(update): make default GOLDEN_UPDATE env var check case-insensitive (#10) 2025-03-24 12:53:34 +00:00
8f4d3d4170 chore(deps/dev): upgrade development dependencies, including golangci-lint (#12) 2025-03-24 12:52:20 +00:00
b2112ca475 ci(release): add release-please config and workflow job (#7) 2025-03-24 11:36:39 +00:00
27e0134701 doc(readme): fix badges 2025-03-22 02:00:17 +00:00
85ae6e9ae3 feat(go)!: upgrade to Go 1.17 (from 1.15) (#6)
BREAKING CHANGE: Go 1.17 or later is now required, up from Go 1.15.
2025-03-22 01:37:06 +00:00
f5a03af9ce ci(deps): update actions and lint config to get them all running again (#5) 2025-03-22 01:23:12 +00:00
140 changed files with 912 additions and 3591 deletions

3
.github/.release-please-manifest.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
".": "0.2.1"
}

15
.github/release-please-config.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"bootstrap-sha": "edb189f0863fddd94211a5b4c5b5bcf7a90fbd10",
"packages": {
".": {
"release-type": "go",
"changelog-path": "CHANGELOG.md",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"always-update": true,
"draft": false,
"prerelease": false
}
},
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
}

View File

@@ -6,29 +6,28 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
checks: write
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
version: v1.43
env:
VERBOSE: "true"
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.64
tidy:
name: Tidy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.15
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
go-version-file: go.mod
- name: Check if mods are tidy
run: make check-tidy
@@ -36,18 +35,12 @@ jobs:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.15
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
go-version-file: go.mod
- name: Publish coverage
uses: paambaati/codeclimate-action@v2.7.4
uses: paambaati/codeclimate-action@v9.0.0
env:
VERBOSE: "true"
GOMAXPROCS: 4
@@ -68,20 +61,30 @@ jobs:
- macos-latest
- windows-latest
go_version:
- "1.15"
- "1.16"
- "1.17"
- "1.18"
- "1.19"
- "1.20"
- "stable"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go_version }}
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
check-latest: true
- name: Run tests
run: go test -v -count=1 -race ./...
release-please:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
outputs:
release_created: ${{ steps.release-please.outputs.release_created }}
version: ${{ steps.release-please.outputs.version }}
steps:
- uses: jimeh/release-please-manifest-action@v2
id: release-please
with:
app-id: ${{ secrets.RELEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}

5
.gitignore vendored
View File

@@ -1,6 +1,5 @@
bin/*
coverage.out
testdata/TestFile*
testdata/TestGet*
testdata/TestSet*
testdata/*
!testdata/TestExample*

View File

@@ -4,18 +4,13 @@ linters-settings:
statements: 150
gocyclo:
min-complexity: 20
golint:
min-confidence: 0
govet:
check-shadowing: true
enable-all: true
disable:
- fieldalignment
lll:
line-length: 80
tab-width: 4
maligned:
suggest-new: true
misspell:
locale: US
@@ -24,13 +19,11 @@ linters:
enable:
- asciicheck
- bodyclose
- deadcode
- depguard
- copyloopvar
- durationcheck
- errcheck
- errorlint
- exhaustive
- exportloopref
- funlen
- gochecknoinits
- goconst
@@ -58,13 +51,10 @@ linters:
- rowserrcheck
- sqlclosecheck
- staticcheck
- structcheck
- tparallel
- typecheck
- unconvert
- unparam
- unused
- varcheck
- wastedassign
- whitespace

View File

@@ -2,6 +2,28 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [0.2.1](https://github.com/jimeh/go-golden/compare/v0.2.0...v0.2.1) (2025-04-05)
### Features
* **options:** add optional Options arguments to New() function ([#14](https://github.com/jimeh/go-golden/issues/14)) ([550ba17](https://github.com/jimeh/go-golden/commit/550ba17fb0be76372eab83f2494ee4ffff804fa6))
## [0.2.0](https://github.com/jimeh/go-golden/compare/v0.1.0...v0.2.0) (2025-03-24)
### ⚠ BREAKING CHANGES
* **go:** Go 1.17 or later is now required, up from Go 1.15.
### Features
* **defaults:** expose a Default *Golden instance ([#9](https://github.com/jimeh/go-golden/issues/9)) ([e46dc12](https://github.com/jimeh/go-golden/commit/e46dc124ff22e52dfc050174c7b3de980c980912))
* **do:** add new Do function to simplify golden usage ([#11](https://github.com/jimeh/go-golden/issues/11)) ([bc86ba7](https://github.com/jimeh/go-golden/commit/bc86ba7a6d9f4374c9fb15f78e51eedf50df04d2))
* **go:** upgrade to Go 1.17 (from 1.15) ([#6](https://github.com/jimeh/go-golden/issues/6)) ([85ae6e9](https://github.com/jimeh/go-golden/commit/85ae6e9ae3c4222d68faee0c44a1fd105a2e04b4))
* **interface:** use TestingT interface intead of *testing.T in function signatures ([#13](https://github.com/jimeh/go-golden/issues/13)) ([62e8344](https://github.com/jimeh/go-golden/commit/62e8344ff33dccf0a5666e94361a143d34558bf0))
* **update:** make default GOLDEN_UPDATE env var check case-insensitive ([#10](https://github.com/jimeh/go-golden/issues/10)) ([6817ec6](https://github.com/jimeh/go-golden/commit/6817ec6101558b3914984d4ee3fe816d534f87bb))
## 0.1.0 (2021-10-28)

View File

@@ -34,23 +34,19 @@ SHELL := env \
# Tools
#
TOOLS += $(TOOLDIR)/gobin
$(TOOLDIR)/gobin:
GO111MODULE=off go get -u github.com/myitcv/gobin
# external tool
define tool # 1: binary-name, 2: go-import-path
TOOLS += $(TOOLDIR)/$(1)
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile
gobin $(V) "$(2)"
$(TOOLDIR)/$(1): Makefile
GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
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.43))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod))
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc@latest))
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports@latest))
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
.PHONY: tools
tools: $(TOOLS)
@@ -186,20 +182,3 @@ check-tidy:
docs: $(TOOLDIR)/godoc
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
@godoc -http=127.0.0.1:6060
#
# Release
#
.PHONY: new-version
new-version: check-npx
npx standard-version
.PHONY: next-version
next-version: check-npx
npx standard-version --dry-run
.PHONY: check-npx
check-npx:
$(if $(shell which npx),,\
$(error No npx found in PATH, please install NodeJS))

View File

@@ -10,26 +10,12 @@
</p>
<p align="center">
<a href="https://pkg.go.dev/github.com/jimeh/go-golden">
<img src="https://img.shields.io/badge/%E2%80%8B-reference-387b97.svg?logo=go&logoColor=white"
alt="Go Reference">
</a>
<a href="https://github.com/jimeh/go-golden/actions">
<img src="https://img.shields.io/github/workflow/status/jimeh/go-golden/CI.svg?logo=github" alt="Actions Status">
</a>
<a href="https://codeclimate.com/github/jimeh/go-golden">
<img src="https://img.shields.io/codeclimate/coverage/jimeh/go-golden.svg?logo=code%20climate" alt="Coverage">
</a>
<a href="https://github.com/jimeh/go-golden/issues">
<img src="https://img.shields.io/github/issues-raw/jimeh/go-golden.svg?style=flat&logo=github&logoColor=white"
alt="GitHub issues">
</a>
<a href="https://github.com/jimeh/go-golden/pulls">
<img src="https://img.shields.io/github/issues-pr-raw/jimeh/go-golden.svg?style=flat&logo=github&logoColor=white" alt="GitHub pull requests">
</a>
<a href="https://github.com/jimeh/go-golden/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/jimeh/go-golden.svg?style=flat" alt="License Status">
</a>
<a href="https://pkg.go.dev/github.com/jimeh/go-golden"><img src="https://img.shields.io/badge/%E2%80%8B-reference-387b97.svg?logo=go&logoColor=white" alt="Go Reference"></a>
<a href="https://github.com/jimeh/go-golden/actions"><img src="https://img.shields.io/github/actions/workflow/status/jimeh/go-golden/ci.yml?logo=github" alt="Actions Status"></a>
<a href="https://codeclimate.com/github/jimeh/go-golden"><img src="https://img.shields.io/codeclimate/coverage/jimeh/go-golden.svg?logo=code%20climate" alt="Coverage"></a>
<a href="https://github.com/jimeh/go-golden/issues"><img src="https://img.shields.io/github/issues-raw/jimeh/go-golden.svg?style=flat&logo=github&logoColor=white" alt="GitHub issues"></a>
<a href="https://github.com/jimeh/go-golden/pulls"><img src="https://img.shields.io/github/issues-pr-raw/jimeh/go-golden.svg?style=flat&logo=github&logoColor=white" alt="GitHub pull requests"></a>
<a href="https://github.com/jimeh/go-golden/blob/master/LICENSE"><img src="https://img.shields.io/github/license/jimeh/go-golden.svg?style=flat" alt="License Status"></a>
</p>
## Import
@@ -42,15 +28,12 @@ import "github.com/jimeh/go-golden"
```go
func TestExampleMyStruct(t *testing.T) {
got, err := json.Marshal(&MyStruct{Foo: "Bar"})
require.NoError(t, err)
got, err := json.Marshal(&MyStruct{Foo: "Bar"})
require.NoError(t, err)
if golden.Update() {
golden.Set(t, got)
}
want := golden.Get(t)
want := golden.Do(t, got)
assert.Equal(t, want, got)
assert.Equal(t, want, got)
}
```
@@ -58,6 +41,15 @@ The above example will read/write to:
- `testdata/TestExampleMyStruct.golden`
The call to `golden.Do()` is equivalent to:
```go
if golden.Update() {
golden.Set(t, got)
}
want := golden.Get(t)
```
To update the golden file (have `golden.Update()` return `true`), simply set the
`GOLDEN_UPDATE` environment variable to one of `1`, `y`, `t`, `yes`, `on`, or
`true` when running tests.
@@ -70,4 +62,4 @@ for documentation and examples.
## License
[MIT](https://github.com/jimeh/go-golden/blob/main/LICENSE)
[MIT](https://github.com/jimeh/go-golden/blob/master/LICENSE)

View File

@@ -1,69 +0,0 @@
package golden
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 TestingT, v interface{}) {
t.Helper()
defaultAsserter.JSONMarshaling(t, v)
}
// AssertJSONMarshalingP 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.
func AssertJSONMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
defaultAsserter.JSONMarshalingP(t, v, want)
}
// AssertXMLMarshaling 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.
func AssertXMLMarshaling(t TestingT, v interface{}) {
t.Helper()
defaultAsserter.XMLMarshaling(t, v)
}
// AssertXMLMarshalingP 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.
func AssertXMLMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
defaultAsserter.XMLMarshalingP(t, v, want)
}
// AssertYAMLMarshaling 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.
func AssertYAMLMarshaling(t TestingT, v interface{}) {
t.Helper()
defaultAsserter.YAMLMarshaling(t, v)
}
// AssertYAMLMarshalingP 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.
func AssertYAMLMarshalingP(t TestingT, v, want interface{}) {
t.Helper()
defaultAsserter.YAMLMarshalingP(t, v, want)
}

View File

@@ -1,68 +0,0 @@
package golden_test
import (
"testing"
"github.com/jimeh/go-golden"
)
type MyBook struct {
FooBar string `json:"foo_bar,omitempty" yaml:"fooBar,omitempty" xml:"Foo_Bar,omitempty"`
Bar string `json:"-" yaml:"-" xml:"-"`
baz string
}
// TestExampleMyBookMarshaling reads/writes the following golden files:
//
// testdata/TestExampleMyBookMarshaling/marshaled_json.golden
// testdata/TestExampleMyBookMarshaling/marshaled_xml.golden
// testdata/TestExampleMyBookMarshaling/marshaled_yaml.golden
//
func TestExampleMyBookMarshaling(t *testing.T) {
obj := &MyBook{FooBar: "Hello World!"}
golden.AssertJSONMarshaling(t, obj)
golden.AssertYAMLMarshaling(t, obj)
golden.AssertXMLMarshaling(t, obj)
}
// TestExampleMyBookMarshalingP reads/writes the following golden files:
//
// testdata/TestExampleMyBookMarshalingP/marshaled_json.golden
// testdata/TestExampleMyBookMarshalingP/marshaled_xml.golden
// testdata/TestExampleMyBookMarshalingP/marshaled_yaml.golden
//
func TestExampleMyBookMarshalingP(t *testing.T) {
obj := &MyBook{FooBar: "Hello World!", Bar: "Oops", baz: "nope!"}
want := &MyBook{FooBar: "Hello World!"}
golden.AssertJSONMarshalingP(t, obj, want)
golden.AssertYAMLMarshalingP(t, obj, want)
golden.AssertXMLMarshalingP(t, obj, want)
}
// TestExampleMyBookMarshalingTabular reads/writes the following golden files:
//
// testdata/TestExampleMyBookMarshalingTabular/empty/marshaled_json.golden
// testdata/TestExampleMyBookMarshalingTabular/empty/marshaled_xml.golden
// testdata/TestExampleMyBookMarshalingTabular/empty/marshaled_yaml.golden
// testdata/TestExampleMyBookMarshalingTabular/full/marshaled_json.golden
// testdata/TestExampleMyBookMarshalingTabular/full/marshaled_xml.golden
// testdata/TestExampleMyBookMarshalingTabular/full/marshaled_yaml.golden
//
func TestExampleMyBookMarshalingTabular(t *testing.T) {
tests := []struct {
name string
obj *MyBook
}{
{name: "empty", obj: &MyBook{}},
{name: "full", obj: &MyBook{FooBar: "Hello World!"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
golden.AssertJSONMarshaling(t, tt.obj)
golden.AssertYAMLMarshaling(t, tt.obj)
golden.AssertXMLMarshaling(t, tt.obj)
})
}
}

View File

@@ -1,559 +0,0 @@
package golden
import (
"bytes"
"encoding/xml"
"fmt"
"regexp"
"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"
)
//
// Helpers
//
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 {
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"`
Year int `json:"year,omitempty" yaml:"year,omitempty" xml:"year,omitempty"`
}
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"`
Date *time.Time `json:"date,omitempty" yaml:"date,omitempty" xml:"date,omitempty"`
Rank int `json:"-" yaml:"-" xml:"-"`
order int
}
// comic is used for testing custom marshal/unmarshal functions on a type.
type comic struct {
ID string
Name string
Issue string
Ignored string
}
type xmlComic struct {
ID string `xml:"id,attr"`
Name string `xml:",chardata"`
Issue string `xml:"issue,attr"`
}
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 {
m := regexp.MustCompile(`^{\s*"(.*?)":\s*"(.*?)=(.*)"\s*}$`)
matches := m.FindSubmatch(bytes.TrimSpace(data))
if matches == nil {
return nil
}
s.ID = string(matches[1])
s.Name = string(matches[2])
s.Issue = string(matches[3])
return nil
}
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 {
// Horribly hacky code, but it works and specifically only needs to extract
// these specific three values.
if len(value.Content) == 2 {
s.ID = value.Content[0].Value
if len(value.Content[1].Content) == 2 {
s.Name = value.Content[1].Content[0].Value
s.Issue = value.Content[1].Content[1].Value
}
}
return nil
}
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 {
x := &xmlComic{}
_ = d.DecodeElement(x, &start)
v := comic{ID: x.ID, Name: x.Name, Issue: x.Issue}
*s = v
return nil
}
func boolPtr(b bool) *bool {
return &b
}
func intPtr(i int) *int {
return &i
}
func stringPtr(s string) *string {
return &s
}
//
// Test cases
//
var marhalingTestCases = []struct {
name string
v interface{}
}{
{
name: "true bool pointer",
v: boolPtr(true),
},
{
name: "false bool pointer",
v: boolPtr(false),
},
{
name: "int pointer",
v: intPtr(42),
},
{
name: "string pointer",
v: stringPtr("hello world"),
},
{
name: "empty struct",
v: &book{},
},
{
name: "partial struct",
v: &book{
ID: "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
Title: "The Traveler",
},
},
{
name: "full struct",
v: &book{
ID: "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
Title: "The Traveler",
Author: &author{
FirstName: "John",
LastName: "Twelve Hawks",
},
Year: 2005,
},
},
{
name: "custom marshaling",
v: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
},
},
}
var articleDate = time.Date(
2021, time.October, 27, 23, 30, 34, 0, time.FixedZone("", 1*60*60),
).UTC()
var marshalingPTestCases = []struct {
name string
v interface{}
want interface{}
}{
{
name: "true bool pointer",
v: boolPtr(true),
want: boolPtr(true),
},
{
name: "false bool pointer",
v: boolPtr(false),
want: boolPtr(false),
},
{
name: "int pointer",
v: intPtr(42),
want: intPtr(42),
},
{
name: "string pointer",
v: stringPtr("hello world"),
want: stringPtr("hello world"),
},
{
name: "empty struct",
v: &article{},
want: &article{},
},
{
name: "partial struct",
v: &book{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
},
want: &book{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
},
},
{
name: "full struct",
v: &article{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
Author: &author{
FirstName: "Doc",
LastName: "Brown",
},
Date: &articleDate,
Rank: 8,
order: 16,
},
want: &article{
ID: "10eec54d-e30a-4428-be18-01095d889126",
Title: "Time Travel",
Author: &author{
FirstName: "Doc",
LastName: "Brown",
},
Date: &articleDate,
},
},
{
name: "custom marshaling",
v: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
Ignored: "don't pay attention to this :)",
},
want: &comic{
ID: "2fd5af35-b85e-4f03-8eba-524be28d7a5b",
Name: "Hello World!",
Issue: "Forty Two",
},
},
}
//
// 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) {
AssertJSONMarshaling(t, tt.v)
})
}
}
func TestAssertJSONMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
AssertJSONMarshalingP(t, tt.v, tt.want)
})
}
}
func TestAssertXMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
AssertXMLMarshaling(t, tt.v)
})
}
}
func TestAssertXMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
AssertXMLMarshalingP(t, tt.v, tt.want)
})
}
}
func TestAssertYAMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
AssertYAMLMarshaling(t, tt.v)
})
}
}
func TestAssertYAMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
AssertYAMLMarshalingP(t, tt.v, tt.want)
})
}
}
func TestAssert_JSONMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.JSONMarshaling(t, tt.v)
})
}
}
func TestAssert_JSONMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.JSONMarshalingP(t, tt.v, tt.want)
})
}
}
func TestAssert_XMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.XMLMarshaling(t, tt.v)
})
}
}
func TestAssert_XMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.XMLMarshalingP(t, tt.v, tt.want)
})
}
}
func TestAssert_YAMLMarshaling(t *testing.T) {
for _, tt := range marhalingTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.YAMLMarshaling(t, tt.v)
})
}
}
func TestAssert_YAMLMarshalingP(t *testing.T) {
for _, tt := range marshalingPTestCases {
t.Run(tt.name, func(t *testing.T) {
assert := NewAsserter()
assert.YAMLMarshalingP(t, tt.v, tt.want)
})
}
}

View File

@@ -1,178 +0,0 @@
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)
}

View File

@@ -10,31 +10,29 @@ import (
"github.com/stretchr/testify/require"
)
// The tests in this file are examples from the README and the package-level Go
// documentation.
type MyStruct struct {
Foo string `json:"foo,omitempty"`
}
// TestExampleMyStruct reads/writes the following golden file:
//
// testdata/TestExampleMyStruct.golden
//
// testdata/TestExampleMyStruct.golden
func TestExampleMyStruct(t *testing.T) {
got, err := json.Marshal(&MyStruct{Foo: "Bar"})
require.NoError(t, err)
if golden.Update() {
golden.Set(t, got)
}
want := golden.Get(t)
want := golden.Do(t, got)
assert.Equal(t, want, got)
}
// TestExampleMyStructTabular reads/writes the following golden files:
//
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
//
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
func TestExampleMyStructTabular(t *testing.T) {
tests := []struct {
name string
@@ -48,10 +46,7 @@ func TestExampleMyStructTabular(t *testing.T) {
got, err := json.Marshal(tt.obj)
require.NoError(t, err)
if golden.Update() {
golden.Set(t, got)
}
want := golden.Get(t)
want := golden.Do(t, got)
assert.Equal(t, want, got)
})
@@ -60,29 +55,25 @@ func TestExampleMyStructTabular(t *testing.T) {
// TestExampleMyStructP reads/writes the following golden file:
//
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
//
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
func TestExampleMyStructP(t *testing.T) {
gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
if golden.Update() {
golden.SetP(t, "json", gotJSON)
golden.SetP(t, "xml", gotXML)
}
wantJSON := golden.DoP(t, "json", gotJSON)
wantXML := golden.DoP(t, "xml", gotXML)
assert.Equal(t, golden.GetP(t, "json"), gotJSON)
assert.Equal(t, golden.GetP(t, "xml"), gotXML)
assert.Equal(t, wantJSON, gotJSON)
assert.Equal(t, wantXML, gotXML)
}
// TestExampleMyStructTabularP reads/writes the following golden file:
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
func TestExampleMyStructTabularP(t *testing.T) {
tests := []struct {
name string
@@ -96,13 +87,11 @@ func TestExampleMyStructTabularP(t *testing.T) {
gotJSON, _ := json.Marshal(tt.obj)
gotXML, _ := xml.Marshal(tt.obj)
if golden.Update() {
golden.SetP(t, "json", gotJSON)
golden.SetP(t, "xml", gotXML)
}
wantJSON := golden.DoP(t, "json", gotJSON)
wantXML := golden.DoP(t, "xml", gotXML)
assert.Equal(t, golden.GetP(t, "json"), gotJSON)
assert.Equal(t, golden.GetP(t, "xml"), gotXML)
assert.Equal(t, wantJSON, gotJSON)
assert.Equal(t, wantXML, gotXML)
})
}
}

12
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/jimeh/go-golden
go 1.15
go 1.17
require github.com/stretchr/testify v1.10.0
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
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

38
go.sum
View File

@@ -1,33 +1,19 @@
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

594
golden.go
View File

@@ -1,239 +1,200 @@
// 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 the JSON, YAML, and XML formats of structs.
// with a focus on simplicity through it's default behavior.
//
// Golden file names are based on the name of the test function and any subtest
// Golden file names are based on the name of the test function and any sub-test
// names by calling t.Name(). File names are sanitized to ensure they're
// compatible with Linux, macOS and Windows systems regardless of what
// characters might be in a subtest's name.
// compatible with Linux, macOS and Windows systems regardless of what crazy
// characters might be in a sub-test's name.
//
// Assertion Helpers
// # Usage
//
// There are marshaling assertion helpers for JSON, YAML, and XML. Each helper
// operates in two stages:
// Typical usage should look something like this:
//
// 1. Marshal the provided object to a byte slice and read the corresponding
// golden file from disk followed by verifying both are identical.
// func TestExampleMyStruct(t *testing.T) {
// got, err := json.Marshal(&MyStruct{Foo: "Bar"})
// require.NoError(t, err)
//
// 2. Unmarshal the content of the golden file, verifying that the result is
// identical to the original given object.
// want := golden.Do(t, got)
//
// Typical usage of a assertion helper would look something like this in a
// tabular test:
//
// type MyStruct struct {
// FooBar string `json:"foo_bar" yaml:"fooBar" xml:"Foo_Bar"`
// }
//
// func TestMyStructMarshaling(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty", obj: &MyStruct{}},
// {name: "full", obj: &MyStruct{FooBar: "Hello World!"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// golden.AssertJSONMarshaling(t, tt.obj)
// golden.AssertYAMLMarshaling(t, tt.obj)
// golden.AssertXMLMarshaling(t, tt.obj)
// })
// }
// }
//
// The above example will read from the following golden files:
//
// testdata/TestMyStructMarshaling/empty/marshaled_json.golden
// testdata/TestMyStructMarshaling/empty/marshaled_xml.golden
// testdata/TestMyStructMarshaling/empty/marshaled_yaml.golden
// testdata/TestMyStructMarshaling/full/marshaled_json.golden
// testdata/TestMyStructMarshaling/full/marshaled_xml.golden
// testdata/TestMyStructMarshaling/full/marshaled_yaml.golden
//
// If a corresponding golden file cannot be found on disk, the test will fail.
// To create/update golden files, simply set the GOLDEN_UPDATE environment
// variable to one of "1", "y", "t", "yes", "on", or "true" when running the
// tests.
//
// Golden files should be committed to source control, as it allow tests to fail
// when the marshaling results for an object changes.
//
// Golden Usage
//
// 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"})
// require.NoError(t, err)
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
//
// assert.Equal(t, want, got)
// }
// assert.Equal(t, want, got)
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStruct.golden
// testdata/TestExampleMyStruct.golden
//
// The call to golden.Do() is equivalent to:
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
//
// To update the golden file (have golden.Update() return true), simply set the
// GOLDEN_UPDATE environment variable to one of "1", "y", "t", "yes", "on", or
// "true" when running tests.
//
// Sub-Tests
// # Sub-Tests
//
// As the golden filename is based on t.Name(), it works with sub-tests too,
// ensuring each sub-test gets it's own golden file. For example:
// ensuring each sub-test gets its own golden file. For example:
//
// func TestExampleMyStructTabular(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := json.Marshal(tt.obj)
// require.NoError(t, err)
// func TestExampleMyStructTabular(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := json.Marshal(tt.obj)
// require.NoError(t, err)
//
// if golden.Update() {
// golden.Set(t, got)
// }
// want := golden.Get(t)
// want := golden.Do(t, got)
//
// assert.Equal(t, want, got)
// })
// }
// }
// assert.Equal(t, want, got)
// })
// }
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
// testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_struct.golden
//
// Multiple Golden Files in a Single Test
// # Multiple Golden Files in a Single Test
//
// The "P" suffixed methods, GetP(), SetP(), and FileP(), all take a name
// argument which allows using specific golden files within a given *testing.T
// instance.
//
// func TestExampleMyStructP(t *testing.T) {
// gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
// gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
// func TestExampleMyStructP(t *testing.T) {
// gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
// gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
//
// if golden.Update() {
// golden.SetP(t, "json", gotJSON)
// golden.SetP(t, "xml", gotXML)
// }
// wantJSON := golden.DoP(t, "json", gotJSON)
// wantXML := golden.DoP(t, "xml", gotXML)
//
// assert.Equal(t, golden.GetP(t, "json"), gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML)
// }
// assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, wantXML, gotXML)
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
// testdata/TestExampleMyStructP/json.golden
// testdata/TestExampleMyStructP/xml.golden
//
// This works with tabular tests too of course:
//
// func TestExampleMyStructTabularP(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// gotJSON, _ := json.Marshal(tt.obj)
// gotXML, _ := xml.Marshal(tt.obj)
// func TestExampleMyStructTabularP(t *testing.T) {
// tests := []struct {
// name string
// obj *MyStruct
// }{
// {name: "empty struct", obj: &MyStruct{}},
// {name: "full struct", obj: &MyStruct{Foo: "Bar"}},
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// gotJSON, _ := json.Marshal(tt.obj)
// gotXML, _ := xml.Marshal(tt.obj)
//
// if golden.Update() {
// golden.SetP(t, "json", gotJSON)
// golden.SetP(t, "xml", gotXML)
// }
// wantJSON := golden.DoP(t, "json", gotJSON)
// wantXML := golden.DoP(t, "xml", gotXML)
//
// assert.Equal(t, golden.GetP(t, "json"), gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML)
// })
// }
// }
// assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, wantXML, gotXML)
// })
// }
// }
//
// The above example will read/write to:
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
//
// testdata/TestExampleMyStructTabularP/empty_struct/json.golden
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
package golden
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/jimeh/go-golden/sanitize"
"github.com/spf13/afero"
)
// 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 (
// Default is the default *Golden instance. All package-level functions use
// the Default instance.
Default = New()
var defaultGolden = New()
// DefaultDirMode is the default DirMode value used by New().
DefaultDirMode = os.FileMode(0o755)
// DefaultFileMode is the default FileMode value used by New().
DefaultFileMode = os.FileMode(0o644)
// DefaultSuffix is the default Suffix value used by New().
DefaultSuffix = ".golden"
// DefaultDirname is the default Dirname value used by New().
DefaultDirname = "testdata"
// DefaultUpdateFunc is the default UpdateFunc value used by New().
DefaultUpdateFunc = EnvUpdateFunc
)
// Do is a convenience function for calling Update(), Set(), and Get() in a
// single call. If Update() returns true, data will be written to the golden
// file using Set(), before reading it back with Get().
func Do(t TestingT, data []byte) []byte {
t.Helper()
return Default.Do(t, data)
}
// 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 {
func File(t TestingT) string {
t.Helper()
return defaultGolden.File(t)
return Default.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 Get(t *testing.T) []byte {
func Get(t TestingT) []byte {
t.Helper()
return defaultGolden.Get(t)
return Default.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 Set(t *testing.T, data []byte) {
func Set(t TestingT, data []byte) {
t.Helper()
defaultGolden.Set(t, data)
Default.Set(t, data)
}
// DoP is a convenience function for calling Update(), SetP(), and GetP() in a
// single call. If Update() returns true, data will be written to the golden
// file using SetP(), before reading it back with GetP().
func DoP(t TestingT, name string, data []byte) []byte {
t.Helper()
return Default.DoP(t, name, data)
}
// FileP returns the filename of the specifically named golden file for the
// given *testing.T instance as determined by t.Name().
func FileP(t *testing.T, name string) string {
func FileP(t TestingT, name string) string {
t.Helper()
return defaultGolden.FileP(t, name)
return Default.FileP(t, name)
}
// GetP returns the content of the specifically named golden file belonging
@@ -242,10 +203,10 @@ func FileP(t *testing.T, name string) string {
//
// This is very similar to Get(), but it allows multiple different golden files
// to be used within the same one *testing.T instance.
func GetP(t *testing.T, name string) []byte {
func GetP(t TestingT, name string) []byte {
t.Helper()
return defaultGolden.GetP(t, name)
return Default.GetP(t, name)
}
// SetP writes given data of the specifically named golden file belonging to
@@ -254,10 +215,10 @@ func GetP(t *testing.T, name string) []byte {
//
// This is very similar to Set(), but it allows multiple different golden files
// to be used within the same one *testing.T instance.
func SetP(t *testing.T, name string, data []byte) {
func SetP(t TestingT, name string, data []byte) {
t.Helper()
defaultGolden.SetP(t, name, data)
Default.SetP(t, name, data)
}
// Update returns true when golden is set to update golden files. Should be used
@@ -267,294 +228,207 @@ 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 defaultGolden.Update()
return Default.Update()
}
// Golden handles all interactions with golden files. The top-level package
// 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
// 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
// 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
// FileMode determines the file system permissions of any created or updated
// golden files written to disk.
FileMode os.FileMode
// 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)
// Suffix determines the filename suffix for all golden files. Typically
// this would be ".golden".
Suffix 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
// 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
// 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
// UpdateFunc is used to determine if golden files should be updated or
// not. Its boolean return value is returned by Update().
UpdateFunc UpdateFunc
}
// 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,
// New returns a new *Golden instance with default values correctly populated.
// It accepts zero or more Option functions that can modify the default values.
func New(opts ...Option) *Golden {
g := &Golden{
DirMode: DefaultDirMode,
FileMode: DefaultFileMode,
Suffix: DefaultSuffix,
Dirname: DefaultDirname,
UpdateFunc: DefaultUpdateFunc,
}
for _, opt := range options {
opt.apply(g)
for _, opt := range opts {
opt(g)
}
return g
}
type Option interface {
apply(*golden)
// Do is a convenience function for calling Update(), Set(), and Get() in a
// single call. If Update() returns true, data will be written to the golden
// file using Set(), before reading it back with Get().
func (s *Golden) Do(t TestingT, data []byte) []byte {
t.Helper()
if s.Update() {
s.Set(t, data)
}
return s.Get(t)
}
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 {
// File returns the filename of the golden file for the given *testing.T
// instance as determined by t.Name().
func (s *Golden) File(t TestingT) string {
t.Helper()
return s.file(t, "")
}
func (s *golden) Get(t TestingT) []byte {
// 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 TestingT) []byte {
t.Helper()
return s.get(t, "")
}
func (s *golden) Set(t TestingT, data []byte) {
// 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 TestingT, data []byte) {
t.Helper()
s.set(t, "", data)
}
func (s *golden) FileP(t TestingT, name string) string {
// DoP is a convenience function for calling Update(), SetP(), and GetP() in a
// single call. If Update() returns true, data will be written to the golden
// file using SetP(), before reading it back with GetP().
func (s *Golden) DoP(t TestingT, name string, data []byte) []byte {
t.Helper()
if name == "" {
t.Fatalf("golden: test name cannot be empty")
t.Fatalf("golden: name cannot be empty")
}
if s.Update() {
s.SetP(t, name, data)
}
return s.GetP(t, name)
}
// 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 TestingT, name string) string {
t.Helper()
if name == "" {
t.Fatalf("golden: name cannot be empty")
}
return s.file(t, name)
}
func (s *golden) GetP(t TestingT, name string) []byte {
// 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 TestingT, name string) []byte {
t.Helper()
if name == "" {
t.Fatal("golden: name cannot be empty")
t.Fatalf("golden: name cannot be empty")
}
return s.get(t, name)
}
func (s *golden) SetP(t TestingT, name string, data []byte) {
// 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 TestingT, name string, data []byte) {
t.Helper()
if name == "" {
t.Fatal("golden: name cannot be empty")
t.Fatalf("golden: name cannot be empty")
}
s.set(t, name, data)
}
func (s *golden) file(t TestingT, name string) string {
t.Helper()
// 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) file(t TestingT, name string) string {
if t.Name() == "" {
t.Fatalf(
"golden: could not determine filename for given %T instance", t,
)
t.Fatalf("golden: could not determine filename")
}
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, sanitize.Filename(s))
clean = append(clean, sanitizeFilename(s))
}
return strings.Join(clean, string(os.PathSeparator))
}
func (s *golden) get(t TestingT, name string) []byte {
t.Helper()
func (s *Golden) get(t TestingT, name string) []byte {
f := s.file(t, name)
b, err := afero.ReadFile(s.fs, f)
b, err := os.ReadFile(f)
if err != nil {
t.Fatalf("golden: %s", err.Error())
t.Fatalf("golden: failed reading %s: %s", f, err.Error())
}
return b
}
func (s *golden) set(t TestingT, name string, data []byte) {
t.Helper()
func (s *Golden) set(t TestingT, name string, data []byte) {
f := s.file(t, name)
dir := filepath.Dir(f)
if s.logOnWrite {
t.Logf("golden: writing golden file: %s", f)
}
t.Logf("golden: writing .golden file: %s", f)
err := s.fs.MkdirAll(dir, s.dirMode)
err := os.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)
err = os.WriteFile(f, data, s.FileMode)
if err != nil {
t.Fatalf("golden: filed to write file: %s", err.Error())
}
}
func (s *golden) Update() bool {
return s.updateFunc()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
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
}

View File

@@ -1,318 +0,0 @@
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(`<book>
<title></title>
<price>0</price>
</book>`,
),
},
{
name: "empty struct (2)",
args: args{v: &shoe{}},
want: []byte(`<shoe>
<make></make>
<size>0</size>
</shoe>`,
),
},
{
name: "full struct (1)",
args: args{
v: &book{
Title: "a",
Author: "b",
Price: 499,
},
},
want: []byte(`<book>
<title>a</title>
<author>b</author>
<price>499</price>
</book>`,
),
},
{
name: "empty struct (2)",
args: args{
v: &shoe{
Make: "a",
Model: "b",
Size: 42,
},
},
want: []byte(`<shoe>
<make>a</make>
<model>b</model>
<size>42</size>
</shoe>`,
),
},
{
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))
})
}
}

View File

@@ -1,133 +0,0 @@
package golden
import (
"reflect"
"strings"
"github.com/jimeh/go-golden/sanitize"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type (
MarshalFunc func(interface{}) ([]byte, error)
UnmarshalFunc func([]byte, interface{}) error
)
// 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
// Name of the format the MarshalAsserter handles.
Format string
// GoldName is the name of the golden file used when marshaling. This is by
// default set based on Format using NewMarshalAsserter. For example if
// Format is set to "JSON", GoldName will be set to "marshaled_json" by
// default.
GoldName string
// MarshalFunc is the function used to marshal given objects.
MarshalFunc MarshalFunc
// UnmarshalFunc is the function used to unmarshal given objects.
UnmarshalFunc UnmarshalFunc
// 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
}
// New returns a new MarshalingAsserter.
func NewMarshalingAsserter(
golden Golden,
format string,
marshalFunc MarshalFunc,
unmarshalFunc UnmarshalFunc,
normalizeLineBreaks bool,
) *MarshalingAsserter {
goldName := "marshaled_" + strings.ToLower(sanitize.Filename(format))
return &MarshalingAsserter{
Golden: golden,
Format: format,
GoldName: goldName,
MarshalFunc: marshalFunc,
UnmarshalFunc: unmarshalFunc,
NormalizeLineBreaks: normalizeLineBreaks,
}
}
// Marshaling asserts that the given "v" value marshals via the provided encoder
// 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 (s *MarshalingAsserter) Marshaling(t TestingT, v interface{}) {
t.Helper()
s.MarshalingP(t, v, v)
}
// MarshalingP asserts that the given "v" value marshals via the provided
// encoder 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.
func (s *MarshalingAsserter) MarshalingP(
t TestingT,
v interface{},
want interface{},
) {
t.Helper()
if reflect.ValueOf(want).Kind() != reflect.Ptr {
require.FailNowf(t,
"golden: only pointer types can be asserted",
"%T is not a pointer type", want,
)
}
marshaled, err := s.MarshalFunc(v)
require.NoErrorf(t,
err, "golden: failed to %s marshal %T: %+v", s.Format, v, v,
)
if s.NormalizeLineBreaks {
marshaled = sanitize.LineBreaks(marshaled)
}
if s.Golden.Update() {
s.Golden.SetP(t, s.GoldName, marshaled)
}
gold := s.Golden.GetP(t, s.GoldName)
if s.NormalizeLineBreaks {
gold = sanitize.LineBreaks(gold)
}
switch strings.ToLower(s.Format) {
case "json":
assert.JSONEq(t, string(gold), string(marshaled))
case "yaml", "yml":
assert.YAMLEq(t, string(gold), string(marshaled))
default:
assert.Equal(t, string(gold), string(marshaled))
}
got := reflect.New(reflect.TypeOf(want).Elem()).Interface()
err = s.UnmarshalFunc(gold, got)
f := s.Golden.FileP(t, s.GoldName)
require.NoErrorf(t, err,
"golden: failed to %s unmarshal %T from %s", s.Format, got, f,
)
assert.Equalf(t, want, got,
"golden: unmarshaling from golden file does not match "+
"expected object; golden file: %s", f,
)
}

View File

@@ -1,98 +0,0 @@
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,
)
})
}
}

43
options.go Normal file
View File

@@ -0,0 +1,43 @@
package golden
import (
"os"
)
// Option is a function that modifies a Golden instance.
type Option func(*Golden)
// WithDirMode sets the directory mode for a Golden instance.
func WithDirMode(mode os.FileMode) Option {
return func(g *Golden) {
g.DirMode = mode
}
}
// WithFileMode sets the file mode for a Golden instance.
func WithFileMode(mode os.FileMode) Option {
return func(g *Golden) {
g.FileMode = mode
}
}
// WithSuffix sets the file suffix for a Golden instance.
func WithSuffix(suffix string) Option {
return func(g *Golden) {
g.Suffix = suffix
}
}
// WithDirname sets the directory name for a Golden instance.
func WithDirname(dirname string) Option {
return func(g *Golden) {
g.Dirname = dirname
}
}
// WithUpdateFunc sets the update function for a Golden instance.
func WithUpdateFunc(updateFunc UpdateFunc) Option {
return func(g *Golden) {
g.UpdateFunc = updateFunc
}
}

58
options_test.go Normal file
View File

@@ -0,0 +1,58 @@
package golden
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithDirMode(t *testing.T) {
customMode := os.FileMode(0o700)
g := &Golden{}
opt := WithDirMode(customMode)
opt(g)
assert.Equal(t, customMode, g.DirMode)
}
func TestWithFileMode(t *testing.T) {
customMode := os.FileMode(0o600)
g := &Golden{}
opt := WithFileMode(customMode)
opt(g)
assert.Equal(t, customMode, g.FileMode)
}
func TestWithSuffix(t *testing.T) {
customSuffix := ".custom"
g := &Golden{}
opt := WithSuffix(customSuffix)
opt(g)
assert.Equal(t, customSuffix, g.Suffix)
}
func TestWithDirname(t *testing.T) {
customDirname := "custom-testdata"
g := &Golden{}
opt := WithDirname(customDirname)
opt(g)
assert.Equal(t, customDirname, g.Dirname)
}
func TestWithUpdateFunc(t *testing.T) {
customUpdateFunc := func() bool { return true }
g := &Golden{}
opt := WithUpdateFunc(customUpdateFunc)
opt(g)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
}

View File

@@ -1,4 +1,4 @@
package sanitize
package golden
import (
"regexp"
@@ -15,7 +15,7 @@ var (
)
)
func Filename(name string) string {
func sanitizeFilename(name string) string {
if reservedNames.MatchString(name) || winReserved.MatchString(name) {
var b []byte
for i := 0; i < len(name); i++ {

View File

@@ -1,21 +0,0 @@
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
}

View File

@@ -1,67 +0,0 @@
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)
})
}
}

View File

@@ -1,13 +1,12 @@
package sanitize_test
package golden
import (
"testing"
"github.com/jimeh/go-golden/sanitize"
"github.com/stretchr/testify/assert"
)
func TestFilename(t *testing.T) {
func Test_sanitizeFilename(t *testing.T) {
tests := []struct {
name string
filename string
@@ -70,7 +69,6 @@ func TestFilename(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: "___"},
@@ -118,7 +116,7 @@ func TestFilename(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := sanitize.Filename(tt.filename)
got := sanitizeFilename(tt.filename)
assert.Equal(t, tt.want, got)
})

View File

@@ -1,3 +0,0 @@
{
"2fd5af35-b85e-4f03-8eba-524be28d7a5b": "Hello World!=Forty Two"
}

View File

@@ -1,4 +0,0 @@
{
"id": "",
"title": ""
}

View File

@@ -1,9 +0,0 @@
{
"id": "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
"title": "The Traveler",
"author": {
"first_name": "John",
"last_name": "Twelve Hawks"
},
"year": 2005
}

View File

@@ -1,4 +0,0 @@
{
"id": "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
"title": "The Traveler"
}

View File

@@ -1 +0,0 @@
"hello world"

View File

@@ -1,3 +0,0 @@
{
"2fd5af35-b85e-4f03-8eba-524be28d7a5b": "Hello World!=Forty Two"
}

View File

@@ -1,5 +0,0 @@
{
"id": "",
"title": "",
"author": null
}

View File

@@ -1,9 +0,0 @@
{
"id": "10eec54d-e30a-4428-be18-01095d889126",
"title": "Time Travel",
"author": {
"first_name": "Doc",
"last_name": "Brown"
},
"date": "2021-10-27T22:30:34Z"
}

View File

@@ -1,4 +0,0 @@
{
"id": "10eec54d-e30a-4428-be18-01095d889126",
"title": "Time Travel"
}

View File

@@ -1 +0,0 @@
"hello world"

View File

@@ -1 +0,0 @@
<comic id="2fd5af35-b85e-4f03-8eba-524be28d7a5b" issue="Forty Two">Hello World!</comic>

View File

@@ -1,4 +0,0 @@
<book>
<id></id>
<title></title>
</book>

View File

@@ -1 +0,0 @@
<bool>false</bool>

View File

@@ -1,9 +0,0 @@
<book>
<id>cfda163c-d5c1-44a2-909b-5d2ce3a31979</id>
<title>The Traveler</title>
<author>
<first_name>John</first_name>
<last_name>Twelve Hawks</last_name>
</author>
<year>2005</year>
</book>

View File

@@ -1 +0,0 @@
<int>42</int>

View File

@@ -1,4 +0,0 @@
<book>
<id>cfda163c-d5c1-44a2-909b-5d2ce3a31979</id>
<title>The Traveler</title>
</book>

View File

@@ -1 +0,0 @@
<string>hello world</string>

View File

@@ -1 +0,0 @@
<bool>true</bool>

View File

@@ -1 +0,0 @@
<comic id="2fd5af35-b85e-4f03-8eba-524be28d7a5b" issue="Forty Two">Hello World!</comic>

View File

@@ -1,4 +0,0 @@
<article>
<id></id>
<title></title>
</article>

View File

@@ -1 +0,0 @@
<bool>false</bool>

View File

@@ -1,9 +0,0 @@
<article>
<id>10eec54d-e30a-4428-be18-01095d889126</id>
<title>Time Travel</title>
<author>
<first_name>Doc</first_name>
<last_name>Brown</last_name>
</author>
<date>2021-10-27T22:30:34Z</date>
</article>

View File

@@ -1 +0,0 @@
<int>42</int>

View File

@@ -1,4 +0,0 @@
<book>
<id>10eec54d-e30a-4428-be18-01095d889126</id>
<title>Time Travel</title>
</book>

View File

@@ -1 +0,0 @@
<string>hello world</string>

View File

@@ -1 +0,0 @@
<bool>true</bool>

View File

@@ -1,2 +0,0 @@
2fd5af35-b85e-4f03-8eba-524be28d7a5b:
Hello World!: Forty Two

View File

@@ -1,2 +0,0 @@
id: ""
title: ""

View File

@@ -1,6 +0,0 @@
id: cfda163c-d5c1-44a2-909b-5d2ce3a31979
title: The Traveler
author:
first_name: John
last_name: Twelve Hawks
year: 2005

View File

@@ -1,2 +0,0 @@
id: cfda163c-d5c1-44a2-909b-5d2ce3a31979
title: The Traveler

View File

@@ -1 +0,0 @@
hello world

View File

@@ -1,2 +0,0 @@
2fd5af35-b85e-4f03-8eba-524be28d7a5b:
Hello World!: Forty Two

View File

@@ -1,3 +0,0 @@
id: ""
title: ""
author: null

View File

@@ -1,6 +0,0 @@
id: 10eec54d-e30a-4428-be18-01095d889126
title: Time Travel
author:
first_name: Doc
last_name: Brown
date: 2021-10-27T22:30:34Z

View File

@@ -1,2 +0,0 @@
id: 10eec54d-e30a-4428-be18-01095d889126
title: Time Travel

View File

@@ -1 +0,0 @@
hello world

View File

@@ -1,3 +0,0 @@
{
"2fd5af35-b85e-4f03-8eba-524be28d7a5b": "Hello World!=Forty Two"
}

View File

@@ -1,4 +0,0 @@
{
"id": "",
"title": ""
}

View File

@@ -1,9 +0,0 @@
{
"id": "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
"title": "The Traveler",
"author": {
"first_name": "John",
"last_name": "Twelve Hawks"
},
"year": 2005
}

View File

@@ -1,4 +0,0 @@
{
"id": "cfda163c-d5c1-44a2-909b-5d2ce3a31979",
"title": "The Traveler"
}

View File

@@ -1 +0,0 @@
"hello world"

View File

@@ -1,3 +0,0 @@
{
"2fd5af35-b85e-4f03-8eba-524be28d7a5b": "Hello World!=Forty Two"
}

View File

@@ -1,5 +0,0 @@
{
"id": "",
"title": "",
"author": null
}

View File

@@ -1,9 +0,0 @@
{
"id": "10eec54d-e30a-4428-be18-01095d889126",
"title": "Time Travel",
"author": {
"first_name": "Doc",
"last_name": "Brown"
},
"date": "2021-10-27T22:30:34Z"
}

View File

@@ -1,4 +0,0 @@
{
"id": "10eec54d-e30a-4428-be18-01095d889126",
"title": "Time Travel"
}

View File

@@ -1 +0,0 @@
"hello world"

View File

@@ -1 +0,0 @@
<comic id="2fd5af35-b85e-4f03-8eba-524be28d7a5b" issue="Forty Two">Hello World!</comic>

View File

@@ -1,4 +0,0 @@
<book>
<id></id>
<title></title>
</book>

View File

@@ -1 +0,0 @@
<bool>false</bool>

View File

@@ -1,9 +0,0 @@
<book>
<id>cfda163c-d5c1-44a2-909b-5d2ce3a31979</id>
<title>The Traveler</title>
<author>
<first_name>John</first_name>
<last_name>Twelve Hawks</last_name>
</author>
<year>2005</year>
</book>

View File

@@ -1 +0,0 @@
<int>42</int>

View File

@@ -1,4 +0,0 @@
<book>
<id>cfda163c-d5c1-44a2-909b-5d2ce3a31979</id>
<title>The Traveler</title>
</book>

View File

@@ -1 +0,0 @@
<string>hello world</string>

View File

@@ -1 +0,0 @@
<bool>true</bool>

View File

@@ -1 +0,0 @@
<comic id="2fd5af35-b85e-4f03-8eba-524be28d7a5b" issue="Forty Two">Hello World!</comic>

Some files were not shown because too many files have changed in this diff Show More