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
17 changed files with 879 additions and 306 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: lint:
name: Lint name: Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
checks: write
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: golangci-lint - uses: actions/setup-go@v5
uses: golangci/golangci-lint-action@v2
with: with:
version: v1.42 go-version: stable
env: - name: golangci-lint
VERBOSE: "true" uses: golangci/golangci-lint-action@v6
with:
version: v1.64
tidy: tidy:
name: Tidy name: Tidy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
with: with:
go-version: 1.15 go-version-file: go.mod
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Check if mods are tidy - name: Check if mods are tidy
run: make check-tidy run: make check-tidy
@@ -36,18 +35,12 @@ jobs:
name: Coverage name: Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
with: with:
go-version: 1.15 go-version-file: go.mod
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Publish coverage - name: Publish coverage
uses: paambaati/codeclimate-action@v2.7.4 uses: paambaati/codeclimate-action@v9.0.0
env: env:
VERBOSE: "true" VERBOSE: "true"
GOMAXPROCS: 4 GOMAXPROCS: 4
@@ -68,20 +61,30 @@ jobs:
- macos-latest - macos-latest
- windows-latest - windows-latest
go_version: go_version:
- "1.15"
- "1.16"
- "1.17" - "1.17"
- "1.18"
- "1.19"
- "1.20"
- "stable"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.go_version }} go-version: ${{ matrix.go_version }}
- uses: actions/cache@v2 check-latest: true
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests - name: Run tests
run: go test -v -count=1 -race ./... 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 }}

View File

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

View File

