├── .gitignore ├── testdata ├── TestRead │ ├── success-read-data.golden │ ├── success-read-data.filename.golden │ ├── success-read-nil.filename.golden │ ├── error-reading-file-permission-denied.filename.golden │ ├── success-read-nil.golden │ └── error-reading-file-permission-denied.golden ├── TestTool_Run │ ├── fatalities-run.golden │ └── successful-run.golden ├── TestTool_Read │ ├── success-read-data.golden │ ├── with-set-want-field.golden │ ├── success-read-nil.golden │ └── error-reading-file-permission-denied.golden ├── TestTool_noError │ ├── without-error.golden │ └── with-error.golden ├── TestJSONEq │ ├── Succeeded.golden │ ├── unexpected_end_of_JSON_input.golden │ └── Failed.golden ├── TestTool_JSONEq │ ├── Succeeded.golden │ ├── check-for-updates.golden │ ├── unexpected_end_of_JSON_input.golden │ └── Failed.golden ├── TestTool_Equal │ ├── successful_[]-[].golden │ ├── successful_golden-golden.golden │ ├── successful_nil-nil.golden │ ├── failure_[]-nil.golden │ ├── failure_golden-Z29sZGVu.golden │ ├── failure_golden-nil.golden │ ├── failure_nil-[].golden │ └── failure_nil-golden.golden ├── TestTool_mkdir │ ├── error-file-does-not-exist.golden │ ├── fatality-error.golden │ └── error-dir-is-a-file.golden ├── TestTool_write │ ├── write-bytes.golden │ ├── fatality-error.golden │ ├── write-nil.golden │ ├── write-empty.golden │ └── write-nil-with-file-exist.golden ├── TestRun │ ├── run-with-error.golden │ └── run-without-error.golden ├── TestEqual │ ├── successful_[]-[].golden │ ├── successful_golden-golden.golden │ ├── successful_nil-nil.golden │ ├── failure_[]-nil.golden │ ├── failure_golden-Z29sZGVu.golden │ ├── failure_golden-nil.golden │ ├── failure_nil-[].golden │ └── failure_nil-golden.golden ├── TestTool_Assert │ ├── success-assert-data.golden │ ├── error-reading-file-permission-denied.golden │ ├── success-assert-nil-with-error-not-exist.golden │ └── failure-assert-data.golden ├── TestAssert │ ├── success-assert-data.golden │ ├── error-reading-file-permission-denied.golden │ ├── success-assert-nil-with-error-not-exist.golden │ └── failure-assert-data.golden └── Test_jsonFormatter │ └── error.golden ├── internal ├── integration │ ├── testdata │ │ ├── TestEqual.golden │ │ ├── TestEqual.input │ │ ├── TestEqual │ │ │ ├── sublevel-one.input │ │ │ ├── sublevel-one.golden │ │ │ └── sublevel-one │ │ │ │ ├── sublevel-two.golden │ │ │ │ └── sublevel-two.input │ │ ├── TestJSONEq.input │ │ ├── TestJSONEq │ │ │ ├── sublevel-one.input │ │ │ ├── sublevel-one │ │ │ │ ├── sublevel-two.input │ │ │ │ └── sublevel-two.json.golden │ │ │ └── sublevel-one.json.golden │ │ └── TestJSONEq.json.golden │ ├── golden.go │ └── golden_test.go └── tools │ ├── go.mod │ ├── README.md │ ├── tools.go │ └── go.sum ├── go.mod ├── .github ├── dependabot.yml └── workflows │ └── go.yml ├── .gitattributes ├── interceptor.go ├── target.go ├── .editorconfig ├── target_test.go ├── interceptor_test.go ├── testing_test.go ├── LICENSE ├── doc.go ├── go.sum ├── conclusion.go ├── README.md ├── Makefile ├── example_test.go ├── golden.go └── golden_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | -------------------------------------------------------------------------------- /testdata/TestRead/success-read-data.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/TestTool_Run/fatalities-run.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/TestTool_Read/success-read-data.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/TestTool_noError/without-error.golden: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/TestJSONEq/Succeeded.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual.golden: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual.input: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /testdata/TestRead/success-read-data.filename.golden: -------------------------------------------------------------------------------- 1 | testdata/TestRead/success-read-data.input -------------------------------------------------------------------------------- /testdata/TestRead/success-read-nil.filename.golden: -------------------------------------------------------------------------------- 1 | testdata/TestRead/success-read-nil.input -------------------------------------------------------------------------------- /testdata/TestTool_JSONEq/Succeeded.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_Read/with-set-want-field.golden: -------------------------------------------------------------------------------- 1 | golden: read the value from the want field -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual/sublevel-one.input: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /testdata/TestTool_Equal/successful_[]-[].golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual/sublevel-one.golden: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /testdata/TestTool_Equal/successful_golden-golden.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_mkdir/error-file-does-not-exist.golden: -------------------------------------------------------------------------------- 1 | golden: trying to create a directory: "testdata" -------------------------------------------------------------------------------- /testdata/TestTool_write/write-bytes.golden: -------------------------------------------------------------------------------- 1 | golden: start write to file: TestTool_write/write-bytes.golden -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xorcare/golden 2 | 3 | go 1.12 4 | 5 | require github.com/stretchr/testify v1.11.1 6 | -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual/sublevel-one/sublevel-two.golden: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /internal/integration/testdata/TestEqual/sublevel-one/sublevel-two.input: -------------------------------------------------------------------------------- 1 | 01f2515f2a7e294ea03bd8434a7447687d12571b 2 | -------------------------------------------------------------------------------- /testdata/TestRun/run-with-error.golden: -------------------------------------------------------------------------------- 1 | golden: file already closed 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_mkdir/fatality-error.golden: -------------------------------------------------------------------------------- 1 | golden: permission denied 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_noError/with-error.golden: -------------------------------------------------------------------------------- 1 | golden: permission denied 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_write/fatality-error.golden: -------------------------------------------------------------------------------- 1 | golden: permission denied 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestRead/error-reading-file-permission-denied.filename.golden: -------------------------------------------------------------------------------- 1 | testdata/TestRead/error-reading-file-permission-denied.input -------------------------------------------------------------------------------- /testdata/TestRead/success-read-nil.golden: -------------------------------------------------------------------------------- 1 | golden: read the value of nil since it is not found file: testdata/TestRead/success-read-nil.input -------------------------------------------------------------------------------- /testdata/TestTool_Read/success-read-nil.golden: -------------------------------------------------------------------------------- 1 | golden: read the value of nil since it is not found file: TestTool_Read/success-read-nil.golden -------------------------------------------------------------------------------- /testdata/TestRead/error-reading-file-permission-denied.golden: -------------------------------------------------------------------------------- 1 | golden: permission denied 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestEqual/successful_[]-[].golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestRun/run-without-error.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_Read/error-reading-file-permission-denied.golden: -------------------------------------------------------------------------------- 1 | golden: permission denied 2 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Run/successful-run.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestEqual/successful_golden-golden.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_Assert/success-assert-data.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_mkdir/error-dir-is-a-file.golden: -------------------------------------------------------------------------------- 1 | golden: test dir is a file: testdata/TestTool_mkdir.golden 2 | golden_test: method called *golden.bufferTB.Fail() -------------------------------------------------------------------------------- /internal/tools/go.mod: -------------------------------------------------------------------------------- 1 | module tools 2 | 3 | go 1.12 4 | 5 | require ( 6 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b 7 | golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb 8 | ) 9 | -------------------------------------------------------------------------------- /testdata/TestTool_write/write-nil.golden: -------------------------------------------------------------------------------- 1 | golden: trying to create a directory: "TestTool_write" 2 | golden: start write to file: TestTool_write/write-nil.golden 3 | golden: nil value will not be written -------------------------------------------------------------------------------- /testdata/TestTool_write/write-empty.golden: -------------------------------------------------------------------------------- 1 | golden: test dir is a file: TestTool_write 2 | golden_test: method called *golden.bufferTB.Fail() 3 | golden: start write to file: TestTool_write/write-empty.golden -------------------------------------------------------------------------------- /testdata/TestAssert/success-assert-data.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden_test: method called *golden.bufferTB.Helper() -------------------------------------------------------------------------------- /testdata/TestTool_Equal/successful_nil-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden: read the value of nil since it is not found file: testdata/TestTool_Equal/successful_nil-nil.golden -------------------------------------------------------------------------------- /testdata/TestEqual/successful_nil-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden: read the value of nil since it is not found file: testdata/TestEqual/successful_nil-nil.golden -------------------------------------------------------------------------------- /testdata/TestTool_Assert/error-reading-file-permission-denied.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden: permission denied 4 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_JSONEq/check-for-updates.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden: updating file: testdata/TestTool_JSONEq/check-for-updates.json.golden 3 | golden: start write to file: testdata/TestTool_JSONEq/check-for-updates.json.golden -------------------------------------------------------------------------------- /testdata/TestTool_Assert/success-assert-nil-with-error-not-exist.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden: read the value of nil since it is not found file: TestTool_Assert/success-assert-nil-with-error-not-exist.golden -------------------------------------------------------------------------------- /testdata/TestAssert/error-reading-file-permission-denied.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden_test: method called *golden.bufferTB.Helper() 4 | golden: permission denied 5 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_write/write-nil-with-file-exist.golden: -------------------------------------------------------------------------------- 1 | golden: test dir is a file: TestTool_write 2 | golden_test: method called *golden.bufferTB.Fail() 3 | golden: start write to file: TestTool_write/write-nil-with-file-exist.golden 4 | golden: nil value will not be written 5 | golden: current test bytes file will be deleted -------------------------------------------------------------------------------- /internal/tools/README.md: -------------------------------------------------------------------------------- 1 | # tools 2 | 3 | This directory includes go.mod and go.sum, which contain versions of the tools. 4 | 5 | ## License 6 | 7 | © Vasiliy Vasilyuk, 2019 8 | 9 | Released under the [BSD 3-Clause License][LICENSE]. 10 | 11 | [LICENSE]: https://git.io/fhjjx 'BSD 3-Clause "New" or "Revised" License' 12 | -------------------------------------------------------------------------------- /testdata/TestAssert/success-assert-nil-with-error-not-exist.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden_test: method called *golden.bufferTB.Helper() 4 | golden: read the value of nil since it is not found file: testdata/TestAssert/success-assert-nil-with-error-not-exist.golden -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build tools 6 | // +build tools 7 | 8 | package tools_test 9 | 10 | import ( 11 | _ "golang.org/x/lint/golint" 12 | _ "golang.org/x/tools/cmd/goimports" 13 | ) 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: gomod 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | - package-ecosystem: github-actions 11 | directory: / 12 | schedule: 13 | interval: monthly 14 | -------------------------------------------------------------------------------- /internal/integration/golden.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package integration is needed to test the functionality of golden 7 | in integration with the file system. Without side effects from 8 | other tests and TestMain. 9 | */ 10 | package integration 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto-detect text files, ensure they use LF. 2 | * text=auto eol=lf 3 | 4 | Makefile text eol=lf whitespace=blank-at-eol,-blank-at-eof,-tab-in-indent,indent-with-non-tab,space-before-tab,tabwidth=4 5 | *.go text eol=lf whitespace=blank-at-eol,-blank-at-eof,-tab-in-indent,indent-with-non-tab,space-before-tab,tabwidth=4 6 | *.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,tab-in-indent,space-before-tab 7 | -------------------------------------------------------------------------------- /testdata/Test_jsonFormatter/error.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | 4 | Error Trace: 5 | Error: Data ("") needs to be valid json. 6 | JSON parsing error: "unexpected end of JSON input" 7 | Test: Test_jsonFormatter/error 8 | 9 | golden_test: method called *golden.bufferTB.Fail() 10 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestJSONEq/unexpected_end_of_JSON_input.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Expected value ('') is not valid json. 5 | JSON parsing error: 'unexpected end of JSON input' 6 | 7 | golden_test: method called *golden.bufferTB.Fail() 8 | 9 | Error Trace: 10 | Error: Expected value ('') is not valid json. 11 | JSON parsing error: 'unexpected end of JSON input' 12 | 13 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_JSONEq/unexpected_end_of_JSON_input.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Expected value ('') is not valid json. 5 | JSON parsing error: 'unexpected end of JSON input' 6 | 7 | golden_test: method called *golden.bufferTB.Fail() 8 | 9 | Error Trace: 10 | Error: Expected value ('') is not valid json. 11 | JSON parsing error: 'unexpected end of JSON input' 12 | 13 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Assert/failure-assert-data.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "Z29sZGVu" 7 | actual : "golden" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | -Z29sZGVu 14 | +golden 15 | 16 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestAssert/failure-assert-data.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden_test: method called *golden.bufferTB.Helper() 4 | 5 | Error Trace: 6 | Error: Not equal: 7 | expected: "Z29sZGVu" 8 | actual : "golden" 9 | 10 | Diff: 11 | --- Expected 12 | +++ Actual 13 | @@ -1 +1 @@ 14 | -Z29sZGVu 15 | +golden 16 | 17 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /interceptor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var _ assert.TestingT = new(interceptor) 14 | var _ fmt.Stringer = interceptor("") 15 | 16 | // interceptor need to intercept the output of logs from testify.assert. 17 | type interceptor string 18 | 19 | func (i *interceptor) Errorf(format string, args ...interface{}) { 20 | *i += interceptor(fmt.Sprintf(format, args...)) 21 | } 22 | 23 | func (i interceptor) String() string { 24 | return string(i) 25 | } 26 | -------------------------------------------------------------------------------- /target.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import "fmt" 8 | 9 | var _ fmt.Stringer = target(0) 10 | 11 | const ( 12 | // Golden file target. 13 | Golden target = iota 14 | // Input file target. 15 | Input 16 | // latest the maximum target used. Should not be used in your code. 17 | latest 18 | ) 19 | 20 | type target uint 21 | 22 | func (t target) String() string { 23 | switch t { 24 | case Golden: 25 | return "golden" 26 | case Input: 27 | return "input" 28 | default: 29 | panic(fmt.Sprintf("unsupported target: %d", t)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.go] 10 | indent_style = tab 11 | 12 | [{*.yaml,*.yml}] 13 | indent_size = 2 14 | ij_yaml_align_values_properties = do_not_align 15 | ij_yaml_autoinsert_sequence_marker = true 16 | ij_yaml_block_mapping_on_new_line = false 17 | ij_yaml_indent_sequence_value = true 18 | ij_yaml_keep_indents_on_empty_lines = false 19 | ij_yaml_keep_line_breaks = true 20 | ij_yaml_sequence_on_new_line = false 21 | ij_yaml_space_before_colon = false 22 | ij_yaml_spaces_within_braces = false 23 | ij_yaml_spaces_within_brackets = false 24 | 25 | [Makefile] 26 | indent_style = tab 27 | -------------------------------------------------------------------------------- /testdata/TestTool_Equal/failure_[]-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Not equal: 5 | expected: "" 6 | actual : "[]byte(nil)" 7 | 8 | Diff: 9 | --- Expected 10 | +++ Actual 11 | @@ -1 +1 @@ 12 | - 13 | +[]byte(nil) 14 | 15 | golden_test: method called *golden.bufferTB.Fail() 16 | 17 | Error Trace: 18 | Error: Not equal: 19 | expected: "" 20 | actual : "[]byte(nil)" 21 | 22 | Diff: 23 | --- Expected 24 | +++ Actual 25 | @@ -1 +1 @@ 26 | - 27 | +[]byte(nil) 28 | 29 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Equal/failure_golden-Z29sZGVu.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Not equal: 5 | expected: "golden" 6 | actual : "Z29sZGVu" 7 | 8 | Diff: 9 | --- Expected 10 | +++ Actual 11 | @@ -1 +1 @@ 12 | -golden 13 | +Z29sZGVu 14 | 15 | golden_test: method called *golden.bufferTB.Fail() 16 | 17 | Error Trace: 18 | Error: Not equal: 19 | expected: "golden" 20 | actual : "Z29sZGVu" 21 | 22 | Diff: 23 | --- Expected 24 | +++ Actual 25 | @@ -1 +1 @@ 26 | -golden 27 | +Z29sZGVu 28 | 29 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Equal/failure_golden-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Not equal: 5 | expected: "golden" 6 | actual : "[]byte(nil)" 7 | 8 | Diff: 9 | --- Expected 10 | +++ Actual 11 | @@ -1 +1 @@ 12 | -golden 13 | +[]byte(nil) 14 | 15 | golden_test: method called *golden.bufferTB.Fail() 16 | 17 | Error Trace: 18 | Error: Not equal: 19 | expected: "golden" 20 | actual : "[]byte(nil)" 21 | 22 | Diff: 23 | --- Expected 24 | +++ Actual 25 | @@ -1 +1 @@ 26 | -golden 27 | +[]byte(nil) 28 | 29 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestEqual/failure_[]-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "" 7 | actual : "[]byte(nil)" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | - 14 | +[]byte(nil) 15 | 16 | golden_test: method called *golden.bufferTB.Fail() 17 | 18 | Error Trace: 19 | Error: Not equal: 20 | expected: "" 21 | actual : "[]byte(nil)" 22 | 23 | Diff: 24 | --- Expected 25 | +++ Actual 26 | @@ -1 +1 @@ 27 | - 28 | +[]byte(nil) 29 | 30 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Go 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | workflow_dispatch: 12 | jobs: 13 | ci: 14 | name: Static analysis and testing 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v6 21 | with: 22 | go-version: "1.21" 23 | cache: false 24 | 25 | - name: Check that all packages are compiling 26 | run: make build 27 | 28 | - name: Run all test 29 | run: make test 30 | 31 | - name: Installing tools 32 | run: make tools 33 | 34 | - name: Run all checks 35 | run: make check 36 | 37 | - name: Upload coverage reports to Codecov 38 | uses: codecov/codecov-action@v5 39 | -------------------------------------------------------------------------------- /testdata/TestEqual/failure_golden-Z29sZGVu.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "golden" 7 | actual : "Z29sZGVu" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | -golden 14 | +Z29sZGVu 15 | 16 | golden_test: method called *golden.bufferTB.Fail() 17 | 18 | Error Trace: 19 | Error: Not equal: 20 | expected: "golden" 21 | actual : "Z29sZGVu" 22 | 23 | Diff: 24 | --- Expected 25 | +++ Actual 26 | @@ -1 +1 @@ 27 | -golden 28 | +Z29sZGVu 29 | 30 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestEqual/failure_golden-nil.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "golden" 7 | actual : "[]byte(nil)" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | -golden 14 | +[]byte(nil) 15 | 16 | golden_test: method called *golden.bufferTB.Fail() 17 | 18 | Error Trace: 19 | Error: Not equal: 20 | expected: "golden" 21 | actual : "[]byte(nil)" 22 | 23 | Diff: 24 | --- Expected 25 | +++ Actual 26 | @@ -1 +1 @@ 27 | -golden 28 | +[]byte(nil) 29 | 30 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Equal/failure_nil-[].golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden: read the value of nil since it is not found file: testdata/TestTool_Equal/failure_nil-[].golden 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "[]byte(nil)" 7 | actual : "" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | -[]byte(nil) 14 | + 15 | 16 | golden_test: method called *golden.bufferTB.Fail() 17 | 18 | Error Trace: 19 | Error: Not equal: 20 | expected: "[]byte(nil)" 21 | actual : "" 22 | 23 | Diff: 24 | --- Expected 25 | +++ Actual 26 | @@ -1 +1 @@ 27 | -[]byte(nil) 28 | + 29 | 30 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_Equal/failure_nil-golden.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden: read the value of nil since it is not found file: testdata/TestTool_Equal/failure_nil-golden.golden 3 | 4 | Error Trace: 5 | Error: Not equal: 6 | expected: "[]byte(nil)" 7 | actual : "golden" 8 | 9 | Diff: 10 | --- Expected 11 | +++ Actual 12 | @@ -1 +1 @@ 13 | -[]byte(nil) 14 | +golden 15 | 16 | golden_test: method called *golden.bufferTB.Fail() 17 | 18 | Error Trace: 19 | Error: Not equal: 20 | expected: "[]byte(nil)" 21 | actual : "golden" 22 | 23 | Diff: 24 | --- Expected 25 | +++ Actual 26 | @@ -1 +1 @@ 27 | -[]byte(nil) 28 | +golden 29 | 30 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestEqual/failure_nil-[].golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden: read the value of nil since it is not found file: testdata/TestEqual/failure_nil-[].golden 4 | 5 | Error Trace: 6 | Error: Not equal: 7 | expected: "[]byte(nil)" 8 | actual : "" 9 | 10 | Diff: 11 | --- Expected 12 | +++ Actual 13 | @@ -1 +1 @@ 14 | -[]byte(nil) 15 | + 16 | 17 | golden_test: method called *golden.bufferTB.Fail() 18 | 19 | Error Trace: 20 | Error: Not equal: 21 | expected: "[]byte(nil)" 22 | actual : "" 23 | 24 | Diff: 25 | --- Expected 26 | +++ Actual 27 | @@ -1 +1 @@ 28 | -[]byte(nil) 29 | + 30 | 31 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /target_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func Test_target_String(t *testing.T) { 14 | tests := []struct { 15 | target target 16 | want string 17 | runner func(assert.TestingT, assert.PanicTestFunc, ...interface{}) bool 18 | }{ 19 | { 20 | target: Golden, 21 | want: "golden", 22 | runner: assert.NotPanics, 23 | }, 24 | { 25 | target: Input, 26 | want: "input", 27 | runner: assert.NotPanics, 28 | }, 29 | { 30 | target: latest, 31 | want: "unsupported target: 2", 32 | runner: assert.Panics, 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.want, func(t *testing.T) { 37 | tt.runner(t, func() { 38 | assert.Equal(t, tt.want, tt.target.String()) 39 | }) 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /testdata/TestEqual/failure_nil-golden.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | golden_test: method called *golden.bufferTB.Helper() 3 | golden: read the value of nil since it is not found file: testdata/TestEqual/failure_nil-golden.golden 4 | 5 | Error Trace: 6 | Error: Not equal: 7 | expected: "[]byte(nil)" 8 | actual : "golden" 9 | 10 | Diff: 11 | --- Expected 12 | +++ Actual 13 | @@ -1 +1 @@ 14 | -[]byte(nil) 15 | +golden 16 | 17 | golden_test: method called *golden.bufferTB.Fail() 18 | 19 | Error Trace: 20 | Error: Not equal: 21 | expected: "[]byte(nil)" 22 | actual : "golden" 23 | 24 | Diff: 25 | --- Expected 26 | +++ Actual 27 | @@ -1 +1 @@ 28 | -[]byte(nil) 29 | +golden 30 | 31 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestJSONEq/Failed.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Not equal: 5 | expected: map[string]interface {}{"data":interface {}(nil)} 6 | actual : map[string]interface {}{} 7 | 8 | Diff: 9 | --- Expected 10 | +++ Actual 11 | @@ -1,3 +1,2 @@ 12 | -(map[string]interface {}) (len=1) { 13 | - (string) (len=4) "data": (interface {}) 14 | +(map[string]interface {}) { 15 | } 16 | 17 | golden_test: method called *golden.bufferTB.Fail() 18 | 19 | Error Trace: 20 | Error: Not equal: 21 | expected: map[string]interface {}{"data":interface {}(nil)} 22 | actual : map[string]interface {}{} 23 | 24 | Diff: 25 | --- Expected 26 | +++ Actual 27 | @@ -1,3 +1,2 @@ 28 | -(map[string]interface {}) (len=1) { 29 | - (string) (len=4) "data": (interface {}) 30 | +(map[string]interface {}) { 31 | } 32 | 33 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /testdata/TestTool_JSONEq/Failed.golden: -------------------------------------------------------------------------------- 1 | golden_test: method called *golden.bufferTB.Helper() 2 | 3 | Error Trace: 4 | Error: Not equal: 5 | expected: map[string]interface {}{"data":interface {}(nil)} 6 | actual : map[string]interface {}{} 7 | 8 | Diff: 9 | --- Expected 10 | +++ Actual 11 | @@ -1,3 +1,2 @@ 12 | -(map[string]interface {}) (len=1) { 13 | - (string) (len=4) "data": (interface {}) 14 | +(map[string]interface {}) { 15 | } 16 | 17 | golden_test: method called *golden.bufferTB.Fail() 18 | 19 | Error Trace: 20 | Error: Not equal: 21 | expected: map[string]interface {}{"data":interface {}(nil)} 22 | actual : map[string]interface {}{} 23 | 24 | Diff: 25 | --- Expected 26 | +++ Actual 27 | @@ -1,3 +1,2 @@ 28 | -(map[string]interface {}) (len=1) { 29 | - (string) (len=4) "data": (interface {}) 30 | +(map[string]interface {}) { 31 | } 32 | 33 | golden_test: method called *golden.bufferTB.FailNow() -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq.input: -------------------------------------------------------------------------------- 1 | {"blocks":[{"hash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f","ver":1,"prev_block":"0000000000000000000000000000000000000000000000000000000000000000","next_block":["00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"],"mrkl_root":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","time":1231006505,"bits":486604799,"fee":0,"nonce":2083236893,"n_tx":1,"size":285,"block_index":14849,"main_chain":true,"height":0,"tx":[{"lock_time":0,"ver":1,"size":204,"inputs":[{"sequence":4294967295,"witness":"","script":"04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"}],"weight":816,"time":1231006505,"tx_index":14849,"vin_sz":1,"hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","vout_sz":1,"relayed_by":"0.0.0.0","out":[{"addr_tag_link":"https:\/\/en.bitcoin.it\/wiki\/Genesis_block","addr_tag":"Genesis of Bitcoin","spent":false,"tx_index":14849,"type":0,"addr":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","value":5000000000,"n":0,"script":"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"}]}]}]} -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq/sublevel-one.input: -------------------------------------------------------------------------------- 1 | {"blocks":[{"hash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f","ver":1,"prev_block":"0000000000000000000000000000000000000000000000000000000000000000","next_block":["00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"],"mrkl_root":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","time":1231006505,"bits":486604799,"fee":0,"nonce":2083236893,"n_tx":1,"size":285,"block_index":14849,"main_chain":true,"height":0,"tx":[{"lock_time":0,"ver":1,"size":204,"inputs":[{"sequence":4294967295,"witness":"","script":"04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"}],"weight":816,"time":1231006505,"tx_index":14849,"vin_sz":1,"hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","vout_sz":1,"relayed_by":"0.0.0.0","out":[{"addr_tag_link":"https:\/\/en.bitcoin.it\/wiki\/Genesis_block","addr_tag":"Genesis of Bitcoin","spent":false,"tx_index":14849,"type":0,"addr":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","value":5000000000,"n":0,"script":"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"}]}]}]} -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq/sublevel-one/sublevel-two.input: -------------------------------------------------------------------------------- 1 | {"blocks":[{"hash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f","ver":1,"prev_block":"0000000000000000000000000000000000000000000000000000000000000000","next_block":["00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"],"mrkl_root":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","time":1231006505,"bits":486604799,"fee":0,"nonce":2083236893,"n_tx":1,"size":285,"block_index":14849,"main_chain":true,"height":0,"tx":[{"lock_time":0,"ver":1,"size":204,"inputs":[{"sequence":4294967295,"witness":"","script":"04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"}],"weight":816,"time":1231006505,"tx_index":14849,"vin_sz":1,"hash":"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b","vout_sz":1,"relayed_by":"0.0.0.0","out":[{"addr_tag_link":"https:\/\/en.bitcoin.it\/wiki\/Genesis_block","addr_tag":"Genesis of Bitcoin","spent":false,"tx_index":14849,"type":0,"addr":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","value":5000000000,"n":0,"script":"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"}]}]}]} 2 | -------------------------------------------------------------------------------- /interceptor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func Test_interceptor_Errorf(t *testing.T) { 13 | t.Run("by-pointer", func(t *testing.T) { 14 | i := new(interceptor) 15 | i.Errorf("%s", t.Name()) 16 | if t.Name() != string(*i) { 17 | t.Fatalf("%T.Errorf() error got = %q, want %q", *i, t.Name(), string(*i)) 18 | } 19 | }) 20 | t.Run("by-value", func(t *testing.T) { 21 | i := interceptor("") 22 | i.Errorf("%s", t.Name()) 23 | if t.Name() != string(i) { 24 | t.Fatalf("%T.Errorf() error got = %q, want %q", i, t.Name(), string(i)) 25 | } 26 | }) 27 | } 28 | 29 | func Test_interceptor_String(t *testing.T) { 30 | tests := []struct { 31 | stringer fmt.Stringer 32 | want string 33 | }{ 34 | {stringer: interceptor(""), want: ""}, 35 | {stringer: new(interceptor), want: ""}, 36 | {stringer: interceptor("golden"), want: "golden"}, 37 | } 38 | for _, tt := range tests { 39 | t.Run("", func(t *testing.T) { 40 | if got := tt.stringer.String(); got != tt.want { 41 | t.Errorf("interceptor.String() = %v, want %v", got, tt.want) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /testing_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "fmt" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | var _ TestingTB = new(bufferTB) 14 | 15 | type bufferTB struct { 16 | logs []string 17 | name string 18 | } 19 | 20 | func (m bufferTB) String() string { 21 | return strings.Join(m.logs, "\n") 22 | } 23 | 24 | func (m bufferTB) Bytes() []byte { 25 | return []byte(m.String()) 26 | } 27 | 28 | func (m *bufferTB) Errorf(format string, args ...interface{}) { 29 | m.Logf(format, args...) 30 | m.Fail() 31 | } 32 | 33 | func (m *bufferTB) Fail() { 34 | m.Logf("golden_test: method called %T.Fail()", m) 35 | } 36 | 37 | func (m *bufferTB) FailNow() { 38 | msg := fmt.Sprintf("golden_test: method called %T.FailNow()", m) 39 | m.Logf(msg) 40 | panic(msg) 41 | } 42 | 43 | func (m *bufferTB) Fatalf(format string, args ...interface{}) { 44 | m.Logf(format, args...) 45 | m.FailNow() 46 | } 47 | 48 | func (m *bufferTB) Helper() { 49 | m.Logf("golden_test: method called %T.Helper()", m) 50 | } 51 | 52 | func (m *bufferTB) Logf(format string, args ...interface{}) { 53 | msg := fmt.Sprintf(format, args...) 54 | re := regexp.MustCompile(`(?im)^\t?Error\ Trace\:([\S\s\n]+)^\t?Error\:`) 55 | msg = re.ReplaceAllString(msg, "\tError Trace:\n\tError:") 56 | m.logs = append(m.logs, msg) 57 | } 58 | 59 | func (m bufferTB) Name() string { 60 | return m.name 61 | } 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019-2024 Vasiliy Vasilyuk All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package golden testing with golden files in Go. A golden file is the expected 7 | output of test, stored as a separate file rather than as a string literal inside 8 | the test code. So when the test is executed, it will read data from the file and 9 | compare it to the output produced by the functionality under test. 10 | 11 | When writing unit tests, there comes a point when you need to check that the 12 | complex output of a function matches your expectations. This could be binary 13 | data (like an Image, JSON, HTML etc). Golden files are a way of ensuring your 14 | function output matches its .golden file. It’s a pattern used in the Go standard 15 | library. 16 | 17 | One of the advantages of the gold files approach is that you can easily update 18 | the test data using the command line flag without copying the data into the text 19 | variables of go this is very convenient in case of significant changes in the 20 | behavior of the system but also requires attention to the changed test data and 21 | checking the correctness of the new golden results. 22 | 23 | A special cli is provided in the package. The special flag `-update` is 24 | provided in the package for conveniently updating ethos files, for example, 25 | using the following command: 26 | 27 | go test ./... -update 28 | 29 | Golden files are placed in directory `testdata` this directory is ignored by 30 | the standard tools go, and it can accommodate a variety of data used in test or 31 | samples. 32 | */ 33 | package golden 34 | -------------------------------------------------------------------------------- /internal/integration/golden_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package integration 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/xorcare/golden" 13 | ) 14 | 15 | func TestEqual(t *testing.T) { 16 | testTree(t, func(t *testing.T) { 17 | golden.Equal(t, golden.Read(t)).FailNow() 18 | assert.NotNil(t, golden.Read(t), "input data cannot be empty") 19 | assert.NotNil(t, golden.SetTest(t).Read(), "golden data cannot be empty") 20 | }) 21 | } 22 | 23 | func TestJSONEq(t *testing.T) { 24 | testTree(t, func(t *testing.T) { 25 | golden.JSONEq(t, string(golden.Read(t))).FailNow() 26 | assert.NotNil(t, golden.Read(t), "input data cannot be empty") 27 | assert.NotNil(t, golden.SetTest(t).SetPrefix("json").Read(), "golden data cannot be empty") 28 | }) 29 | } 30 | 31 | // testTree needed to run a test function in tests with three levels of nesting. 32 | func testTree(t *testing.T, f func(t *testing.T)) { 33 | // Simple test data without structure nesting. 34 | // testdata/TestExample.input 35 | // testdata/TestExample.golden 36 | f(t) 37 | t.Run("sublevel-one", func(t *testing.T) { 38 | // Tests with one level of nesting. 39 | // testdata/TestExample/sublevel-one.input 40 | // testdata/TestExample/sublevel-one.golden 41 | f(t) 42 | t.Run("sublevel-two", func(t *testing.T) { 43 | // Test with the second level of nesting. 44 | // testdata/TestExample/sublevel-one/sublevel-two.golden 45 | // testdata/TestExample/sublevel-one/sublevel-two.input 46 | f(t) 47 | }) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 10 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 11 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 12 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 13 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 14 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq.json.golden: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": [ 3 | { 4 | "bits": 486604799, 5 | "block_index": 14849, 6 | "fee": 0, 7 | "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 8 | "height": 0, 9 | "main_chain": true, 10 | "mrkl_root": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 11 | "n_tx": 1, 12 | "next_block": [ 13 | "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" 14 | ], 15 | "nonce": 2083236893, 16 | "prev_block": "0000000000000000000000000000000000000000000000000000000000000000", 17 | "size": 285, 18 | "time": 1231006505, 19 | "tx": [ 20 | { 21 | "hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 22 | "inputs": [ 23 | { 24 | "script": "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", 25 | "sequence": 4294967295, 26 | "witness": "" 27 | } 28 | ], 29 | "lock_time": 0, 30 | "out": [ 31 | { 32 | "addr": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 33 | "addr_tag": "Genesis of Bitcoin", 34 | "addr_tag_link": "https://en.bitcoin.it/wiki/Genesis_block", 35 | "n": 0, 36 | "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", 37 | "spent": false, 38 | "tx_index": 14849, 39 | "type": 0, 40 | "value": 5000000000 41 | } 42 | ], 43 | "relayed_by": "0.0.0.0", 44 | "size": 204, 45 | "time": 1231006505, 46 | "tx_index": 14849, 47 | "ver": 1, 48 | "vin_sz": 1, 49 | "vout_sz": 1, 50 | "weight": 816 51 | } 52 | ], 53 | "ver": 1 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq/sublevel-one.json.golden: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": [ 3 | { 4 | "bits": 486604799, 5 | "block_index": 14849, 6 | "fee": 0, 7 | "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 8 | "height": 0, 9 | "main_chain": true, 10 | "mrkl_root": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 11 | "n_tx": 1, 12 | "next_block": [ 13 | "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" 14 | ], 15 | "nonce": 2083236893, 16 | "prev_block": "0000000000000000000000000000000000000000000000000000000000000000", 17 | "size": 285, 18 | "time": 1231006505, 19 | "tx": [ 20 | { 21 | "hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 22 | "inputs": [ 23 | { 24 | "script": "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", 25 | "sequence": 4294967295, 26 | "witness": "" 27 | } 28 | ], 29 | "lock_time": 0, 30 | "out": [ 31 | { 32 | "addr": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 33 | "addr_tag": "Genesis of Bitcoin", 34 | "addr_tag_link": "https://en.bitcoin.it/wiki/Genesis_block", 35 | "n": 0, 36 | "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", 37 | "spent": false, 38 | "tx_index": 14849, 39 | "type": 0, 40 | "value": 5000000000 41 | } 42 | ], 43 | "relayed_by": "0.0.0.0", 44 | "size": 204, 45 | "time": 1231006505, 46 | "tx_index": 14849, 47 | "ver": 1, 48 | "vin_sz": 1, 49 | "vout_sz": 1, 50 | "weight": 816 51 | } 52 | ], 53 | "ver": 1 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /internal/integration/testdata/TestJSONEq/sublevel-one/sublevel-two.json.golden: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": [ 3 | { 4 | "bits": 486604799, 5 | "block_index": 14849, 6 | "fee": 0, 7 | "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 8 | "height": 0, 9 | "main_chain": true, 10 | "mrkl_root": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 11 | "n_tx": 1, 12 | "next_block": [ 13 | "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" 14 | ], 15 | "nonce": 2083236893, 16 | "prev_block": "0000000000000000000000000000000000000000000000000000000000000000", 17 | "size": 285, 18 | "time": 1231006505, 19 | "tx": [ 20 | { 21 | "hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 22 | "inputs": [ 23 | { 24 | "script": "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", 25 | "sequence": 4294967295, 26 | "witness": "" 27 | } 28 | ], 29 | "lock_time": 0, 30 | "out": [ 31 | { 32 | "addr": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 33 | "addr_tag": "Genesis of Bitcoin", 34 | "addr_tag_link": "https://en.bitcoin.it/wiki/Genesis_block", 35 | "n": 0, 36 | "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", 37 | "spent": false, 38 | "tx_index": 14849, 39 | "type": 0, 40 | "value": 5000000000 41 | } 42 | ], 43 | "relayed_by": "0.0.0.0", 44 | "size": 204, 45 | "time": 1231006505, 46 | "tx_index": 14849, 47 | "ver": 1, 48 | "vin_sz": 1, 49 | "vout_sz": 1, 50 | "weight": 816 51 | } 52 | ], 53 | "ver": 1 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /conclusion.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import "fmt" 8 | 9 | // Conclusion interface wrapping conclusion. 10 | type Conclusion interface { 11 | // Failed reports whether the function has failed. 12 | Failed() bool 13 | // Fail marks the function as having failed but continues execution. 14 | // Also accompanying messages will be printed in the output of the test. 15 | // ATTENTION! executed only if expression is false `Failed() == true`. 16 | Fail() 17 | // FailNow marks the function as having failed and stops its execution 18 | // by calling runtime.Goexit (which then runs all deferred calls in the 19 | // current goroutine). 20 | // ATTENTION! executed only if expression is false `Failed() == true`. 21 | FailNow() 22 | } 23 | 24 | type conclusion struct { 25 | successful bool 26 | t TestingTB 27 | diff fmt.Stringer 28 | } 29 | 30 | func newConclusion(test TestingTB) conclusion { 31 | return conclusion{t: test} 32 | } 33 | 34 | // Failed reports whether the function has failed. 35 | func (c conclusion) Failed() bool { 36 | return !c.successful 37 | } 38 | 39 | // Fail marks the function as having failed but continues execution. 40 | // Also accompanying messages will be printed in the output of the test. 41 | // ATTENTION! executed only if expression is false `Failed() == true`. 42 | func (c conclusion) Fail() { 43 | if c.Failed() { 44 | c.t.Logf("%s", c.diff) 45 | c.t.Fail() 46 | } 47 | } 48 | 49 | // FailNow marks the function as having failed and stops its execution 50 | // by calling runtime.Goexit (which then runs all deferred calls in the 51 | // current goroutine). 52 | // ATTENTION! executed only if expression is false `Failed() == true`. 53 | func (c conclusion) FailNow() { 54 | if c.Failed() { 55 | c.t.Logf("%s", c.diff) 56 | c.t.FailNow() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golden 2 | 3 | [![codecov](https://codecov.io/gh/xorcare/golden/badge.svg)](https://codecov.io/gh/xorcare/golden) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/xorcare/golden)](https://goreportcard.com/report/github.com/xorcare/golden) 5 | [![GoDoc](https://godoc.org/github.com/xorcare/golden?status.svg)](https://godoc.org/github.com/xorcare/golden) 6 | 7 | Package golden testing with golden files in Go. A golden file is the expected 8 | output of test, stored as a separate file rather than as a string literal inside 9 | the test code. So when the test is executed, it will read data from the file and 10 | compare it to the output produced by the functionality under test. 11 | 12 | When writing unit tests, there comes a point when you need to check that the 13 | complex output of a function matches your expectations. This could be binary 14 | data (like an Image, JSON, HTML etc). Golden files are a way of ensuring your 15 | function output matches its .golden file. It’s a pattern used in the Go standard 16 | library. 17 | 18 | One of the advantages of the gold files approach is that you can easily update 19 | the test data using the command line flag without copying the data into the text 20 | variables of go this is very convenient in case of significant changes in the 21 | behavior of the system but also requires attention to the changed test data and 22 | checking the correctness of the new golden results. 23 | 24 | A special cli is provided in the package. The special flag `-update` is 25 | provided in the package for conveniently updating ethos files, for example, 26 | using the following command: 27 | 28 | go test ./... -update 29 | 30 | Golden files are placed in directory `testdata` this directory is ignored by 31 | the standard tools go, and it can accommodate a variety of data used in test or 32 | samples. 33 | 34 | ## Installation 35 | 36 | ```bash 37 | go get github.com/xorcare/golden 38 | ``` 39 | 40 | ## Examples 41 | 42 | * [golden.Assert](https://godoc.org/github.com/xorcare/golden#example-Assert) 43 | * [golden.Read](https://godoc.org/github.com/xorcare/golden#example-Read) 44 | * [golden.Run](https://godoc.org/github.com/xorcare/golden#example-Run) 45 | 46 | ## Inspiration 47 | 48 | * [Golden Files—Why you should use them](https://medium.com/@jarifibrahim/golden-files-why-you-should-use-them-47087ec994bf) 49 | * [Testing with golden files in Go](https://medium.com/soon-london/testing-with-golden-files-in-go-7fccc71c43d3) 50 | * [Go advanced testing tips & tricks](https://medium.com/@povilasve/go-advanced-tips-tricks-a872503ac859) 51 | -------------------------------------------------------------------------------- /internal/tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 2 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 3 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 4 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 5 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 6 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 7 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 8 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 9 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 10 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 11 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 12 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 13 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 14 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 15 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 16 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 18 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 19 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 20 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 21 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 22 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 23 | golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb h1:z5+u0pkAUPUWd3taoTialQ2JAMo4Wo1Z3L25U4ZV9r0= 24 | golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 25 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 27 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 28 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Based on https://git.io/fjkGc 2 | 3 | # The name of the file recommended in the standard 4 | # documentation go test -cover and used codecov.io 5 | # to check code coverage. 6 | COVER_FILE ?= coverage.out 7 | 8 | # Main targets. 9 | .DEFAULT_GOAL := help 10 | 11 | .PHONY: bench 12 | bench: ## Run benchmarks 13 | @go test ./... -bench=. -run="Benchmark*" 14 | 15 | .PHONY: build 16 | build: ## Build the project binary 17 | @go build ./... 18 | 19 | .PHONY: ci 20 | ci: check checkstate ## Target for integration with ci pipeline 21 | 22 | .PHONY: check 23 | check: static test build ## Check project with static checks and unit tests 24 | 25 | $(COVER_FILE): 26 | @$(MAKE) test 27 | 28 | .PHONY: cover 29 | cover: $(COVER_FILE) ## Output coverage in human readable an HTML 30 | @go tool cover -html=$(COVER_FILE) 31 | @rm -f $(COVER_FILE) 32 | 33 | .PHONY: dep 34 | dep: ## Install and sync go modules dependencies, beautify go.mod and go.sum files 35 | @go mod download 36 | @go mod tidy 37 | @go mod download 38 | @go mod verify 39 | 40 | .PHONY: help 41 | help: ## Print this help 42 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ 43 | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 44 | 45 | .PHONY: imports 46 | imports: tools ## Check and fix import section by import rules 47 | @test -z $$(for d in $$(go list -f {{.Dir}} ./...); do goimports -e -l -local $$(go list) -w $$d/*.go; done) 48 | 49 | .PHONY: lint 50 | lint: tools ## Check the project with lint 51 | @go list ./... | grep -v /vendor/ | xargs -L1 golint -set_exit_status 52 | 53 | .PHONY: static 54 | static: imports vet lint ## Run static checks (lint, imports, vet, ...) all over the project 55 | 56 | .PHONY: test 57 | test: ## Run unit tests 58 | @go test ./... -count=1 -race 59 | @go test ./... -count=1 -coverprofile=$(COVER_FILE) -covermode=atomic $d 60 | @go tool cover -func=$(COVER_FILE) | grep ^total 61 | 62 | .PHONY: testin 63 | testin: ## Run integration tests 64 | @go test `go list ./... | grep -v /vendor/` -tags=integration -coverprofile=$(COVER_FILE) -covermode=atomic $d 65 | @go tool cover -func=$(COVER_FILE) | grep ^total 66 | 67 | .PHONY: testup 68 | testup: ## Run unit tests with golden files update 69 | @find . -type f -name '*.golden' -exec rm -f {} \; 70 | @go test `go list ./... | grep -v /vendor/` -update 71 | 72 | CDTOOLS ?= cd internal/tools && 73 | .PHONY: tools 74 | tools: ## Install all needed tools, e.g. for static checks 75 | @$(CDTOOLS) go install golang.org/x/lint/golint 76 | @$(CDTOOLS) go install golang.org/x/tools/cmd/goimports 77 | 78 | .PHONY: toolsup 79 | toolsup: ## Update all needed tools, e.g. for static checks 80 | @$(CDTOOLS) go mod tidy 81 | @$(CDTOOLS) go get golang.org/x/lint/golint@latest 82 | @$(CDTOOLS) go get golang.org/x/tools/cmd/goimports@latest 83 | @$(CDTOOLS) go mod download 84 | @$(CDTOOLS) go mod verify 85 | @$(MAKE) tools 86 | 87 | .PHONY: vet 88 | vet: ## Check the project with vet 89 | @go vet ./... 90 | 91 | .PHONY: checkstate 92 | checkstate: tools ## Checking the relevance of dependencies, and tools. Also, the absence of arbitrary changes when performing checks. 93 | @echo 'checking the relevance of the dependency list' 94 | @go mod tidy 95 | @git diff --exit-code go.mod go.sum 96 | @echo 'checking the relevance of the committed dependencies' 97 | @go mod vendor 98 | @git diff --exit-code vendor 99 | @echo 'checking the relevance of the committed generated files' 100 | @go generate 101 | @exit $$(git status -s | wc -l) 102 | @echo 'checking the relevance of the committed golden files' 103 | @make testup 104 | @exit $$(git status -s | wc -l) 105 | @go mod verify 106 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden_test 6 | 7 | import ( 8 | "encoding/base64" 9 | "fmt" 10 | "path" 11 | "regexp" 12 | "runtime" 13 | "strings" 14 | "unicode" 15 | 16 | "github.com/stretchr/testify/assert" 17 | 18 | "github.com/xorcare/golden" 19 | ) 20 | 21 | // In the current example, the value of `got` will be compared with the value 22 | // of `want` that we get from the file `testdata/ExampleAssert.golden` and 23 | // after comparing the data if there is a difference, the test will be aborted 24 | // with an error. If you run the test flag is used with the `-update` data from 25 | // the variable `got` is written in the golden file. 26 | // 27 | // The test name is assumed to be equal to ExampleAssert. 28 | func ExampleAssert() { 29 | t := newTestingT() 30 | 31 | got, err := base64.RawURLEncoding.DecodeString("Z29sZGVu") 32 | assert.NoError(t, err) 33 | 34 | golden.Assert(t, got) 35 | 36 | // Output: 37 | // golden: read the value of nil since it is not found file: testdata/TestExamples/ExampleAssert.golden 38 | // 39 | // Error Trace: 40 | // Error: Not equal: 41 | // expected: "[]byte(nil)" 42 | // actual : "golden" 43 | // 44 | // Diff: 45 | // --- Expected 46 | // +++ Actual 47 | // @@ -1 +1 @@ 48 | // -[]byte(nil) 49 | // +golden 50 | 51 | } 52 | 53 | // In the current example, when you run the Run function, the data from the 54 | // `testdata/ExampleRun.input` file will be read and with this data will be 55 | // called the function, which is passed in, as a result of the function 56 | // execution we will get the `got` data and possibly an error, which will 57 | // be processed by the internal method implementation. The `got` will be 58 | // compared with the meaning `want` which we get from the file 59 | // `test data/ExampleRun.golden` and after data comparison in case of 60 | // differences, the test will be fail. If you run the test flag is used with 61 | // the `-update` data from the variable `got` is written in the golden file. 62 | // 63 | // The test name is assumed to be equal to ExampleRun. 64 | func ExampleRun() { 65 | t := newTestingT() 66 | 67 | golden.Run(t, func(input []byte) (got []byte, err error) { 68 | return base64.RawURLEncoding.DecodeString(string(input)) 69 | }) 70 | // Output: 71 | // golden: read the value of nil since it is not found file: testdata/TestExamples/ExampleRun.input 72 | // golden: read the value of nil since it is not found file: testdata/TestExamples/ExampleRun.golden 73 | // 74 | // Error Trace: 75 | // Error: Not equal: 76 | // expected: "[]byte(nil)" 77 | // actual : "" 78 | // 79 | // Diff: 80 | // --- Expected 81 | // +++ Actual 82 | // @@ -1 +1 @@ 83 | // -[]byte(nil) 84 | // + 85 | } 86 | 87 | // ExampleRead the example shows how you can use the global api to read files 88 | // together with the already considered golden.Assert. 89 | // 90 | // The test name is assumed to be equal to ExampleRead. 91 | func ExampleRead() { 92 | t := newTestingT() 93 | 94 | input := string(golden.Read(t)) 95 | got, err := base64.RawURLEncoding.DecodeString(input) 96 | assert.NoError(t, err) 97 | 98 | golden.Assert(t, got) 99 | 100 | // Output: 101 | // golden: read the value of nil since it is not found file: testdata/TestExamples/ExampleRead.input 102 | // golden: read the value of nil since it is not found file: testdata/TestExamples/ExampleRead.golden 103 | // 104 | // Error Trace: 105 | // Error: Not equal: 106 | // expected: "[]byte(nil)" 107 | // actual : "" 108 | // 109 | // Diff: 110 | // --- Expected 111 | // +++ Actual 112 | // @@ -1 +1 @@ 113 | // -[]byte(nil) 114 | // + 115 | } 116 | 117 | type T struct { 118 | name string 119 | } 120 | 121 | func (t *T) Fail() {} 122 | func (t *T) FailNow() {} 123 | func (t *T) Helper() {} 124 | 125 | func (t T) Name() string { return t.name } 126 | 127 | func (t *T) Errorf(format string, args ...interface{}) { t.Logf(format, args...) } 128 | func (t *T) Fatalf(format string, args ...interface{}) { t.Logf(format, args...) } 129 | 130 | func (t *T) Logf(format string, args ...interface{}) { 131 | msg := fmt.Sprintf(format, args...) 132 | // Removed the trace, it contains line numbers and changes dynamically, 133 | // it is not convenient to see in the examples. 134 | re := regexp.MustCompile(`(?im)^\t?Error\ Trace\:([\S\s\n]+)^\t?Error\:`) 135 | msg = re.ReplaceAllString(msg, "\tError Trace:\n\tError:") 136 | 137 | msg = strings.Replace(msg, "\t", "", -1) 138 | 139 | // Trimming lines consisting only of spaces or containing spaces to the right. 140 | re = regexp.MustCompile(`(?im)^(.*)$`) 141 | msg = re.ReplaceAllStringFunc(msg, func(s string) string { 142 | return strings.TrimRightFunc(s, unicode.IsSpace) 143 | }) 144 | 145 | fmt.Println(msg) 146 | } 147 | 148 | func newTestingT() *T { 149 | t := T{name: "TestExamples"} 150 | t.name = path.Join(t.name, caller(2)) 151 | return &t 152 | } 153 | 154 | func caller(skip int) string { 155 | pc, _, _, ok := runtime.Caller(skip) 156 | if !ok { 157 | panic(fmt.Sprintf("Couldn't get the caller info level: %d", skip)) 158 | } 159 | 160 | fp := runtime.FuncForPC(pc).Name() 161 | parts := strings.Split(fp, ".") 162 | fn := parts[len(parts)-1] 163 | 164 | return fn 165 | } 166 | -------------------------------------------------------------------------------- /golden.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "encoding/json" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "unicode" 17 | 18 | "github.com/stretchr/testify/assert" 19 | ) 20 | 21 | // TestingTB is the interface common to T and B. 22 | type TestingTB interface { 23 | Name() string 24 | Logf(format string, args ...interface{}) 25 | Errorf(format string, args ...interface{}) 26 | Fatalf(format string, args ...interface{}) 27 | FailNow() 28 | Fail() 29 | } 30 | 31 | type testingHelper interface { 32 | Helper() 33 | } 34 | 35 | // Tool implements the basic logic of working with golden files. 36 | // All functionality is implemented through a non-mutating state 37 | // machine, which at a certain point in time can perform an action 38 | // on the basis of the state or change any parameter by creating 39 | // a new copy of the state. 40 | type Tool struct { 41 | test TestingTB 42 | dir string 43 | fileMode os.FileMode 44 | modeDir os.FileMode 45 | target target 46 | flag *bool 47 | prefix string 48 | extension string 49 | // want it stores manually set expected data, if it is nil, then the 50 | // data will be read from the files, otherwise the value from this 51 | // field will be taken. 52 | want []byte 53 | 54 | mkdirAll func(path string, perm os.FileMode) error 55 | readFile func(filename string) ([]byte, error) 56 | remove func(name string) error 57 | stat func(name string) (os.FileInfo, error) 58 | writeFile func(filename string, data []byte, perm os.FileMode) error 59 | } 60 | 61 | // tool object with predefined parameters intended for use in global 62 | // functions that provide a simplified api for convenient interaction 63 | // with the functionality of the package. 64 | var _golden = Tool{ 65 | // dir testdata is the directory for test data already accepted 66 | // in the standard library, which is also ignored by standard 67 | // go tools and should not change in your tests. 68 | dir: "testdata", 69 | fileMode: 0644, 70 | modeDir: 0755, 71 | target: Golden, 72 | 73 | mkdirAll: os.MkdirAll, 74 | readFile: ioutil.ReadFile, 75 | remove: os.Remove, 76 | stat: os.Stat, 77 | writeFile: ioutil.WriteFile, 78 | } 79 | 80 | const updateEnvName = "GOLDEN_UPDATE" 81 | 82 | func getUpdateEnv() bool { 83 | env := os.Getenv(updateEnvName) 84 | if env == "" { 85 | env = strconv.FormatBool(false) 86 | } 87 | 88 | is, err := strconv.ParseBool(env) 89 | if err != nil { 90 | const msg = "cannot parse flag %q, error: %v" 91 | panic(fmt.Sprintf(msg, updateEnvName, err)) 92 | } 93 | 94 | return is 95 | } 96 | 97 | func init() { 98 | _golden.flag = flag.Bool("getUpdateEnv", getUpdateEnv(), "update test golden files") 99 | } 100 | 101 | // Assert is a tool to compare the actual value obtained in the test and 102 | // the value from the golden file. Also, built-in functionality for 103 | // updating golden files using the command line flag. 104 | func Assert(t TestingTB, got []byte) { 105 | if h, ok := t.(testingHelper); ok { 106 | h.Helper() 107 | } 108 | SetTest(t).Assert(got) 109 | } 110 | 111 | // Equal is a tool to compare the actual value obtained in the test and 112 | // the value from the golden file. Also, built-in functionality for 113 | // updating golden files using the command line flag. 114 | func Equal(t TestingTB, got []byte) Conclusion { 115 | if h, ok := t.(testingHelper); ok { 116 | h.Helper() 117 | } 118 | return SetTest(t).Equal(got) 119 | } 120 | 121 | // Read is a functional for reading both input and golden files using 122 | // the appropriate target. 123 | func Read(t TestingTB) []byte { 124 | return SetTest(t).SetTarget(Input).Read() 125 | } 126 | 127 | // Run is a functional that automates the process of reading the input file 128 | // of the test bytes and the execution of the input function of testing and 129 | // checking the results. 130 | func Run(t TestingTB, do func(input []byte) (outcome []byte, err error)) { 131 | SetTest(t).Run(do) 132 | } 133 | 134 | // SetTest is a mechanism to create a new copy of the base Tool object for 135 | // advanced use. This method replaces the constructor for the Tool structure. 136 | func SetTest(t TestingTB) Tool { 137 | return _golden.SetTest(t) 138 | } 139 | 140 | // SetWant a place to set the expected want manually. 141 | // want value it stores manually set expected data, if it is nil, 142 | // then the data will be read from the files, otherwise the value 143 | // from this field will be taken. 144 | func SetWant(t TestingTB, bs []byte) Tool { 145 | return _golden.SetTest(t).SetWant(bs) 146 | } 147 | 148 | // Assert is a tool to compare the actual value obtained in the test and 149 | // the value from the golden file. Also, built-in functionality for 150 | // updating golden files using the command line flag. 151 | func (t Tool) Assert(got []byte) { 152 | t.Update(got) 153 | if h, ok := t.test.(testingHelper); ok { 154 | h.Helper() 155 | } 156 | t.Equal(got).FailNow() 157 | } 158 | 159 | // Equal is a tool to compare the actual value obtained in the test and 160 | // the value from the golden file. Also, built-in functionality for 161 | // updating golden files using the command line flag. 162 | func (t Tool) Equal(got []byte) Conclusion { 163 | t.Update(got) 164 | if h, ok := t.test.(testingHelper); ok { 165 | h.Helper() 166 | } 167 | 168 | want := t.SetTarget(Golden).Read() 169 | 170 | if want == nil { 171 | want = []byte(fmt.Sprintf("%#v", want)) 172 | } 173 | if got == nil { 174 | got = []byte(fmt.Sprintf("%#v", got)) 175 | } 176 | 177 | i := new(interceptor) 178 | c := newConclusion(t.test) 179 | c.successful = assert.Equal(i, string(want), string(got)) 180 | c.diff = i 181 | 182 | return c 183 | } 184 | 185 | // JSONEq is a tool to compare the actual JSON value obtained in the test and 186 | // the value from the golden file. Also, built-in functionality for 187 | // updating golden files using the command line flag. 188 | func (t Tool) JSONEq(got string) Conclusion { 189 | if h, ok := t.test.(testingHelper); ok { 190 | h.Helper() 191 | } 192 | 193 | return t.jsonEqual(got) 194 | } 195 | 196 | func (t Tool) jsonEqual(got string) conclusion { 197 | t.setExtension("json").update(func() []byte { 198 | return []byte(jsonFormatter(t.test, got)) 199 | }) 200 | 201 | want := t.setExtension("json").SetTarget(Golden).Read() 202 | i := new(interceptor) 203 | c := newConclusion(t.test) 204 | c.successful = assert.JSONEq(i, string(want), string(got)) 205 | c.diff = i 206 | return c 207 | } 208 | 209 | // JSONEq is a tool to compare the actual JSON value obtained in the test and 210 | // the value from the golden file. Also, built-in functionality for 211 | // updating golden files using the command line flag. 212 | func JSONEq(tb TestingTB, got string) Conclusion { 213 | if h, ok := tb.(testingHelper); ok { 214 | h.Helper() 215 | } 216 | 217 | return _golden.SetTest(tb).jsonEqual(got) 218 | } 219 | 220 | // Read is a functional for reading both input and golden files using 221 | // the appropriate target. 222 | func (t Tool) Read() (bs []byte) { 223 | if t.want != nil { 224 | t.test.Logf("golden: read the value from the want field") 225 | bs = make([]byte, len(t.want)) 226 | copy(bs, t.want) 227 | 228 | return bs 229 | } 230 | 231 | bs, err := t.readFile(t.path()) 232 | if os.IsNotExist(err) { 233 | const f = "golden: read the value of nil since it is not found file: %s" 234 | t.test.Logf(f, t.path()) 235 | return nil 236 | } else if err != nil { 237 | t.test.Fatalf("golden: %s", err) 238 | } 239 | 240 | return bs 241 | } 242 | 243 | // Run is a functional that automates the process of reading the input file 244 | // of the test bytes and the execution of the input function of testing and 245 | // checking the results. 246 | func (t Tool) Run(do func(input []byte) (got []byte, err error)) { 247 | bs, err := do(t.SetTarget(Input).Read()) 248 | t.noError(err) 249 | t.Assert(bs) 250 | } 251 | 252 | // SetPrefix a prefix value setter. 253 | func (t Tool) SetPrefix(prefix string) Tool { 254 | t.prefix = rewrite(prefix) 255 | return t 256 | } 257 | 258 | // SetTarget a target value setter. 259 | func (t Tool) SetTarget(tar target) Tool { 260 | t.target = tar 261 | return t 262 | } 263 | 264 | // SetTest a test value setter in the call chain must be used first 265 | // to prevent abnormal situations when using other methods. 266 | func (t Tool) SetTest(tb TestingTB) Tool { 267 | t.test = tb 268 | return t 269 | } 270 | 271 | // SetWant a place to set the expected want manually. 272 | // want value it stores manually set expected data, if it is nil, 273 | // then the data will be read from the files, otherwise the value 274 | // from this field will be taken. 275 | func (t Tool) SetWant(bs []byte) Tool { 276 | t.want = nil 277 | if bs != nil { 278 | t.want = make([]byte, len(bs)) 279 | copy(t.want, bs) 280 | } 281 | 282 | return t 283 | } 284 | 285 | // Update functional reviewer is the need to update the golden files 286 | // and doing it. 287 | func (t Tool) Update(bs []byte) { 288 | t.update(func() []byte { return bs }) 289 | } 290 | 291 | // write is a functional for writing both input and golden files using 292 | // the appropriate target. 293 | func (t Tool) write(bs []byte) { 294 | path := t.path() 295 | t.mkdir(filepath.Dir(path)) 296 | t.test.Logf("golden: start write to file: %s", path) 297 | if bs == nil { 298 | t.test.Logf("golden: nil value will not be written") 299 | fileInfo, err := t.stat(path) 300 | if err == nil && !fileInfo.IsDir() { 301 | t.test.Logf("golden: current test bytes file will be deleted") 302 | t.noError(t.remove(path)) 303 | } 304 | if !os.IsNotExist(err) { 305 | t.noError(err) 306 | } 307 | } else { 308 | t.noError(t.writeFile(path, bs, t.fileMode)) 309 | } 310 | } 311 | 312 | // mkdir the mechanism to create the directory. 313 | func (t Tool) mkdir(loc string) { 314 | fileInfo, err := t.stat(loc) 315 | switch { 316 | case err != nil && os.IsNotExist(err): 317 | t.test.Logf("golden: trying to create a directory: %q", loc) 318 | err = t.mkdirAll(loc, t.modeDir) 319 | case err == nil && !fileInfo.IsDir(): 320 | t.test.Errorf("golden: test dir is a file: %s", loc) 321 | } 322 | 323 | t.noError(err) 324 | } 325 | 326 | // noError fails the test if an err is not nil. 327 | func (t Tool) noError(err error) { 328 | if err != nil { 329 | t.test.Fatalf("golden: %s", err) 330 | } 331 | } 332 | 333 | // path is getter to get the path to the file containing the test data. 334 | func (t Tool) path() (path string) { 335 | format := "%s" 336 | args := []interface{}{t.test.Name()} 337 | 338 | if t.prefix != "" { 339 | args = append(args, t.prefix) 340 | } 341 | if t.extension != "" { 342 | args = append(args, t.extension) 343 | } 344 | 345 | // Add a target expansion. Always added last. 346 | args = append(args, t.target.String()) 347 | // We add placeholders for the number of parameters excluding the name 348 | // of the test to print all the parameters. 349 | format += strings.Repeat(".%s", len(args)-1) 350 | return filepath.Join(t.dir, fmt.Sprintf(format, args...)) 351 | } 352 | 353 | func (t Tool) update(f func() []byte) { 354 | if t.flag != nil && *t.flag && t.want == nil { 355 | t.test.Logf("golden: updating file: %s", t.path()) 356 | t.write(f()) 357 | } 358 | } 359 | 360 | func (t Tool) setExtension(ext string) Tool { 361 | t.extension = ext 362 | return t 363 | } 364 | 365 | // rewrite rewrites a subname to having only printable characters and no white 366 | // space. 367 | func rewrite(str string) string { 368 | bs := make([]byte, 0, len(str)) 369 | for _, b := range str { 370 | switch { 371 | case unicode.IsSpace(b): 372 | bs = append(bs, '_') 373 | case !strconv.IsPrint(b): 374 | s := strconv.QuoteRune(b) 375 | bs = append(bs, s[1:len(s)-1]...) 376 | default: 377 | bs = append(bs, string(b)...) 378 | } 379 | } 380 | return string(bs) 381 | } 382 | 383 | func jsonFormatter(t TestingTB, str string) string { 384 | var value interface{} 385 | if err := json.Unmarshal([]byte(str), &value); err != nil { 386 | const format = "Data (%q) needs to be valid json.\nJSON parsing error: %q" 387 | assert.FailNow(t, fmt.Sprintf(format, str, err)) 388 | } 389 | 390 | bs, err := json.MarshalIndent(value, "", "\t") 391 | assert.NoError(t, err) 392 | 393 | return string(bs) 394 | } 395 | -------------------------------------------------------------------------------- /golden_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2024 Vasiliy Vasilyuk. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package golden 6 | 7 | import ( 8 | "bytes" 9 | "os" 10 | "path/filepath" 11 | "reflect" 12 | "testing" 13 | "time" 14 | 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | // _goldie is used for as a tool golden, but inside tests. 19 | var _goldie = _golden 20 | 21 | func TestMain(m *testing.M) { 22 | _goldie.flag = _golden.flag 23 | _golden.flag = nil 24 | os.Exit(m.Run()) 25 | } 26 | 27 | func TestAssert(t *testing.T) { 28 | type args struct { 29 | test *bufferTB 30 | got []byte 31 | } 32 | type readFile struct { 33 | error error 34 | bytes []byte 35 | } 36 | tests := []struct { 37 | name string 38 | args args 39 | readFile readFile 40 | recover bool 41 | }{ 42 | { 43 | name: "success-assert-nil-with-error-not-exist", 44 | args: args{ 45 | test: new(bufferTB), 46 | got: nil, 47 | }, 48 | readFile: readFile{ 49 | bytes: nil, 50 | error: os.ErrNotExist, 51 | }, 52 | recover: false, 53 | }, 54 | { 55 | name: "success-assert-data", 56 | args: args{ 57 | test: new(bufferTB), 58 | got: []byte("golden"), 59 | }, 60 | readFile: readFile{ 61 | bytes: []byte("golden"), 62 | error: nil, 63 | }, 64 | recover: false, 65 | }, 66 | { 67 | name: "error-reading-file-permission-denied", 68 | args: args{ 69 | test: new(bufferTB), 70 | got: nil, 71 | }, 72 | readFile: readFile{ 73 | bytes: nil, 74 | error: os.ErrPermission, 75 | }, 76 | recover: true, 77 | }, 78 | { 79 | name: "failure-assert-data", 80 | args: args{ 81 | test: new(bufferTB), 82 | got: []byte("golden"), 83 | }, 84 | readFile: readFile{ 85 | bytes: []byte("Z29sZGVu"), 86 | error: nil, 87 | }, 88 | recover: true, 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | origin := _golden 94 | defer func() { _golden = origin }() 95 | 96 | _golden.readFile = func(filename string) (bytes []byte, e error) { 97 | t.Logf(`os.ReadFile(%q) `, filename) 98 | return tt.readFile.bytes, tt.readFile.error 99 | } 100 | defer func() { 101 | if r := recover(); (r == nil) == tt.recover { 102 | t.Error(r) 103 | } 104 | _goldie.SetTest(t).Assert(tt.args.test.Bytes()) 105 | }() 106 | tt.args.test.name = t.Name() 107 | Assert(tt.args.test, tt.args.got) 108 | }) 109 | } 110 | } 111 | 112 | func TestRead(t *testing.T) { 113 | type args struct { 114 | test *bufferTB 115 | } 116 | type readFile struct { 117 | error error 118 | bytes []byte 119 | } 120 | tests := []struct { 121 | name string 122 | args args 123 | want []byte 124 | readFile readFile 125 | recover bool 126 | }{ 127 | { 128 | name: "success-read-data", 129 | want: []byte("golden"), 130 | args: args{ 131 | test: new(bufferTB), 132 | }, 133 | readFile: readFile{ 134 | bytes: []byte("golden"), 135 | error: nil, 136 | }, 137 | recover: false, 138 | }, 139 | { 140 | name: "success-read-nil", 141 | want: nil, 142 | args: args{ 143 | test: new(bufferTB), 144 | }, 145 | readFile: readFile{ 146 | bytes: nil, 147 | error: os.ErrNotExist, 148 | }, 149 | recover: false, 150 | }, 151 | { 152 | name: "error-reading-file-permission-denied", 153 | want: nil, 154 | args: args{ 155 | test: new(bufferTB), 156 | }, 157 | readFile: readFile{ 158 | bytes: nil, 159 | error: os.ErrPermission, 160 | }, 161 | recover: true, 162 | }, 163 | } 164 | for _, tt := range tests { 165 | t.Run(tt.name, func(t *testing.T) { 166 | origin := _golden 167 | defer func() { _golden = origin }() 168 | 169 | _golden.readFile = func(filename string) (bytes []byte, e error) { 170 | t.Logf(`os.ReadFile(%q) `, filename) 171 | _goldie.SetTest(t).SetPrefix("filename").Assert([]byte(filename)) 172 | return tt.readFile.bytes, tt.readFile.error 173 | } 174 | defer func() { 175 | if r := recover(); (r == nil) == tt.recover { 176 | t.Error(r) 177 | } 178 | _goldie.SetTest(t).Assert(tt.args.test.Bytes()) 179 | }() 180 | tt.args.test.name = t.Name() 181 | got := Read(tt.args.test) 182 | if !bytes.Equal(got, tt.want) { 183 | t.Errorf("Read() = %v, want %v", got, tt.want) 184 | } 185 | }) 186 | } 187 | } 188 | 189 | func TestRun(t *testing.T) { 190 | type args struct { 191 | test *bufferTB 192 | do func(input []byte) (outcome []byte, err error) 193 | } 194 | tests := []struct { 195 | name string 196 | args args 197 | recover bool 198 | }{ 199 | { 200 | name: "run-without-error", 201 | args: args{ 202 | test: new(bufferTB), 203 | do: func(input []byte) (outcome []byte, err error) { 204 | return nil, nil 205 | }, 206 | }, 207 | recover: false, 208 | }, 209 | { 210 | name: "run-with-error", 211 | args: args{ 212 | test: new(bufferTB), 213 | do: func(input []byte) (outcome []byte, err error) { 214 | return nil, os.ErrClosed 215 | }, 216 | }, 217 | recover: true, 218 | }, 219 | } 220 | for _, tt := range tests { 221 | t.Run(tt.name, func(t *testing.T) { 222 | origin := _golden 223 | defer func() { _golden = origin }() 224 | 225 | _golden.readFile = func(filename string) (bytes []byte, e error) { 226 | t.Logf(`os.ReadFile(%q)`, filename) 227 | return nil, nil 228 | } 229 | defer func() { 230 | if r := recover(); (r == nil) == tt.recover { 231 | t.Error(r) 232 | } 233 | _goldie.SetTest(t).Assert(tt.args.test.Bytes()) 234 | }() 235 | tt.args.test.name = t.Name() 236 | Run(tt.args.test, tt.args.do) 237 | }) 238 | } 239 | } 240 | 241 | func TestSetTest(t *testing.T) { 242 | type args struct { 243 | test TestingTB 244 | } 245 | m := new(bufferTB) 246 | tests := []struct { 247 | name string 248 | args args 249 | want Tool 250 | }{ 251 | { 252 | name: "set-test-nil", 253 | args: args{}, 254 | want: Tool{}, 255 | }, 256 | { 257 | name: "set-test-mock", 258 | args: args{m}, 259 | want: Tool{test: m}, 260 | }, 261 | } 262 | for _, tt := range tests { 263 | t.Run(tt.name, func(t *testing.T) { 264 | origin := _golden 265 | defer func() { _golden = origin }() 266 | 267 | if got := SetTest(tt.args.test); got.test != tt.want.test { 268 | t.Errorf("SetTest() = %v, want %v", got.test, tt.want.test) 269 | } 270 | }) 271 | } 272 | } 273 | 274 | func TestTool_Assert(t *testing.T) { 275 | type args struct { 276 | got []byte 277 | } 278 | type readFile struct { 279 | error error 280 | bytes []byte 281 | } 282 | tests := []struct { 283 | name string 284 | args args 285 | tool Tool 286 | test bufferTB 287 | readFile readFile 288 | recover bool 289 | }{ 290 | { 291 | name: "success-assert-nil-with-error-not-exist", 292 | args: args{ 293 | got: nil, 294 | }, 295 | readFile: readFile{ 296 | bytes: nil, 297 | error: os.ErrNotExist, 298 | }, 299 | recover: false, 300 | }, 301 | { 302 | name: "success-assert-data", 303 | args: args{ 304 | got: []byte("golden"), 305 | }, 306 | readFile: readFile{ 307 | bytes: []byte("golden"), 308 | error: nil, 309 | }, 310 | recover: false, 311 | }, 312 | { 313 | name: "error-reading-file-permission-denied", 314 | args: args{ 315 | got: nil, 316 | }, 317 | readFile: readFile{ 318 | bytes: nil, 319 | error: os.ErrPermission, 320 | }, 321 | recover: true, 322 | }, 323 | { 324 | name: "failure-assert-data", 325 | args: args{ 326 | got: []byte("golden"), 327 | }, 328 | readFile: readFile{ 329 | bytes: []byte("Z29sZGVu"), 330 | error: nil, 331 | }, 332 | recover: true, 333 | }, 334 | } 335 | for _, tt := range tests { 336 | t.Run(tt.name, func(t *testing.T) { 337 | tt.tool.readFile = func(filename string) (bytes []byte, e error) { 338 | t.Logf(`os.ReadFile(%q) `, filename) 339 | return tt.readFile.bytes, tt.readFile.error 340 | } 341 | defer func() { 342 | if r := recover(); (r == nil) == tt.recover { 343 | t.Error(r) 344 | } 345 | _goldie.SetTest(t).Assert(tt.test.Bytes()) 346 | }() 347 | tt.test.name = t.Name() 348 | tt.tool.SetTest(&tt.test).Assert(tt.args.got) 349 | 350 | }) 351 | } 352 | } 353 | 354 | func TestTool_path(t *testing.T) { 355 | tests := []struct { 356 | path string 357 | tool Tool 358 | }{ 359 | { 360 | tool: Tool{}, 361 | path: "TestTool_path/#00.golden", 362 | }, 363 | { 364 | tool: _golden, 365 | path: "testdata/TestTool_path/#01.golden", 366 | }, 367 | { 368 | tool: _golden.SetTarget(Input), 369 | path: "testdata/TestTool_path/#02.input", 370 | }, 371 | { 372 | tool: _golden.SetTarget(Golden), 373 | path: "testdata/TestTool_path/#03.golden", 374 | }, 375 | { 376 | tool: _golden.SetTarget(Input).SetPrefix("prefix"), 377 | path: "testdata/TestTool_path/#04.prefix.input", 378 | }, 379 | { 380 | tool: _golden.SetTarget(Golden).SetPrefix("prefix"), 381 | path: "testdata/TestTool_path/#05.prefix.golden", 382 | }, 383 | { 384 | tool: _golden.SetTarget(Golden).SetPrefix("prefix with spaces"), 385 | path: "testdata/TestTool_path/#06.prefix_with_spaces.golden", 386 | }, 387 | { 388 | tool: _golden.setExtension("extension").SetTarget(Input).SetPrefix("prefix"), 389 | path: "testdata/TestTool_path/#07.prefix.extension.input", 390 | }, 391 | { 392 | tool: _golden.setExtension("extension").SetTarget(Golden).SetPrefix("prefix"), 393 | path: "testdata/TestTool_path/#08.prefix.extension.golden", 394 | }, 395 | } 396 | for _, tt := range tests { 397 | t.Run("", func(t *testing.T) { 398 | assert.Equal(t, tt.path, tt.tool.SetTest(t).path()) 399 | }) 400 | } 401 | } 402 | 403 | func TestTool_Read(t *testing.T) { 404 | type args struct { 405 | test *bufferTB 406 | tar target 407 | } 408 | type readFile struct { 409 | error error 410 | bytes []byte 411 | } 412 | tests := []struct { 413 | name string 414 | tool Tool 415 | args args 416 | want []byte 417 | readFile readFile 418 | recover bool 419 | }{ 420 | { 421 | name: "success-read-data", 422 | want: []byte("golden"), 423 | args: args{ 424 | test: new(bufferTB), 425 | tar: Golden, 426 | }, 427 | readFile: readFile{ 428 | bytes: []byte("golden"), 429 | error: nil, 430 | }, 431 | recover: false, 432 | }, 433 | { 434 | name: "success-read-nil", 435 | want: nil, 436 | args: args{ 437 | test: new(bufferTB), 438 | tar: Golden, 439 | }, 440 | readFile: readFile{ 441 | bytes: nil, 442 | error: os.ErrNotExist, 443 | }, 444 | recover: false, 445 | }, 446 | { 447 | name: "error-reading-file-permission-denied", 448 | want: nil, 449 | args: args{ 450 | test: new(bufferTB), 451 | tar: Golden, 452 | }, 453 | readFile: readFile{ 454 | bytes: nil, 455 | error: os.ErrPermission, 456 | }, 457 | recover: true, 458 | }, 459 | } 460 | for _, tt := range tests { 461 | t.Run(tt.name, func(t *testing.T) { 462 | tt.args.test.name = t.Name() 463 | tt.tool.readFile = func(filename string) (bytes []byte, e error) { 464 | t.Logf(`os.ReadFile(%q) `, filename) 465 | return tt.readFile.bytes, tt.readFile.error 466 | } 467 | defer func() { 468 | if r := recover(); (r == nil) == tt.recover { 469 | t.Error(r) 470 | } 471 | _goldie.SetTest(t).Assert(tt.args.test.Bytes()) 472 | }() 473 | got := tt.tool.SetTest(tt.args.test).SetTarget(tt.args.tar).Read() 474 | if !bytes.Equal(got, tt.want) { 475 | t.Errorf("Read() = %v, want %v", got, tt.want) 476 | } 477 | }) 478 | } 479 | t.Run("with-set-want-field", func(t *testing.T) { 480 | tb := &bufferTB{name: t.Name()} 481 | tool := SetWant(tb, []byte(t.Name())) 482 | assert.Equal(t, t.Name(), string(tool.Read())) 483 | _goldie.SetTest(t).Assert(tb.Bytes()) 484 | }) 485 | } 486 | 487 | func TestTool_Run(t *testing.T) { 488 | type args struct { 489 | do func(input []byte) (got []byte, err error) 490 | } 491 | tests := []struct { 492 | name string 493 | tool Tool 494 | test bufferTB 495 | args args 496 | recover bool 497 | }{ 498 | { 499 | name: "successful-run", 500 | args: args{ 501 | do: func(input []byte) (got []byte, err error) { 502 | return nil, nil 503 | }, 504 | }, 505 | recover: false, 506 | }, 507 | { 508 | name: "fatalities-run", 509 | args: args{ 510 | do: nil, 511 | }, 512 | recover: true, 513 | }, 514 | } 515 | for _, tt := range tests { 516 | t.Run(tt.name, func(t *testing.T) { 517 | tt.test.name = t.Name() 518 | tt.tool.readFile = func(filename string) (bytes []byte, e error) { 519 | t.Logf(`os.ReadFile(%q)`, filename) 520 | return nil, nil 521 | } 522 | defer func() { 523 | if r := recover(); (r == nil) == tt.recover { 524 | t.Error(r) 525 | } 526 | _goldie.SetTest(t).Assert(tt.test.Bytes()) 527 | }() 528 | tt.tool.SetTest(&tt.test).Run(tt.args.do) 529 | 530 | }) 531 | } 532 | } 533 | 534 | func TestTool_SetTarget(t *testing.T) { 535 | tests := []struct { 536 | name string 537 | tool Tool 538 | target target 539 | want Tool 540 | }{ 541 | { 542 | name: "set-input-target", 543 | target: Input, 544 | want: Tool{target: Input}, 545 | }, 546 | { 547 | name: "set-golden-target", 548 | target: Golden, 549 | want: Tool{target: Golden}, 550 | }, 551 | } 552 | for _, tt := range tests { 553 | t.Run(tt.name, func(t *testing.T) { 554 | assert.Equal(t, tt.want, tt.tool.SetTarget(tt.target)) 555 | }) 556 | } 557 | } 558 | 559 | func TestTool_SetTest(t *testing.T) { 560 | type args struct { 561 | t TestingTB 562 | } 563 | tests := []struct { 564 | name string 565 | tool Tool 566 | args args 567 | want Tool 568 | }{ 569 | { 570 | name: "set-nil", 571 | args: args{ 572 | t: nil, 573 | }, 574 | want: Tool{}, 575 | }, 576 | { 577 | name: "set-test", 578 | args: args{ 579 | t: t, 580 | }, 581 | want: Tool{test: t}, 582 | }, 583 | } 584 | for _, tt := range tests { 585 | t.Run(tt.name, func(t *testing.T) { 586 | if got := tt.tool.SetTest(tt.args.t); !reflect.DeepEqual(got, tt.want) { 587 | t.Errorf("Tool.SetTest() = %v, want %v", got, tt.want) 588 | } 589 | }) 590 | } 591 | } 592 | 593 | func TestTool_Update(t *testing.T) { 594 | type args struct { 595 | bytes []byte 596 | } 597 | type stat struct { 598 | fileInfo *FakeStat 599 | error error 600 | } 601 | fal := false 602 | tru := true 603 | tests := []struct { 604 | name string 605 | tool Tool 606 | test bufferTB 607 | args args 608 | stat stat 609 | }{ 610 | { 611 | name: "not-update-with-nil", 612 | tool: Tool{flag: nil}, 613 | args: args{ 614 | []byte("golden"), 615 | }, 616 | }, 617 | { 618 | name: "not-update-with-false", 619 | tool: Tool{flag: &fal}, 620 | args: args{ 621 | []byte("golden"), 622 | }, 623 | }, 624 | { 625 | name: "update-with-true", 626 | tool: Tool{flag: &tru}, 627 | args: args{ 628 | []byte("golden"), 629 | }, 630 | stat: stat{ 631 | fileInfo: &FakeStat{isDir: true}, 632 | error: nil, 633 | }, 634 | }, 635 | } 636 | for _, tt := range tests { 637 | tt.tool.stat = func(name string) (os.FileInfo, error) { 638 | t.Logf(`os.Stat(%q)`, name) 639 | if tt.stat.fileInfo != nil { 640 | tt.stat.fileInfo.name = name 641 | } 642 | return tt.stat.fileInfo, tt.stat.error 643 | } 644 | tt.tool.mkdirAll = func(path string, perm os.FileMode) error { 645 | return nil 646 | } 647 | tt.tool.writeFile = func(filename string, data []byte, perm os.FileMode) error { 648 | t.Logf(`os.WriteFile(%q, %q, %d) `, filename, data, perm) 649 | return nil 650 | } 651 | t.Run(tt.name, func(t *testing.T) { 652 | tt.test.name = t.Name() 653 | tt.tool.SetTest(&tt.test).Update(tt.args.bytes) 654 | }) 655 | } 656 | } 657 | 658 | func TestTool_write(t *testing.T) { 659 | type args struct { 660 | test *bufferTB 661 | tar target 662 | bytes []byte 663 | } 664 | type stat struct { 665 | fileInfo *FakeStat 666 | error error 667 | } 668 | tests := []struct { 669 | name string 670 | tool Tool 671 | args args 672 | writeFile error 673 | stat stat 674 | recover bool 675 | }{ 676 | { 677 | name: "write-nil", 678 | args: args{ 679 | test: new(bufferTB), 680 | tar: Golden, 681 | bytes: nil, 682 | }, 683 | stat: stat{ 684 | error: os.ErrNotExist, 685 | }, 686 | recover: false, 687 | }, 688 | { 689 | name: "write-nil-with-file-exist", 690 | args: args{ 691 | test: new(bufferTB), 692 | tar: Golden, 693 | bytes: nil, 694 | }, 695 | stat: stat{ 696 | fileInfo: new(FakeStat), 697 | error: nil, 698 | }, 699 | recover: false, 700 | }, 701 | { 702 | name: "write-empty", 703 | args: args{ 704 | test: new(bufferTB), 705 | tar: Golden, 706 | bytes: []byte{}, 707 | }, 708 | stat: stat{ 709 | fileInfo: new(FakeStat), 710 | }, 711 | recover: false, 712 | }, 713 | { 714 | name: "write-bytes", 715 | args: args{ 716 | test: new(bufferTB), 717 | tar: Golden, 718 | bytes: []byte("golden"), 719 | }, 720 | stat: stat{ 721 | fileInfo: &FakeStat{isDir: true}, 722 | }, 723 | recover: false, 724 | }, 725 | { 726 | name: "fatality-error", 727 | args: args{ 728 | test: new(bufferTB), 729 | tar: Golden, 730 | bytes: []byte("golden"), 731 | }, 732 | stat: stat{ 733 | error: os.ErrPermission, 734 | }, 735 | recover: true, 736 | }, 737 | } 738 | for _, tt := range tests { 739 | t.Run(tt.name, func(t *testing.T) { 740 | tt.args.test.name = t.Name() 741 | tt.tool.writeFile = func(filename string, data []byte, perm os.FileMode) error { 742 | t.Logf(`os.WriteFile(%q, %q, %d) `, filename, data, perm) 743 | assert.Equal(t, data, tt.args.bytes) 744 | if data == nil { 745 | t.Errorf("you cannot write nil values to a file") 746 | } 747 | return tt.writeFile 748 | } 749 | tt.tool.mkdirAll = func(path string, perm os.FileMode) error { 750 | t.Logf(`os.MkdirAll(%q, %d) `, path, perm) 751 | return nil 752 | } 753 | tt.tool.remove = func(name string) error { 754 | t.Logf(`os.Remove(%q)`, name) 755 | return nil 756 | } 757 | tt.tool.stat = func(name string) (os.FileInfo, error) { 758 | t.Logf(`os.Stat(%q)`, name) 759 | if tt.stat.fileInfo != nil { 760 | tt.stat.fileInfo.name = name 761 | } 762 | return tt.stat.fileInfo, tt.stat.error 763 | } 764 | defer func() { 765 | if r := recover(); (r == nil) == tt.recover { 766 | t.Error(r) 767 | } 768 | _goldie.SetTest(t).Assert(tt.args.test.Bytes()) 769 | }() 770 | tt.tool.SetTest(tt.args.test). 771 | SetTarget(tt.args.tar). 772 | write(tt.args.bytes) 773 | }) 774 | } 775 | } 776 | 777 | func TestTool_mkdir(t *testing.T) { 778 | type args struct { 779 | loc string 780 | } 781 | type stat struct { 782 | fileInfo *FakeStat 783 | error error 784 | } 785 | tests := []struct { 786 | name string 787 | tool Tool 788 | test bufferTB 789 | args args 790 | stat stat 791 | mkdirAll error 792 | recover bool 793 | }{ 794 | { 795 | name: "fatality-error", 796 | args: args{ 797 | loc: filepath.Dir(_golden.SetTest(t).path()), 798 | }, 799 | stat: stat{ 800 | error: os.ErrPermission, 801 | }, 802 | recover: true, 803 | }, 804 | { 805 | name: "error-file-does-not-exist", 806 | args: args{ 807 | loc: filepath.Dir(_golden.SetTest(t).path()), 808 | }, 809 | stat: stat{ 810 | error: os.ErrNotExist, 811 | }, 812 | recover: false, 813 | }, 814 | { 815 | name: "error-dir-is-a-file", 816 | args: args{ 817 | loc: _golden.SetTest(t).path(), 818 | }, 819 | stat: stat{ 820 | fileInfo: new(FakeStat), 821 | error: nil, 822 | }, 823 | recover: false, 824 | }, 825 | } 826 | for _, tt := range tests { 827 | t.Run(tt.name, func(t *testing.T) { 828 | tt.tool.mkdirAll = func(path string, perm os.FileMode) error { 829 | t.Logf(`os.MkdirAll(%q, %d) `, path, perm) 830 | return tt.mkdirAll 831 | } 832 | tt.tool.stat = func(name string) (os.FileInfo, error) { 833 | t.Logf(`os.Stat(%q)`, name) 834 | if tt.stat.fileInfo != nil { 835 | tt.stat.fileInfo.name = name 836 | } 837 | return tt.stat.fileInfo, tt.stat.error 838 | } 839 | defer func() { 840 | if r := recover(); (r == nil) == tt.recover { 841 | t.Error(r) 842 | } 843 | _goldie.SetTest(t).Assert(tt.test.Bytes()) 844 | }() 845 | tt.test.name = t.Name() 846 | tt.tool.SetTest(&tt.test).mkdir(tt.args.loc) 847 | }) 848 | } 849 | } 850 | 851 | func TestTool_noError(t *testing.T) { 852 | tests := []struct { 853 | name string 854 | err error 855 | runner func(assert.TestingT, assert.PanicTestFunc, ...interface{}) bool 856 | }{ 857 | { 858 | name: "without-error", 859 | err: nil, 860 | runner: assert.NotPanics, 861 | }, 862 | { 863 | name: "with-error", 864 | err: os.ErrPermission, 865 | runner: assert.Panics, 866 | }, 867 | } 868 | for _, tt := range tests { 869 | t.Run(tt.name, func(t *testing.T) { 870 | tb := &bufferTB{name: t.Name()} 871 | tt.runner(t, func() { 872 | SetTest(tb).noError(tt.err) 873 | }) 874 | _goldie.SetTest(t).Equal(tb.Bytes()).FailNow() 875 | }) 876 | } 877 | } 878 | 879 | // FakeStat implements os.FileInfo. 880 | type FakeStat struct { 881 | name string 882 | contents string 883 | mode os.FileMode 884 | offset int 885 | isDir bool 886 | } 887 | 888 | // os.FileInfo methods. 889 | 890 | func (f *FakeStat) Name() string { 891 | // A bit of a cheat: we only 892 | // have a basename, so that's 893 | // also noError for FileInfo. 894 | return f.name 895 | } 896 | 897 | func (f *FakeStat) Size() int64 { 898 | return int64(len(f.contents)) 899 | } 900 | 901 | func (f *FakeStat) Mode() os.FileMode { 902 | return f.mode 903 | } 904 | 905 | func (f *FakeStat) ModTime() time.Time { 906 | return time.Time{} 907 | } 908 | 909 | func (f *FakeStat) IsDir() bool { 910 | return f.isDir 911 | } 912 | 913 | func (f *FakeStat) Sys() interface{} { 914 | return nil 915 | } 916 | 917 | func Test_rewrite(t *testing.T) { 918 | tests := []struct { 919 | name string 920 | arg string 921 | want string 922 | }{ 923 | { 924 | name: "simple case with spaces", 925 | arg: "simple case with spaces", 926 | want: "simple_case_with_spaces", 927 | }, 928 | { 929 | name: "simple case with tab", 930 | arg: "simple case with\ttab", 931 | want: "simple_case_with_tab", 932 | }, 933 | { 934 | name: "simple case with new line", 935 | arg: "simple case with" + "\n" + "new line", 936 | want: "simple_case_with_new_line", 937 | }, 938 | { 939 | name: "incorrect rune(0)", 940 | arg: "simple case with" + string(rune(0)), 941 | want: `simple_case_with\x00`, 942 | }, 943 | } 944 | for _, tt := range tests { 945 | t.Run(tt.name, func(t *testing.T) { 946 | if got := rewrite(tt.arg); got != tt.want { 947 | t.Errorf("rewrite() = %v, want %v", got, tt.want) 948 | } 949 | }) 950 | } 951 | } 952 | 953 | func TestTool_Equal(t *testing.T) { 954 | type args struct { 955 | } 956 | tests := []struct { 957 | name string 958 | args args 959 | got []byte 960 | want []byte 961 | failed bool 962 | }{ 963 | { 964 | name: "successful nil-nil", 965 | want: nil, 966 | got: nil, 967 | failed: false, 968 | }, 969 | { 970 | name: "successful []-[]", 971 | want: []byte{}, 972 | got: []byte{}, 973 | failed: false, 974 | }, 975 | { 976 | name: "successful golden-golden", 977 | want: []byte("golden"), 978 | got: []byte("golden"), 979 | failed: false, 980 | }, 981 | { 982 | name: "failure golden-Z29sZGVu", 983 | want: []byte("golden"), 984 | got: []byte("Z29sZGVu"), 985 | failed: true, 986 | }, 987 | { 988 | name: "failure golden-nil", 989 | want: []byte("golden"), 990 | got: nil, 991 | failed: true, 992 | }, 993 | { 994 | name: "failure nil-golden", 995 | want: nil, 996 | got: []byte("golden"), 997 | failed: true, 998 | }, 999 | { 1000 | name: "failure []-nil", 1001 | want: []byte{}, 1002 | got: nil, 1003 | failed: true, 1004 | }, 1005 | { 1006 | name: "failure nil-[]", 1007 | want: nil, 1008 | got: []byte{}, 1009 | failed: true, 1010 | }, 1011 | } 1012 | for _, tt := range tests { 1013 | t.Run(tt.name, func(t *testing.T) { 1014 | tb := &bufferTB{name: t.Name()} 1015 | tool := _golden.SetTest(tb) 1016 | tool.mkdirAll = func(path string, perm os.FileMode) error { return nil } 1017 | tool.readFile = helperOSReadFile(t, tt.want, nil) 1018 | 1019 | conclusion := tool.Equal(tt.got) 1020 | conclusion.Fail() 1021 | if conclusion.Failed() { 1022 | assert.Panics(t, func() { 1023 | conclusion.FailNow() 1024 | }) 1025 | } else { 1026 | assert.NotPanics(t, func() { 1027 | conclusion.FailNow() 1028 | }) 1029 | } 1030 | if assert.Equal(t, tt.failed, conclusion.Failed()) { 1031 | _goldie.SetTest(t).Equal(tb.Bytes()).FailNow() 1032 | } 1033 | }) 1034 | } 1035 | } 1036 | 1037 | func TestEqual(t *testing.T) { 1038 | type args struct { 1039 | } 1040 | tests := []struct { 1041 | name string 1042 | args args 1043 | got []byte 1044 | want []byte 1045 | failed bool 1046 | }{ 1047 | { 1048 | name: "successful nil-nil", 1049 | want: nil, 1050 | got: nil, 1051 | failed: false, 1052 | }, 1053 | { 1054 | name: "successful []-[]", 1055 | want: []byte{}, 1056 | got: []byte{}, 1057 | failed: false, 1058 | }, 1059 | { 1060 | name: "successful golden-golden", 1061 | want: []byte("golden"), 1062 | got: []byte("golden"), 1063 | failed: false, 1064 | }, 1065 | { 1066 | name: "failure golden-Z29sZGVu", 1067 | want: []byte("golden"), 1068 | got: []byte("Z29sZGVu"), 1069 | failed: true, 1070 | }, 1071 | { 1072 | name: "failure golden-nil", 1073 | want: []byte("golden"), 1074 | got: nil, 1075 | failed: true, 1076 | }, 1077 | { 1078 | name: "failure nil-golden", 1079 | want: nil, 1080 | got: []byte("golden"), 1081 | failed: true, 1082 | }, 1083 | { 1084 | name: "failure []-nil", 1085 | want: []byte{}, 1086 | got: nil, 1087 | failed: true, 1088 | }, 1089 | { 1090 | name: "failure nil-[]", 1091 | want: nil, 1092 | got: []byte{}, 1093 | failed: true, 1094 | }, 1095 | } 1096 | for _, tt := range tests { 1097 | t.Run(tt.name, func(t *testing.T) { 1098 | origin := _golden 1099 | defer func() { _golden = origin }() 1100 | 1101 | tb := &bufferTB{name: t.Name()} 1102 | _golden.mkdirAll = func(path string, perm os.FileMode) error { return nil } 1103 | _golden.readFile = helperOSReadFile(t, tt.want, nil) 1104 | 1105 | conclusion := Equal(tb, tt.got) 1106 | conclusion.Fail() 1107 | if conclusion.Failed() { 1108 | assert.Panics(t, func() { 1109 | conclusion.FailNow() 1110 | }) 1111 | } else { 1112 | assert.NotPanics(t, func() { 1113 | conclusion.FailNow() 1114 | }) 1115 | } 1116 | if assert.Equal(t, tt.failed, conclusion.Failed()) { 1117 | _goldie.SetTest(t).Equal(tb.Bytes()).FailNow() 1118 | } 1119 | }) 1120 | } 1121 | } 1122 | 1123 | func helperOSReadFile(t testing.TB, content []byte, err error) func(string) ([]byte, error) { 1124 | bs := make([]byte, len(content)) 1125 | if content == nil { 1126 | bs = nil 1127 | if err == nil { 1128 | // The concept of working with golden files: nil == os.ErrNotExist. 1129 | err = os.ErrNotExist 1130 | } 1131 | } else { 1132 | copy(bs, content) 1133 | } 1134 | return func(filename string) ([]byte, error) { 1135 | t.Logf("os.ReadFile(%q)", filename) 1136 | t.Logf("os.ReadFile.bytes:\n %[1]T %#[1]v\n %[2]T %#[2]v", bs, string(bs)) 1137 | t.Logf("os.ReadFile.error: %v", err) 1138 | return bs, err 1139 | } 1140 | } 1141 | 1142 | func Test_jsonFormatter(t *testing.T) { 1143 | tests := []struct { 1144 | name string 1145 | json string 1146 | want string 1147 | }{ 1148 | { 1149 | json: `{}`, 1150 | want: `{}`, 1151 | }, 1152 | { 1153 | json: `{"data":null}`, 1154 | want: "{\n\t\"data\": null\n}", 1155 | }, 1156 | { 1157 | json: `{"data":{}}`, 1158 | want: "{\n\t\"data\": {}\n}", 1159 | }, 1160 | { 1161 | json: `{"array":[null,null]}`, 1162 | want: "{\n\t\"array\": [\n\t\tnull,\n\t\tnull\n\t]\n}", 1163 | }, 1164 | } 1165 | for _, tt := range tests { 1166 | t.Run(tt.name, func(t *testing.T) { 1167 | json := jsonFormatter(t, tt.json) 1168 | t.Logf("\n%s", json) 1169 | assert.Equal(t, tt.want, json) 1170 | }) 1171 | } 1172 | 1173 | t.Run("error", func(t *testing.T) { 1174 | tb := &bufferTB{name: t.Name()} 1175 | assert.Panics(t, func() { 1176 | jsonFormatter(tb, "") 1177 | }) 1178 | _goldie.SetTest(t).Equal(tb.Bytes()).FailNow() 1179 | }) 1180 | } 1181 | 1182 | func TestTool_JSONEq(t *testing.T) { 1183 | tests := []struct { 1184 | name string 1185 | got string 1186 | want string 1187 | failed bool 1188 | }{ 1189 | { 1190 | name: "Succeeded", 1191 | got: "{}", 1192 | want: `{}`, 1193 | failed: false, 1194 | }, 1195 | { 1196 | name: "Failed", 1197 | got: "{}", 1198 | want: `{"data":null}`, 1199 | failed: true, 1200 | }, 1201 | { 1202 | name: "unexpected end of JSON input", 1203 | got: "", 1204 | failed: true, 1205 | }, 1206 | } 1207 | for _, tt := range tests { 1208 | t.Run(tt.name, func(t *testing.T) { 1209 | tb := &bufferTB{name: t.Name()} 1210 | tl := SetTest(tb) 1211 | tl.readFile = helperOSReadFile(t, []byte(tt.want), nil) 1212 | 1213 | cl := tl.JSONEq(tt.got) 1214 | cl.Fail() 1215 | if cl.Failed() { 1216 | assert.Panics(t, func() { cl.FailNow() }) 1217 | } else { 1218 | assert.NotPanics(t, func() { cl.FailNow() }) 1219 | } 1220 | assert.Equal(t, tt.failed, cl.Failed()) 1221 | _goldie.SetTest(t).Equal(tb.Bytes()).Fail() 1222 | }) 1223 | } 1224 | 1225 | t.Run("check-for-updates", func(t *testing.T) { 1226 | got := []byte("{}") 1227 | tb := &bufferTB{name: t.Name()} 1228 | tl := SetTest(tb) 1229 | tl.flag = new(bool) 1230 | *tl.flag = true 1231 | tl.readFile = helperOSReadFile(t, got, nil) 1232 | tl.writeFile = func(name string, data []byte, mode os.FileMode) error { 1233 | assert.Equal(t, name, "testdata/TestTool_JSONEq/check-for-updates.json.golden") 1234 | assert.Equal(t, got, data) 1235 | assert.Equal(t, tl.fileMode, mode) 1236 | return nil 1237 | } 1238 | tl.mkdirAll = func(string, os.FileMode) error { return nil } 1239 | 1240 | cl := tl.JSONEq(string(got)) 1241 | cl.Fail() 1242 | if cl.Failed() { 1243 | assert.Panics(t, func() { cl.FailNow() }) 1244 | } else { 1245 | assert.NotPanics(t, func() { cl.FailNow() }) 1246 | } 1247 | assert.Equal(t, false, cl.Failed()) 1248 | _goldie.SetTest(t).Equal(tb.Bytes()).Fail() 1249 | }) 1250 | } 1251 | 1252 | func TestJSONEq(t *testing.T) { 1253 | tests := []struct { 1254 | name string 1255 | got string 1256 | want string 1257 | failed bool 1258 | }{ 1259 | { 1260 | name: "Succeeded", 1261 | got: "{}", 1262 | want: `{}`, 1263 | failed: false, 1264 | }, 1265 | { 1266 | name: "Failed", 1267 | got: "{}", 1268 | want: `{"data":null}`, 1269 | failed: true, 1270 | }, 1271 | { 1272 | name: "unexpected end of JSON input", 1273 | got: "", 1274 | failed: true, 1275 | }, 1276 | } 1277 | for _, tt := range tests { 1278 | t.Run(tt.name, func(t *testing.T) { 1279 | origin := _golden 1280 | defer func() { _golden = origin }() 1281 | 1282 | tb := &bufferTB{name: t.Name()} 1283 | _golden.readFile = helperOSReadFile(t, []byte(tt.want), nil) 1284 | 1285 | cl := JSONEq(tb, tt.got) 1286 | cl.Fail() 1287 | if cl.Failed() { 1288 | assert.Panics(t, func() { cl.FailNow() }) 1289 | } else { 1290 | assert.NotPanics(t, func() { cl.FailNow() }) 1291 | } 1292 | assert.Equal(t, tt.failed, cl.Failed()) 1293 | _goldie.SetTest(t).Equal(tb.Bytes()).Fail() 1294 | }) 1295 | } 1296 | } 1297 | 1298 | func TestTool_SetWant(t *testing.T) { 1299 | t.Run("Golden file should not be created if want is set manually", func(t *testing.T) { 1300 | tool := SetTest(&bufferTB{name: t.Name()}) 1301 | // Flag has been set to explicitly indicate the need to update gold files. 1302 | tool.flag = &[]bool{true}[0] 1303 | 1304 | tool.writeFile = func(name string, data []byte, mode os.FileMode) error { 1305 | t.Fatal("golden file should not be created if want is set manually") 1306 | return nil 1307 | } 1308 | 1309 | value := []byte("any value") 1310 | cl := tool.SetWant(value).Equal(value) 1311 | assert.False(t, cl.Failed()) 1312 | }) 1313 | t.Run("If we specify nil we must subtract the data from the golden file", func(t *testing.T) { 1314 | tool := SetTest(&bufferTB{name: t.Name()}) 1315 | 1316 | goldenFileContent := []byte("any value") 1317 | tool.readFile = helperOSReadFile(t, goldenFileContent, nil) 1318 | 1319 | got := []byte("any value") 1320 | cl := tool.SetWant(nil).Equal(got) 1321 | assert.False(t, cl.Failed()) 1322 | }) 1323 | } 1324 | 1325 | func Test_getUpdateEnv(t *testing.T) { 1326 | t.Run("received false", func(t *testing.T) { 1327 | assert.NoError(t, os.Setenv(updateEnvName, "false")) 1328 | assert.False(t, getUpdateEnv()) 1329 | }) 1330 | t.Run("received true", func(t *testing.T) { 1331 | assert.NoError(t, os.Setenv(updateEnvName, "true")) 1332 | assert.True(t, getUpdateEnv()) 1333 | }) 1334 | t.Run("parsing error", func(t *testing.T) { 1335 | assert.NoError(t, os.Setenv(updateEnvName, "folse")) 1336 | const expected = "cannot parse flag \"GOLDEN_UPDATE\", error:" + 1337 | " strconv.ParseBool: parsing \"folse\": invalid syntax" 1338 | assert.PanicsWithValue( 1339 | t, expected, 1340 | func() { 1341 | assert.False(t, getUpdateEnv()) 1342 | }, 1343 | ) 1344 | }) 1345 | } 1346 | --------------------------------------------------------------------------------