mirror of
https://github.com/jimeh/go-golden.git
synced 2026-02-19 11:16:47 +00:00
Compare commits
12 Commits
improve-te
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17b04717f | ||
| 550ba17fb0 | |||
|
|
8a88e5174b | ||
| bc86ba7a6d | |||
| 62e8344ff3 | |||
| e46dc124ff | |||
| 6817ec6101 | |||
| 8f4d3d4170 | |||
| b2112ca475 | |||
|
27e0134701
|
|||
| 85ae6e9ae3 | |||
| f5a03af9ce |
3
.github/.release-please-manifest.json
vendored
Normal file
3
.github/.release-please-manifest.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
".": "0.2.1"
|
||||
}
|
||||
15
.github/release-please-config.json
vendored
Normal file
15
.github/release-please-config.json
vendored
Normal 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"
|
||||
}
|
||||
39
.github/workflows/ci.yml
vendored
39
.github/workflows/ci.yml
vendored
@@ -6,18 +6,19 @@ jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.18"
|
||||
cache: false
|
||||
go-version: stable
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.56
|
||||
env:
|
||||
VERBOSE: "true"
|
||||
version: v1.64
|
||||
|
||||
tidy:
|
||||
name: Tidy
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.18"
|
||||
go-version-file: go.mod
|
||||
- name: Check if mods are tidy
|
||||
run: make check-tidy
|
||||
|
||||
@@ -37,9 +38,9 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.18"
|
||||
go-version-file: go.mod
|
||||
- name: Publish coverage
|
||||
uses: paambaati/codeclimate-action@v5.0.0
|
||||
uses: paambaati/codeclimate-action@v9.0.0
|
||||
env:
|
||||
VERBOSE: "true"
|
||||
GOMAXPROCS: 4
|
||||
@@ -60,16 +61,30 @@ jobs:
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
go_version:
|
||||
- "1.17"
|
||||
- "1.18"
|
||||
- "1.19"
|
||||
- "1.20"
|
||||
- "1.21"
|
||||
- "1.22"
|
||||
- "stable"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
go-version: ${{ matrix.go_version }}
|
||||
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
5
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
bin/*
|
||||
coverage.out
|
||||
|
||||
testdata/TestFile*
|
||||
testdata/TestGet*
|
||||
testdata/TestSet*
|
||||
testdata/*
|
||||
!testdata/TestExample*
|
||||
|
||||
@@ -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,12 +19,11 @@ linters:
|
||||
enable:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- copyloopvar
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
@@ -57,17 +51,17 @@ linters:
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- tparallel
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- wastedassign
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
exclude:
|
||||
- Using the variable on range scope `tt` in function literal
|
||||
- Using the variable on range scope `tc` in function literal
|
||||
exclude-rules:
|
||||
- path: "_test\\.go"
|
||||
linters:
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
19
Makefile
19
Makefile
@@ -45,7 +45,7 @@ endef
|
||||
$(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.56))
|
||||
$(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
|
||||
@@ -182,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))
|
||||
|
||||
48
README.md
48
README.md
@@ -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)
|
||||
|
||||
@@ -10,6 +10,9 @@ 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"`
|
||||
}
|
||||
@@ -21,10 +24,7 @@ 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)
|
||||
}
|
||||
@@ -46,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)
|
||||
})
|
||||
@@ -64,13 +61,11 @@ 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:
|
||||
@@ -92,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)
|
||||
})
|
||||
}
|
||||
}
|
||||
46
fs.go
46
fs.go
@@ -1,46 +0,0 @@
|
||||
package golden
|
||||
|
||||
import "os"
|
||||
|
||||
type FS interface {
|
||||
// MkdirAll creates a directory named path, along with any necessary
|
||||
// parents, and returns nil, or else returns an error. The permission bits
|
||||
// perm (before umask) are used for all directories that MkdirAll creates.
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
|
||||
// ReadFile reads the named file and returns the contents. A successful call
|
||||
// returns err == nil, not err == EOF. Because ReadFile reads the whole
|
||||
// file, it does not treat an EOF from Read as an error to be reported.
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
|
||||
// WriteFile writes data to a file named by filename. If the file does not
|
||||
// exist, WriteFile creates it with permissions perm; otherwise WriteFile
|
||||
// truncates it before writing, without changing permissions.
|
||||
WriteFile(name string, data []byte, perm os.FileMode) error
|
||||
}
|
||||
|
||||
type fsImpl struct{}
|
||||
|
||||
var _ FS = fsImpl{}
|
||||
|
||||
// NewFS returns a new FS instance which operates against the host file system
|
||||
// via calls to functions in the os package.
|
||||
func NewFS() FS {
|
||||
return fsImpl{}
|
||||
}
|
||||
|
||||
// DefaultFS is the default FS instance used by all top-level package functions,
|
||||
// including the Default Golden instance, and also the New function.
|
||||
var DefaultFS = NewFS()
|
||||
|
||||
func (fsImpl) MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func (fsImpl) ReadFile(filename string) ([]byte, error) {
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
func (fsImpl) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
return os.WriteFile(filename, data, perm)
|
||||
}
|
||||
131
fs_test.go
131
fs_test.go
@@ -1,131 +0,0 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMkdirAll(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
perm os.FileMode
|
||||
wantErr bool
|
||||
}{
|
||||
{"create new dir", "newdir", 0o755, false},
|
||||
{"create nested dirs", "nested/dir/structure", 0o755, false},
|
||||
{"invalid path", string([]byte{0, 0}), 0o755, true},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
path := filepath.Join(tempDir, tt.path)
|
||||
err := fs.MkdirAll(path, tt.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
_, err := os.Stat(path)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
sampleFilePath := filepath.Join(tempDir, "sample.txt")
|
||||
sampleContent := []byte("Hello, world!")
|
||||
err := os.WriteFile(sampleFilePath, sampleContent, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"read existing file", sampleFilePath, sampleContent, false},
|
||||
{"file does not exist", "nonexistent.txt", nil, true},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := fs.ReadFile(tt.filename)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(tt.want), string(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"write to new file",
|
||||
"newfile.txt",
|
||||
[]byte("new content"),
|
||||
0o644,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"overwrite existing file",
|
||||
"existing.txt",
|
||||
[]byte("overwritten content"),
|
||||
0o644,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid filename",
|
||||
string([]byte{0, 0}),
|
||||
[]byte("invalid filename"),
|
||||
0o644,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"non-existent directory",
|
||||
"nonexistentdir/newfile.txt",
|
||||
[]byte("this will fail"),
|
||||
0o644,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
fs := NewFS()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filePath := filepath.Join(tempDir, tt.filename)
|
||||
err := fs.WriteFile(filePath, tt.data, tt.perm)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
content, err := os.ReadFile(filePath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.data, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
7
go.mod
7
go.mod
@@ -1,11 +1,8 @@
|
||||
module github.com/jimeh/go-golden
|
||||
|
||||
go 1.18
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/jimeh/envctl v0.1.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
require github.com/stretchr/testify v1.10.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
||||
13
go.sum
13
go.sum
@@ -1,14 +1,17 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
173
gold.go
173
gold.go
@@ -1,173 +0,0 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/go-golden/sanitize"
|
||||
)
|
||||
|
||||
// gold is the underlying struct that implements the Golden interface.
|
||||
type gold 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 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 = &gold{}
|
||||
|
||||
func (g *gold) Do(t TestingT, data []byte) []byte {
|
||||
t.Helper()
|
||||
|
||||
if g.Update() {
|
||||
g.Set(t, data)
|
||||
}
|
||||
|
||||
return g.Get(t)
|
||||
}
|
||||
|
||||
func (g *gold) DoP(t TestingT, name string, data []byte) []byte {
|
||||
t.Helper()
|
||||
|
||||
if g.Update() {
|
||||
g.SetP(t, name, data)
|
||||
}
|
||||
|
||||
return g.GetP(t, name)
|
||||
}
|
||||
|
||||
func (g *gold) File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return g.file(t, "")
|
||||
}
|
||||
|
||||
func (g *gold) FileP(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return g.file(t, name)
|
||||
}
|
||||
|
||||
func (g *gold) file(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
if t.Name() == "" {
|
||||
t.Fatalf("golden: could not determine filename for TestingT instance")
|
||||
}
|
||||
|
||||
base := []string{g.dirname, filepath.FromSlash(t.Name())}
|
||||
if name != "" {
|
||||
base = append(base, name)
|
||||
}
|
||||
|
||||
f := filepath.Clean(filepath.Join(base...) + g.suffix)
|
||||
|
||||
dirty := strings.Split(f, string(os.PathSeparator))
|
||||
clean := make([]string, 0, len(dirty))
|
||||
for _, s := range dirty {
|
||||
clean = append(clean, sanitize.Filename(s))
|
||||
}
|
||||
|
||||
return strings.Join(clean, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (g *gold) Get(t TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return g.get(t, "")
|
||||
}
|
||||
|
||||
func (g *gold) GetP(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return g.get(t, name)
|
||||
}
|
||||
|
||||
func (g *gold) get(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
|
||||
b, err := g.fs.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: %s", err.Error())
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (g *gold) Set(t TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
g.set(t, "", data)
|
||||
}
|
||||
|
||||
func (g *gold) SetP(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
g.set(t, name, data)
|
||||
}
|
||||
|
||||
func (g *gold) set(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
f := g.file(t, name)
|
||||
dir := filepath.Dir(f)
|
||||
|
||||
if g.logOnWrite {
|
||||
t.Logf("golden: writing golden file: %s", f)
|
||||
}
|
||||
|
||||
err := g.fs.MkdirAll(dir, g.dirMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: failed to create directory: %s", err.Error())
|
||||
}
|
||||
|
||||
err = g.fs.WriteFile(f, data, g.fileMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: filed to write file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (g *gold) Update() bool {
|
||||
return g.updateFunc()
|
||||
}
|
||||
528
gold_test.go
528
gold_test.go
@@ -1,528 +0,0 @@
|
||||
package golden
|
||||
|
||||
// func Test_gold_File(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// fields fields
|
||||
// want string
|
||||
// wantFatals []string
|
||||
// }{
|
||||
// {
|
||||
// name: "top-level",
|
||||
// testName: "TestFooBar",
|
||||
// want: filepath.Join("testdata", "TestFooBar.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here.golden",
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// wantFatals: []string{
|
||||
// "golden: could not determine filename for TestingT instance",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("testdata", "TestFozBaz.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar.golden",
|
||||
// ),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// }
|
||||
|
||||
// ft := &fakeTestingT{name: tt.testName}
|
||||
|
||||
// var got string
|
||||
// testInGoroutine(t, func() {
|
||||
// got = g.File(ft)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantFatals, ft.fatals)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_FileP(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// goldenName string
|
||||
// fields fields
|
||||
// want string
|
||||
// wantFatals []string
|
||||
// }{
|
||||
// {
|
||||
// name: "top-level",
|
||||
// testName: "TestFooBar",
|
||||
// goldenName: "yaml",
|
||||
// want: filepath.Join("testdata", "TestFooBar", "yaml.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// goldenName: "json",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here", "json.golden",
|
||||
// ),
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// goldenName: "json",
|
||||
// wantFatals: []string{
|
||||
// "golden: could not determine filename for TestintT instance",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// goldenName: "xml",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar", "xml.golden"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// goldenName: "toml",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("testdata", "TestFozBaz", "toml.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// goldenName: "json",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// want: filepath.Join("goldenfiles", "TestFozBar", "json.goldfile"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// goldenName: "yml",
|
||||
// want: filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar", "yml.golden",
|
||||
// ),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// }
|
||||
|
||||
// ft := &fakeTestingT{name: tt.testName}
|
||||
|
||||
// var got string
|
||||
// testInGoroutine(t, func() {
|
||||
// got = g.FileP(ft, tt.goldenName)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantFatals, ft.fatals)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_Get(t *testing.T) {
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// fields fields
|
||||
// files map[string][]byte
|
||||
// want []byte
|
||||
// wantAborted bool
|
||||
// wantFailCount int
|
||||
// wantTestOutput []string
|
||||
// }{
|
||||
// {
|
||||
// name: "file exists",
|
||||
// testName: "TestFooBar",
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFooBar.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "file is missing",
|
||||
// testName: "TestFooBar",
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file exists",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here.golden",
|
||||
// ): []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// want: []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file is missing",
|
||||
// testName: "TestFooBar/not_really_here",
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "not_really_here.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: could not determine filename for given " +
|
||||
// "*mocktesting.T instance\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("goldenfiles", "TestFozBar.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFozBaz.goldfile"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("goldenfiles", "TestFozBar.goldfile"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// fs := NewFS() // TODO: Replace with in-memory stub FS.
|
||||
// for f, b := range tt.files {
|
||||
// _ = fs.WriteFile(f, b, 0o644)
|
||||
// }
|
||||
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// fs: fs,
|
||||
// }
|
||||
|
||||
// mt := mocktesting.NewT(tt.testName)
|
||||
|
||||
// var got []byte
|
||||
// mocktesting.Go(func() {
|
||||
// got = g.Get(mt)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
|
||||
// assert.Equal(t,
|
||||
// tt.wantFailCount, mt.FailedCount(), "failed count",
|
||||
// )
|
||||
// assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_gold_GetP(t *testing.T) {
|
||||
// type args struct {
|
||||
// name string
|
||||
// }
|
||||
// type fields struct {
|
||||
// suffix *string
|
||||
// dirname *string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// testName string
|
||||
// args args
|
||||
// fields fields
|
||||
// files map[string][]byte
|
||||
// want []byte
|
||||
// wantAborted bool
|
||||
// wantFailCount int
|
||||
// wantTestOutput []string
|
||||
// }{
|
||||
// {
|
||||
// name: "file exists",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: "yaml"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join("testdata", "TestFooBar", "yaml.golden"): []byte(
|
||||
// "foo: bar\nhello: world",
|
||||
// ),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "file is missing",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: "yaml"},
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "yaml.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file exists",
|
||||
// testName: "TestFooBar/it_is_here",
|
||||
// args: args{name: "plain"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "it_is_here", "plain.golden",
|
||||
// ): []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// want: []byte("this is really here ^_^\n"),
|
||||
// },
|
||||
// {
|
||||
// name: "sub-test file is missing",
|
||||
// testName: "TestFooBar/not_really_here",
|
||||
// args: args{name: "plain"},
|
||||
// files: map[string][]byte{},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: open " + filepath.Join(
|
||||
// "testdata", "TestFooBar", "not_really_here", "plain.golden",
|
||||
// ) + ": file does not exist\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank test name",
|
||||
// testName: "",
|
||||
// args: args{name: "plain"},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: could not determine filename for given " +
|
||||
// "*mocktesting.T instance\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "blank name",
|
||||
// testName: "TestFooBar",
|
||||
// args: args{name: ""},
|
||||
// wantAborted: true,
|
||||
// wantFailCount: 1,
|
||||
// wantTestOutput: []string{
|
||||
// "golden: name cannot be empty\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname",
|
||||
// testName: "TestFozBar",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "goldenfiles", "TestFozBar", "yaml.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom suffix",
|
||||
// testName: "TestFozBaz",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFozBaz", "yaml.goldfile",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "custom dirname and suffix",
|
||||
// testName: "TestFozBar",
|
||||
// args: args{name: "yaml"},
|
||||
// fields: fields{
|
||||
// dirname: stringPtr("goldenfiles"),
|
||||
// suffix: stringPtr(".goldfile"),
|
||||
// },
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "goldenfiles", "TestFozBar", "yaml.goldfile",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// {
|
||||
// name: "invalid chars in test name",
|
||||
// testName: `TestFooBar/foo?<>:*|"bar`,
|
||||
// args: args{name: "trash"},
|
||||
// files: map[string][]byte{
|
||||
// filepath.Join(
|
||||
// "testdata", "TestFooBar", "foo_______bar", "trash.golden",
|
||||
// ): []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// want: []byte("foo: bar\nhello: world"),
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// fs := NewFS() // TODO: Replace with in-memory stub FS
|
||||
// for f, b := range tt.files {
|
||||
// _ = fs.WriteFile(f, b, 0o644)
|
||||
// }
|
||||
|
||||
// if tt.fields.suffix == nil {
|
||||
// tt.fields.suffix = stringPtr(".golden")
|
||||
// }
|
||||
// if tt.fields.dirname == nil {
|
||||
// tt.fields.dirname = stringPtr("testdata")
|
||||
// }
|
||||
|
||||
// g := &gold{
|
||||
// suffix: *tt.fields.suffix,
|
||||
// dirname: *tt.fields.dirname,
|
||||
// fs: fs,
|
||||
// }
|
||||
|
||||
// mt := mocktesting.NewT(tt.testName)
|
||||
|
||||
// var got []byte
|
||||
// mocktesting.Go(func() {
|
||||
// got = g.GetP(mt, tt.args.name)
|
||||
// })
|
||||
|
||||
// assert.Equal(t, tt.want, got)
|
||||
// assert.Equal(t, tt.wantAborted, mt.Aborted(), "aborted")
|
||||
// assert.Equal(t,
|
||||
// tt.wantFailCount, mt.FailedCount(), "failed count",
|
||||
// )
|
||||
// assert.Equal(t, tt.wantTestOutput, mt.Output(), "test output")
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
437
golden.go
437
golden.go
@@ -1,10 +1,10 @@
|
||||
// Package golden is yet another package for working with *.golden test files,
|
||||
// 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
|
||||
// names by calling t.Name(). File names are sanitized to ensure they are
|
||||
// compatible with Linux, macOS and Windows systems regardless of what
|
||||
// characters might be in a subtest's name.
|
||||
// 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 crazy
|
||||
// characters might be in a sub-test's name.
|
||||
//
|
||||
// # Usage
|
||||
//
|
||||
@@ -19,7 +19,7 @@
|
||||
// assert.Equal(t, want, got)
|
||||
// }
|
||||
//
|
||||
// The above example will attempt to read/write to:
|
||||
// The above example will read/write to:
|
||||
//
|
||||
// testdata/TestExampleMyStruct.golden
|
||||
//
|
||||
@@ -37,7 +37,7 @@
|
||||
// # 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 {
|
||||
@@ -66,7 +66,7 @@
|
||||
//
|
||||
// # Multiple Golden Files in a Single Test
|
||||
//
|
||||
// The "P" suffixed methods, GetP(), SetP(), DoP(), 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
|
||||
// instance.
|
||||
//
|
||||
@@ -88,27 +88,27 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
// wantJSON := golden.DoP(t, "json", gotJSON)
|
||||
// wantXML := golden.DoP(t, "xml", gotXML)
|
||||
// wantJSON := golden.DoP(t, "json", gotJSON)
|
||||
// wantXML := golden.DoP(t, "xml", gotXML)
|
||||
//
|
||||
// assert.Equal(t, wantJSON, gotJSON)
|
||||
// assert.Equal(t, wantXML, gotXML)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// assert.Equal(t, wantJSON, gotJSON)
|
||||
// assert.Equal(t, wantXML, gotXML)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The above example will read/write to:
|
||||
//
|
||||
@@ -118,113 +118,32 @@
|
||||
// testdata/TestExampleMyStructTabularP/full_struct/xml.golden
|
||||
package golden
|
||||
|
||||
import "os"
|
||||
|
||||
var (
|
||||
// DefaultGolden is the default Golden instance used by all top-level
|
||||
// package functions.
|
||||
DefaultGolden = New()
|
||||
|
||||
// DefaultDirMode is the default file system permissions used for any
|
||||
// created directories to hold golden files.
|
||||
DefaultDirMode = os.FileMode(0o755)
|
||||
|
||||
// DefaultFileMode is the default file system permissions used for any
|
||||
// created or updated golden files written to disk.
|
||||
DefaultFileMode = os.FileMode(0o644)
|
||||
|
||||
// DefaultSuffix is the default filename suffix used for all golden files.
|
||||
DefaultSuffix = ".golden"
|
||||
|
||||
// DefaultDirname is the default name of the top-level directory used to
|
||||
// hold golden files.
|
||||
DefaultDirname = "testdata"
|
||||
|
||||
// DefaultUpdateFunc is the default function used to determine if golden
|
||||
// files should be updated or not. It is called by Update().
|
||||
DefaultUpdateFunc = EnvUpdateFunc
|
||||
|
||||
// DefaultLogOnWrite is the default value for logOnWrite on all Golden
|
||||
// instances.
|
||||
DefaultLogOnWrite = true
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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
|
||||
var (
|
||||
// Default is the default *Golden instance. All package-level functions use
|
||||
// the Default instance.
|
||||
Default = New()
|
||||
|
||||
// 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
|
||||
// DefaultDirMode is the default DirMode value used by New().
|
||||
DefaultDirMode = os.FileMode(0o755)
|
||||
|
||||
// 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)
|
||||
// DefaultFileMode is the default FileMode value used by New().
|
||||
DefaultFileMode = os.FileMode(0o644)
|
||||
|
||||
// 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().
|
||||
Do(t TestingT, data []byte) []byte
|
||||
// DefaultSuffix is the default Suffix value used by New().
|
||||
DefaultSuffix = ".golden"
|
||||
|
||||
// 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
|
||||
// DefaultDirname is the default Dirname value used by New().
|
||||
DefaultDirname = "testdata"
|
||||
|
||||
// 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)
|
||||
|
||||
// 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().
|
||||
DoP(t TestingT, name string, data []byte) []byte
|
||||
|
||||
// Update returns true when golden is set to update golden files. Should be
|
||||
// used to determine if golden.Set() or golden.SetP() should be called or
|
||||
// not.
|
||||
//
|
||||
// Default behavior uses EnvUpdateFunc() to check if the "GOLDEN_UPDATE"
|
||||
// environment variable is set to a truthy value. To customize set a new
|
||||
// UpdateFunc value on *Golden.
|
||||
Update() bool
|
||||
}
|
||||
|
||||
// New returns a new Golden instance. 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 := &gold{
|
||||
dirMode: DefaultDirMode,
|
||||
fileMode: DefaultFileMode,
|
||||
suffix: DefaultSuffix,
|
||||
dirname: DefaultDirname,
|
||||
updateFunc: DefaultUpdateFunc,
|
||||
fs: DefaultFS,
|
||||
logOnWrite: DefaultLogOnWrite,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt.apply(g)
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
// 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
|
||||
@@ -232,16 +151,7 @@ func New(options ...Option) Golden {
|
||||
func Do(t TestingT, data []byte) []byte {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.Do(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 DefaultGolden.DoP(t, name, data)
|
||||
return Default.Do(t, data)
|
||||
}
|
||||
|
||||
// File returns the filename of the golden file for the given *testing.T
|
||||
@@ -249,15 +159,7 @@ func DoP(t TestingT, name string, data []byte) []byte {
|
||||
func File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.File(t)
|
||||
}
|
||||
|
||||
// FileP returns the filename of the specifically named golden file for the
|
||||
// given *testing.T instance as determined by t.Name().
|
||||
func FileP(t TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.FileP(t, name)
|
||||
return Default.File(t)
|
||||
}
|
||||
|
||||
// Get returns the content of the golden file for the given *testing.T instance
|
||||
@@ -266,7 +168,33 @@ func FileP(t TestingT, name string) string {
|
||||
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 TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
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 TestingT, name string) string {
|
||||
t.Helper()
|
||||
|
||||
return Default.FileP(t, name)
|
||||
}
|
||||
|
||||
// GetP returns the content of the specifically named golden file belonging
|
||||
@@ -278,16 +206,7 @@ func Get(t TestingT) []byte {
|
||||
func GetP(t TestingT, name string) []byte {
|
||||
t.Helper()
|
||||
|
||||
return DefaultGolden.GetP(t, name)
|
||||
}
|
||||
|
||||
// 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 TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
DefaultGolden.Set(t, data)
|
||||
return Default.GetP(t, name)
|
||||
}
|
||||
|
||||
// SetP writes given data of the specifically named golden file belonging to
|
||||
@@ -299,7 +218,7 @@ func Set(t TestingT, 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
|
||||
@@ -309,5 +228,207 @@ func SetP(t TestingT, 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 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
|
||||
|
||||
// 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 would be ".golden".
|
||||
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 "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
|
||||
}
|
||||
|
||||
// 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 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
|
||||
// instance as determined by t.Name().
|
||||
func (s *Golden) File(t TestingT) string {
|
||||
t.Helper()
|
||||
|
||||
return s.file(t, "")
|
||||
}
|
||||
|
||||
// Get returns the content of the golden file for the given *testing.T instance
|
||||
// as determined by t.Name(). If no golden file can be found/read, it will fail
|
||||
// the test by calling t.Fatal().
|
||||
func (s *Golden) Get(t TestingT) []byte {
|
||||
t.Helper()
|
||||
|
||||
return s.get(t, "")
|
||||
}
|
||||
|
||||
// Set writes given data to the golden file for the given *testing.T instance as
|
||||
// determined by t.Name(). If writing fails it will fail the test by calling
|
||||
// t.Fatal() with error details.
|
||||
func (s *Golden) Set(t TestingT, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
return s.get(t, name)
|
||||
}
|
||||
|
||||
// SetP writes given data of the specifically named golden file belonging to
|
||||
// the given *testing.T instance as determined by t.Name(). If writing fails it
|
||||
// will fail the test with t.Fatal() detailing the error.
|
||||
//
|
||||
// This is very similar to Set(), but it allows multiple different golden files
|
||||
// to be used within the same one *testing.T instance.
|
||||
func (s *Golden) SetP(t TestingT, name string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
if name == "" {
|
||||
t.Fatalf("golden: name cannot be empty")
|
||||
}
|
||||
|
||||
s.set(t, name, data)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
base := []string{s.Dirname, filepath.FromSlash(t.Name())}
|
||||
if name != "" {
|
||||
base = append(base, name)
|
||||
}
|
||||
|
||||
f := filepath.Clean(filepath.Join(base...) + s.Suffix)
|
||||
|
||||
dirty := strings.Split(f, string(os.PathSeparator))
|
||||
clean := make([]string, 0, len(dirty))
|
||||
for _, s := range dirty {
|
||||
clean = append(clean, sanitizeFilename(s))
|
||||
}
|
||||
|
||||
return strings.Join(clean, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (s *Golden) get(t TestingT, name string) []byte {
|
||||
f := s.file(t, name)
|
||||
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: failed reading %s: %s", f, err.Error())
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (s *Golden) set(t TestingT, name string, data []byte) {
|
||||
f := s.file(t, name)
|
||||
dir := filepath.Dir(f)
|
||||
|
||||
t.Logf("golden: writing .golden file: %s", f)
|
||||
|
||||
err := os.MkdirAll(dir, s.DirMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: failed to create directory: %s", err.Error())
|
||||
}
|
||||
|
||||
err = os.WriteFile(f, data, s.FileMode)
|
||||
if err != nil {
|
||||
t.Fatalf("golden: filed to write file: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
1540
golden_test.go
1540
golden_test.go
File diff suppressed because it is too large
Load Diff
91
options.go
91
options.go
@@ -1,80 +1,43 @@
|
||||
package golden
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type Option interface {
|
||||
apply(*gold)
|
||||
}
|
||||
// Option is a function that modifies a Golden instance.
|
||||
type Option func(*Golden)
|
||||
|
||||
type optionFunc func(*gold)
|
||||
|
||||
func (fn optionFunc) apply(g *gold) {
|
||||
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.
|
||||
// WithDirMode sets the directory mode for a Golden instance.
|
||||
func WithDirMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.dirMode = mode
|
||||
})
|
||||
return 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.
|
||||
// WithFileMode sets the file mode for a Golden instance.
|
||||
func WithFileMode(mode os.FileMode) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.fileMode = mode
|
||||
})
|
||||
return 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".
|
||||
// WithSuffix sets the file suffix for a Golden instance.
|
||||
func WithSuffix(suffix string) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
g.suffix = suffix
|
||||
})
|
||||
return 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 *gold) {
|
||||
g.dirname = name
|
||||
})
|
||||
// WithDirname sets the directory name for a Golden instance.
|
||||
func WithDirname(dirname string) Option {
|
||||
return func(g *Golden) {
|
||||
g.Dirname = dirname
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *gold) {
|
||||
g.updateFunc = fn
|
||||
})
|
||||
}
|
||||
|
||||
// WithFS sets the afero.Fs instance which is used for all file system
|
||||
// operations to read/write golden files.
|
||||
//
|
||||
// When this option is not provided, the default value is afero.NewOsFs().
|
||||
func WithFS(fs FS) Option {
|
||||
return optionFunc(func(g *gold) {
|
||||
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 *gold) {
|
||||
g.logOnWrite = false
|
||||
})
|
||||
// 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
58
options_test.go
Normal 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)
|
||||
}
|
||||
@@ -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++ {
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -1,275 +0,0 @@
|
||||
package testfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
isDir bool
|
||||
}
|
||||
|
||||
type FS struct {
|
||||
Pwd string
|
||||
Nodes map[string]*Node
|
||||
}
|
||||
|
||||
func New() *FS {
|
||||
return &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o700, isDir: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) MkdirAll(name string, perm os.FileMode) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
dirs := []string{name}
|
||||
for d := path.Dir(name); d != "/"; d = path.Dir(d) {
|
||||
dirs = append(dirs, d)
|
||||
}
|
||||
dirs = append(dirs, "/")
|
||||
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
dir := dirs[i]
|
||||
parent := path.Dir(dir)
|
||||
|
||||
if info, ok := fs.Nodes[dir]; ok {
|
||||
if !info.isDir {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: dir,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
parentInfo, ok := fs.Nodes[parent]
|
||||
if !ok {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: parent,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if !parentInfo.isDir {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: parent,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
// Ensure all parent directories have execute permissions, and direct
|
||||
// parent also has write permission.
|
||||
if parentInfo.perm&0o100 == 0 || i == 1 && parentInfo.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: dir,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
fs.Nodes[dir] = &Node{perm: perm, isDir: true}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) ReadFile(name string) ([]byte, error) {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
_, err := fs.checkParents(name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if !ok {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info.isDir {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("is a directory"),
|
||||
}
|
||||
}
|
||||
if info.perm&0o400 == 0 {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
return info.data, nil
|
||||
}
|
||||
|
||||
func (fs *FS) WriteFile(name string, data []byte, perm os.FileMode) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
parent, err := fs.checkParents(name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if ok {
|
||||
if info.isDir {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("is a directory"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return error if file exists and has no write permission, or if the file
|
||||
// does not exist and the direct parent has no write permission.
|
||||
if ok && info.perm&0o200 == 0 || !ok && parent.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
fs.Nodes[name] = &Node{data: data, perm: perm}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) Remove(name string) error {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
parent, err := fs.checkParents(name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if parent != nil && parent.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
|
||||
info, ok := fs.Nodes[name]
|
||||
if !ok {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info.perm&0o200 == 0 {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
if info.isDir {
|
||||
for p := range fs.Nodes {
|
||||
if strings.HasPrefix(p, name) && p != name {
|
||||
return &os.PathError{
|
||||
Op: "remove",
|
||||
Path: name,
|
||||
Err: errors.New("directory not empty"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(fs.Nodes, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FS) Exists(name string) bool {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
_, ok := fs.Nodes[name]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (fs *FS) FileMode(name string) (os.FileMode, error) {
|
||||
if !path.IsAbs(name) && name != "" {
|
||||
name = path.Join(fs.Pwd, name)
|
||||
}
|
||||
|
||||
if info, ok := fs.Nodes[name]; ok {
|
||||
return info.perm, nil
|
||||
}
|
||||
|
||||
return 0, &os.PathError{
|
||||
Op: "open",
|
||||
Path: name,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FS) checkParents(absPath string, noExistError bool) (*Node, error) {
|
||||
var parents []string
|
||||
for d := path.Dir(absPath); d != "/"; d = path.Dir(d) {
|
||||
parents = append(parents, d)
|
||||
}
|
||||
parents = append(parents, "/")
|
||||
var directParent *Node
|
||||
|
||||
for i := 0; i < len(parents); i++ {
|
||||
dir := parents[i]
|
||||
info, ok := fs.Nodes[dir]
|
||||
if !ok && noExistError {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("no such file or directory"),
|
||||
}
|
||||
}
|
||||
if info != nil && !info.isDir {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("not a directory"),
|
||||
}
|
||||
}
|
||||
// Ensure all parent directories have execute permissions.
|
||||
if info != nil && info.perm&0o100 == 0 {
|
||||
return nil, &os.PathError{
|
||||
Op: "open",
|
||||
Path: dir,
|
||||
Err: errors.New("permission denied"),
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
directParent = info
|
||||
}
|
||||
}
|
||||
|
||||
return directParent, nil
|
||||
}
|
||||
@@ -1,670 +0,0 @@
|
||||
package testfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFSMkdirAll(t *testing.T) {
|
||||
type args struct {
|
||||
path string
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
want map[string]*Node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "create relative new dir",
|
||||
args: args{path: "newdir", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/newdir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute new dir",
|
||||
args: args{path: "/opt/newdir", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/newdir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "create relative nested dirs",
|
||||
args: args{path: "nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs",
|
||||
args: args{path: "/opt/nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/nested": {perm: 0o755, isDir: true},
|
||||
"/opt/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/opt/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative nested dirs with other perms",
|
||||
args: args{path: "nested/dir/structure", perm: 0o750},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o750, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o750, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o750, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs with other perms",
|
||||
args: args{path: "/opt/nested/dir/structure", perm: 0o750},
|
||||
want: map[string]*Node{
|
||||
"/opt": {perm: 0o750, isDir: true},
|
||||
"/opt/nested": {perm: 0o750, isDir: true},
|
||||
"/opt/nested/dir": {perm: 0o750, isDir: true},
|
||||
"/opt/nested/dir/structure": {perm: 0o750, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative nested dirs with existing dirs",
|
||||
args: args{path: "nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create absolute nested dirs with existing dirs",
|
||||
args: args{path: "/root/nested/dir/structure", perm: 0o755},
|
||||
want: map[string]*Node{
|
||||
"/root/nested": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir": {perm: 0o755, isDir: true},
|
||||
"/root/nested/dir/structure": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create relative under file",
|
||||
args: args{path: "file/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root/file": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute under file",
|
||||
args: args{path: "/root/file/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root/file": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create relative directory without execute permission",
|
||||
args: args{path: "dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute directory without execute permission",
|
||||
args: args{path: "/root/dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create relative directory without write permission",
|
||||
args: args{path: "dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o444},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "create absolute directory without write permission",
|
||||
args: args{path: "/root/dir/newdir", perm: 0o755},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o444},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o700, isDir: true},
|
||||
},
|
||||
}
|
||||
|
||||
for fp, info := range tt.nodes {
|
||||
fs.Nodes[fp] = info
|
||||
}
|
||||
|
||||
err := fs.MkdirAll(tt.args.path, tt.args.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
for fp, info := range tt.want {
|
||||
got := fs.Nodes[fp]
|
||||
assert.Equal(t, info, got, "path: %s", fp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSReadFile(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative read existing file",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "absolute read existing file",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "relative file does not exist",
|
||||
args: args{name: "nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file does not exist",
|
||||
args: args{name: "/opt/nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file is a directory",
|
||||
args: args{name: "dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file is a directory",
|
||||
args: args{name: "/opt/dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file permission denied",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory read permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o355, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
want: []byte("file content"),
|
||||
},
|
||||
{
|
||||
name: "relative no directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no grandparent directory execute permission",
|
||||
args: args{name: "foo/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/foo": {perm: 0o755, isDir: true},
|
||||
"/root/foo/file.txt": {data: []byte("hello"), perm: 0o200},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
got, err := fs.ReadFile(tt.args.name)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSWriteFile(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
data []byte
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
wantPath string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative write to new file",
|
||||
args: args{
|
||||
name: "newfile.txt",
|
||||
data: []byte("new content"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute write to new file",
|
||||
args: args{
|
||||
name: "/opt/newfile.txt",
|
||||
data: []byte("new content"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative overwrite existing file",
|
||||
args: args{
|
||||
name: "existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/existing": {data: []byte("existing"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite existing file",
|
||||
args: args{
|
||||
name: "/opt/existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/existing": {data: []byte("existing"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative overwrite file permissions denied",
|
||||
args: args{
|
||||
name: "existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/existing.txt": {data: []byte("existing"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite file permissions denied",
|
||||
args: args{
|
||||
name: "/opt/existing.txt",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/existing.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/existing.txt": {data: []byte("existing"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative overwrite directory",
|
||||
args: args{
|
||||
name: "dir",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o644, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute overwrite directory",
|
||||
args: args{
|
||||
name: "/opt/dir",
|
||||
data: []byte("overwritten"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/dir",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o644, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative write to non-existent directory",
|
||||
args: args{
|
||||
name: "nonexistentdir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/nonexistentdir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute write to non-existent directory",
|
||||
args: args{
|
||||
name: "/opt/nonexistentdir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/opt/nonexistentdir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative write parent directory is a file",
|
||||
args: args{
|
||||
name: "file/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/file/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/file": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no parent directory write permission denied",
|
||||
args: args{
|
||||
name: "dir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o500, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no parent directory execute permission denied",
|
||||
args: args{
|
||||
name: "dir/newfile.txt",
|
||||
data: []byte("this will fail"),
|
||||
perm: 0o644,
|
||||
},
|
||||
wantPath: "/tmp/dir/newfile.txt",
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/tmp": {perm: 0o755, isDir: true},
|
||||
"/tmp/dir": {perm: 0o600, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/tmp",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
err := fs.WriteFile(tt.args.name, tt.args.data, tt.args.perm)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if _, ok := tt.nodes[tt.wantPath]; ok {
|
||||
assert.Equal(t,
|
||||
tt.nodes[tt.wantPath],
|
||||
fs.Nodes[tt.wantPath],
|
||||
)
|
||||
} else {
|
||||
assert.NotContains(t, fs.Nodes, tt.wantPath)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
got := fs.Nodes[tt.wantPath]
|
||||
assert.Equal(t, tt.args.data, got.data)
|
||||
assert.Equal(t, tt.args.perm, got.perm)
|
||||
assert.Equal(t, false, got.isDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFSRemove(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
nodes map[string]*Node
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "relative remove existing file",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute remove existing file",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative file does not exist",
|
||||
args: args{name: "nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file does not exist",
|
||||
args: args{name: "/opt/nonexistent.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative file is a directory",
|
||||
args: args{name: "dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "absolute file is a directory",
|
||||
args: args{name: "/opt/dir"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/dir": {perm: 0o755, isDir: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relative file permission denied",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o755, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "absolute file permission denied",
|
||||
args: args{name: "/opt/file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/": {perm: 0o755, isDir: true},
|
||||
"/opt": {perm: 0o755, isDir: true},
|
||||
"/opt/file.txt": {data: []byte("file content"), perm: 0o400},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory write permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o555, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/file.txt": {data: []byte("file content"), perm: 0o644},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative no grandparent directory execute permission",
|
||||
args: args{name: "file.txt"},
|
||||
nodes: map[string]*Node{
|
||||
"/root": {perm: 0o655, isDir: true},
|
||||
"/root/dir": {perm: 0o755, isDir: true},
|
||||
"/root/dir/file.txt": {
|
||||
data: []byte("file content"), perm: 0o644,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fs := &FS{
|
||||
Pwd: "/root",
|
||||
Nodes: tt.nodes,
|
||||
}
|
||||
|
||||
err := fs.Remove(tt.args.name)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package golden
|
||||
|
||||
// TestingT is a interface describing a sub-set of methods of *testing.T which
|
||||
// golden uses.
|
||||
type TestingT interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Helper()
|
||||
Logf(format string, args ...interface{})
|
||||
@@ -1,30 +0,0 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type fakeTestingT struct {
|
||||
helper bool
|
||||
name string
|
||||
logs []string
|
||||
fatals []string
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Helper() {
|
||||
m.helper = true
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Fatalf(format string, args ...interface{}) {
|
||||
m.fatals = append(m.fatals, fmt.Sprintf(format, args...))
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Logf(format string, args ...interface{}) {
|
||||
m.logs = append(m.logs, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (m *fakeTestingT) Name() string {
|
||||
return m.name
|
||||
}
|
||||
22
update.go
22
update.go
@@ -1,7 +1,6 @@
|
||||
package golden
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
@@ -18,29 +17,10 @@ type UpdateFunc func() bool
|
||||
func EnvUpdateFunc() bool {
|
||||
env := os.Getenv("GOLDEN_UPDATE")
|
||||
for _, v := range truthyStrings {
|
||||
if strings.ToLower(env) == v {
|
||||
if strings.EqualFold(env, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
updateFlagSet *flag.FlagSet
|
||||
updateFlag bool
|
||||
)
|
||||
|
||||
// UpdateFunc returns a function that checks a -update flag is set.
|
||||
func FlagUpdateFunc() bool {
|
||||
if updateFlagSet == nil {
|
||||
updateFlagSet = flag.NewFlagSet("golden", flag.ContinueOnError)
|
||||
updateFlagSet.BoolVar(&updateFlag,
|
||||
"update", false, "update golden files",
|
||||
)
|
||||
}
|
||||
|
||||
_ = updateFlagSet.Parse(os.Args[1:])
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
148
update_test.go
148
update_test.go
@@ -3,7 +3,6 @@ package golden
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jimeh/envctl"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -41,181 +40,94 @@ var envUpdateFuncTestCases = []struct {
|
||||
env: map[string]string{"GOLDEN_UPDATE": "y"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to Y",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "Y"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to n",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "n"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to N",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "N"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to t",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "t"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to T",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "T"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to f",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "f"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to F",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "F"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to yes",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "yes"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to Yes",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "Yes"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to YeS",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "YeS"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to YES",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "YES"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to no",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "no"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to No",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "No"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to nO",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "nO"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to NO",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "NO"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to on",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "on"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to oN",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "oN"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to On",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "On"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to ON",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "ON"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to off",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "off"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to Off",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "Off"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to oFF",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "oFF"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to OFF",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "OFF"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to true",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "true"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to True",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "True"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to TruE",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "TruE"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to TRUE",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "TRUE"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to false",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "false"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to False",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "False"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to FaLsE",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "FaLsE"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to FALSE",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "FALSE"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to foobarnopebbq",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "foobarnopebbq"},
|
||||
want: false,
|
||||
},
|
||||
// Case-insensitive test cases
|
||||
{
|
||||
name: "GOLDEN_UPDATE set to FOOBARNOPEBBQ",
|
||||
env: map[string]string{"GOLDEN_UPDATE": "FOOBARNOPEBBQ"},
|
||||
want: false,
|
||||
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) {
|
||||
for _, tt := range envUpdateFuncTestCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
envctl.WithClean(tt.env, func() {
|
||||
got := EnvUpdateFunc()
|
||||
for k, v := range tt.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
got := EnvUpdateFunc()
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user