@@ -34,23 +34,19 @@ SHELL := env \
# Tools # Tools
# #
TOOLS += $(TOOLDIR)/gobin
$(TOOLDIR)/gobin:
GO111MODULE=off go get -u github.com/myitcv/gobin
# external tool # external tool
define tool # 1: binary-name, 2: go-import-path define tool # 1: binary-name, 2: go-import-path
TOOLS += $(TOOLDIR)/$(1) TOOLS += $(TOOLDIR)/$(1)
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile $(TOOLDIR)/$(1): Makefile
gobin $(V) "$(2)" GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
endef endef
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc)) $(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc@latest))
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt)) $(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
$(eval $(call tool,goimports,golang.org/x/tools/cmd/goimports)) $(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.42)) $(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod)) $(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
.PHONY: tools .PHONY: tools
tools: $(TOOLS) tools: $(TOOLS)
@@ -186,20 +182,3 @@ check-tidy:
docs: $(TOOLDIR)/godoc docs: $(TOOLDIR)/godoc
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/) $(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
@godoc -http=127.0.0.1:6060 @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>
<p align="center"> <p align="center">
<a href="https://pkg.go.dev/github.com/jimeh/go-golden"> <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>
<img src="https://img.shields.io/badge/%E2%80%8B-reference-387b97.svg?logo=go&logoColor=white" <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>
alt="Go Reference"> <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> <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/actions"> <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>
<img src="https://img.shields.io/github/workflow/status/jimeh/go-golden/CI.svg?logo=github" alt="Actions Status"> <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>
<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> </p>
## Import ## Import
@@ -45,10 +31,7 @@ func TestExampleMyStruct(t *testing.T) {
got, err := json.Marshal(&MyStruct{Foo: "Bar"}) got, err := json.Marshal(&MyStruct{Foo: "Bar"})
require.NoError(t, err) require.NoError(t, err)
if golden.Update() { want := golden.Do(t, got)
golden.Set(t, got)
}
want := golden.Get(t)
assert.Equal(t, want, got) assert.Equal(t, want, got)
} }
@@ -58,6 +41,15 @@ The above example will read/write to:
- `testdata/TestExampleMyStruct.golden` - `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 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 `GOLDEN_UPDATE` environment variable to one of `1`, `y`, `t`, `yes`, `on`, or
`true` when running tests. `true` when running tests.

View File

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

9
go.mod
View File

@@ -1,8 +1,11 @@
module github.com/jimeh/go-golden module github.com/jimeh/go-golden
go 1.15 go 1.17
require github.com/stretchr/testify v1.10.0
require ( require (
github.com/jimeh/envctl v0.1.0 github.com/davecgh/go-spew v1.1.1 // indirect
github.com/stretchr/testify v1.7.0 github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

19
go.sum
View File

@@ -1,14 +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/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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/jimeh/envctl v0.1.0/go.mod h1:aM27ffBbO1yUBKUzgJGCUorS4z+wyh+qhQe1ruxXZZo= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=

235
golden.go
View File

@@ -1,12 +1,12 @@
// Package golden is yet another package for working with *.golden test files, // Package golden is yet another package for working with *.golden test files,
// with a focus on simplicity through it's default behavior. // 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 // names by calling t.Name(). File names are sanitized to ensure they're
// compatible with Linux, macOS and Windows systems regardless of what crazy // compatible with Linux, macOS and Windows systems regardless of what crazy
// characters might be in a subtest's name. // characters might be in a sub-test's name.
// //
// Usage // # Usage
// //
// Typical usage should look something like this: // Typical usage should look something like this:
// //
@@ -14,10 +14,7 @@
// got, err := json.Marshal(&MyStruct{Foo: "Bar"}) // got, err := json.Marshal(&MyStruct{Foo: "Bar"})
// require.NoError(t, err) // require.NoError(t, err)
// //
// if golden.Update() { // want := golden.Do(t, got)
// golden.Set(t, got)
// }
// want := golden.Get(t)
// //
// assert.Equal(t, want, got) // assert.Equal(t, want, got)
// } // }
@@ -26,14 +23,21 @@
// //
// 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 // 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 // GOLDEN_UPDATE environment variable to one of "1", "y", "t", "yes", "on", or
// "true" when running tests. // "true" when running tests.
// //
// Sub-Tests // # Sub-Tests
// //
// As the golden filename is based on t.Name(), it works with sub-tests too, // 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) { // func TestExampleMyStructTabular(t *testing.T) {
// tests := []struct { // tests := []struct {
@@ -48,10 +52,7 @@
// got, err := json.Marshal(tt.obj) // got, err := json.Marshal(tt.obj)
// require.NoError(t, err) // require.NoError(t, err)
// //
// if golden.Update() { // want := golden.Do(t, got)
// golden.Set(t, got)
// }
// want := golden.Get(t)
// //
// assert.Equal(t, want, got) // assert.Equal(t, want, got)
// }) // })
@@ -63,7 +64,7 @@
// testdata/TestExampleMyStructTabular/empty_struct.golden // testdata/TestExampleMyStructTabular/empty_struct.golden
// testdata/TestExampleMyStructTabular/full_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 // The "P" suffixed methods, GetP(), SetP(), and FileP(), all take a name
// argument which allows using specific golden files within a given *testing.T // argument which allows using specific golden files within a given *testing.T
@@ -73,13 +74,11 @@
// gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"}) // gotJSON, _ := json.Marshal(&MyStruct{Foo: "Bar"})
// gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"}) // gotXML, _ := xml.Marshal(&MyStruct{Foo: "Bar"})
// //
// if golden.Update() { // wantJSON := golden.DoP(t, "json", gotJSON)
// golden.SetP(t, "json", gotJSON) // wantXML := golden.DoP(t, "xml", gotXML)
// golden.SetP(t, "xml", gotXML)
// }
// //
// assert.Equal(t, golden.GetP(t, "json"), gotJSON) // assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML) // assert.Equal(t, wantXML, gotXML)
// } // }
// //
// The above example will read/write to: // The above example will read/write to:
@@ -102,13 +101,11 @@
// gotJSON, _ := json.Marshal(tt.obj) // gotJSON, _ := json.Marshal(tt.obj)
// gotXML, _ := xml.Marshal(tt.obj) // gotXML, _ := xml.Marshal(tt.obj)
// //
// if golden.Update() { // wantJSON := golden.DoP(t, "json", gotJSON)
// golden.SetP(t, "json", gotJSON) // wantXML := golden.DoP(t, "xml", gotXML)
// golden.SetP(t, "xml", gotXML)
// }
// //
// assert.Equal(t, golden.GetP(t, "json"), gotJSON) // assert.Equal(t, wantJSON, gotJSON)
// assert.Equal(t, golden.GetP(t, "xml"), gotXML) // assert.Equal(t, wantXML, gotXML)
// }) // })
// } // }
// } // }
@@ -119,60 +116,85 @@
// testdata/TestExampleMyStructTabularP/empty_struct/xml.golden // testdata/TestExampleMyStructTabularP/empty_struct/xml.golden
// testdata/TestExampleMyStructTabularP/full_struct/json.golden // testdata/TestExampleMyStructTabularP/full_struct/json.golden
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden // testdata/TestExampleMyStructTabularP/full_struct/xml.golden
//
package golden package golden
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing"
) )
const ( var (
DefaultDirMode = 0o755 // Default is the default *Golden instance. All package-level functions use
DefaultFileMode = 0o644 // the Default instance.
Default = 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" DefaultSuffix = ".golden"
// DefaultDirname is the default Dirname value used by New().
DefaultDirname = "testdata" DefaultDirname = "testdata"
// DefaultUpdateFunc is the default UpdateFunc value used by New().
DefaultUpdateFunc = EnvUpdateFunc
) )
var 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()
var global = New() return Default.Do(t, data)
}
// File returns the filename of the golden file for the given *testing.T // File returns the filename of the golden file for the given *testing.T
// instance as determined by t.Name(). // instance as determined by t.Name().
func File(t *testing.T) string { func File(t TestingT) string {
t.Helper() t.Helper()
return global.File(t) return Default.File(t)
} }
// Get returns the content of the golden file for the given *testing.T instance // 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 // as determined by t.Name(). If no golden file can be found/read, it will fail
// the test by calling t.Fatal(). // the test by calling t.Fatal().
func Get(t *testing.T) []byte { func Get(t TestingT) []byte {
t.Helper() t.Helper()
return global.Get(t) return Default.Get(t)
} }
// Set writes given data to the golden file for the given *testing.T instance as // 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 // determined by t.Name(). If writing fails it will fail the test by calling
// t.Fatal() with error details. // t.Fatal() with error details.
func Set(t *testing.T, data []byte) { func Set(t TestingT, data []byte) {
t.Helper() t.Helper()
global.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 // FileP returns the filename of the specifically named golden file for the
// given *testing.T instance as determined by t.Name(). // 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() t.Helper()
return global.FileP(t, name) return Default.FileP(t, name)
} }
// GetP returns the content of the specifically named golden file belonging // GetP returns the content of the specifically named golden file belonging
@@ -181,10 +203,10 @@ func FileP(t *testing.T, name string) string {
// //
// This is very similar to Get(), but it allows multiple different golden files // This is very similar to Get(), but it allows multiple different golden files
// to be used within the same one *testing.T instance. // 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() t.Helper()
return global.GetP(t, name) return Default.GetP(t, name)
} }
// SetP writes given data of the specifically named golden file belonging to // SetP writes given data of the specifically named golden file belonging to
@@ -193,10 +215,10 @@ func GetP(t *testing.T, name string) []byte {
// //
// This is very similar to Set(), but it allows multiple different golden files // This is very similar to Set(), but it allows multiple different golden files
// to be used within the same one *testing.T instance. // 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() t.Helper()
global.SetP(t, name, data) Default.SetP(t, name, data)
} }
// Update returns true when golden is set to update golden files. Should be used // Update returns true when golden is set to update golden files. Should be used
@@ -206,7 +228,7 @@ func SetP(t *testing.T, name string, data []byte) {
// environment variable is set to a truthy value. To customize create a custom // environment variable is set to a truthy value. To customize create a custom
// *Golden instance with New() and set a new UpdateFunc value. // *Golden instance with New() and set a new UpdateFunc value.
func Update() bool { func Update() bool {
return global.Update() return Default.Update()
} }
// Golden handles all interactions with golden files. The top-level package // Golden handles all interactions with golden files. The top-level package
@@ -221,7 +243,7 @@ type Golden struct {
FileMode os.FileMode FileMode os.FileMode
// Suffix determines the filename suffix for all golden files. Typically // Suffix determines the filename suffix for all golden files. Typically
// this should be ".golden", but can be changed here if needed. // this would be ".golden".
Suffix string Suffix string
// Dirname is the name of the top-level directory at the root of the package // Dirname is the name of the top-level directory at the root of the package
@@ -234,48 +256,87 @@ type Golden struct {
UpdateFunc UpdateFunc UpdateFunc UpdateFunc
} }
// New returns a new *Golden instance with default values correctly // New returns a new *Golden instance with default values correctly populated.
// populated. This is ideally how you should create a custom *Golden, and then // It accepts zero or more Option functions that can modify the default values.
// modify the relevant fields as you see fit. func New(opts ...Option) *Golden {
func New() *Golden { g := &Golden{
return &Golden{
DirMode: DefaultDirMode, DirMode: DefaultDirMode,
FileMode: DefaultFileMode, FileMode: DefaultFileMode,
Suffix: DefaultSuffix, Suffix: DefaultSuffix,
Dirname: DefaultDirname, Dirname: DefaultDirname,
UpdateFunc: DefaultUpdateFunc, UpdateFunc: DefaultUpdateFunc,
} }
for _, opt := range opts {
opt(g)
}
return g
}
// 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)
} }
// File returns the filename of the golden file for the given *testing.T // File returns the filename of the golden file for the given *testing.T
// instance as determined by t.Name(). // instance as determined by t.Name().
func (s *Golden) File(t *testing.T) string { func (s *Golden) File(t TestingT) string {
t.Helper()
return s.file(t, "") return s.file(t, "")
} }
// Get returns the content of the golden file for the given *testing.T instance // 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 // as determined by t.Name(). If no golden file can be found/read, it will fail
// the test by calling t.Fatal(). // 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, "") return s.get(t, "")
} }
// Set writes given data to the golden file for the given *testing.T instance as // 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 // determined by t.Name(). If writing fails it will fail the test by calling
// t.Fatal() with error details. // 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) s.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 (s *Golden) DoP(t TestingT, name string, data []byte) []byte {
t.Helper()
if name == "" {
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 // FileP returns the filename of the specifically named golden file for the
// given *testing.T instance as determined by t.Name(). // given *testing.T instance as determined by t.Name().
func (s *Golden) FileP(t *testing.T, name string) string { func (s *Golden) FileP(t TestingT, name string) string {
if name == "" { t.Helper()
if t != nil {
t.Fatal("golden: name cannot be empty")
}
return "" if name == "" {
t.Fatalf("golden: name cannot be empty")
} }
return s.file(t, name) return s.file(t, name)
@@ -287,11 +348,11 @@ func (s *Golden) FileP(t *testing.T, name string) string {
// //
// This is very similar to Get(), but it allows multiple different golden files // This is very similar to Get(), but it allows multiple different golden files
// to be used within the same one *testing.T instance. // 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 {
if name == "" { t.Helper()
t.Fatal("golden: name cannot be empty")
return nil if name == "" {
t.Fatalf("golden: name cannot be empty")
} }
return s.get(t, name) return s.get(t, name)
@@ -303,19 +364,29 @@ func (s *Golden) GetP(t *testing.T, name string) []byte {
// //
// This is very similar to Set(), but it allows multiple different golden files // This is very similar to Set(), but it allows multiple different golden files
// to be used within the same one *testing.T instance. // 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 == "" { if name == "" {
t.Fatal("golden: name cannot be empty") t.Fatalf("golden: name cannot be empty")
} }
s.set(t, name, data) s.set(t, name, data)
} }
func (s *Golden) file(t *testing.T, name string) string { // Update returns true when golden is set to update golden files. Should be used
if t.Name() == "" { // to determine if golden.Set() or golden.SetP() should be called or not.
t.Fatalf("golden: could not determine filename for: %+v", t) //
// 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()
}
return "" func (s *Golden) file(t TestingT, name string) string {
if t.Name() == "" {
t.Fatalf("golden: could not determine filename")
} }
base := []string{s.Dirname, filepath.FromSlash(t.Name())} base := []string{s.Dirname, filepath.FromSlash(t.Name())}
@@ -334,10 +405,10 @@ func (s *Golden) file(t *testing.T, name string) string {
return strings.Join(clean, string(os.PathSeparator)) 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 {
f := s.file(t, name) f := s.file(t, name)
b, err := ioutil.ReadFile(f) b, err := os.ReadFile(f)
if err != nil { if err != nil {
t.Fatalf("golden: failed reading %s: %s", f, err.Error()) t.Fatalf("golden: failed reading %s: %s", f, err.Error())
} }
@@ -345,7 +416,7 @@ func (s *Golden) get(t *testing.T, name string) []byte {
return b return b
} }
func (s *Golden) set(t *testing.T, name string, data []byte) { func (s *Golden) set(t TestingT, name string, data []byte) {
f := s.file(t, name) f := s.file(t, name)
dir := filepath.Dir(f) dir := filepath.Dir(f)
@@ -354,22 +425,10 @@ func (s *Golden) set(t *testing.T, name string, data []byte) {
err := os.MkdirAll(dir, s.DirMode) err := os.MkdirAll(dir, s.DirMode)
if err != nil { if err != nil {
t.Fatalf("golden: failed to create directory: %s", err.Error()) t.Fatalf("golden: failed to create directory: %s", err.Error())
return
} }
err = ioutil.WriteFile(f, data, s.FileMode) err = os.WriteFile(f, data, s.FileMode)
if err != nil { if err != nil {
t.Fatalf("golden: filed to write file: %s", err.Error()) 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()
}

View File

@@ -1,16 +1,296 @@
package golden package golden
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime"
"testing" "testing"
"github.com/jimeh/envctl"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// assertSameFunc asserts that two functions are the same by comparing their
// function names via reflection.
func assertSameFunc(t *testing.T, want, got interface{}) {
t.Helper()
// Verify both arguments are functions
wantType := reflect.TypeOf(want)
gotType := reflect.TypeOf(got)
if wantType.Kind() != reflect.Func {
t.Fatalf(
"assertSameFunc: 'want' argument is not a function, got %s",
wantType.Kind(),
)
}
if gotType.Kind() != reflect.Func {
t.Fatalf(
"assertSameFunc: 'got' argument is not a function, got %s",
gotType.Kind(),
)
}
gotFP := reflect.ValueOf(got).Pointer()
gotFuncName := runtime.FuncForPC(gotFP).Name()
wantFP := reflect.ValueOf(want).Pointer()
wantFuncName := runtime.FuncForPC(wantFP).Name()
assert.Equal(t, wantFuncName, gotFuncName)
}
func TestDefaults(t *testing.T) {
t.Run("Default", func(t *testing.T) {
assert.IsType(t, &Golden{}, Default)
assert.Equal(t, DefaultDirMode, Default.DirMode)
assert.Equal(t, DefaultFileMode, Default.FileMode)
assert.Equal(t, DefaultSuffix, Default.Suffix)
assert.Equal(t, DefaultDirname, Default.Dirname)
assertSameFunc(t, EnvUpdateFunc, Default.UpdateFunc)
})
t.Run("DefaultDirMode", func(t *testing.T) {
assert.Equal(t, os.FileMode(0o755), DefaultDirMode)
})
t.Run("DefaultFileMode", func(t *testing.T) {
assert.Equal(t, os.FileMode(0o644), DefaultFileMode)
})
t.Run("DefaultSuffix", func(t *testing.T) {
assert.Equal(t, ".golden", DefaultSuffix)
})
t.Run("DefaultDirname", func(t *testing.T) {
assert.Equal(t, "testdata", DefaultDirname)
})
t.Run("DefaultUpdateFunc", func(t *testing.T) {
assertSameFunc(t, EnvUpdateFunc, DefaultUpdateFunc)
})
t.Run("customized Default* variables", func(t *testing.T) {
// Capture the default values before we change them.
defaultDirMode := DefaultDirMode
defaultFileMode := DefaultFileMode
defaultSuffix := DefaultSuffix
defaultDirname := DefaultDirname
defaultUpdateFunc := DefaultUpdateFunc
// Restore the default values after the test.
t.Cleanup(func() {
DefaultDirMode = defaultDirMode
DefaultFileMode = defaultFileMode
DefaultSuffix = defaultSuffix
DefaultDirname = defaultDirname
DefaultUpdateFunc = defaultUpdateFunc
})
// Set all the default values to new values.
DefaultDirMode = os.FileMode(0o700)
DefaultFileMode = os.FileMode(0o600)
DefaultSuffix = ".gold"
DefaultDirname = "goldenfiles"
updateFunc := func() bool { return true }
DefaultUpdateFunc = updateFunc
// Create a new Golden instance with the new values.
got := New()
assert.Equal(t, DefaultDirMode, got.DirMode)
assert.Equal(t, DefaultFileMode, got.FileMode)
assert.Equal(t, DefaultSuffix, got.Suffix)
assert.Equal(t, DefaultDirname, got.Dirname)
assertSameFunc(t, updateFunc, got.UpdateFunc)
})
}
func TestNew(t *testing.T) {
t.Run("defaults (no options)", func(t *testing.T) {
g := New()
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
// Test each option individually
t.Run("WithDirMode", func(t *testing.T) {
customMode := os.FileMode(0o700)
g := New(WithDirMode(customMode))
assert.Equal(t, customMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
t.Run("WithFileMode", func(t *testing.T) {
customMode := os.FileMode(0o600)
g := New(WithFileMode(customMode))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, customMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
t.Run("WithSuffix", func(t *testing.T) {
customSuffix := ".my-suffix"
g := New(WithSuffix(customSuffix))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, customSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
t.Run("WithDirname", func(t *testing.T) {
customDirname := "fixtures/generated"
g := New(WithDirname(customDirname))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, customDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
t.Run("WithUpdateFunc", func(t *testing.T) {
customUpdateFunc := func() bool { return true }
g := New(WithUpdateFunc(customUpdateFunc))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
})
// Test multiple options at once
t.Run("MultipleOptions", func(t *testing.T) {
customDirMode := os.FileMode(0o700)
customFileMode := os.FileMode(0o600)
customSuffix := ".fixture"
customDirname := "fixtures"
customUpdateFunc := func() bool { return true }
g := New(
WithDirMode(customDirMode),
WithFileMode(customFileMode),
WithSuffix(customSuffix),
WithDirname(customDirname),
WithUpdateFunc(customUpdateFunc),
)
assert.Equal(t, customDirMode, g.DirMode)
assert.Equal(t, customFileMode, g.FileMode)
assert.Equal(t, customSuffix, g.Suffix)
assert.Equal(t, customDirname, g.Dirname)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
})
}
func TestDo(t *testing.T) {
t.Cleanup(func() {
err := os.RemoveAll(filepath.Join("testdata", "TestDo"))
require.NoError(t, err)
err = os.Remove(filepath.Join("testdata", "TestDo.golden"))
require.NoError(t, err)
})
//
// Test when Update is false
//
content := []byte("This is the golden file for TestDo")
err := os.MkdirAll("testdata", 0o755)
require.NoError(t, err)
err = os.WriteFile(
filepath.Join("testdata", "TestDo.golden"),
content, 0o600,
)
require.NoError(t, err)
newContent := []byte("This should not be written")
t.Setenv("GOLDEN_UPDATE", "false")
got := Do(t, newContent)
assert.Equal(t, content, got)
// Verify file wasn't changed
fileContent, err := os.ReadFile(
filepath.Join("testdata", "TestDo.golden"),
)
require.NoError(t, err)
assert.Equal(t, content, fileContent)
//
// Test when Update is true
//
updatedContent := []byte("This is the updated content for TestDo")
t.Setenv("GOLDEN_UPDATE", "true")
got = Do(t, updatedContent)
assert.Equal(t, updatedContent, got)
// Verify file was updated
fileContent, err = os.ReadFile(
filepath.Join("testdata", "TestDo.golden"),
)
require.NoError(t, err)
assert.Equal(t, updatedContent, fileContent)
//
// Test with sub-tests
//
tests := []struct {
name string
content []byte
}{
{
name: "simple",
content: []byte("Simple content for sub-test"),
},
{
name: "complex/path",
content: []byte("Complex path content for sub-test"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test with Update true
t.Setenv("GOLDEN_UPDATE", "true")
got := Do(t, tt.content)
assert.Equal(t, tt.content, got)
// Verify file was written with correct content
f := File(t)
fileContent, err := os.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.content, fileContent)
// Test with Update false
t.Setenv("GOLDEN_UPDATE", "false")
newContent := []byte(
"This should not be written in sub-test",
)
got = Do(t, newContent)
assert.Equal(t, tt.content, got)
// Verify file wasn't changed
f = File(t)
fileContent, err = os.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.content, fileContent)
})
}
}
func TestFile(t *testing.T) { func TestFile(t *testing.T) {
got := File(t) got := File(t)
@@ -58,8 +338,8 @@ func TestGet(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
content := []byte("foobar\nhello world :)") content := []byte("foobar\nhello world :)")
err = ioutil.WriteFile( //nolint:gosec err = os.WriteFile(
filepath.Join("testdata", "TestGet.golden"), content, 0o644, filepath.Join("testdata", "TestGet.golden"), content, 0o600,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -109,7 +389,7 @@ func TestGet(t *testing.T) {
err := os.MkdirAll(dir, 0o755) err := os.MkdirAll(dir, 0o755)
require.NoError(t, err) require.NoError(t, err)
err = ioutil.WriteFile(f, tt.want, 0o644) //nolint:gosec err = os.WriteFile(f, tt.want, 0o600)
require.NoError(t, err) require.NoError(t, err)
got := Get(t) got := Get(t)
@@ -131,7 +411,7 @@ func TestSet(t *testing.T) {
content := []byte("This is the default golden file for TestSet ^_^") content := []byte("This is the default golden file for TestSet ^_^")
Set(t, content) Set(t, content)
b, err := ioutil.ReadFile(filepath.Join("testdata", "TestSet.golden")) b, err := os.ReadFile(filepath.Join("testdata", "TestSet.golden"))
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, content, b) assert.Equal(t, content, b)
@@ -177,7 +457,7 @@ func TestSet(t *testing.T) {
Set(t, tt.content) Set(t, tt.content)
got, err := ioutil.ReadFile(f) got, err := os.ReadFile(f)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tt.file, f) assert.Equal(t, tt.file, f)
@@ -186,6 +466,97 @@ func TestSet(t *testing.T) {
} }
} }
func TestDoP(t *testing.T) {
t.Cleanup(func() {
err := os.RemoveAll(filepath.Join("testdata", "TestDoP"))
require.NoError(t, err)
})
//
// Test when Update is false
//
name := "test-format"
content := []byte("This is the golden file for TestDoP")
err := os.MkdirAll(filepath.Join("testdata", "TestDoP"), 0o755)
require.NoError(t, err)
goldenFile := filepath.Join("testdata", "TestDoP", name+".golden")
err = os.WriteFile(goldenFile, content, 0o600)
require.NoError(t, err)
newContent := []byte("This should not be written")
t.Setenv("GOLDEN_UPDATE", "false")
got := DoP(t, name, newContent)
assert.Equal(t, content, got)
// Verify file wasn't changed
fileContent, err := os.ReadFile(goldenFile)
require.NoError(t, err)
assert.Equal(t, content, fileContent)
//
// Test when Update is true
//
updatedContent := []byte("This is the updated content for TestDoP")
t.Setenv("GOLDEN_UPDATE", "true")
got = DoP(t, name, updatedContent)
assert.Equal(t, updatedContent, got)
// Verify file was updated
fileContent, err = os.ReadFile(goldenFile)
require.NoError(t, err)
assert.Equal(t, updatedContent, fileContent)
//
// Test with sub-tests
//
tests := []struct {
testName string
name string
content []byte
}{
{
testName: "json format",
name: "json",
content: []byte(`{"key": "value"}`),
},
{
testName: "xml format",
name: "xml",
content: []byte(`<root><key>value</key></root>`),
},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
// Test with Update true
t.Setenv("GOLDEN_UPDATE", "true")
got := DoP(t, tt.name, tt.content)
assert.Equal(t, tt.content, got)
// Verify file was written with correct content
f := FileP(t, tt.name)
fileContent, err := os.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.content, fileContent)
// Test with Update false
t.Setenv("GOLDEN_UPDATE", "false")
newContent := []byte(
"This should not be written in sub-test",
)
got = DoP(t, tt.name, newContent)
assert.Equal(t, tt.content, got)
// Verify file wasn't changed
f = FileP(t, tt.name)
fileContent, err = os.ReadFile(f)
require.NoError(t, err)
assert.Equal(t, tt.content, fileContent)
})
}
}
func TestFileP(t *testing.T) { func TestFileP(t *testing.T) {
got := FileP(t, "sub-name") got := FileP(t, "sub-name")
assert.Equal(t, assert.Equal(t,
@@ -247,9 +618,9 @@ func TestGetP(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
content := []byte("this is the named golden file for TestGetP") content := []byte("this is the named golden file for TestGetP")
err = ioutil.WriteFile( //nolint:gosec err = os.WriteFile(
filepath.Join("testdata", "TestGetP", "sub-name.golden"), filepath.Join("testdata", "TestGetP", "sub-name.golden"),
content, 0o644, content, 0o600,
) )
require.NoError(t, err) require.NoError(t, err)
@@ -331,7 +702,7 @@ func TestGetP(t *testing.T) {
err := os.MkdirAll(dir, 0o755) err := os.MkdirAll(dir, 0o755)
require.NoError(t, err) require.NoError(t, err)
err = ioutil.WriteFile(f, tt.want, 0o644) //nolint:gosec err = os.WriteFile(f, tt.want, 0o600)
require.NoError(t, err) require.NoError(t, err)
got := GetP(t, tt.named) got := GetP(t, tt.named)
@@ -351,7 +722,7 @@ func TestSetP(t *testing.T) {
content := []byte("This is the named golden file for TestSetP ^_^") content := []byte("This is the named golden file for TestSetP ^_^")
SetP(t, "sub-name", content) SetP(t, "sub-name", content)
b, err := ioutil.ReadFile( b, err := os.ReadFile(
filepath.Join("testdata", "TestSetP", "sub-name.golden"), filepath.Join("testdata", "TestSetP", "sub-name.golden"),
) )
require.NoError(t, err) require.NoError(t, err)
@@ -421,7 +792,7 @@ func TestSetP(t *testing.T) {
SetP(t, tt.named, tt.content) SetP(t, tt.named, tt.content)
got, err := ioutil.ReadFile(f) got, err := os.ReadFile(f)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tt.file, f) assert.Equal(t, tt.file, f)
@@ -433,11 +804,13 @@ func TestSetP(t *testing.T) {
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
for _, tt := range envUpdateFuncTestCases { for _, tt := range envUpdateFuncTestCases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
envctl.WithClean(tt.env, func() { for k, v := range tt.env {
t.Setenv(k, v)
}
got := Update() got := Update()
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })
})
} }
} }

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)
}

9
testing_t.go Normal file
View File

@@ -0,0 +1,9 @@
package golden
type TestingT interface {
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
Logf(format string, args ...interface{})
Name() string
}

View File

@@ -1,6 +1,9 @@
package golden package golden
import "os" import (
"os"
"strings"
)
var truthyStrings = []string{"1", "y", "t", "yes", "on", "true"} var truthyStrings = []string{"1", "y", "t", "yes", "on", "true"}
@@ -14,7 +17,7 @@ type UpdateFunc func() bool
func EnvUpdateFunc() bool { func EnvUpdateFunc() bool {
env := os.Getenv("GOLDEN_UPDATE") env := os.Getenv("GOLDEN_UPDATE")
for _, v := range truthyStrings { for _, v := range truthyStrings {
if env == v { if strings.EqualFold(env, v) {
return true return true
} }
} }

View File

@@ -3,7 +3,6 @@ package golden
import ( import (
"testing" "testing"
"github.com/jimeh/envctl"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -91,16 +90,44 @@ var envUpdateFuncTestCases = []struct {
env: map[string]string{"GOLDEN_UPDATE": "foobarnopebbq"}, env: map[string]string{"GOLDEN_UPDATE": "foobarnopebbq"},
want: false, want: false,
}, },
// Case-insensitive test cases
{
name: "GOLDEN_UPDATE set to Y (uppercase)",
env: map[string]string{"GOLDEN_UPDATE": "Y"},
want: true,
},
{
name: "GOLDEN_UPDATE set to TRUE (uppercase)",
env: map[string]string{"GOLDEN_UPDATE": "TRUE"},
want: true,
},
{
name: "GOLDEN_UPDATE set to Yes (mixed case)",
env: map[string]string{"GOLDEN_UPDATE": "Yes"},
want: true,
},
{
name: "GOLDEN_UPDATE set to ON (uppercase)",
env: map[string]string{"GOLDEN_UPDATE": "ON"},
want: true,
},
{
name: "GOLDEN_UPDATE set to TrUe (mixed case)",
env: map[string]string{"GOLDEN_UPDATE": "TrUe"},
want: true,
},
} }
func TestEnvUpdateFunc(t *testing.T) { func TestEnvUpdateFunc(t *testing.T) {
for _, tt := range envUpdateFuncTestCases { for _, tt := range envUpdateFuncTestCases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
envctl.WithClean(tt.env, func() { for k, v := range tt.env {
t.Setenv(k, v)
}
got := EnvUpdateFunc() got := EnvUpdateFunc()
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })
})
} }
} }