pax_global_header00006660000000000000000000000064134100115760014510gustar00rootroot0000000000000052 comment=1083505acf35a0bd8a696b26837e1fb3187a7a83 gotest.tools-2.3.0/000077500000000000000000000000001341001157600141565ustar00rootroot00000000000000gotest.tools-2.3.0/.circleci/000077500000000000000000000000001341001157600160115ustar00rootroot00000000000000gotest.tools-2.3.0/.circleci/config.yml000066400000000000000000000050671341001157600200110ustar00rootroot00000000000000version: 2 jobs: lint: working_directory: /work docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker - run: command: docker version - run: name: "Install Dependencies" command: | docker build --target dev-with-source --tag cli-builder:$CIRCLE_BUILD_NUM . docker run --name \ deps-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \ dep ensure docker cp \ deps-$CIRCLE_BUILD_NUM:/go/src/gotest.tools/vendor \ vendor - run: name: "Lint" command: | docker build --target dev-with-source --tag cli-linter:$CIRCLE_BUILD_NUM . docker run --rm cli-linter:$CIRCLE_BUILD_NUM test-golang-1.9: working_directory: /work docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker - run: name: "Unit Test GO 1.9" command: scripts/ci/test 1.9-alpine test-golang-1.8: working_directory: /work docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker - run: name: "Unit Test GO 1.8" command: scripts/ci/test 1.8-alpine test-golang-1.10: working_directory: /work docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker - run: name: "Unit Test GO 1.10" command: scripts/ci/test 1.10-alpine test-golang-1.11: working_directory: /work docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker - run: name: "Unit Test GO 1.11" command: scripts/ci/test 1.11-alpine coverage: docker: [{image: 'circleci/golang:1.11'}] working_directory: /go/src/gotest.tools steps: - checkout - run: name: "Download dependencies" command: | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure go get gotest.tools/gotestsum - run: name: "Coverage" command: | mkdir -p junitxml/unit gotestsum --junitfile=junitxml/unit/junit.xml -- \ -coverprofile=coverage.txt ./... bash <(curl -s https://codecov.io/bash) - store_test_results: path: junitxml/ workflows: version: 2 ci: jobs: - lint - test-golang-1.8 - test-golang-1.9 - test-golang-1.10 - test-golang-1.11 - coverage gotest.tools-2.3.0/.codecov.yml000066400000000000000000000001651341001157600164030ustar00rootroot00000000000000coverage: status: project: default: threshold: 2 patch: default: threshold: 20 gotest.tools-2.3.0/.gitignore000066400000000000000000000000541341001157600161450ustar00rootroot00000000000000vendor/ .dobi/ Gopkg.lock .depsources dist/ gotest.tools-2.3.0/.gometalinter.json000066400000000000000000000010061341001157600176160ustar00rootroot00000000000000{ "Vendor": true, "Deadline": "2m", "Concurrency": 2, "Sort": ["linter", "severity", "path"], "Exclude": [ "example_test\\.go", "assert/cmd/gty-migrate-from-testify/testdata", "internal/difflib/difflib.go" ], "Enable": [ "deadcode", "errcheck", "gocyclo", "gofmt", "goimports", "golint", "gosimple", "ineffassign", "interfacer", "lll", "misspell", "unconvert", "unparam", "unused", "vet" ], "Cyclo": 10, "LineLength": 100 } gotest.tools-2.3.0/CONTRIBUTING.md000066400000000000000000000015461341001157600164150ustar00rootroot00000000000000# Contributing to gotest.tools Thank you for your interest in contributing to the project! Below are some suggestions which may make the process easier. ## Pull requests Pull requests for new features should generally be preceded by an issue explaining the feature and why it is necessary. Pull requests for bug fixes are always appreciated. They should almost always include a test which fails without the bug fix. ## Dependencies At this time both a `Gopkg.toml` for `dep` and `go.mod` for go modules exist in the repo. The `Gopkg.toml` remains so that projects using earlier versions of Go are able to find compatible versions of dependencies. If you need to make a change to a dependency: 1. Update `Gopkg.toml`. 2. Run the following to sync the changes to `go.mod`. ``` dep ensure rm go.mod go.sum go mod init gotestsum go mod tidy ``` gotest.tools-2.3.0/Dockerfile000066400000000000000000000030411341001157600161460ustar00rootroot00000000000000 ARG GOLANG_VERSION FROM golang:${GOLANG_VERSION:-1.11-alpine} as golang RUN apk add -U curl git bash WORKDIR /go/src/gotest.tools ENV CGO_ENABLED=0 ENV PS1="# " FROM golang as tools ARG FILEWATCHER=v0.3.2 RUN go get -d github.com/dnephin/filewatcher && \ cd /go/src/github.com/dnephin/filewatcher && \ git checkout -q "$FILEWATCHER" && \ go build -o /usr/bin/filewatcher . ARG DEP_TAG=v0.4.1 RUN go get -d github.com/golang/dep/cmd/dep && \ cd /go/src/github.com/golang/dep && \ git checkout -q "$DEP_TAG" && \ go build -o /usr/bin/dep ./cmd/dep ARG GOTESTSUM=v0.3.2 RUN go get -d gotest.tools/gotestsum && \ cd /go/src/gotest.tools/gotestsum && \ git checkout -q "$GOTESTSUM" && \ go build -v -o /usr/bin/gotestsum . FROM golang as dev COPY --from=tools /usr/bin/filewatcher /usr/bin/filewatcher COPY --from=tools /usr/bin/dep /usr/bin/dep COPY --from=tools /usr/bin/gotestsum /usr/bin/gotestsum FROM dev as dev-with-source COPY . . FROM golang as linter ARG GOMETALINTER=v2.0.11 RUN go get -d github.com/alecthomas/gometalinter && \ cd /go/src/github.com/alecthomas/gometalinter && \ git checkout -q "$GOMETALINTER" && \ go build -v -o /usr/local/bin/gometalinter . && \ gometalinter --install && \ rm -rf /go/src/* /go/pkg/* ENTRYPOINT ["/usr/local/bin/gometalinter"] CMD ["--config=.gometalinter.json", "./..."] FROM linter as linter-with-source COPY . . gotest.tools-2.3.0/Gopkg.toml000066400000000000000000000007031341001157600161220ustar00rootroot00000000000000 [[constraint]] name = "github.com/pkg/errors" version = "0.8.0" [[constraint]] name = "github.com/spf13/pflag" version = "1.0.3" [[constraint]] name = "github.com/google/go-cmp" version = "0.2.0" [[constraint]] # Commit a53bc13 dropped support for go1.8. Pinning the previous commit for # now. We can remove this when we drop support for go1.8. revision = "e96c4e24768da594adeb5eed27c8ecd547a3d4f1" name = "golang.org/x/tools" gotest.tools-2.3.0/LICENSE000066400000000000000000000010611341001157600151610ustar00rootroot00000000000000Copyright 2018 gotest.tools authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gotest.tools-2.3.0/README.md000066400000000000000000000031451341001157600154400ustar00rootroot00000000000000# gotest.tools A collection of packages to augment `testing` and support common patterns. [![GoDoc](https://godoc.org/gotest.tools?status.svg)](https://godoc.org/gotest.tools) [![CircleCI](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotest.tools/tree/master) [![Go Reportcard](https://goreportcard.com/badge/gotest.tools)](https://goreportcard.com/report/gotest.tools) ## Packages * [assert](http://godoc.org/gotest.tools/assert) - compare values and fail the test when a comparison fails * [env](http://godoc.org/gotest.tools/env) - test code which uses environment variables * [fs](http://godoc.org/gotest.tools/fs) - create temporary files and compare a filesystem tree to an expected value * [golden](http://godoc.org/gotest.tools/golden) - compare large multi-line strings against values frozen in golden files * [icmd](http://godoc.org/gotest.tools/icmd) - execute binaries and test the output * [poll](http://godoc.org/gotest.tools/poll) - test asynchronous code by polling until a desired state is reached * [skip](http://godoc.org/gotest.tools/skip) - skip a test and print the source code of the condition used to skip the test ## Related * [gotest.tools/gotestsum](https://github.com/gotestyourself/gotestsum) - go test runner with custom output * [maxbrunsfeld/counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) - generate fakes for interfaces * [jonboulle/clockwork](https://github.com/jonboulle/clockwork) - a fake clock for testing code that uses `time` ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). gotest.tools-2.3.0/appveyor.yml000066400000000000000000000012211341001157600165420ustar00rootroot00000000000000version: "{build}" image: Visual Studio 2017 clone_folder: c:\gopath\src\gotest.tools environment: PATH: c:\go110\bin;%PATH% GOROOT: c:\go110 GOPATH: c:\gopath DEPVERSION: v0.4.1 init: - git config --global core.symlinks true install: - go version - go env deploy: false build_script: - appveyor DownloadFile https://github.com/golang/dep/releases/download/%DEPVERSION%/dep-windows-amd64.exe - appveyor DownloadFile https://github.com/gotestyourself/gotestsum/releases/download/v0.3.0/gotestsum_0.3.0_windows_amd64.tar.gz - tar -xzf gotestsum_0.3.0_windows_amd64.tar.gz - dep-windows-amd64.exe ensure test_script: - gotestsum gotest.tools-2.3.0/assert/000077500000000000000000000000001341001157600154575ustar00rootroot00000000000000gotest.tools-2.3.0/assert/assert.go000066400000000000000000000216521341001157600173150ustar00rootroot00000000000000/*Package assert provides assertions for comparing expected values to actual values. When an assertion fails a helpful error message is printed. Assert and Check Assert() and Check() both accept a Comparison, and fail the test when the comparison fails. The one difference is that Assert() will end the test execution immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()), return the value of the comparison, then proceed with the rest of the test case. Example usage The example below shows assert used with some common types. import ( "testing" "gotest.tools/assert" is "gotest.tools/assert/cmp" ) func TestEverything(t *testing.T) { // booleans assert.Assert(t, ok) assert.Assert(t, !missing) // primitives assert.Equal(t, count, 1) assert.Equal(t, msg, "the message") assert.Assert(t, total != 10) // NotEqual // errors assert.NilError(t, closer.Close()) assert.Error(t, err, "the exact error message") assert.ErrorContains(t, err, "includes this") assert.ErrorType(t, err, os.IsNotExist) // complex types assert.DeepEqual(t, result, myStruct{Name: "title"}) assert.Assert(t, is.Len(items, 3)) assert.Assert(t, len(sequence) != 0) // NotEmpty assert.Assert(t, is.Contains(mapping, "key")) // pointers and interface assert.Assert(t, is.Nil(ref)) assert.Assert(t, ref != nil) // NotNil } Comparisons Package https://godoc.org/gotest.tools/assert/cmp provides many common comparisons. Additional comparisons can be written to compare values in other ways. See the example Assert (CustomComparison). Automated migration from testify gty-migrate-from-testify is a binary which can update source code which uses testify assertions to use the assertions provided by this package. See http://bit.do/cmd-gty-migrate-from-testify. */ package assert // import "gotest.tools/assert" import ( "fmt" "go/ast" "go/token" gocmp "github.com/google/go-cmp/cmp" "gotest.tools/assert/cmp" "gotest.tools/internal/format" "gotest.tools/internal/source" ) // BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage. type BoolOrComparison interface{} // TestingT is the subset of testing.T used by the assert package. type TestingT interface { FailNow() Fail() Log(args ...interface{}) } type helperT interface { Helper() } const failureMessage = "assertion failed: " // nolint: gocyclo func assert( t TestingT, failer func(), argSelector argSelector, comparison BoolOrComparison, msgAndArgs ...interface{}, ) bool { if ht, ok := t.(helperT); ok { ht.Helper() } var success bool switch check := comparison.(type) { case bool: if check { return true } logFailureFromBool(t, msgAndArgs...) // Undocumented legacy comparison without Result type case func() (success bool, message string): success = runCompareFunc(t, check, msgAndArgs...) case nil: return true case error: msg := "error is not nil: " t.Log(format.WithCustomMessage(failureMessage+msg+check.Error(), msgAndArgs...)) case cmp.Comparison: success = runComparison(t, argSelector, check, msgAndArgs...) case func() cmp.Result: success = runComparison(t, argSelector, check, msgAndArgs...) default: t.Log(fmt.Sprintf("invalid Comparison: %v (%T)", check, check)) } if success { return true } failer() return false } func runCompareFunc( t TestingT, f func() (success bool, message string), msgAndArgs ...interface{}, ) bool { if ht, ok := t.(helperT); ok { ht.Helper() } if success, message := f(); !success { t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...)) return false } return true } func logFailureFromBool(t TestingT, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } const stackIndex = 3 // Assert()/Check(), assert(), formatFailureFromBool() const comparisonArgPos = 1 args, err := source.CallExprArgs(stackIndex) if err != nil { t.Log(err.Error()) return } msg, err := boolFailureMessage(args[comparisonArgPos]) if err != nil { t.Log(err.Error()) msg = "expression is false" } t.Log(format.WithCustomMessage(failureMessage+msg, msgAndArgs...)) } func boolFailureMessage(expr ast.Expr) (string, error) { if binaryExpr, ok := expr.(*ast.BinaryExpr); ok && binaryExpr.Op == token.NEQ { x, err := source.FormatNode(binaryExpr.X) if err != nil { return "", err } y, err := source.FormatNode(binaryExpr.Y) if err != nil { return "", err } return x + " is " + y, nil } if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.NOT { x, err := source.FormatNode(unaryExpr.X) if err != nil { return "", err } return x + " is true", nil } formatted, err := source.FormatNode(expr) if err != nil { return "", err } return "expression is false: " + formatted, nil } // Assert performs a comparison. If the comparison fails the test is marked as // failed, a failure message is logged, and execution is stopped immediately. // // The comparison argument may be one of three types: bool, cmp.Comparison or // error. // When called with a bool the failure message will contain the literal source // code of the expression. // When called with a cmp.Comparison the comparison is responsible for producing // a helpful failure message. // When called with an error a nil value is considered success. A non-nil error // is a failure, and Error() is used as the failure message. func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsFromComparisonCall, comparison, msgAndArgs...) } // Check performs a comparison. If the comparison fails the test is marked as // failed, a failure message is logged, and Check returns false. Otherwise returns // true. // // See Assert for details about the comparison arg and failure messages. func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) bool { if ht, ok := t.(helperT); ok { ht.Helper() } return assert(t, t.Fail, argsFromComparisonCall, comparison, msgAndArgs...) } // NilError fails the test immediately if err is not nil. // This is equivalent to Assert(t, err) func NilError(t TestingT, err error, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, err, msgAndArgs...) } // Equal uses the == operator to assert two values are equal and fails the test // if they are not equal. // // If the comparison fails Equal will use the variable names for x and y as part // of the failure message to identify the actual and expected values. // // If either x or y are a multi-line string the failure message will include a // unified diff of the two values. If the values only differ by whitespace // the unified diff will be augmented by replacing whitespace characters with // visible characters to identify the whitespace difference. // // This is equivalent to Assert(t, cmp.Equal(x, y)). func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, cmp.Equal(x, y), msgAndArgs...) } // DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are // equal and fails the test if they are not equal. // // Package https://godoc.org/gotest.tools/assert/opt provides some additional // commonly used Options. // // This is equivalent to Assert(t, cmp.DeepEqual(x, y)). func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, cmp.DeepEqual(x, y, opts...)) } // Error fails the test if err is nil, or the error message is not the expected // message. // Equivalent to Assert(t, cmp.Error(err, message)). func Error(t TestingT, err error, message string, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, cmp.Error(err, message), msgAndArgs...) } // ErrorContains fails the test if err is nil, or the error message does not // contain the expected substring. // Equivalent to Assert(t, cmp.ErrorContains(err, substring)). func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, cmp.ErrorContains(err, substring), msgAndArgs...) } // ErrorType fails the test if err is nil, or err is not the expected type. // // Expected can be one of: // a func(error) bool which returns true if the error is the expected type, // an instance of (or a pointer to) a struct of the expected type, // a pointer to an interface the error is expected to implement, // a reflect.Type of the expected struct or interface. // // Equivalent to Assert(t, cmp.ErrorType(err, expected)). func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert(t, t.FailNow, argsAfterT, cmp.ErrorType(err, expected), msgAndArgs...) } gotest.tools-2.3.0/assert/assert_test.go000066400000000000000000000210351341001157600203470ustar00rootroot00000000000000package assert import ( "fmt" "os" "testing" gocmp "github.com/google/go-cmp/cmp" "gotest.tools/assert/cmp" ) type fakeTestingT struct { failNowed bool failed bool msgs []string } func (f *fakeTestingT) FailNow() { f.failNowed = true } func (f *fakeTestingT) Fail() { f.failed = true } func (f *fakeTestingT) Log(args ...interface{}) { f.msgs = append(f.msgs, args[0].(string)) } func (f *fakeTestingT) Helper() {} func TestAssertWithBoolFailure(t *testing.T) { fakeT := &fakeTestingT{} Assert(fakeT, 1 == 6) expectFailNowed(t, fakeT, "assertion failed: expression is false: 1 == 6") } func TestAssertWithBoolFailureNotEqual(t *testing.T) { fakeT := &fakeTestingT{} var err error Assert(fakeT, err != nil) expectFailNowed(t, fakeT, "assertion failed: err is nil") } func TestAssertWithBoolFailureNotTrue(t *testing.T) { fakeT := &fakeTestingT{} badNews := true Assert(fakeT, !badNews) expectFailNowed(t, fakeT, "assertion failed: badNews is true") } func TestAssertWithBoolFailureAndExtraMessage(t *testing.T) { fakeT := &fakeTestingT{} Assert(fakeT, 1 > 5, "sometimes things fail") expectFailNowed(t, fakeT, "assertion failed: expression is false: 1 > 5: sometimes things fail") } func TestAssertWithBoolSuccess(t *testing.T) { fakeT := &fakeTestingT{} Assert(fakeT, 1 < 5) expectSuccess(t, fakeT) } func TestAssertWithBoolMultiLineFailure(t *testing.T) { fakeT := &fakeTestingT{} Assert(fakeT, func() bool { for range []int{1, 2, 3, 4} { } return false }()) expectFailNowed(t, fakeT, `assertion failed: expression is false: func() bool { for range []int{1, 2, 3, 4} { } return false }()`) } type exampleComparison struct { success bool message string } func (c exampleComparison) Compare() (bool, string) { return c.success, c.message } func TestAssertWithComparisonSuccess(t *testing.T) { fakeT := &fakeTestingT{} cmp := exampleComparison{success: true} Assert(fakeT, cmp.Compare) expectSuccess(t, fakeT) } func TestAssertWithComparisonFailure(t *testing.T) { fakeT := &fakeTestingT{} cmp := exampleComparison{message: "oops, not good"} Assert(fakeT, cmp.Compare) expectFailNowed(t, fakeT, "assertion failed: oops, not good") } func TestAssertWithComparisonAndExtraMessage(t *testing.T) { fakeT := &fakeTestingT{} cmp := exampleComparison{message: "oops, not good"} Assert(fakeT, cmp.Compare, "extra stuff %v", true) expectFailNowed(t, fakeT, "assertion failed: oops, not good: extra stuff true") } type customError struct{} func (e *customError) Error() string { return "custom error" } func TestNilErrorSuccess(t *testing.T) { fakeT := &fakeTestingT{} var err error NilError(fakeT, err) expectSuccess(t, fakeT) NilError(fakeT, nil) expectSuccess(t, fakeT) var customErr *customError NilError(fakeT, customErr) } func TestNilErrorFailure(t *testing.T) { fakeT := &fakeTestingT{} NilError(fakeT, fmt.Errorf("this is the error")) expectFailNowed(t, fakeT, "assertion failed: error is not nil: this is the error") } func TestCheckFailure(t *testing.T) { fakeT := &fakeTestingT{} if Check(fakeT, 1 == 2) { t.Error("expected check to return false on failure") } expectFailed(t, fakeT, "assertion failed: expression is false: 1 == 2") } func TestCheckSuccess(t *testing.T) { fakeT := &fakeTestingT{} if !Check(fakeT, 1 == 1) { t.Error("expected check to return true on success") } expectSuccess(t, fakeT) } func TestCheckEqualFailure(t *testing.T) { fakeT := &fakeTestingT{} actual, expected := 5, 9 Check(fakeT, cmp.Equal(actual, expected)) expectFailed(t, fakeT, "assertion failed: 5 (actual int) != 9 (expected int)") } func TestEqualSuccess(t *testing.T) { fakeT := &fakeTestingT{} Equal(fakeT, 1, 1) expectSuccess(t, fakeT) Equal(fakeT, "abcd", "abcd") expectSuccess(t, fakeT) } func TestEqualFailure(t *testing.T) { fakeT := &fakeTestingT{} actual, expected := 1, 3 Equal(fakeT, actual, expected) expectFailNowed(t, fakeT, "assertion failed: 1 (actual int) != 3 (expected int)") } func TestEqualFailureTypes(t *testing.T) { fakeT := &fakeTestingT{} Equal(fakeT, 3, uint(3)) expectFailNowed(t, fakeT, `assertion failed: 3 (int) != 3 (uint)`) } func TestEqualFailureWithSelectorArgument(t *testing.T) { fakeT := &fakeTestingT{} type tc struct { expected string } var testcase = tc{expected: "foo"} Equal(fakeT, "ok", testcase.expected) expectFailNowed(t, fakeT, "assertion failed: ok (string) != foo (testcase.expected string)") } func TestEqualFailureWithIndexExpr(t *testing.T) { fakeT := &fakeTestingT{} expected := map[string]string{"foo": "bar"} Equal(fakeT, "ok", expected["foo"]) expectFailNowed(t, fakeT, `assertion failed: ok (string) != bar (expected["foo"] string)`) } func TestEqualFailureWithCallExprArgument(t *testing.T) { fakeT := &fakeTestingT{} ce := customError{} Equal(fakeT, "", ce.Error()) expectFailNowed(t, fakeT, "assertion failed: (string) != custom error (string)") } func TestAssertFailureWithOfflineComparison(t *testing.T) { fakeT := &fakeTestingT{} a := 1 b := 2 // store comparison in a variable, so ast lookup can't find it comparison := cmp.Equal(a, b) Assert(fakeT, comparison) // expected value wont have variable names expectFailNowed(t, fakeT, "assertion failed: 1 (int) != 2 (int)") } type testingT interface { Errorf(msg string, args ...interface{}) Fatalf(msg string, args ...interface{}) } func expectFailNowed(t testingT, fakeT *fakeTestingT, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if fakeT.failed { t.Errorf("should not have failed, got messages %s", fakeT.msgs) } if !fakeT.failNowed { t.Fatalf("should have failNowed with message %s", expected) } if fakeT.msgs[0] != expected { t.Fatalf("should have failure message %q, got %q", expected, fakeT.msgs[0]) } } // nolint: unparam func expectFailed(t testingT, fakeT *fakeTestingT, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if fakeT.failNowed { t.Errorf("should not have failNowed, got messages %s", fakeT.msgs) } if !fakeT.failed { t.Fatalf("should have failed with message %s", expected) } if fakeT.msgs[0] != expected { t.Fatalf("should have failure message %q, got %q", expected, fakeT.msgs[0]) } } func expectSuccess(t testingT, fakeT *fakeTestingT) { if ht, ok := t.(helperT); ok { ht.Helper() } if fakeT.failNowed { t.Errorf("should not have failNowed, got messages %s", fakeT.msgs) } if fakeT.failed { t.Errorf("should not have failed, got messages %s", fakeT.msgs) } } type stub struct { a string b int } func TestDeepEqualSuccess(t *testing.T) { actual := stub{"ok", 1} expected := stub{"ok", 1} fakeT := &fakeTestingT{} DeepEqual(fakeT, actual, expected, gocmp.AllowUnexported(stub{})) expectSuccess(t, fakeT) } func TestDeepEqualFailure(t *testing.T) { actual := stub{"ok", 1} expected := stub{"ok", 2} fakeT := &fakeTestingT{} DeepEqual(fakeT, actual, expected, gocmp.AllowUnexported(stub{})) expectFailNowed(t, fakeT, "assertion failed: "+` --- actual +++ expected {assert.stub}.b: -: 1 +: 2 `) } func TestErrorFailure(t *testing.T) { t.Run("nil error", func(t *testing.T) { fakeT := &fakeTestingT{} var err error Error(fakeT, err, "this error") expectFailNowed(t, fakeT, "assertion failed: expected an error, got nil") }) t.Run("different error", func(t *testing.T) { fakeT := &fakeTestingT{} err := fmt.Errorf("the actual error") Error(fakeT, err, "this error") expected := `assertion failed: expected error "this error", got "the actual error"` expectFailNowed(t, fakeT, expected) }) } func TestErrorContainsFailure(t *testing.T) { t.Run("nil error", func(t *testing.T) { fakeT := &fakeTestingT{} var err error ErrorContains(fakeT, err, "this error") expectFailNowed(t, fakeT, "assertion failed: expected an error, got nil") }) t.Run("different error", func(t *testing.T) { fakeT := &fakeTestingT{} err := fmt.Errorf("the actual error") ErrorContains(fakeT, err, "this error") expected := `assertion failed: expected error to contain "this error", got "the actual error"` expectFailNowed(t, fakeT, expected) }) } func TestErrorTypeFailure(t *testing.T) { t.Run("nil error", func(t *testing.T) { fakeT := &fakeTestingT{} var err error ErrorType(fakeT, err, os.IsNotExist) expectFailNowed(t, fakeT, "assertion failed: error is nil, not os.IsNotExist") }) t.Run("different error", func(t *testing.T) { fakeT := &fakeTestingT{} err := fmt.Errorf("the actual error") ErrorType(fakeT, err, os.IsNotExist) expected := `assertion failed: error is the actual error (*errors.errorString), not os.IsNotExist` expectFailNowed(t, fakeT, expected) }) } gotest.tools-2.3.0/assert/cmd/000077500000000000000000000000001341001157600162225ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/000077500000000000000000000000001341001157600231015ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/call.go000066400000000000000000000124001341001157600243400ustar00rootroot00000000000000package main import ( "bytes" "fmt" "go/ast" "go/format" "go/token" ) // call wraps an testify assert ast.CallExpr and exposes properties of the // expression to facilitate migrating the expression to a gotest.tools/assert type call struct { fileset *token.FileSet expr *ast.CallExpr xIdent *ast.Ident selExpr *ast.SelectorExpr assert string } func (c call) String() string { buf := new(bytes.Buffer) format.Node(buf, token.NewFileSet(), c.expr) return buf.String() } func (c call) StringWithFileInfo() string { if c.fileset.File(c.expr.Pos()) == nil { return fmt.Sprintf("%s at unknown file", c) } return fmt.Sprintf("%s at %s:%d", c, relativePath(c.fileset.File(c.expr.Pos()).Name()), c.fileset.Position(c.expr.Pos()).Line) } // testingT returns the first argument of the call, which is assumed to be a // *ast.Ident of *testing.T or a compatible interface. func (c call) testingT() ast.Expr { if len(c.expr.Args) == 0 { return nil } return c.expr.Args[0] } // extraArgs returns the arguments of the expression starting from index func (c call) extraArgs(index int) []ast.Expr { if len(c.expr.Args) <= index { return nil } return c.expr.Args[index:] } // args returns a range of arguments from the expression func (c call) args(from, to int) []ast.Expr { return c.expr.Args[from:to] } // arg returns a single argument from the expression func (c call) arg(index int) ast.Expr { return c.expr.Args[index] } // newCallFromCallExpr returns a new call build from a ast.CallExpr. Returns false // if the call expression does not have the expected ast nodes. func newCallFromCallExpr(callExpr *ast.CallExpr, migration migration) (call, bool) { c := call{} selector, ok := callExpr.Fun.(*ast.SelectorExpr) if !ok { return c, false } ident, ok := selector.X.(*ast.Ident) if !ok { return c, false } return call{ fileset: migration.fileset, xIdent: ident, selExpr: selector, expr: callExpr, }, true } // newTestifyCallFromNode returns a call that wraps a valid testify assertion. // Returns false if the call expression is not a testify assertion. func newTestifyCallFromNode(callExpr *ast.CallExpr, migration migration) (call, bool) { tcall, ok := newCallFromCallExpr(callExpr, migration) if !ok { return tcall, false } testifyNewAssignStmt := testifyAssertionsAssignment(tcall, migration) switch { case testifyNewAssignStmt != nil: return updateCallForTestifyNew(tcall, testifyNewAssignStmt, migration) case isTestifyPkgCall(tcall, migration): tcall.assert = migration.importNames.funcNameFromTestifyName(tcall.xIdent.Name) return tcall, true } return tcall, false } // isTestifyPkgCall returns true if the call is a testify package-level assertion // (as apposed to an assertion method on the Assertions type) // // TODO: check if the xIdent.Obj.Decl is an import declaration instead of // assuming that a name matching the import name is always an import. Some code // may shadow import names, which could lead to incorrect results. func isTestifyPkgCall(tcall call, migration migration) bool { return migration.importNames.matchesTestify(tcall.xIdent) } // testifyAssertionsAssignment returns an ast.AssignStmt if the call is a testify // call from an Assertions object returned from assert.New(t) (not a package level // assert). Otherwise returns nil. func testifyAssertionsAssignment(tcall call, migration migration) *ast.AssignStmt { if tcall.xIdent.Obj == nil { return nil } assignStmt, ok := tcall.xIdent.Obj.Decl.(*ast.AssignStmt) if !ok { return nil } if isAssignmentFromAssertNew(assignStmt, migration) { return assignStmt } return nil } func updateCallForTestifyNew( tcall call, testifyNewAssignStmt *ast.AssignStmt, migration migration, ) (call, bool) { testifyNewCallExpr := callExprFromAssignment(testifyNewAssignStmt) if testifyNewCallExpr == nil { return tcall, false } testifyNewCall, ok := newCallFromCallExpr(testifyNewCallExpr, migration) if !ok { return tcall, false } tcall.assert = migration.importNames.funcNameFromTestifyName(testifyNewCall.xIdent.Name) tcall.expr = addMissingTestingTArgToCallExpr(tcall.expr, testifyNewCall.testingT()) return tcall, true } // addMissingTestingTArgToCallExpr adds a testingT arg as the first arg of the // ast.CallExpr and returns a copy of the ast.CallExpr func addMissingTestingTArgToCallExpr(callExpr *ast.CallExpr, testingT ast.Expr) *ast.CallExpr { return &ast.CallExpr{ Fun: callExpr.Fun, Args: append([]ast.Expr{removePos(testingT)}, callExpr.Args...), } } func removePos(node ast.Expr) ast.Expr { switch typed := node.(type) { case *ast.Ident: return &ast.Ident{Name: typed.Name} } return node } // TODO: use pkgInfo and walkForType instead? func isAssignmentFromAssertNew(assign *ast.AssignStmt, migration migration) bool { callExpr := callExprFromAssignment(assign) if callExpr == nil { return false } tcall, ok := newCallFromCallExpr(callExpr, migration) if !ok { return false } if !migration.importNames.matchesTestify(tcall.xIdent) { return false } if len(tcall.expr.Args) != 1 { return false } return tcall.selExpr.Sel.Name == "New" } func callExprFromAssignment(assign *ast.AssignStmt) *ast.CallExpr { if len(assign.Rhs) != 1 { return nil } callExpr, ok := assign.Rhs[0].(*ast.CallExpr) if !ok { return nil } return callExpr } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/call_test.go000066400000000000000000000012711341001157600254030ustar00rootroot00000000000000package main import ( "go/ast" "go/token" "testing" "gotest.tools/assert" ) func TestCall_String(t *testing.T) { c := &call{ expr: &ast.CallExpr{Fun: ast.NewIdent("myFunc")}, } assert.Equal(t, c.String(), "myFunc()") } func TestCall_StringWithFileInfo(t *testing.T) { c := &call{ fileset: token.NewFileSet(), expr: &ast.CallExpr{ Fun: &ast.Ident{ Name: "myFunc", NamePos: 17, }}, } t.Run("unknown file", func(t *testing.T) { assert.Equal(t, c.StringWithFileInfo(), "myFunc() at unknown file") }) t.Run("at position", func(t *testing.T) { c.fileset.AddFile("source.go", 10, 100) assert.Equal(t, c.StringWithFileInfo(), "myFunc() at source.go:1") }) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/doc.go000066400000000000000000000007511341001157600242000ustar00rootroot00000000000000/* Command gty-migrate-from-testify migrates packages from testify/assert and testify/require to gotest.tools/assert. $ go get gotest.tools/assert/cmd/gty-migrate-from-testify Usage: gty-migrate-from-testify [OPTIONS] PACKAGE [PACKAGE...] See --help for full usage. To run on all packages (including external test packages) use: go list \ -f '{{.ImportPath}} {{if .XTestGoFiles}}{{"\n"}}{{.ImportPath}}_test{{end}}' \ ./... | xargs gty-migrate-from-testify */ package main gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/importer.go000066400000000000000000000040001341001157600252630ustar00rootroot00000000000000package main import ( "fmt" "go/build" "io/ioutil" "os" "path" "path/filepath" "strings" "github.com/pkg/errors" ) const ( pkgGocheck = "github.com/go-check/check" pkgGopkgGocheck = "gopkg.in/check.v1" ) var allTestingTPkgs = append( allTestifyPks, pkgGocheck, pkgGopkgGocheck, ) func newFakeImporter() (*fakeImporter, error) { tmpDir, err := ioutil.TempDir("", "gty-migrate-from-testify") err = errors.Wrapf(err, "failed to create temporary directory") return &fakeImporter{tmpDir: tmpDir}, err } type fakeImporter struct { tmpDir string } func (f *fakeImporter) Cleanup() error { return os.RemoveAll(f.tmpDir) } func (f *fakeImporter) Import( ctx *build.Context, path string, dir string, mode build.ImportMode, ) (*build.Package, error) { pkg, err := ctx.Import(path, dir, mode) if err == nil { return pkg, err } for _, pkgName := range allTestingTPkgs { if pkgName == path { return importStubPackage(f.tmpDir, pkgName) } } return pkg, err } func importStubPackage(tmpDir string, importPath string) (*build.Package, error) { pkgName := strings.TrimSuffix(path.Base(importPath), ".v1") pkgFilePath := filepath.Join(tmpDir, pkgName) if err := os.MkdirAll(pkgFilePath, 0755); err != nil { return nil, errors.Wrapf(err, "failed to create stub package directory") } const filename = "fixture.go" if err := writeStubFile(filepath.Join(pkgFilePath, filename), pkgName); err != nil { return nil, errors.Wrapf(err, "failed to write stub file") } return &build.Package{ Dir: pkgFilePath, Name: pkgName, ImportPath: importPath, GoFiles: []string{filename}, }, nil } func writeStubFile(path string, pkgName string) error { content := []byte(fmt.Sprintf(stubFixtureContent, pkgName)) return ioutil.WriteFile(path, content, 0644) } const stubFixtureContent = ` package %s type TestingT interface { Errorf(format string, args ...interface{}) FailNow() } func New(t TestingT) *Assertions { return &Assertions{} } type Assertions struct {} type C struct{} ` gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/main.go000066400000000000000000000136331341001157600243620ustar00rootroot00000000000000package main import ( "bytes" "fmt" "go/ast" "go/build" "go/format" "go/parser" "go/token" "io/ioutil" "log" "os" "path" "path/filepath" "strings" "github.com/pkg/errors" "github.com/spf13/pflag" "golang.org/x/tools/go/loader" "golang.org/x/tools/imports" ) type options struct { pkgs []string dryRun bool debug bool cmpImportName string showLoaderErrors bool useAllFiles bool } func main() { name := os.Args[0] flags, opts := setupFlags(name) handleExitError(name, flags.Parse(os.Args[1:])) setupLogging(opts) opts.pkgs = flags.Args() handleExitError(name, run(*opts)) } func setupLogging(opts *options) { log.SetFlags(0) enableDebug = opts.debug } var enableDebug = false func debugf(msg string, args ...interface{}) { if enableDebug { log.Printf("DEBUG: "+msg, args...) } } func setupFlags(name string) (*pflag.FlagSet, *options) { opts := options{} flags := pflag.NewFlagSet(name, pflag.ContinueOnError) flags.BoolVar(&opts.dryRun, "dry-run", false, "don't write changes to file") flags.BoolVar(&opts.debug, "debug", false, "enable debug logging") flags.StringVar(&opts.cmpImportName, "cmp-pkg-import-alias", "is", "import alias to use for the assert/cmp package") flags.BoolVar(&opts.showLoaderErrors, "print-loader-errors", false, "print errors from loading source") flags.BoolVar(&opts.useAllFiles, "ignore-build-tags", false, "migrate all files ignoring build tags") flags.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: %s [OPTIONS] PACKAGE [PACKAGE...] Migrate calls from testify/{assert|require} to gotest.tools/assert. %s`, name, flags.FlagUsages()) } return flags, &opts } func handleExitError(name string, err error) { switch { case err == nil: return case err == pflag.ErrHelp: os.Exit(0) default: log.Println(name + ": Error: " + err.Error()) os.Exit(3) } } func run(opts options) error { program, err := loadProgram(opts) if err != nil { return errors.Wrapf(err, "failed to load program") } pkgs := program.InitialPackages() debugf("package count: %d", len(pkgs)) fileset := program.Fset for _, pkg := range pkgs { for _, astFile := range pkg.Files { absFilename := fileset.File(astFile.Pos()).Name() filename := relativePath(absFilename) importNames := newImportNames(astFile.Imports, opts) if !importNames.hasTestifyImports() { debugf("skipping file %s, no imports", filename) continue } debugf("migrating %s with imports: %#v", filename, importNames) m := migration{ file: astFile, fileset: fileset, importNames: importNames, pkgInfo: pkg, } migrateFile(m) if opts.dryRun { continue } raw, err := formatFile(m) if err != nil { return errors.Wrapf(err, "failed to format %s", filename) } if err := ioutil.WriteFile(absFilename, raw, 0); err != nil { return errors.Wrapf(err, "failed to write file %s", filename) } } } return nil } func loadProgram(opts options) (*loader.Program, error) { fakeImporter, err := newFakeImporter() if err != nil { return nil, err } defer fakeImporter.Cleanup() conf := loader.Config{ Fset: token.NewFileSet(), ParserMode: parser.ParseComments, Build: buildContext(opts), AllowErrors: true, FindPackage: fakeImporter.Import, } for _, pkg := range opts.pkgs { conf.ImportWithTests(pkg) } if !opts.showLoaderErrors { conf.TypeChecker.Error = func(e error) {} } program, err := conf.Load() if opts.showLoaderErrors { for p, pkg := range program.AllPackages { if len(pkg.Errors) > 0 { fmt.Printf("Package %s loaded with some errors:\n", p.Name()) for _, err := range pkg.Errors { fmt.Println(" ", err.Error()) } } } } return program, err } func buildContext(opts options) *build.Context { c := build.Default c.UseAllFiles = opts.useAllFiles if val, ok := os.LookupEnv("GOPATH"); ok { c.GOPATH = val } return &c } func relativePath(p string) string { cwd, err := os.Getwd() if err != nil { return p } rel, err := filepath.Rel(cwd, p) if err != nil { return p } return rel } type importNames struct { testifyAssert string testifyRequire string assert string cmp string } func (p importNames) hasTestifyImports() bool { return p.testifyAssert != "" || p.testifyRequire != "" } func (p importNames) matchesTestify(ident *ast.Ident) bool { return ident.Name == p.testifyAssert || ident.Name == p.testifyRequire } func (p importNames) funcNameFromTestifyName(name string) string { switch name { case p.testifyAssert: return funcNameCheck case p.testifyRequire: return funcNameAssert default: panic("unexpected testify import name " + name) } } func newImportNames(imports []*ast.ImportSpec, opt options) importNames { importNames := importNames{ assert: path.Base(pkgAssert), cmp: path.Base(pkgCmp), } for _, spec := range imports { switch strings.Trim(spec.Path.Value, `"`) { case pkgTestifyAssert, pkgGopkgTestifyAssert: importNames.testifyAssert = identOrDefault(spec.Name, "assert") case pkgTestifyRequire, pkgGopkgTestifyRequire: importNames.testifyRequire = identOrDefault(spec.Name, "require") default: if importedAs(spec, path.Base(pkgAssert)) { importNames.assert = "gtyassert" } } } if opt.cmpImportName != "" { importNames.cmp = opt.cmpImportName } return importNames } func importedAs(spec *ast.ImportSpec, pkg string) bool { if path.Base(strings.Trim(spec.Path.Value, `""`)) == pkg { return true } return spec.Name != nil && spec.Name.Name == pkg } func identOrDefault(ident *ast.Ident, def string) string { if ident != nil { return ident.Name } return def } func formatFile(migration migration) ([]byte, error) { buf := new(bytes.Buffer) err := format.Node(buf, migration.fileset, migration.file) if err != nil { return nil, err } filename := migration.fileset.File(migration.file.Pos()).Name() return imports.Process(filename, buf.Bytes(), nil) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/main_test.go000066400000000000000000000023071341001157600254150ustar00rootroot00000000000000package main import ( "io/ioutil" "testing" "github.com/google/go-cmp/cmp" "gotest.tools/assert" "gotest.tools/env" "gotest.tools/fs" "gotest.tools/golden" ) func TestRun(t *testing.T) { setupLogging(&options{}) dir := fs.NewDir(t, "test-run", fs.WithDir("src/example.com/example", fs.FromDir("testdata/full"))) defer dir.Remove() defer env.Patch(t, "GO111MODULE", "off")() defer env.Patch(t, "GOPATH", dir.Path())() err := run(options{ pkgs: []string{"example.com/example"}, showLoaderErrors: true, }) assert.NilError(t, err) raw, err := ioutil.ReadFile(dir.Join("src/example.com/example/some_test.go")) assert.NilError(t, err) golden.Assert(t, string(raw), "full-expected/some_test.go") } func TestSetupFlags(t *testing.T) { flags, opts := setupFlags("testing") assert.Assert(t, flags.Usage != nil) err := flags.Parse([]string{ "--dry-run", "--debug", "--cmp-pkg-import-alias=foo", "--print-loader-errors", }) assert.NilError(t, err) expected := &options{ dryRun: true, debug: true, cmpImportName: "foo", showLoaderErrors: true, } assert.DeepEqual(t, opts, expected, cmpOptions) } var cmpOptions = cmp.AllowUnexported(options{}) gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/migrate.go000066400000000000000000000235521341001157600250670ustar00rootroot00000000000000package main import ( "go/ast" "go/token" "go/types" "log" "path" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/loader" ) const ( pkgTestifyAssert = "github.com/stretchr/testify/assert" pkgGopkgTestifyAssert = "gopkg.in/stretchr/testify.v1/assert" pkgTestifyRequire = "github.com/stretchr/testify/require" pkgGopkgTestifyRequire = "gopkg.in/stretchr/testify.v1/require" pkgAssert = "gotest.tools/assert" pkgCmp = "gotest.tools/assert/cmp" ) const ( funcNameAssert = "Assert" funcNameCheck = "Check" ) var allTestifyPks = []string{ pkgTestifyAssert, pkgTestifyRequire, pkgGopkgTestifyAssert, pkgGopkgTestifyRequire, } type migration struct { file *ast.File fileset *token.FileSet importNames importNames pkgInfo *loader.PackageInfo } func migrateFile(migration migration) { astutil.Apply(migration.file, nil, replaceCalls(migration)) updateImports(migration) } func updateImports(migration migration) { for _, remove := range allTestifyPks { astutil.DeleteImport(migration.fileset, migration.file, remove) } var alias string if migration.importNames.assert != path.Base(pkgAssert) { alias = migration.importNames.assert } astutil.AddNamedImport(migration.fileset, migration.file, alias, pkgAssert) if migration.importNames.cmp != path.Base(pkgCmp) { alias = migration.importNames.cmp } astutil.AddNamedImport(migration.fileset, migration.file, alias, pkgCmp) } type emptyNode struct{} func (n emptyNode) Pos() token.Pos { return 0 } func (n emptyNode) End() token.Pos { return 0 } var removeNode = emptyNode{} func replaceCalls(migration migration) func(cursor *astutil.Cursor) bool { return func(cursor *astutil.Cursor) bool { var newNode ast.Node switch typed := cursor.Node().(type) { case *ast.SelectorExpr: newNode = getReplacementTestingT(typed, migration.importNames) case *ast.CallExpr: newNode = getReplacementAssertion(typed, migration) case *ast.AssignStmt: newNode = getReplacementAssignment(typed, migration) } switch newNode { case nil: case removeNode: cursor.Delete() default: cursor.Replace(newNode) } return true } } func getReplacementTestingT(selector *ast.SelectorExpr, names importNames) ast.Node { xIdent, ok := selector.X.(*ast.Ident) if !ok { return nil } if selector.Sel.Name != "TestingT" || !names.matchesTestify(xIdent) { return nil } return &ast.SelectorExpr{ X: &ast.Ident{Name: names.assert, NamePos: xIdent.NamePos}, Sel: selector.Sel, } } func getReplacementAssertion(callExpr *ast.CallExpr, migration migration) ast.Node { tcall, ok := newTestifyCallFromNode(callExpr, migration) if !ok { return nil } if len(tcall.expr.Args) < 2 { return convertTestifySingleArgCall(tcall) } return convertTestifyAssertion(tcall, migration) } func getReplacementAssignment(assign *ast.AssignStmt, migration migration) ast.Node { if isAssignmentFromAssertNew(assign, migration) { return removeNode } return nil } func convertTestifySingleArgCall(tcall call) ast.Node { switch tcall.selExpr.Sel.Name { case "TestingT": // handled as SelectorExpr return nil case "New": // handled by getReplacementAssignment return nil default: log.Printf("%s: skipping unknown selector", tcall.StringWithFileInfo()) return nil } } // nolint: gocyclo func convertTestifyAssertion(tcall call, migration migration) ast.Node { imports := migration.importNames switch tcall.selExpr.Sel.Name { case "NoError", "NoErrorf": return convertNoError(tcall, imports) case "True", "Truef": return convertTrue(tcall, imports) case "False", "Falsef": return convertFalse(tcall, imports) case "Equal", "Equalf", "Exactly", "Exactlyf", "EqualValues", "EqualValuesf": return convertEqual(tcall, migration) case "Contains", "Containsf": return convertTwoArgComparison(tcall, imports, "Contains") case "Len", "Lenf": return convertTwoArgComparison(tcall, imports, "Len") case "Panics", "Panicsf": return convertOneArgComparison(tcall, imports, "Panics") case "EqualError", "EqualErrorf": return convertEqualError(tcall, imports) case "Error", "Errorf": return convertError(tcall, imports) case "Empty", "Emptyf": return convertEmpty(tcall, imports) case "Nil", "Nilf": return convertNil(tcall, migration) case "NotNil", "NotNilf": return convertNegativeComparison(tcall, imports, &ast.Ident{Name: "nil"}, 2) case "NotEqual", "NotEqualf": return convertNegativeComparison(tcall, imports, tcall.arg(2), 3) case "Fail", "Failf": return convertFail(tcall, "Error") case "FailNow", "FailNowf": return convertFail(tcall, "Fatal") case "NotEmpty", "NotEmptyf": return convertNotEmpty(tcall, imports) case "NotZero", "NotZerof": zero := &ast.BasicLit{Kind: token.INT, Value: "0"} return convertNegativeComparison(tcall, imports, zero, 2) } log.Printf("%s: skipping unsupported assertion", tcall.StringWithFileInfo()) return nil } func newCallExpr(x, sel string, args []ast.Expr) *ast.CallExpr { return &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{Name: x}, Sel: &ast.Ident{Name: sel}, }, Args: args, } } func newCallExprArgs(t ast.Expr, cmp ast.Expr, extra ...ast.Expr) []ast.Expr { return append(append([]ast.Expr{t}, cmp), extra...) } func newCallExprWithPosition(tcall call, imports importNames, args []ast.Expr) *ast.CallExpr { return &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: imports.assert, NamePos: tcall.xIdent.NamePos, }, Sel: &ast.Ident{Name: tcall.assert}, }, Args: args, } } func convertNoError(tcall call, imports importNames) ast.Node { // use assert.NilError() for require.NoError() if tcall.assert == funcNameAssert { return newCallExprWithoutComparison(tcall, imports, "NilError") } // use assert.Check() for assert.NoError() return newCallExprWithoutComparison(tcall, imports, "Check") } func convertEqualError(tcall call, imports importNames) ast.Node { if tcall.assert == funcNameAssert { return newCallExprWithoutComparison(tcall, imports, "Error") } return convertTwoArgComparison(tcall, imports, "Error") } func newCallExprWithoutComparison(tcall call, imports importNames, name string) ast.Node { return &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: imports.assert, NamePos: tcall.xIdent.NamePos, }, Sel: &ast.Ident{Name: name}, }, Args: tcall.expr.Args, } } func convertOneArgComparison(tcall call, imports importNames, cmpName string) ast.Node { return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), newCallExpr(imports.cmp, cmpName, []ast.Expr{tcall.arg(1)}), tcall.extraArgs(2)...)) } func convertTrue(tcall call, imports importNames) ast.Node { return newCallExprWithPosition(tcall, imports, tcall.expr.Args) } func convertFalse(tcall call, imports importNames) ast.Node { return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), &ast.UnaryExpr{Op: token.NOT, X: tcall.arg(1)}, tcall.extraArgs(2)...)) } func convertEqual(tcall call, migration migration) ast.Node { imports := migration.importNames hasExtraArgs := len(tcall.extraArgs(3)) > 0 cmpEqual := convertTwoArgComparison(tcall, imports, "Equal") if tcall.assert == funcNameAssert { cmpEqual = newCallExprWithoutComparison(tcall, imports, "Equal") } cmpDeepEqual := convertTwoArgComparison(tcall, imports, "DeepEqual") if tcall.assert == funcNameAssert && !hasExtraArgs { cmpDeepEqual = newCallExprWithoutComparison(tcall, imports, "DeepEqual") } gotype := walkForType(migration.pkgInfo, tcall.arg(1)) if isUnknownType(gotype) { gotype = walkForType(migration.pkgInfo, tcall.arg(2)) } if isUnknownType(gotype) { return cmpDeepEqual } switch gotype.Underlying().(type) { case *types.Basic: return cmpEqual default: return cmpDeepEqual } } func convertTwoArgComparison(tcall call, imports importNames, cmpName string) ast.Node { return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), newCallExpr(imports.cmp, cmpName, tcall.args(1, 3)), tcall.extraArgs(3)...)) } func convertError(tcall call, imports importNames) ast.Node { cmpArgs := []ast.Expr{ tcall.arg(1), &ast.BasicLit{Kind: token.STRING, Value: `""`}} return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), newCallExpr(imports.cmp, "ErrorContains", cmpArgs), tcall.extraArgs(2)...)) } func convertEmpty(tcall call, imports importNames) ast.Node { cmpArgs := []ast.Expr{ tcall.arg(1), &ast.BasicLit{Kind: token.INT, Value: "0"}, } return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), newCallExpr(imports.cmp, "Len", cmpArgs), tcall.extraArgs(2)...)) } func convertNil(tcall call, migration migration) ast.Node { gotype := walkForType(migration.pkgInfo, tcall.arg(1)) if gotype != nil && gotype.String() == "error" { return convertNoError(tcall, migration.importNames) } return convertOneArgComparison(tcall, migration.importNames, "Nil") } func convertNegativeComparison( tcall call, imports importNames, right ast.Expr, extra int, ) ast.Node { return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), &ast.BinaryExpr{X: tcall.arg(1), Op: token.NEQ, Y: right}, tcall.extraArgs(extra)...)) } func convertFail(tcall call, selector string) ast.Node { extraArgs := tcall.extraArgs(1) if len(extraArgs) > 1 { selector = selector + "f" } return &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: tcall.testingT(), Sel: &ast.Ident{Name: selector}, }, Args: extraArgs, } } func convertNotEmpty(tcall call, imports importNames) ast.Node { lenExpr := &ast.CallExpr{ Fun: &ast.Ident{Name: "len"}, Args: tcall.args(1, 2), } zeroExpr := &ast.BasicLit{Kind: token.INT, Value: "0"} return newCallExprWithPosition(tcall, imports, newCallExprArgs( tcall.testingT(), &ast.BinaryExpr{X: lenExpr, Op: token.NEQ, Y: zeroExpr}, tcall.extraArgs(2)...)) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/migrate_test.go000066400000000000000000000127351341001157600261270ustar00rootroot00000000000000package main import ( "go/parser" "go/token" "testing" "golang.org/x/tools/go/loader" "gotest.tools/assert" "gotest.tools/assert/cmp" ) func TestMigrateFileReplacesTestingT(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSomething(t *testing.T) { a := assert.TestingT b := require.TestingT c := require.TestingT(t) if a == b {} } func do(t require.TestingT) {} ` migration := newMigrationFromSource(t, source) migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" ) func TestSomething(t *testing.T) { a := assert.TestingT b := assert.TestingT c := assert.TestingT(t) if a == b { } } func do(t assert.TestingT) {} ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } func newMigrationFromSource(t *testing.T, source string) migration { fileset := token.NewFileSet() nodes, err := parser.ParseFile( fileset, "foo.go", source, parser.AllErrors|parser.ParseComments) assert.NilError(t, err) fakeImporter, err := newFakeImporter() assert.NilError(t, err) defer fakeImporter.Cleanup() opts := options{} conf := loader.Config{ Fset: fileset, ParserMode: parser.ParseComments, Build: buildContext(opts), AllowErrors: true, FindPackage: fakeImporter.Import, } conf.TypeChecker.Error = func(e error) {} conf.CreateFromFiles("foo.go", nodes) prog, err := conf.Load() assert.NilError(t, err) pkgInfo := prog.InitialPackages()[0] return migration{ file: pkgInfo.Files[0], fileset: fileset, importNames: newImportNames(nodes.Imports, opts), pkgInfo: pkgInfo, } } func TestMigrateFileWithNamedCmpPackage(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/assert" ) func TestSomething(t *testing.T) { assert.Equal(t, "a", "b") } ` migration := newMigrationFromSource(t, source) migration.importNames.cmp = "is" migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" is "gotest.tools/assert/cmp" ) func TestSomething(t *testing.T) { assert.Check(t, is.Equal("a", "b")) } ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } func TestMigrateFileWithCommentsOnAssert(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/assert" ) func TestSomething(t *testing.T) { // This is going to fail assert.Equal(t, "a", "b") } ` migration := newMigrationFromSource(t, source) migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" ) func TestSomething(t *testing.T) { // This is going to fail assert.Check(t, cmp.Equal("a", "b")) } ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } func TestMigrateFileConvertNilToNilError(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" ) func TestSomething(t *testing.T) { var err error assert.Nil(t, err) require.Nil(t, err) } ` migration := newMigrationFromSource(t, source) migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" ) func TestSomething(t *testing.T) { var err error assert.Check(t, err) assert.NilError(t, err) } ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } func TestMigrateFileConvertAssertNew(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSomething(t *testing.T) { is := assert.New(t) is.Equal("one", "two") is.NotEqual("one", "two") assert := require.New(t) assert.Equal("one", "two") assert.NotEqual("one", "two") } func TestOtherName(z *testing.T) { is := require.New(z) is.Equal("one", "two") } ` migration := newMigrationFromSource(t, source) migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" ) func TestSomething(t *testing.T) { assert.Check(t, cmp.Equal("one", "two")) assert.Check(t, "one" != "two") assert.Equal(t, "one", "two") assert.Assert(t, "one" != "two") } func TestOtherName(z *testing.T) { assert.Equal(z, "one", "two") } ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } func TestMigrateFileWithExtraArgs(t *testing.T) { source := ` package foo import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSomething(t *testing.T) { var err error assert.Error(t, err, "this is a comment") assert.Empty(t, nil, "more comment") require.Equal(t, []string{}, []string{}, "because") } ` migration := newMigrationFromSource(t, source) migration.importNames.cmp = "is" migrateFile(migration) expected := `package foo import ( "testing" "gotest.tools/assert" is "gotest.tools/assert/cmp" ) func TestSomething(t *testing.T) { var err error assert.Check(t, is.ErrorContains(err, ""), "this is a comment") assert.Check(t, is.Len(nil, 0), "more comment") assert.Assert(t, is.DeepEqual([]string{}, []string{}), "because") } ` actual, err := formatFile(migration) assert.NilError(t, err) assert.Assert(t, cmp.Equal(expected, string(actual))) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/testdata/000077500000000000000000000000001341001157600247125ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/testdata/full-expected/000077500000000000000000000000001341001157600274535ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/testdata/full-expected/some_test.go000066400000000000000000000057041341001157600320120ustar00rootroot00000000000000package foo import ( "fmt" "testing" "github.com/go-check/check" "gotest.tools/assert" "gotest.tools/assert/cmp" ) type mystruct struct { a int expected int } func TestFirstThing(t *testing.T) { rt := assert.TestingT(t) assert.Check(t, cmp.Equal("foo", "bar")) assert.Check(t, cmp.Equal(1, 2)) assert.Check(t, false) assert.Check(t, !true) assert.NilError(rt, nil) assert.Check(t, cmp.DeepEqual(map[string]bool{"a": true}, nil)) assert.Check(t, cmp.DeepEqual([]int{1}, nil)) assert.Equal(rt, "a", "B") } func TestSecondThing(t *testing.T) { var foo mystruct assert.DeepEqual(t, foo, mystruct{}) assert.DeepEqual(t, mystruct{}, mystruct{}) assert.Check(t, nil, "foo %d", 3) assert.NilError(t, nil, "foo %d", 3) assert.Check(t, cmp.ErrorContains(fmt.Errorf("foo"), "")) assert.Assert(t, 77 != 0) } func TestOthers(t *testing.T) { assert.Check(t, cmp.Contains([]string{}, "foo")) assert.Assert(t, cmp.Len([]int{}, 3)) assert.Check(t, cmp.Panics(func() { panic("foo") })) assert.Error(t, fmt.Errorf("bad days"), "good days") assert.Check(t, nil != nil) t.Error("why") t.Fatal("why not") assert.Assert(t, len([]bool{}) != 0) // Unsupported asseert assert.NotContains(t, []bool{}, true) } func TestAssertNew(t *testing.T) { assert.Check(t, cmp.Equal("a", "b")) } type unit struct { c *testing.T } func thing(t *testing.T) unit { return unit{c: t} } func TestStoredTestingT(t *testing.T) { u := thing(t) assert.Check(u.c, cmp.Equal("A", "b")) u = unit{c: t} assert.Check(u.c, cmp.Equal("A", "b")) } func TestNotNamedT(c *testing.T) { assert.Check(c, cmp.Equal("A", "b")) } func TestEqualsWithComplexTypes(t *testing.T) { expected := []int{1, 2, 3} assert.Check(t, cmp.DeepEqual(expected, nil)) expectedM := map[int]bool{} assert.Check(t, cmp.DeepEqual(expectedM, nil)) expectedI := 123 assert.Check(t, cmp.Equal(expectedI, 0)) assert.Check(t, cmp.Equal(doInt(), 3)) // TODO: struct field } func doInt() int { return 1 } func TestEqualWithPrimitiveTypes(t *testing.T) { s := "foo" ptrString := &s assert.Check(t, cmp.Equal(*ptrString, "foo")) assert.Check(t, cmp.Equal(doInt(), doInt())) x := doInt() y := doInt() assert.Check(t, cmp.Equal(x, y)) tc := mystruct{a: 3, expected: 5} assert.Check(t, cmp.Equal(tc.a, tc.expected)) } func TestTableTest(t *testing.T) { var testcases = []struct { opts []string actual string expected string expectedOpts []string }{ { opts: []string{"a", "b"}, actual: "foo", expected: "else", }, } for _, testcase := range testcases { assert.Check(t, cmp.Equal(testcase.actual, testcase.expected)) assert.Check(t, cmp.DeepEqual(testcase.opts, testcase.expectedOpts)) } } func TestWithChecker(c *check.C) { var err error assert.Check(c, err) } func HelperWithAssertTestingT(t assert.TestingT) { var err error assert.Check(t, err, "with assert.TestingT") } func BenchmarkSomething(b *testing.B) { var err error assert.Check(b, err) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/testdata/full/000077500000000000000000000000001341001157600256545ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/testdata/full/some_test.go000066400000000000000000000053421341001157600302110ustar00rootroot00000000000000package foo import ( "fmt" "testing" "github.com/go-check/check" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type mystruct struct { a int expected int } func TestFirstThing(t *testing.T) { rt := require.TestingT(t) assert.Equal(t, "foo", "bar") assert.Equal(t, 1, 2) assert.True(t, false) assert.False(t, true) require.NoError(rt, nil) assert.Equal(t, map[string]bool{"a": true}, nil) assert.Equal(t, []int{1}, nil) require.Equal(rt, "a", "B") } func TestSecondThing(t *testing.T) { var foo mystruct require.Equal(t, foo, mystruct{}) require.Equal(t, mystruct{}, mystruct{}) assert.NoError(t, nil, "foo %d", 3) require.NoError(t, nil, "foo %d", 3) assert.Error(t, fmt.Errorf("foo")) require.NotZero(t, 77) } func TestOthers(t *testing.T) { assert.Contains(t, []string{}, "foo") require.Len(t, []int{}, 3) assert.Panics(t, func() { panic("foo") }) require.EqualError(t, fmt.Errorf("bad days"), "good days") assert.NotNil(t, nil) assert.Fail(t, "why") assert.FailNow(t, "why not") require.NotEmpty(t, []bool{}) // Unsupported asseert assert.NotContains(t, []bool{}, true) } func TestAssertNew(t *testing.T) { a := assert.New(t) a.Equal("a", "b") } type unit struct { c *testing.T } func thing(t *testing.T) unit { return unit{c: t} } func TestStoredTestingT(t *testing.T) { u := thing(t) assert.Equal(u.c, "A", "b") u = unit{c: t} assert.Equal(u.c, "A", "b") } func TestNotNamedT(c *testing.T) { assert.Equal(c, "A", "b") } func TestEqualsWithComplexTypes(t *testing.T) { expected := []int{1, 2, 3} assert.Equal(t, expected, nil) expectedM := map[int]bool{} assert.Equal(t, expectedM, nil) expectedI := 123 assert.Equal(t, expectedI, 0) assert.Equal(t, doInt(), 3) // TODO: struct field } func doInt() int { return 1 } func TestEqualWithPrimitiveTypes(t *testing.T) { s := "foo" ptrString := &s assert.Equal(t, *ptrString, "foo") assert.Equal(t, doInt(), doInt()) x := doInt() y := doInt() assert.Equal(t, x, y) tc := mystruct{a: 3, expected: 5} assert.Equal(t, tc.a, tc.expected) } func TestTableTest(t *testing.T) { var testcases = []struct { opts []string actual string expected string expectedOpts []string }{ { opts: []string{"a", "b"}, actual: "foo", expected: "else", }, } for _, testcase := range testcases { assert.Equal(t, testcase.actual, testcase.expected) assert.Equal(t, testcase.opts, testcase.expectedOpts) } } func TestWithChecker(c *check.C) { var err error assert.NoError(c, err) } func HelperWithAssertTestingT(t assert.TestingT) { var err error assert.NoError(t, err, "with assert.TestingT") } func BenchmarkSomething(b *testing.B) { var err error assert.NoError(b, err) } gotest.tools-2.3.0/assert/cmd/gty-migrate-from-testify/walktype.go000066400000000000000000000012151341001157600252670ustar00rootroot00000000000000package main import ( "go/ast" "go/types" "golang.org/x/tools/go/loader" ) // walkForType walks the AST tree and returns the type of the expression func walkForType(pkgInfo *loader.PackageInfo, node ast.Node) types.Type { var result types.Type visit := func(node ast.Node) bool { if expr, ok := node.(ast.Expr); ok { if typeAndValue, ok := pkgInfo.Types[expr]; ok { result = typeAndValue.Type return false } } return true } ast.Inspect(node, visit) return result } func isUnknownType(typ types.Type) bool { if typ == nil { return true } basic, ok := typ.(*types.Basic) return ok && basic.Kind() == types.Invalid } gotest.tools-2.3.0/assert/cmp/000077500000000000000000000000001341001157600162365ustar00rootroot00000000000000gotest.tools-2.3.0/assert/cmp/compare.go000066400000000000000000000242311341001157600202150ustar00rootroot00000000000000/*Package cmp provides Comparisons for Assert and Check*/ package cmp // import "gotest.tools/assert/cmp" import ( "fmt" "reflect" "regexp" "strings" "github.com/google/go-cmp/cmp" "gotest.tools/internal/format" ) // Comparison is a function which compares values and returns ResultSuccess if // the actual value matches the expected value. If the values do not match the // Result will contain a message about why it failed. type Comparison func() Result // DeepEqual compares two values using google/go-cmp (http://bit.do/go-cmp) // and succeeds if the values are equal. // // The comparison can be customized using comparison Options. // Package https://godoc.org/gotest.tools/assert/opt provides some additional // commonly used Options. func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison { return func() (result Result) { defer func() { if panicmsg, handled := handleCmpPanic(recover()); handled { result = ResultFailure(panicmsg) } }() diff := cmp.Diff(x, y, opts...) if diff == "" { return ResultSuccess } return multiLineDiffResult(diff) } } func handleCmpPanic(r interface{}) (string, bool) { if r == nil { return "", false } panicmsg, ok := r.(string) if !ok { panic(r) } switch { case strings.HasPrefix(panicmsg, "cannot handle unexported field"): return panicmsg, true } panic(r) } func toResult(success bool, msg string) Result { if success { return ResultSuccess } return ResultFailure(msg) } // RegexOrPattern may be either a *regexp.Regexp or a string that is a valid // regexp pattern. type RegexOrPattern interface{} // Regexp succeeds if value v matches regular expression re. // // Example: // assert.Assert(t, cmp.Regexp("^[0-9a-f]{32}$", str)) // r := regexp.MustCompile("^[0-9a-f]{32}$") // assert.Assert(t, cmp.Regexp(r, str)) func Regexp(re RegexOrPattern, v string) Comparison { match := func(re *regexp.Regexp) Result { return toResult( re.MatchString(v), fmt.Sprintf("value %q does not match regexp %q", v, re.String())) } return func() Result { switch regex := re.(type) { case *regexp.Regexp: return match(regex) case string: re, err := regexp.Compile(regex) if err != nil { return ResultFailure(err.Error()) } return match(re) default: return ResultFailure(fmt.Sprintf("invalid type %T for regex pattern", regex)) } } } // Equal succeeds if x == y. See assert.Equal for full documentation. func Equal(x, y interface{}) Comparison { return func() Result { switch { case x == y: return ResultSuccess case isMultiLineStringCompare(x, y): diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)}) return multiLineDiffResult(diff) } return ResultFailureTemplate(` {{- .Data.x}} ( {{- with callArg 0 }}{{ formatNode . }} {{end -}} {{- printf "%T" .Data.x -}} ) != {{ .Data.y}} ( {{- with callArg 1 }}{{ formatNode . }} {{end -}} {{- printf "%T" .Data.y -}} )`, map[string]interface{}{"x": x, "y": y}) } } func isMultiLineStringCompare(x, y interface{}) bool { strX, ok := x.(string) if !ok { return false } strY, ok := y.(string) if !ok { return false } return strings.Contains(strX, "\n") || strings.Contains(strY, "\n") } func multiLineDiffResult(diff string) Result { return ResultFailureTemplate(` --- {{ with callArg 0 }}{{ formatNode . }}{{else}}←{{end}} +++ {{ with callArg 1 }}{{ formatNode . }}{{else}}→{{end}} {{ .Data.diff }}`, map[string]interface{}{"diff": diff}) } // Len succeeds if the sequence has the expected length. func Len(seq interface{}, expected int) Comparison { return func() (result Result) { defer func() { if e := recover(); e != nil { result = ResultFailure(fmt.Sprintf("type %T does not have a length", seq)) } }() value := reflect.ValueOf(seq) length := value.Len() if length == expected { return ResultSuccess } msg := fmt.Sprintf("expected %s (length %d) to have length %d", seq, length, expected) return ResultFailure(msg) } } // Contains succeeds if item is in collection. Collection may be a string, map, // slice, or array. // // If collection is a string, item must also be a string, and is compared using // strings.Contains(). // If collection is a Map, contains will succeed if item is a key in the map. // If collection is a slice or array, item is compared to each item in the // sequence using reflect.DeepEqual(). func Contains(collection interface{}, item interface{}) Comparison { return func() Result { colValue := reflect.ValueOf(collection) if !colValue.IsValid() { return ResultFailure(fmt.Sprintf("nil does not contain items")) } msg := fmt.Sprintf("%v does not contain %v", collection, item) itemValue := reflect.ValueOf(item) switch colValue.Type().Kind() { case reflect.String: if itemValue.Type().Kind() != reflect.String { return ResultFailure("string may only contain strings") } return toResult( strings.Contains(colValue.String(), itemValue.String()), fmt.Sprintf("string %q does not contain %q", collection, item)) case reflect.Map: if itemValue.Type() != colValue.Type().Key() { return ResultFailure(fmt.Sprintf( "%v can not contain a %v key", colValue.Type(), itemValue.Type())) } return toResult(colValue.MapIndex(itemValue).IsValid(), msg) case reflect.Slice, reflect.Array: for i := 0; i < colValue.Len(); i++ { if reflect.DeepEqual(colValue.Index(i).Interface(), item) { return ResultSuccess } } return ResultFailure(msg) default: return ResultFailure(fmt.Sprintf("type %T does not contain items", collection)) } } } // Panics succeeds if f() panics. func Panics(f func()) Comparison { return func() (result Result) { defer func() { if err := recover(); err != nil { result = ResultSuccess } }() f() return ResultFailure("did not panic") } } // Error succeeds if err is a non-nil error, and the error message equals the // expected message. func Error(err error, message string) Comparison { return func() Result { switch { case err == nil: return ResultFailure("expected an error, got nil") case err.Error() != message: return ResultFailure(fmt.Sprintf( "expected error %q, got %s", message, formatErrorMessage(err))) } return ResultSuccess } } // ErrorContains succeeds if err is a non-nil error, and the error message contains // the expected substring. func ErrorContains(err error, substring string) Comparison { return func() Result { switch { case err == nil: return ResultFailure("expected an error, got nil") case !strings.Contains(err.Error(), substring): return ResultFailure(fmt.Sprintf( "expected error to contain %q, got %s", substring, formatErrorMessage(err))) } return ResultSuccess } } func formatErrorMessage(err error) string { if _, ok := err.(interface { Cause() error }); ok { return fmt.Sprintf("%q\n%+v", err, err) } // This error was not wrapped with github.com/pkg/errors return fmt.Sprintf("%q", err) } // Nil succeeds if obj is a nil interface, pointer, or function. // // Use NilError() for comparing errors. Use Len(obj, 0) for comparing slices, // maps, and channels. func Nil(obj interface{}) Comparison { msgFunc := func(value reflect.Value) string { return fmt.Sprintf("%v (type %s) is not nil", reflect.Indirect(value), value.Type()) } return isNil(obj, msgFunc) } func isNil(obj interface{}, msgFunc func(reflect.Value) string) Comparison { return func() Result { if obj == nil { return ResultSuccess } value := reflect.ValueOf(obj) kind := value.Type().Kind() if kind >= reflect.Chan && kind <= reflect.Slice { if value.IsNil() { return ResultSuccess } return ResultFailure(msgFunc(value)) } return ResultFailure(fmt.Sprintf("%v (type %s) can not be nil", value, value.Type())) } } // ErrorType succeeds if err is not nil and is of the expected type. // // Expected can be one of: // a func(error) bool which returns true if the error is the expected type, // an instance of (or a pointer to) a struct of the expected type, // a pointer to an interface the error is expected to implement, // a reflect.Type of the expected struct or interface. func ErrorType(err error, expected interface{}) Comparison { return func() Result { switch expectedType := expected.(type) { case func(error) bool: return cmpErrorTypeFunc(err, expectedType) case reflect.Type: if expectedType.Kind() == reflect.Interface { return cmpErrorTypeImplementsType(err, expectedType) } return cmpErrorTypeEqualType(err, expectedType) case nil: return ResultFailure(fmt.Sprintf("invalid type for expected: nil")) } expectedType := reflect.TypeOf(expected) switch { case expectedType.Kind() == reflect.Struct, isPtrToStruct(expectedType): return cmpErrorTypeEqualType(err, expectedType) case isPtrToInterface(expectedType): return cmpErrorTypeImplementsType(err, expectedType.Elem()) } return ResultFailure(fmt.Sprintf("invalid type for expected: %T", expected)) } } func cmpErrorTypeFunc(err error, f func(error) bool) Result { if f(err) { return ResultSuccess } actual := "nil" if err != nil { actual = fmt.Sprintf("%s (%T)", err, err) } return ResultFailureTemplate(`error is {{ .Data.actual }} {{- with callArg 1 }}, not {{ formatNode . }}{{end -}}`, map[string]interface{}{"actual": actual}) } func cmpErrorTypeEqualType(err error, expectedType reflect.Type) Result { if err == nil { return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType)) } errValue := reflect.ValueOf(err) if errValue.Type() == expectedType { return ResultSuccess } return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType)) } func cmpErrorTypeImplementsType(err error, expectedType reflect.Type) Result { if err == nil { return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType)) } errValue := reflect.ValueOf(err) if errValue.Type().Implements(expectedType) { return ResultSuccess } return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType)) } func isPtrToInterface(typ reflect.Type) bool { return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Interface } func isPtrToStruct(typ reflect.Type) bool { return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct } gotest.tools-2.3.0/assert/cmp/compare_test.go000066400000000000000000000314451341001157600212610ustar00rootroot00000000000000package cmp import ( "fmt" "go/ast" "io" "reflect" "regexp" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" ) func TestDeepEqual(t *testing.T) { actual := DeepEqual([]string{"a", "b"}, []string{"b", "a"})() expected := ` --- result +++ exp {[]string}[0]: -: "a" +: "b" {[]string}[1]: -: "b" +: "a" ` args := []ast.Expr{&ast.Ident{Name: "result"}, &ast.Ident{Name: "exp"}} assertFailureTemplate(t, actual, args, expected) actual = DeepEqual([]string{"a"}, []string{"a"})() assertSuccess(t, actual) } type Stub struct { unx int } func TestDeepEqualeWithUnexported(t *testing.T) { result := DeepEqual(Stub{}, Stub{unx: 1})() assertFailure(t, result, `cannot handle unexported field: {cmp.Stub}.unx consider using AllowUnexported or cmpopts.IgnoreUnexported`) } func TestRegexp(t *testing.T) { var testcases = []struct { name string regex interface{} value string match bool expErr string }{ { name: "pattern string match", regex: "^[0-9]+$", value: "12123423456", match: true, }, { name: "simple pattern string no match", regex: "bob", value: "Probably", expErr: `value "Probably" does not match regexp "bob"`, }, { name: "pattern string no match", regex: "^1", value: "2123423456", expErr: `value "2123423456" does not match regexp "^1"`, }, { name: "regexp match", regex: regexp.MustCompile("^d[0-9a-f]{8}$"), value: "d1632beef", match: true, }, { name: "invalid regexp", regex: "^1(", value: "2", expErr: "error parsing regexp: missing closing ): `^1(`", }, { name: "invalid type", regex: struct{}{}, value: "some string", expErr: "invalid type struct {} for regex pattern", }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { res := Regexp(tc.regex, tc.value)() if tc.match { assertSuccess(t, res) } else { assertFailure(t, res, tc.expErr) } }) } } func TestLen(t *testing.T) { var testcases = []struct { seq interface{} length int expectedSuccess bool expectedMessage string }{ { seq: []string{"A", "b", "c"}, length: 3, expectedSuccess: true, }, { seq: []string{"A", "b", "c"}, length: 2, expectedMessage: "expected [A b c] (length 3) to have length 2", }, { seq: map[string]int{"a": 1, "b": 2}, length: 2, expectedSuccess: true, }, { seq: [3]string{"a", "b", "c"}, length: 3, expectedSuccess: true, }, { seq: "abcd", length: 4, expectedSuccess: true, }, { seq: "abcd", length: 3, expectedMessage: "expected abcd (length 4) to have length 3", }, } for _, testcase := range testcases { t.Run(fmt.Sprintf("%v len=%d", testcase.seq, testcase.length), func(t *testing.T) { result := Len(testcase.seq, testcase.length)() if testcase.expectedSuccess { assertSuccess(t, result) } else { assertFailure(t, result, testcase.expectedMessage) } }) } } func TestPanics(t *testing.T) { panicker := func() { panic("AHHHHHHHHHHH") } result := Panics(panicker)() assertSuccess(t, result) result = Panics(func() {})() assertFailure(t, result, "did not panic") } type innerstub struct { num int } type stub struct { stub innerstub num int } func TestDeepEqualEquivalenceToReflectDeepEqual(t *testing.T) { var testcases = []struct { left interface{} right interface{} }{ {nil, nil}, {7, 7}, {false, false}, {stub{innerstub{1}, 2}, stub{innerstub{1}, 2}}, {[]int{1, 2, 3}, []int{1, 2, 3}}, {[]byte(nil), []byte(nil)}, {nil, []byte(nil)}, {1, uint64(1)}, {7, "7"}, } for _, testcase := range testcases { expected := reflect.DeepEqual(testcase.left, testcase.right) res := DeepEqual(testcase.left, testcase.right, cmpStub)() if res.Success() != expected { msg := res.(StringResult).FailureMessage() t.Errorf("deepEqual(%v, %v) did not return %v (message %s)", testcase.left, testcase.right, expected, msg) } } } var cmpStub = cmp.AllowUnexported(stub{}, innerstub{}) func TestContains(t *testing.T) { var testcases = []struct { seq interface{} item interface{} expected bool expectedMsg string }{ { seq: error(nil), item: 0, expectedMsg: "nil does not contain items", }, { seq: "abcdef", item: "cde", expected: true, }, { seq: "abcdef", item: "foo", expectedMsg: `string "abcdef" does not contain "foo"`, }, { seq: "abcdef", item: 3, expectedMsg: `string may only contain strings`, }, { seq: map[rune]int{'a': 1, 'b': 2}, item: 'b', expected: true, }, { seq: map[rune]int{'a': 1}, item: 'c', expectedMsg: "map[97:1] does not contain 99", }, { seq: map[int]int{'a': 1, 'b': 2}, item: 'b', expectedMsg: "map[int]int can not contain a int32 key", }, { seq: []interface{}{"a", 1, 'a', 1.0, true}, item: 'a', expected: true, }, { seq: []interface{}{"a", 1, 'a', 1.0, true}, item: 3, expectedMsg: "[a 1 97 1 true] does not contain 3", }, { seq: [3]byte{99, 10, 100}, item: byte(99), expected: true, }, { seq: [3]byte{99, 10, 100}, item: byte(98), expectedMsg: "[99 10 100] does not contain 98", }, } for _, testcase := range testcases { name := fmt.Sprintf("%v in %v", testcase.item, testcase.seq) t.Run(name, func(t *testing.T) { result := Contains(testcase.seq, testcase.item)() if testcase.expected { assertSuccess(t, result) } else { assertFailure(t, result, testcase.expectedMsg) } }) } } func TestEqualMultiLine(t *testing.T) { result := `abcd 1234 aaaa bbbb` exp := `abcd 1111 aaaa bbbb` expected := ` --- result +++ exp @@ -1,4 +1,4 @@ abcd -1234 +1111 aaaa bbbb ` args := []ast.Expr{&ast.Ident{Name: "result"}, &ast.Ident{Name: "exp"}} res := Equal(result, exp)() assertFailureTemplate(t, res, args, expected) } func TestError(t *testing.T) { result := Error(nil, "the error message")() assertFailure(t, result, "expected an error, got nil") // A Wrapped error also includes the stack result = Error(errors.Wrap(errors.New("other"), "wrapped"), "the error message")() assertFailureHasPrefix(t, result, `expected error "the error message", got "wrapped: other" other gotest.tools/assert/cmp.TestError`) msg := "the message" result = Error(errors.New(msg), msg)() assertSuccess(t, result) } func TestErrorContains(t *testing.T) { result := ErrorContains(nil, "the error message")() assertFailure(t, result, "expected an error, got nil") result = ErrorContains(errors.New("other"), "the error")() assertFailureHasPrefix(t, result, `expected error to contain "the error", got "other"`) msg := "the full message" result = ErrorContains(errors.New(msg), "full")() assertSuccess(t, result) } func TestNil(t *testing.T) { result := Nil(nil)() assertSuccess(t, result) var s *string result = Nil(s)() assertSuccess(t, result) var closer io.Closer result = Nil(closer)() assertSuccess(t, result) result = Nil("wrong")() assertFailure(t, result, "wrong (type string) can not be nil") notnil := "notnil" result = Nil(¬nil)() assertFailure(t, result, "notnil (type *string) is not nil") result = Nil([]string{"a"})() assertFailure(t, result, "[a] (type []string) is not nil") } type testingT interface { Errorf(msg string, args ...interface{}) } type helperT interface { Helper() } func assertSuccess(t testingT, res Result) { if ht, ok := t.(helperT); ok { ht.Helper() } if !res.Success() { msg := res.(StringResult).FailureMessage() t.Errorf("expected success, but got failure with message %q", msg) } } func assertFailure(t testingT, res Result, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(StringResult).FailureMessage() if message != expected { t.Errorf("expected \n%q\ngot\n%q\n", expected, message) } } func assertFailureHasPrefix(t testingT, res Result, prefix string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(StringResult).FailureMessage() if !strings.HasPrefix(message, prefix) { t.Errorf("expected \n%v\nto start with\n%v\n", message, prefix) } } // nolint: unparam func assertFailureTemplate(t testingT, res Result, args []ast.Expr, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(templatedResult).FailureMessage(args) if message != expected { t.Errorf("expected \n%q\ngot\n%q\n", expected, message) } } type stubError struct{} func (s stubError) Error() string { return "stub error" } func isErrorOfTypeStub(err error) bool { return reflect.TypeOf(err) == reflect.TypeOf(stubError{}) } type notStubError struct{} func (s notStubError) Error() string { return "not stub error" } func isErrorOfTypeNotStub(err error) bool { return reflect.TypeOf(err) == reflect.TypeOf(notStubError{}) } type specialStubIface interface { Special() } type stubPtrError struct{} func (s *stubPtrError) Error() string { return "stub ptr error" } func TestErrorTypeWithNil(t *testing.T) { var testcases = []struct { name string expType interface{} expected string }{ { name: "with struct", expType: stubError{}, expected: "error is nil, not cmp.stubError", }, { name: "with pointer to struct", expType: &stubPtrError{}, expected: "error is nil, not *cmp.stubPtrError", }, { name: "with interface", expType: (*specialStubIface)(nil), expected: "error is nil, not cmp.specialStubIface", }, { name: "with reflect.Type", expType: reflect.TypeOf(stubError{}), expected: "error is nil, not cmp.stubError", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(nil, testcase.expType)() assertFailure(t, result, testcase.expected) }) } } func TestErrorTypeSuccess(t *testing.T) { var testcases = []struct { name string expType interface{} err error }{ { name: "with function", expType: isErrorOfTypeStub, err: stubError{}, }, { name: "with struct", expType: stubError{}, err: stubError{}, }, { name: "with pointer to struct", expType: &stubPtrError{}, err: &stubPtrError{}, }, { name: "with interface", expType: (*error)(nil), err: stubError{}, }, { name: "with reflect.Type struct", expType: reflect.TypeOf(stubError{}), err: stubError{}, }, { name: "with reflect.Type interface", expType: reflect.TypeOf((*error)(nil)).Elem(), err: stubError{}, }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(testcase.err, testcase.expType)() assertSuccess(t, result) }) } } func TestErrorTypeFailure(t *testing.T) { var testcases = []struct { name string expType interface{} expected string }{ { name: "with struct", expType: notStubError{}, expected: "error is stub error (cmp.stubError), not cmp.notStubError", }, { name: "with pointer to struct", expType: &stubPtrError{}, expected: "error is stub error (cmp.stubError), not *cmp.stubPtrError", }, { name: "with interface", expType: (*specialStubIface)(nil), expected: "error is stub error (cmp.stubError), not cmp.specialStubIface", }, { name: "with reflect.Type struct", expType: reflect.TypeOf(notStubError{}), expected: "error is stub error (cmp.stubError), not cmp.notStubError", }, { name: "with reflect.Type interface", expType: reflect.TypeOf((*specialStubIface)(nil)).Elem(), expected: "error is stub error (cmp.stubError), not cmp.specialStubIface", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(stubError{}, testcase.expType)() assertFailure(t, result, testcase.expected) }) } } func TestErrorTypeInvalid(t *testing.T) { result := ErrorType(stubError{}, nil)() assertFailure(t, result, "invalid type for expected: nil") result = ErrorType(stubError{}, "my type!")() assertFailure(t, result, "invalid type for expected: string") } func TestErrorTypeWithFunc(t *testing.T) { result := ErrorType(nil, isErrorOfTypeStub)() assertFailureTemplate(t, result, []ast.Expr{nil, &ast.Ident{Name: "isErrorOfTypeStub"}}, "error is nil, not isErrorOfTypeStub") result = ErrorType(stubError{}, isErrorOfTypeNotStub)() assertFailureTemplate(t, result, []ast.Expr{nil, &ast.Ident{Name: "isErrorOfTypeNotStub"}}, "error is stub error (cmp.stubError), not isErrorOfTypeNotStub") } gotest.tools-2.3.0/assert/cmp/result.go000066400000000000000000000047711341001157600201140ustar00rootroot00000000000000package cmp import ( "bytes" "fmt" "go/ast" "text/template" "gotest.tools/internal/source" ) // A Result of a Comparison. type Result interface { Success() bool } // StringResult is an implementation of Result that reports the error message // string verbatim and does not provide any templating or formatting of the // message. type StringResult struct { success bool message string } // Success returns true if the comparison was successful. func (r StringResult) Success() bool { return r.success } // FailureMessage returns the message used to provide additional information // about the failure. func (r StringResult) FailureMessage() string { return r.message } // ResultSuccess is a constant which is returned by a ComparisonWithResult to // indicate success. var ResultSuccess = StringResult{success: true} // ResultFailure returns a failed Result with a failure message. func ResultFailure(message string) StringResult { return StringResult{message: message} } // ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure // is returned with the error message as the failure message. func ResultFromError(err error) Result { if err == nil { return ResultSuccess } return ResultFailure(err.Error()) } type templatedResult struct { success bool template string data map[string]interface{} } func (r templatedResult) Success() bool { return r.success } func (r templatedResult) FailureMessage(args []ast.Expr) string { msg, err := renderMessage(r, args) if err != nil { return fmt.Sprintf("failed to render failure message: %s", err) } return msg } // ResultFailureTemplate returns a Result with a template string and data which // can be used to format a failure message. The template may access data from .Data, // the comparison args with the callArg function, and the formatNode function may // be used to format the call args. func ResultFailureTemplate(template string, data map[string]interface{}) Result { return templatedResult{template: template, data: data} } func renderMessage(result templatedResult, args []ast.Expr) (string, error) { tmpl := template.New("failure").Funcs(template.FuncMap{ "formatNode": source.FormatNode, "callArg": func(index int) ast.Expr { if index >= len(args) { return nil } return args[index] }, }) var err error tmpl, err = tmpl.Parse(result.template) if err != nil { return "", err } buf := new(bytes.Buffer) err = tmpl.Execute(buf, map[string]interface{}{ "Data": result.data, }) return buf.String(), err } gotest.tools-2.3.0/assert/example_test.go000066400000000000000000000010231341001157600204740ustar00rootroot00000000000000package assert_test import ( "fmt" "regexp" "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" ) var t = &testing.T{} func ExampleAssert_customComparison() { regexPattern := func(value string, pattern string) cmp.Comparison { return func() cmp.Result { re := regexp.MustCompile(pattern) if re.MatchString(value) { return cmp.ResultSuccess } return cmp.ResultFailure( fmt.Sprintf("%q did not match pattern %q", value, pattern)) } } assert.Assert(t, regexPattern("12345.34", `\d+.\d\d`)) } gotest.tools-2.3.0/assert/opt/000077500000000000000000000000001341001157600162615ustar00rootroot00000000000000gotest.tools-2.3.0/assert/opt/opt.go000066400000000000000000000071761341001157600174250ustar00rootroot00000000000000/*Package opt provides common go-cmp.Options for use with assert.DeepEqual. */ package opt // import "gotest.tools/assert/opt" import ( "fmt" "reflect" "strings" "time" gocmp "github.com/google/go-cmp/cmp" ) // DurationWithThreshold returns a gocmp.Comparer for comparing time.Duration. The // Comparer returns true if the difference between the two Duration values is // within the threshold and neither value is zero. func DurationWithThreshold(threshold time.Duration) gocmp.Option { return gocmp.Comparer(cmpDuration(threshold)) } func cmpDuration(threshold time.Duration) func(x, y time.Duration) bool { return func(x, y time.Duration) bool { if x == 0 || y == 0 { return false } delta := x - y return delta <= threshold && delta >= -threshold } } // TimeWithThreshold returns a gocmp.Comparer for comparing time.Time. The // Comparer returns true if the difference between the two Time values is // within the threshold and neither value is zero. func TimeWithThreshold(threshold time.Duration) gocmp.Option { return gocmp.Comparer(cmpTime(threshold)) } func cmpTime(threshold time.Duration) func(x, y time.Time) bool { return func(x, y time.Time) bool { if x.IsZero() || y.IsZero() { return false } delta := x.Sub(y) return delta <= threshold && delta >= -threshold } } // PathString is a gocmp.FilterPath filter that returns true when path.String() // matches any of the specs. // // The path spec is a dot separated string where each segment is a field name. // Slices, Arrays, and Maps are always matched against every element in the // sequence. gocmp.Indirect, gocmp.Transform, and gocmp.TypeAssertion are always // ignored. // // Note: this path filter is not type safe. Incorrect paths will be silently // ignored. Consider using a type safe path filter for more complex paths. func PathString(specs ...string) func(path gocmp.Path) bool { return func(path gocmp.Path) bool { for _, spec := range specs { if path.String() == spec { return true } } return false } } // PathDebug is a gocmp.FilerPath filter that always returns false. It prints // each path it receives. It can be used to debug path matching problems. func PathDebug(path gocmp.Path) bool { fmt.Printf("PATH string=%s gostring=%s\n", path, path.GoString()) for _, step := range path { fmt.Printf(" STEP %s\ttype=%s\t%s\n", formatStepType(step), step.Type(), stepTypeFields(step)) } return false } func formatStepType(step gocmp.PathStep) string { return strings.Title(strings.TrimPrefix(reflect.TypeOf(step).String(), "*cmp.")) } func stepTypeFields(step gocmp.PathStep) string { switch typed := step.(type) { case gocmp.StructField: return fmt.Sprintf("name=%s", typed.Name()) case gocmp.MapIndex: return fmt.Sprintf("key=%s", typed.Key().Interface()) case gocmp.Transform: return fmt.Sprintf("name=%s", typed.Name()) case gocmp.SliceIndex: return fmt.Sprintf("name=%d", typed.Key()) } return "" } // PathField is a gocmp.FilerPath filter that matches a struct field by name. // PathField will match every instance of the field in a recursive or nested // structure. func PathField(structType interface{}, field string) func(gocmp.Path) bool { typ := reflect.TypeOf(structType) if typ.Kind() != reflect.Struct { panic(fmt.Sprintf("type %s is not a struct", typ)) } if _, ok := typ.FieldByName(field); !ok { panic(fmt.Sprintf("type %s does not have field %s", typ, field)) } return func(path gocmp.Path) bool { return path.Index(-2).Type() == typ && isStructField(path.Index(-1), field) } } func isStructField(step gocmp.PathStep, name string) bool { field, ok := step.(gocmp.StructField) return ok && field.Name() == name } gotest.tools-2.3.0/assert/opt/opt_test.go000066400000000000000000000124351341001157600204560ustar00rootroot00000000000000package opt import ( "testing" "time" gocmp "github.com/google/go-cmp/cmp" "gotest.tools/assert" ) func TestDurationWithThreshold(t *testing.T) { var testcases = []struct { name string x, y, threshold time.Duration expected bool }{ { name: "delta is threshold", threshold: time.Second, x: 3 * time.Second, y: 2 * time.Second, expected: true, }, { name: "delta is negative threshold", threshold: time.Second, x: 2 * time.Second, y: 3 * time.Second, expected: true, }, { name: "delta within threshold", threshold: time.Second, x: 300 * time.Millisecond, y: 100 * time.Millisecond, expected: true, }, { name: "delta within negative threshold", threshold: time.Second, x: 100 * time.Millisecond, y: 300 * time.Millisecond, expected: true, }, { name: "delta outside threshold", threshold: time.Second, x: 5 * time.Second, y: 300 * time.Millisecond, }, { name: "delta outside negative threshold", threshold: time.Second, x: 300 * time.Millisecond, y: 5 * time.Second, }, { name: "x is 0", threshold: time.Second, y: 5 * time.Millisecond, }, { name: "y is 0", threshold: time.Second, x: 5 * time.Millisecond, }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { actual := cmpDuration(testcase.threshold)(testcase.x, testcase.y) assert.Equal(t, actual, testcase.expected) }) } } func TestTimeWithThreshold(t *testing.T) { var now = time.Now() var testcases = []struct { name string x, y time.Time threshold time.Duration expected bool }{ { name: "delta is threshold", threshold: time.Minute, x: now, y: now.Add(time.Minute), expected: true, }, { name: "delta is negative threshold", threshold: time.Minute, x: now, y: now.Add(-time.Minute), expected: true, }, { name: "delta within threshold", threshold: time.Hour, x: now, y: now.Add(time.Minute), expected: true, }, { name: "delta within negative threshold", threshold: time.Hour, x: now, y: now.Add(-time.Minute), expected: true, }, { name: "delta outside threshold", threshold: time.Second, x: now, y: now.Add(time.Minute), }, { name: "delta outside negative threshold", threshold: time.Second, x: now, y: now.Add(-time.Minute), }, { name: "x is 0", threshold: time.Second, y: now, }, { name: "y is 0", threshold: time.Second, x: now, }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { actual := cmpTime(testcase.threshold)(testcase.x, testcase.y) assert.Equal(t, actual, testcase.expected) }) } } type node struct { Value nodeValue Labels map[string]node Children []node Ref *node } type nodeValue struct { Value int } type pathRecorder struct { filter func(p gocmp.Path) bool matches []string } func (p *pathRecorder) record(path gocmp.Path) bool { if p.filter(path) { p.matches = append(p.matches, path.GoString()) } return false } func matchPaths(fixture interface{}, filter func(gocmp.Path) bool) []string { rec := &pathRecorder{filter: filter} gocmp.Equal(fixture, fixture, gocmp.FilterPath(rec.record, gocmp.Ignore())) return rec.matches } func TestPathStringFromStruct(t *testing.T) { fixture := node{ Ref: &node{ Children: []node{ {}, { Labels: map[string]node{ "first": {Value: nodeValue{Value: 3}}, }, }, }, }, } spec := "Ref.Children.Labels.Value" matches := matchPaths(fixture, PathString(spec)) expected := []string{`{opt.node}.Ref.Children[1].Labels["first"].Value`} assert.DeepEqual(t, matches, expected) } func TestPathStringFromSlice(t *testing.T) { fixture := []node{ { Ref: &node{ Children: []node{ {}, { Labels: map[string]node{ "first": {}, "second": { Ref: &node{Value: nodeValue{Value: 3}}, }, }, }, }, }, }, } spec := "Ref.Children.Labels.Ref.Value" matches := matchPaths(fixture, PathString(spec)) expected := []string{`{[]opt.node}[0].Ref.Children[1].Labels["second"].Ref.Value`} assert.DeepEqual(t, matches, expected) } func TestPathField(t *testing.T) { fixture := node{ Value: nodeValue{Value: 3}, Children: []node{ {}, {Value: nodeValue{Value: 2}}, {Ref: &node{Value: nodeValue{Value: 9}}}, }, } filter := PathField(nodeValue{}, "Value") matches := matchPaths(fixture, filter) expected := []string{ "{opt.node}.Value.Value", "{opt.node}.Children[0].Value.Value", "{opt.node}.Children[1].Value.Value", "{opt.node}.Children[2].Value.Value", "{opt.node}.Children[2].Ref.Value.Value", } assert.DeepEqual(t, matches, expected) } func TestPathDebug(t *testing.T) { fixture := node{ Value: nodeValue{Value: 3}, Children: []node{ {Ref: &node{Value: nodeValue{Value: 9}}}, }, Labels: map[string]node{ "label1": {}, }, } gocmp.Equal(fixture, fixture, gocmp.FilterPath(PathDebug, gocmp.Ignore())) } gotest.tools-2.3.0/assert/result.go000066400000000000000000000046631341001157600173350ustar00rootroot00000000000000package assert import ( "fmt" "go/ast" "gotest.tools/assert/cmp" "gotest.tools/internal/format" "gotest.tools/internal/source" ) func runComparison( t TestingT, argSelector argSelector, f cmp.Comparison, msgAndArgs ...interface{}, ) bool { if ht, ok := t.(helperT); ok { ht.Helper() } result := f() if result.Success() { return true } var message string switch typed := result.(type) { case resultWithComparisonArgs: const stackIndex = 3 // Assert/Check, assert, runComparison args, err := source.CallExprArgs(stackIndex) if err != nil { t.Log(err.Error()) } message = typed.FailureMessage(filterPrintableExpr(argSelector(args))) case resultBasic: message = typed.FailureMessage() default: message = fmt.Sprintf("comparison returned invalid Result type: %T", result) } t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...)) return false } type resultWithComparisonArgs interface { FailureMessage(args []ast.Expr) string } type resultBasic interface { FailureMessage() string } // filterPrintableExpr filters the ast.Expr slice to only include Expr that are // easy to read when printed and contain relevant information to an assertion. // // Ident and SelectorExpr are included because they print nicely and the variable // names may provide additional context to their values. // BasicLit and CompositeLit are excluded because their source is equivalent to // their value, which is already available. // Other types are ignored for now, but could be added if they are relevant. func filterPrintableExpr(args []ast.Expr) []ast.Expr { result := make([]ast.Expr, len(args)) for i, arg := range args { if isShortPrintableExpr(arg) { result[i] = arg continue } if starExpr, ok := arg.(*ast.StarExpr); ok { result[i] = starExpr.X continue } } return result } func isShortPrintableExpr(expr ast.Expr) bool { switch expr.(type) { case *ast.Ident, *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr: return true case *ast.BinaryExpr, *ast.UnaryExpr: return true default: // CallExpr, ParenExpr, TypeAssertExpr, KeyValueExpr, StarExpr return false } } type argSelector func([]ast.Expr) []ast.Expr func argsAfterT(args []ast.Expr) []ast.Expr { if len(args) < 1 { return nil } return args[1:] } func argsFromComparisonCall(args []ast.Expr) []ast.Expr { if len(args) < 1 { return nil } if callExpr, ok := args[1].(*ast.CallExpr); ok { return callExpr.Args } return nil } gotest.tools-2.3.0/dobi.yaml000066400000000000000000000016341341001157600157630ustar00rootroot00000000000000meta: project: gotesttools mount=source: bind: . path: /go/src/gotest.tools mount=depsources: bind: ./.depsources path: /go/pkg/dep/sources image=builder: image: gotesttools-dev context: . target: dev args: GOLANG_VERSION: '{env.GOLANG_VERSION:}' image=linter: image: gotesttools-lint context: . target: linter job=shell: use: builder mounts: [source, depsources] interactive: true command: sh job=watch: use: builder mounts: [source] interactive: true command: | filewatcher -x vendor gotestsum -- -test.timeout 10s env: - 'GOTESTSUM_FORMAT=short-verbose' - 'GOTESTTOOLS_DEBUG={env.GOTESTTOOLS_DEBUG:}' job=test-unit: use: builder mounts: [source] interactive: true command: scripts/test-unit job=deps: use: builder mounts: [source, depsources] command: dep ensure job=lint: use: linter mounts: [source] alias=test: tasks: [test-unit] gotest.tools-2.3.0/env/000077500000000000000000000000001341001157600147465ustar00rootroot00000000000000gotest.tools-2.3.0/env/env.go000066400000000000000000000052551341001157600160740ustar00rootroot00000000000000/*Package env provides functions to test code that read environment variables or the current working directory. */ package env // import "gotest.tools/env" import ( "os" "strings" "gotest.tools/assert" "gotest.tools/x/subtest" ) type helperT interface { Helper() } // Patch changes the value of an environment variable, and returns a // function which will reset the the value of that variable back to the // previous state. func Patch(t assert.TestingT, key, value string) func() { if ht, ok := t.(helperT); ok { ht.Helper() } oldValue, ok := os.LookupEnv(key) assert.NilError(t, os.Setenv(key, value)) cleanup := func() { if ht, ok := t.(helperT); ok { ht.Helper() } if !ok { assert.NilError(t, os.Unsetenv(key)) return } assert.NilError(t, os.Setenv(key, oldValue)) } if tc, ok := t.(subtest.TestContext); ok { tc.AddCleanup(cleanup) } return cleanup } // PatchAll sets the environment to env, and returns a function which will // reset the environment back to the previous state. func PatchAll(t assert.TestingT, env map[string]string) func() { if ht, ok := t.(helperT); ok { ht.Helper() } oldEnv := os.Environ() os.Clearenv() for key, value := range env { assert.NilError(t, os.Setenv(key, value), "setenv %s=%s", key, value) } cleanup := func() { if ht, ok := t.(helperT); ok { ht.Helper() } os.Clearenv() for key, oldVal := range ToMap(oldEnv) { assert.NilError(t, os.Setenv(key, oldVal), "setenv %s=%s", key, oldVal) } } if tc, ok := t.(subtest.TestContext); ok { tc.AddCleanup(cleanup) } return cleanup } // ToMap takes a list of strings in the format returned by os.Environ() and // returns a mapping of keys to values. func ToMap(env []string) map[string]string { result := map[string]string{} for _, raw := range env { key, value := getParts(raw) result[key] = value } return result } func getParts(raw string) (string, string) { if raw == "" { return "", "" } // Environment variables on windows can begin with = // http://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx parts := strings.SplitN(raw[1:], "=", 2) key := raw[:1] + parts[0] if len(parts) == 1 { return key, "" } return key, parts[1] } // ChangeWorkingDir to the directory, and return a function which restores the // previous working directory. func ChangeWorkingDir(t assert.TestingT, dir string) func() { if ht, ok := t.(helperT); ok { ht.Helper() } cwd, err := os.Getwd() assert.NilError(t, err) assert.NilError(t, os.Chdir(dir)) cleanup := func() { if ht, ok := t.(helperT); ok { ht.Helper() } assert.NilError(t, os.Chdir(cwd)) } if tc, ok := t.(subtest.TestContext); ok { tc.AddCleanup(cleanup) } return cleanup } gotest.tools-2.3.0/env/env_test.go000066400000000000000000000044671341001157600171370ustar00rootroot00000000000000package env import ( "os" "runtime" "sort" "testing" "gotest.tools/assert" "gotest.tools/fs" "gotest.tools/skip" ) func TestPatchFromUnset(t *testing.T) { key, value := "FOO_IS_UNSET", "VALUE" revert := Patch(t, key, value) assert.Assert(t, value == os.Getenv(key)) revert() _, isSet := os.LookupEnv(key) assert.Assert(t, !isSet) } func TestPatch(t *testing.T) { skip.If(t, os.Getenv("PATH") == "") oldVal := os.Getenv("PATH") key, value := "PATH", "NEWVALUE" revert := Patch(t, key, value) assert.Assert(t, value == os.Getenv(key)) revert() assert.Assert(t, oldVal == os.Getenv(key)) } func TestPatchAll(t *testing.T) { oldEnv := os.Environ() newEnv := map[string]string{ "FIRST": "STARS", "THEN": "MOON", } revert := PatchAll(t, newEnv) actual := os.Environ() sort.Strings(actual) assert.DeepEqual(t, []string{"FIRST=STARS", "THEN=MOON"}, actual) revert() assert.DeepEqual(t, sorted(oldEnv), sorted(os.Environ())) } func TestPatchAllWindows(t *testing.T) { skip.If(t, runtime.GOOS != "windows") oldEnv := os.Environ() newEnv := map[string]string{ "FIRST": "STARS", "THEN": "MOON", "=FINAL": "SUN", "=BAR": "", } revert := PatchAll(t, newEnv) actual := os.Environ() sort.Strings(actual) assert.DeepEqual(t, []string{"=BAR=", "=FINAL=SUN", "FIRST=STARS", "THEN=MOON"}, actual) revert() assert.DeepEqual(t, sorted(oldEnv), sorted(os.Environ())) } func sorted(source []string) []string { sort.Strings(source) return source } func TestToMap(t *testing.T) { source := []string{ "key=value", "novaluekey", "=foo=bar", "z=singlecharkey", "b", "", } actual := ToMap(source) expected := map[string]string{ "key": "value", "novaluekey": "", "=foo": "bar", "z": "singlecharkey", "b": "", "": "", } assert.DeepEqual(t, expected, actual) } func TestChangeWorkingDir(t *testing.T) { tmpDir := fs.NewDir(t, t.Name()) defer tmpDir.Remove() origWorkDir, err := os.Getwd() assert.NilError(t, err) reset := ChangeWorkingDir(t, tmpDir.Path()) t.Run("changed to dir", func(t *testing.T) { wd, err := os.Getwd() assert.NilError(t, err) assert.Equal(t, wd, tmpDir.Path()) }) t.Run("reset dir", func(t *testing.T) { reset() wd, err := os.Getwd() assert.NilError(t, err) assert.Equal(t, wd, origWorkDir) }) } gotest.tools-2.3.0/env/example_test.go000066400000000000000000000005151341001157600177700ustar00rootroot00000000000000package env import "testing" var t = &testing.T{} // Patch an environment variable and defer to return to the previous state func ExamplePatch() { defer Patch(t, "PATH", "/custom/path")() } // Patch all environment variables func ExamplePatchAll() { defer PatchAll(t, map[string]string{ "ONE": "FOO", "TWO": "BAR", })() } gotest.tools-2.3.0/fs/000077500000000000000000000000001341001157600145665ustar00rootroot00000000000000gotest.tools-2.3.0/fs/example_test.go000066400000000000000000000031211341001157600176040ustar00rootroot00000000000000package fs_test import ( "io/ioutil" "os" "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" "gotest.tools/fs" "gotest.tools/golden" ) var t = &testing.T{} // Create a temporary directory which contains a single file func ExampleNewDir() { dir := fs.NewDir(t, "test-name", fs.WithFile("file1", "content\n")) defer dir.Remove() files, err := ioutil.ReadDir(dir.Path()) assert.NilError(t, err) assert.Assert(t, cmp.Len(files, 0)) } // Create a new file with some content func ExampleNewFile() { file := fs.NewFile(t, "test-name", fs.WithContent("content\n"), fs.AsUser(0, 0)) defer file.Remove() content, err := ioutil.ReadFile(file.Path()) assert.NilError(t, err) assert.Equal(t, "content\n", content) } // Create a directory and subdirectory with files func ExampleWithDir() { dir := fs.NewDir(t, "test-name", fs.WithDir("subdir", fs.WithMode(os.FileMode(0700)), fs.WithFile("file1", "content\n")), ) defer dir.Remove() } // Test that a directory contains the expected files, and all the files have the // expected properties. func ExampleEqual() { path := operationWhichCreatesFiles() expected := fs.Expected(t, fs.WithFile("one", "", fs.WithBytes(golden.Get(t, "one.golden")), fs.WithMode(0600)), fs.WithDir("data", fs.WithFile("config", "", fs.MatchAnyFileContent))) assert.Assert(t, fs.Equal(path, expected)) } func operationWhichCreatesFiles() string { return "example-path" } // Add a file to an existing directory func ExampleApply() { dir := fs.NewDir(t, "test-name") defer dir.Remove() fs.Apply(t, dir, fs.WithFile("file1", "content\n")) } gotest.tools-2.3.0/fs/file.go000066400000000000000000000050461341001157600160410ustar00rootroot00000000000000/*Package fs provides tools for creating temporary files, and testing the contents and structure of a directory. */ package fs // import "gotest.tools/fs" import ( "io/ioutil" "os" "path/filepath" "runtime" "strings" "gotest.tools/assert" "gotest.tools/x/subtest" ) // Path objects return their filesystem path. Path may be implemented by a // real filesystem object (such as File and Dir) or by a type which updates // entries in a Manifest. type Path interface { Path() string Remove() } var ( _ Path = &Dir{} _ Path = &File{} ) // File is a temporary file on the filesystem type File struct { path string } type helperT interface { Helper() } // NewFile creates a new file in a temporary directory using prefix as part of // the filename. The PathOps are applied to the before returning the File. func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File { if ht, ok := t.(helperT); ok { ht.Helper() } tempfile, err := ioutil.TempFile("", cleanPrefix(prefix)+"-") assert.NilError(t, err) file := &File{path: tempfile.Name()} assert.NilError(t, tempfile.Close()) assert.NilError(t, applyPathOps(file, ops)) if tc, ok := t.(subtest.TestContext); ok { tc.AddCleanup(file.Remove) } return file } func cleanPrefix(prefix string) string { // windows requires both / and \ are replaced if runtime.GOOS == "windows" { prefix = strings.Replace(prefix, string(os.PathSeparator), "-", -1) } return strings.Replace(prefix, "/", "-", -1) } // Path returns the full path to the file func (f *File) Path() string { return f.path } // Remove the file func (f *File) Remove() { // nolint: errcheck os.Remove(f.path) } // Dir is a temporary directory type Dir struct { path string } // NewDir returns a new temporary directory using prefix as part of the directory // name. The PathOps are applied before returning the Dir. func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir { if ht, ok := t.(helperT); ok { ht.Helper() } path, err := ioutil.TempDir("", cleanPrefix(prefix)+"-") assert.NilError(t, err) dir := &Dir{path: path} assert.NilError(t, applyPathOps(dir, ops)) if tc, ok := t.(subtest.TestContext); ok { tc.AddCleanup(dir.Remove) } return dir } // Path returns the full path to the directory func (d *Dir) Path() string { return d.path } // Remove the directory func (d *Dir) Remove() { // nolint: errcheck os.RemoveAll(d.path) } // Join returns a new path with this directory as the base of the path func (d *Dir) Join(parts ...string) string { return filepath.Join(append([]string{d.Path()}, parts...)...) } gotest.tools-2.3.0/fs/file_test.go000066400000000000000000000025251341001157600170770ustar00rootroot00000000000000package fs_test import ( "os" "testing" "gotest.tools/assert" "gotest.tools/fs" ) func TestNewDirWithOpsAndManifestEqual(t *testing.T) { var userOps []fs.PathOp if os.Geteuid() == 0 { userOps = append(userOps, fs.AsUser(1001, 1002)) } ops := []fs.PathOp{ fs.WithFile("file1", "contenta", fs.WithMode(0400)), fs.WithFile("file2", "", fs.WithBytes([]byte{0, 1, 2})), fs.WithFile("file5", "", userOps...), fs.WithSymlink("link1", "file1"), fs.WithDir("sub", fs.WithFiles(map[string]string{ "file3": "contentb", "file4": "contentc", }), fs.WithMode(0705), ), } dir := fs.NewDir(t, "test-all", ops...) defer dir.Remove() manifestOps := append( ops[:3], fs.WithSymlink("link1", dir.Join("file1")), ops[4], ) assert.Assert(t, fs.Equal(dir.Path(), fs.Expected(t, manifestOps...))) } func TestNewFile(t *testing.T) { t.Run("with test name", func(t *testing.T) { tmpFile := fs.NewFile(t, t.Name()) _, err := os.Stat(tmpFile.Path()) assert.NilError(t, err) tmpFile.Remove() _, err = os.Stat(tmpFile.Path()) assert.ErrorType(t, err, os.IsNotExist) }) t.Run(`with \ in name`, func(t *testing.T) { tmpFile := fs.NewFile(t, `foo\thing`) _, err := os.Stat(tmpFile.Path()) assert.NilError(t, err) tmpFile.Remove() _, err = os.Stat(tmpFile.Path()) assert.ErrorType(t, err, os.IsNotExist) }) } gotest.tools-2.3.0/fs/manifest.go000066400000000000000000000055001341001157600167230ustar00rootroot00000000000000package fs import ( "io" "io/ioutil" "os" "path/filepath" "github.com/pkg/errors" "gotest.tools/assert" ) // Manifest stores the expected structure and properties of files and directories // in a filesystem. type Manifest struct { root *directory } type resource struct { mode os.FileMode uid uint32 gid uint32 } type file struct { resource content io.ReadCloser ignoreCariageReturn bool compareContentFunc func(b []byte) CompareResult } func (f *file) Type() string { return "file" } type symlink struct { resource target string } func (f *symlink) Type() string { return "symlink" } type directory struct { resource items map[string]dirEntry filepathGlobs map[string]*filePath } func (f *directory) Type() string { return "directory" } type dirEntry interface { Type() string } // ManifestFromDir creates a Manifest by reading the directory at path. The // manifest stores the structure and properties of files in the directory. // ManifestFromDir can be used with Equal to compare two directories. func ManifestFromDir(t assert.TestingT, path string) Manifest { if ht, ok := t.(helperT); ok { ht.Helper() } manifest, err := manifestFromDir(path) assert.NilError(t, err) return manifest } func manifestFromDir(path string) (Manifest, error) { info, err := os.Stat(path) switch { case err != nil: return Manifest{}, err case !info.IsDir(): return Manifest{}, errors.Errorf("path %s must be a directory", path) } directory, err := newDirectory(path, info) return Manifest{root: directory}, err } func newDirectory(path string, info os.FileInfo) (*directory, error) { items := make(map[string]dirEntry) children, err := ioutil.ReadDir(path) if err != nil { return nil, err } for _, child := range children { fullPath := filepath.Join(path, child.Name()) items[child.Name()], err = getTypedResource(fullPath, child) if err != nil { return nil, err } } return &directory{ resource: newResourceFromInfo(info), items: items, filepathGlobs: make(map[string]*filePath), }, nil } func getTypedResource(path string, info os.FileInfo) (dirEntry, error) { switch { case info.IsDir(): return newDirectory(path, info) case info.Mode()&os.ModeSymlink != 0: return newSymlink(path, info) // TODO: devices, pipes? default: return newFile(path, info) } } func newSymlink(path string, info os.FileInfo) (*symlink, error) { target, err := os.Readlink(path) if err != nil { return nil, err } return &symlink{ resource: newResourceFromInfo(info), target: target, }, err } func newFile(path string, info os.FileInfo) (*file, error) { // TODO: defer file opening to reduce number of open FDs? readCloser, err := os.Open(path) if err != nil { return nil, err } return &file{ resource: newResourceFromInfo(info), content: readCloser, }, err } gotest.tools-2.3.0/fs/manifest_test.go000066400000000000000000000045111341001157600177630ustar00rootroot00000000000000package fs import ( "bytes" "io" "io/ioutil" "os" "runtime" "strings" "testing" "github.com/google/go-cmp/cmp" "gotest.tools/assert" ) func TestManifestFromDir(t *testing.T) { var defaultFileMode os.FileMode = 0644 var subDirMode = 0755 | os.ModeDir var jFileMode os.FileMode = 0600 if runtime.GOOS == "windows" { defaultFileMode = 0666 subDirMode = 0777 | os.ModeDir jFileMode = 0666 } var userOps []PathOp var expectedUserResource = newResource(defaultFileMode) if os.Geteuid() == 0 { userOps = append(userOps, AsUser(1001, 1002)) expectedUserResource = resource{mode: defaultFileMode, uid: 1001, gid: 1002} } srcDir := NewDir(t, t.Name(), WithFile("j", "content j", WithMode(0600)), WithDir("s", WithFile("k", "content k")), WithSymlink("f", "j"), WithFile("x", "content x", userOps...)) defer srcDir.Remove() expected := Manifest{ root: &directory{ resource: newResource(defaultRootDirMode), items: map[string]dirEntry{ "j": &file{ resource: newResource(jFileMode), content: readCloser("content j"), }, "s": &directory{ resource: newResource(subDirMode), items: map[string]dirEntry{ "k": &file{ resource: newResource(defaultFileMode), content: readCloser("content k"), }, }, filepathGlobs: map[string]*filePath{}, }, "f": &symlink{ resource: newResource(defaultSymlinkMode), target: srcDir.Join("j"), }, "x": &file{ resource: expectedUserResource, content: readCloser("content x"), }, }, filepathGlobs: map[string]*filePath{}, }, } actual := ManifestFromDir(t, srcDir.Path()) assert.DeepEqual(t, actual, expected, cmpManifest) actual.root.items["j"].(*file).content.Close() actual.root.items["x"].(*file).content.Close() actual.root.items["s"].(*directory).items["k"].(*file).content.Close() } var cmpManifest = cmp.Options{ cmp.AllowUnexported(Manifest{}, resource{}, file{}, symlink{}, directory{}), cmp.Comparer(func(x, y io.ReadCloser) bool { if x == nil || y == nil { return x == y } xContent, err := ioutil.ReadAll(x) if err != nil { return false } yContent, err := ioutil.ReadAll(y) if err != nil { return false } return bytes.Equal(xContent, yContent) }), } func readCloser(s string) io.ReadCloser { return ioutil.NopCloser(strings.NewReader(s)) } gotest.tools-2.3.0/fs/manifest_unix.go000066400000000000000000000007561341001157600177760ustar00rootroot00000000000000// +build !windows package fs import ( "os" "syscall" ) const ( defaultRootDirMode = os.ModeDir | 0700 defaultSymlinkMode = os.ModeSymlink | 0777 ) func newResourceFromInfo(info os.FileInfo) resource { statT := info.Sys().(*syscall.Stat_t) return resource{ mode: info.Mode(), uid: statT.Uid, gid: statT.Gid, } } func (p *filePath) SetMode(mode os.FileMode) { p.file.mode = mode } func (p *directoryPath) SetMode(mode os.FileMode) { p.directory.mode = mode | os.ModeDir } gotest.tools-2.3.0/fs/manifest_windows.go000066400000000000000000000007111341001157600204740ustar00rootroot00000000000000package fs import "os" const ( defaultRootDirMode = os.ModeDir | 0777 defaultSymlinkMode = os.ModeSymlink | 0666 ) func newResourceFromInfo(info os.FileInfo) resource { return resource{mode: info.Mode()} } func (p *filePath) SetMode(mode os.FileMode) { bits := mode & 0600 p.file.mode = bits + bits/010 + bits/0100 } // TODO: is mode ignored on windows? func (p *directoryPath) SetMode(mode os.FileMode) { p.directory.mode = defaultRootDirMode } gotest.tools-2.3.0/fs/ops.go000066400000000000000000000153761341001157600157320ustar00rootroot00000000000000package fs import ( "bytes" "io" "io/ioutil" "os" "path/filepath" "strings" "time" "github.com/pkg/errors" "gotest.tools/assert" ) const defaultFileMode = 0644 // PathOp is a function which accepts a Path and performs an operation on that // path. When called with real filesystem objects (File or Dir) a PathOp modifies // the filesystem at the path. When used with a Manifest object a PathOp updates // the manifest to expect a value. type PathOp func(path Path) error type manifestResource interface { SetMode(mode os.FileMode) SetUID(uid uint32) SetGID(gid uint32) } type manifestFile interface { manifestResource SetContent(content io.ReadCloser) } type manifestDirectory interface { manifestResource AddSymlink(path, target string) error AddFile(path string, ops ...PathOp) error AddDirectory(path string, ops ...PathOp) error } // WithContent writes content to a file at Path func WithContent(content string) PathOp { return func(path Path) error { if m, ok := path.(manifestFile); ok { m.SetContent(ioutil.NopCloser(strings.NewReader(content))) return nil } return ioutil.WriteFile(path.Path(), []byte(content), defaultFileMode) } } // WithBytes write bytes to a file at Path func WithBytes(raw []byte) PathOp { return func(path Path) error { if m, ok := path.(manifestFile); ok { m.SetContent(ioutil.NopCloser(bytes.NewReader(raw))) return nil } return ioutil.WriteFile(path.Path(), raw, defaultFileMode) } } // AsUser changes ownership of the file system object at Path func AsUser(uid, gid int) PathOp { return func(path Path) error { if m, ok := path.(manifestResource); ok { m.SetUID(uint32(uid)) m.SetGID(uint32(gid)) return nil } return os.Chown(path.Path(), uid, gid) } } // WithFile creates a file in the directory at path with content func WithFile(filename, content string, ops ...PathOp) PathOp { return func(path Path) error { if m, ok := path.(manifestDirectory); ok { ops = append([]PathOp{WithContent(content), WithMode(defaultFileMode)}, ops...) return m.AddFile(filename, ops...) } fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename)) if err := createFile(fullpath, content); err != nil { return err } return applyPathOps(&File{path: fullpath}, ops) } } func createFile(fullpath string, content string) error { return ioutil.WriteFile(fullpath, []byte(content), defaultFileMode) } // WithFiles creates all the files in the directory at path with their content func WithFiles(files map[string]string) PathOp { return func(path Path) error { if m, ok := path.(manifestDirectory); ok { for filename, content := range files { // TODO: remove duplication with WithFile if err := m.AddFile(filename, WithContent(content), WithMode(defaultFileMode)); err != nil { return err } } return nil } for filename, content := range files { fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename)) if err := createFile(fullpath, content); err != nil { return err } } return nil } } // FromDir copies the directory tree from the source path into the new Dir func FromDir(source string) PathOp { return func(path Path) error { if _, ok := path.(manifestDirectory); ok { return errors.New("use manifest.FromDir") } return copyDirectory(source, path.Path()) } } // WithDir creates a subdirectory in the directory at path. Additional PathOp // can be used to modify the subdirectory func WithDir(name string, ops ...PathOp) PathOp { const defaultMode = 0755 return func(path Path) error { if m, ok := path.(manifestDirectory); ok { ops = append([]PathOp{WithMode(defaultMode)}, ops...) return m.AddDirectory(name, ops...) } fullpath := filepath.Join(path.Path(), filepath.FromSlash(name)) err := os.MkdirAll(fullpath, defaultMode) if err != nil { return err } return applyPathOps(&Dir{path: fullpath}, ops) } } // Apply the PathOps to the File func Apply(t assert.TestingT, path Path, ops ...PathOp) { if ht, ok := t.(helperT); ok { ht.Helper() } assert.NilError(t, applyPathOps(path, ops)) } func applyPathOps(path Path, ops []PathOp) error { for _, op := range ops { if err := op(path); err != nil { return err } } return nil } // WithMode sets the file mode on the directory or file at path func WithMode(mode os.FileMode) PathOp { return func(path Path) error { if m, ok := path.(manifestResource); ok { m.SetMode(mode) return nil } return os.Chmod(path.Path(), mode) } } func copyDirectory(source, dest string) error { entries, err := ioutil.ReadDir(source) if err != nil { return err } for _, entry := range entries { sourcePath := filepath.Join(source, entry.Name()) destPath := filepath.Join(dest, entry.Name()) switch { case entry.IsDir(): if err := os.Mkdir(destPath, 0755); err != nil { return err } if err := copyDirectory(sourcePath, destPath); err != nil { return err } case entry.Mode()&os.ModeSymlink != 0: if err := copySymLink(sourcePath, destPath); err != nil { return err } default: if err := copyFile(sourcePath, destPath); err != nil { return err } } } return nil } func copySymLink(source, dest string) error { link, err := os.Readlink(source) if err != nil { return err } return os.Symlink(link, dest) } func copyFile(source, dest string) error { content, err := ioutil.ReadFile(source) if err != nil { return err } return ioutil.WriteFile(dest, content, 0644) } // WithSymlink creates a symlink in the directory which links to target. // Target must be a path relative to the directory. // // Note: the argument order is the inverse of os.Symlink to be consistent with // the other functions in this package. func WithSymlink(path, target string) PathOp { return func(root Path) error { if v, ok := root.(manifestDirectory); ok { return v.AddSymlink(path, target) } return os.Symlink(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path)) } } // WithHardlink creates a link in the directory which links to target. // Target must be a path relative to the directory. // // Note: the argument order is the inverse of os.Link to be consistent with // the other functions in this package. func WithHardlink(path, target string) PathOp { return func(root Path) error { if _, ok := root.(manifestDirectory); ok { return errors.New("WithHardlink not implemented for manifests") } return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path)) } } // WithTimestamps sets the access and modification times of the file system object // at path. func WithTimestamps(atime, mtime time.Time) PathOp { return func(root Path) error { if _, ok := root.(manifestDirectory); ok { return errors.New("WithTimestamp not implemented for manifests") } return os.Chtimes(root.Path(), atime, mtime) } } gotest.tools-2.3.0/fs/ops_test.go000066400000000000000000000042771341001157600167670ustar00rootroot00000000000000package fs_test import ( "io/ioutil" "os" "path/filepath" "runtime" "testing" "time" "gotest.tools/assert" "gotest.tools/fs" ) func TestFromDir(t *testing.T) { dir := fs.NewDir(t, "test-from-dir", fs.FromDir("testdata/copy-test")) defer dir.Remove() expected := fs.Expected(t, fs.WithFile("1", "1\n"), fs.WithDir("a", fs.WithFile("1", "1\n"), fs.WithFile("2", "2\n"), fs.WithDir("b", fs.WithFile("1", "1\n")))) assert.Assert(t, fs.Equal(dir.Path(), expected)) } func TestFromDirSymlink(t *testing.T) { dir := fs.NewDir(t, "test-from-dir", fs.FromDir("testdata/copy-test-with-symlink")) defer dir.Remove() currentdir, err := os.Getwd() assert.NilError(t, err) link2 := filepath.FromSlash("../2") link3 := "/some/inexistent/link" if runtime.GOOS == "windows" { link3 = filepath.Join(filepath.VolumeName(currentdir), link3) } expected := fs.Expected(t, fs.WithFile("1", "1\n"), fs.WithDir("a", fs.WithFile("1", "1\n"), fs.WithFile("2", "2\n"), fs.WithDir("b", fs.WithFile("1", "1\n"), fs.WithSymlink("2", link2), fs.WithSymlink("3", link3), fs.WithSymlink("4", "5"), ))) assert.Assert(t, fs.Equal(dir.Path(), expected)) } func TestWithTimestamps(t *testing.T) { stamp := time.Date(2011, 11, 11, 5, 55, 55, 0, time.UTC) tmpFile := fs.NewFile(t, t.Name(), fs.WithTimestamps(stamp, stamp)) defer tmpFile.Remove() stat, err := os.Stat(tmpFile.Path()) assert.NilError(t, err) assert.DeepEqual(t, stat.ModTime(), stamp) } func TestApply(t *testing.T) { t.Run("with file", func(t *testing.T) { tmpFile := fs.NewFile(t, "test-update-file", fs.WithContent("contenta")) defer tmpFile.Remove() fs.Apply(t, tmpFile, fs.WithContent("contentb")) content, err := ioutil.ReadFile(tmpFile.Path()) assert.NilError(t, err) assert.Equal(t, string(content), "contentb") }) t.Run("with dir", func(t *testing.T) { tmpDir := fs.NewDir(t, "test-update-dir") defer tmpDir.Remove() fs.Apply(t, tmpDir, fs.WithFile("file1", "contenta")) fs.Apply(t, tmpDir, fs.WithFile("file2", "contentb")) expected := fs.Expected(t, fs.WithFile("file1", "contenta"), fs.WithFile("file2", "contentb")) assert.Assert(t, fs.Equal(tmpDir.Path(), expected)) }) } gotest.tools-2.3.0/fs/path.go000066400000000000000000000111041341001157600160460ustar00rootroot00000000000000package fs import ( "bytes" "io" "io/ioutil" "os" "gotest.tools/assert" ) // resourcePath is an adaptor for resources so they can be used as a Path // with PathOps. type resourcePath struct{} func (p *resourcePath) Path() string { return "manifest: not a filesystem path" } func (p *resourcePath) Remove() {} type filePath struct { resourcePath file *file } func (p *filePath) SetContent(content io.ReadCloser) { p.file.content = content } func (p *filePath) SetUID(uid uint32) { p.file.uid = uid } func (p *filePath) SetGID(gid uint32) { p.file.gid = gid } type directoryPath struct { resourcePath directory *directory } func (p *directoryPath) SetUID(uid uint32) { p.directory.uid = uid } func (p *directoryPath) SetGID(gid uint32) { p.directory.gid = gid } func (p *directoryPath) AddSymlink(path, target string) error { p.directory.items[path] = &symlink{ resource: newResource(defaultSymlinkMode), target: target, } return nil } func (p *directoryPath) AddFile(path string, ops ...PathOp) error { newFile := &file{resource: newResource(0)} p.directory.items[path] = newFile exp := &filePath{file: newFile} return applyPathOps(exp, ops) } func (p *directoryPath) AddGlobFiles(glob string, ops ...PathOp) error { newFile := &file{resource: newResource(0)} newFilePath := &filePath{file: newFile} p.directory.filepathGlobs[glob] = newFilePath return applyPathOps(newFilePath, ops) } func (p *directoryPath) AddDirectory(path string, ops ...PathOp) error { newDir := newDirectoryWithDefaults() p.directory.items[path] = newDir exp := &directoryPath{directory: newDir} return applyPathOps(exp, ops) } // Expected returns a Manifest with a directory structured created by ops. The // PathOp operations are applied to the manifest as expectations of the // filesystem structure and properties. func Expected(t assert.TestingT, ops ...PathOp) Manifest { if ht, ok := t.(helperT); ok { ht.Helper() } newDir := newDirectoryWithDefaults() e := &directoryPath{directory: newDir} assert.NilError(t, applyPathOps(e, ops)) return Manifest{root: newDir} } func newDirectoryWithDefaults() *directory { return &directory{ resource: newResource(defaultRootDirMode), items: make(map[string]dirEntry), filepathGlobs: make(map[string]*filePath), } } func newResource(mode os.FileMode) resource { return resource{ mode: mode, uid: currentUID(), gid: currentGID(), } } func currentUID() uint32 { return normalizeID(os.Getuid()) } func currentGID() uint32 { return normalizeID(os.Getgid()) } func normalizeID(id int) uint32 { // ids will be -1 on windows if id < 0 { return 0 } return uint32(id) } var anyFileContent = ioutil.NopCloser(bytes.NewReader(nil)) // MatchAnyFileContent is a PathOp that updates a Manifest so that the file // at path may contain any content. func MatchAnyFileContent(path Path) error { if m, ok := path.(*filePath); ok { m.SetContent(anyFileContent) } return nil } // MatchContentIgnoreCarriageReturn is a PathOp that ignores cariage return // discrepancies. func MatchContentIgnoreCarriageReturn(path Path) error { if m, ok := path.(*filePath); ok { m.file.ignoreCariageReturn = true } return nil } const anyFile = "*" // MatchExtraFiles is a PathOp that updates a Manifest to allow a directory // to contain unspecified files. func MatchExtraFiles(path Path) error { if m, ok := path.(*directoryPath); ok { m.AddFile(anyFile) } return nil } // CompareResult is the result of comparison. // // See gotest.tools/assert/cmp.StringResult for a convenient implementation of // this interface. type CompareResult interface { Success() bool FailureMessage() string } // MatchFileContent is a PathOp that updates a Manifest to use the provided // function to determine if a file's content matches the expectation. func MatchFileContent(f func([]byte) CompareResult) PathOp { return func(path Path) error { if m, ok := path.(*filePath); ok { m.file.compareContentFunc = f } return nil } } // MatchFilesWithGlob is a PathOp that updates a Manifest to match files using // glob pattern, and check them using the ops. func MatchFilesWithGlob(glob string, ops ...PathOp) PathOp { return func(path Path) error { if m, ok := path.(*directoryPath); ok { m.AddGlobFiles(glob, ops...) } return nil } } // anyFileMode is represented by uint32_max const anyFileMode os.FileMode = 4294967295 // MatchAnyFileMode is a PathOp that updates a Manifest so that the resource at path // will match any file mode. func MatchAnyFileMode(path Path) error { if m, ok := path.(manifestResource); ok { m.SetMode(anyFileMode) } return nil } gotest.tools-2.3.0/fs/report.go000066400000000000000000000152051341001157600164330ustar00rootroot00000000000000package fs import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "runtime" "sort" "strings" "gotest.tools/assert/cmp" "gotest.tools/internal/format" ) // Equal compares a directory to the expected structured described by a manifest // and returns success if they match. If they do not match the failure message // will contain all the differences between the directory structure and the // expected structure defined by the Manifest. // // Equal is a cmp.Comparison which can be used with assert.Assert(). func Equal(path string, expected Manifest) cmp.Comparison { return func() cmp.Result { actual, err := manifestFromDir(path) if err != nil { return cmp.ResultFromError(err) } failures := eqDirectory(string(os.PathSeparator), expected.root, actual.root) if len(failures) == 0 { return cmp.ResultSuccess } msg := fmt.Sprintf("directory %s does not match expected:\n", path) return cmp.ResultFailure(msg + formatFailures(failures)) } } type failure struct { path string problems []problem } type problem string func notEqual(property string, x, y interface{}) problem { return problem(fmt.Sprintf("%s: expected %s got %s", property, x, y)) } func errProblem(reason string, err error) problem { return problem(fmt.Sprintf("%s: %s", reason, err)) } func existenceProblem(filename, reason string, args ...interface{}) problem { return problem(filename + ": " + fmt.Sprintf(reason, args...)) } func eqResource(x, y resource) []problem { var p []problem if x.uid != y.uid { p = append(p, notEqual("uid", x.uid, y.uid)) } if x.gid != y.gid { p = append(p, notEqual("gid", x.gid, y.gid)) } if x.mode != anyFileMode && x.mode != y.mode { p = append(p, notEqual("mode", x.mode, y.mode)) } return p } func removeCarriageReturn(in []byte) []byte { return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1) } // nolint: gocyclo func eqFile(x, y *file) []problem { p := eqResource(x.resource, y.resource) switch { case x.content == nil: p = append(p, existenceProblem("content", "expected content is nil")) return p case x.content == anyFileContent: return p case y.content == nil: p = append(p, existenceProblem("content", "actual content is nil")) return p } xContent, xErr := ioutil.ReadAll(x.content) defer x.content.Close() yContent, yErr := ioutil.ReadAll(y.content) defer y.content.Close() if xErr != nil { p = append(p, errProblem("failed to read expected content", xErr)) } if yErr != nil { p = append(p, errProblem("failed to read actual content", xErr)) } if xErr != nil || yErr != nil { return p } if x.compareContentFunc != nil { r := x.compareContentFunc(yContent) if !r.Success() { p = append(p, existenceProblem("content", r.FailureMessage())) } return p } if x.ignoreCariageReturn || y.ignoreCariageReturn { xContent = removeCarriageReturn(xContent) yContent = removeCarriageReturn(yContent) } if !bytes.Equal(xContent, yContent) { p = append(p, diffContent(xContent, yContent)) } return p } func diffContent(x, y []byte) problem { diff := format.UnifiedDiff(format.DiffConfig{ A: string(x), B: string(y), From: "expected", To: "actual", }) // Remove the trailing newline in the diff. A trailing newline is always // added to a problem by formatFailures. diff = strings.TrimSuffix(diff, "\n") return problem("content:\n" + indent(diff, " ")) } func indent(s, prefix string) string { buf := new(bytes.Buffer) lines := strings.SplitAfter(s, "\n") for _, line := range lines { buf.WriteString(prefix + line) } return buf.String() } func eqSymlink(x, y *symlink) []problem { p := eqResource(x.resource, y.resource) xTarget := x.target yTarget := y.target if runtime.GOOS == "windows" { xTarget = strings.ToLower(xTarget) yTarget = strings.ToLower(yTarget) } if xTarget != yTarget { p = append(p, notEqual("target", x.target, y.target)) } return p } func eqDirectory(path string, x, y *directory) []failure { p := eqResource(x.resource, y.resource) var f []failure matchedFiles := make(map[string]bool) for _, name := range sortedKeys(x.items) { if name == anyFile { continue } matchedFiles[name] = true xEntry := x.items[name] yEntry, ok := y.items[name] if !ok { p = append(p, existenceProblem(name, "expected %s to exist", xEntry.Type())) continue } if xEntry.Type() != yEntry.Type() { p = append(p, notEqual(name, xEntry.Type(), yEntry.Type())) continue } f = append(f, eqEntry(filepath.Join(path, name), xEntry, yEntry)...) } if len(x.filepathGlobs) != 0 { for _, name := range sortedKeys(y.items) { m := matchGlob(name, y.items[name], x.filepathGlobs) matchedFiles[name] = m.match f = append(f, m.failures...) } } if _, ok := x.items[anyFile]; ok { return maybeAppendFailure(f, path, p) } for _, name := range sortedKeys(y.items) { if !matchedFiles[name] { p = append(p, existenceProblem(name, "unexpected %s", y.items[name].Type())) } } return maybeAppendFailure(f, path, p) } func maybeAppendFailure(failures []failure, path string, problems []problem) []failure { if len(problems) > 0 { return append(failures, failure{path: path, problems: problems}) } return failures } func sortedKeys(items map[string]dirEntry) []string { var keys []string for key := range items { keys = append(keys, key) } sort.Strings(keys) return keys } // eqEntry assumes x and y to be the same type func eqEntry(path string, x, y dirEntry) []failure { resp := func(problems []problem) []failure { if len(problems) == 0 { return nil } return []failure{{path: path, problems: problems}} } switch typed := x.(type) { case *file: return resp(eqFile(typed, y.(*file))) case *symlink: return resp(eqSymlink(typed, y.(*symlink))) case *directory: return eqDirectory(path, typed, y.(*directory)) } return nil } type globMatch struct { match bool failures []failure } func matchGlob(name string, yEntry dirEntry, globs map[string]*filePath) globMatch { m := globMatch{} for glob, expectedFile := range globs { ok, err := filepath.Match(glob, name) if err != nil { p := errProblem("failed to match glob pattern", err) f := failure{path: name, problems: []problem{p}} m.failures = append(m.failures, f) } if ok { m.match = true m.failures = eqEntry(name, expectedFile.file, yEntry) return m } } return m } func formatFailures(failures []failure) string { sort.Slice(failures, func(i, j int) bool { return failures[i].path < failures[j].path }) buf := new(bytes.Buffer) for _, failure := range failures { buf.WriteString(failure.path + "\n") for _, problem := range failure.problems { buf.WriteString(" " + string(problem) + "\n") } } return buf.String() } gotest.tools-2.3.0/fs/report_test.go000066400000000000000000000166561341001157600175050ustar00rootroot00000000000000package fs import ( "fmt" "path/filepath" "runtime" "testing" "gotest.tools/assert" is "gotest.tools/assert/cmp" "gotest.tools/skip" ) func TestEqualMissingRoot(t *testing.T) { result := Equal("/bogus/path/does/not/exist", Expected(t))() assert.Assert(t, !result.Success()) expected := "stat /bogus/path/does/not/exist: no such file or directory" if runtime.GOOS == "windows" { expected = "CreateFile /bogus/path/does/not/exist" } assert.Assert(t, is.Contains(result.(cmpFailure).FailureMessage(), expected)) } func TestEqualModeMismatch(t *testing.T) { dir := NewDir(t, t.Name(), WithMode(0500)) defer dir.Remove() result := Equal(dir.Path(), Expected(t))() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: / mode: expected drwx------ got dr-x------ `, dir.Path()) if runtime.GOOS == "windows" { expected = fmtExpected(`directory %s does not match expected: \ mode: expected drwxrwxrwx got dr-xr-xr-x `, dir.Path()) } assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) } func TestEqualRootIsAFile(t *testing.T) { file := NewFile(t, t.Name()) defer file.Remove() result := Equal(file.Path(), Expected(t))() assert.Assert(t, !result.Success()) expected := fmt.Sprintf("path %s must be a directory", file.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) } func TestEqualSuccess(t *testing.T) { dir := NewDir(t, t.Name(), WithMode(0700)) defer dir.Remove() assert.Assert(t, Equal(dir.Path(), Expected(t))) } func TestEqualDirectoryHasWithExtraFiles(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("extra1", "content")) defer dir.Remove() manifest := Expected(t, WithFile("file1", "content")) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: / file1: expected file to exist extra1: unexpected file `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) } func fmtExpected(format string, args ...interface{}) string { return filepath.FromSlash(fmt.Sprintf(format, args...)) } func TestEqualWithMatchAnyFileContent(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("data", "this is some data")) defer dir.Remove() expected := Expected(t, WithFile("data", "different content", MatchAnyFileContent)) assert.Assert(t, Equal(dir.Path(), expected)) } func TestEqualWithFileContent(t *testing.T) { dir := NewDir(t, "assert-test-root", WithFile("file1", "line1\nline2\nline3")) defer dir.Remove() manifest := Expected(t, WithFile("file1", "line2\nline3")) result := Equal(dir.Path(), manifest)() expected := fmtExpected(`directory %s does not match expected: /file1 content: --- expected +++ actual @@ -1,2 +1,3 @@ +line1 line2 line3 `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) } func TestEqualWithMatchContentIgnoreCarriageReturn(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("file1", "line1\r\nline2")) defer dir.Remove() manifest := Expected(t, WithFile("file1", "line1\nline2", MatchContentIgnoreCarriageReturn)) result := Equal(dir.Path(), manifest)() assert.Assert(t, result.Success()) } func TestEqualDirectoryWithMatchExtraFiles(t *testing.T) { file1 := WithFile("file1", "same in both") dir := NewDir(t, t.Name(), file1, WithFile("extra", "some content")) defer dir.Remove() expected := Expected(t, file1, MatchExtraFiles) assert.Assert(t, Equal(dir.Path(), expected)) } func TestEqualManyFailures(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("file1", "same in both"), WithFile("extra", "some content"), WithSymlink("sym1", "extra")) defer dir.Remove() manifest := Expected(t, WithDir("subdir", WithFile("somefile", "")), WithFile("file1", "not the\nsame in both")) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: / subdir: expected directory to exist extra: unexpected file sym1: unexpected symlink /file1 content: --- expected +++ actual @@ -1,2 +1 @@ -not the same in both `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) } type cmpFailure interface { FailureMessage() string } func TestMatchAnyFileMode(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("data", "content", WithMode(0777))) defer dir.Remove() expected := Expected(t, WithFile("data", "content", MatchAnyFileMode)) assert.Assert(t, Equal(dir.Path(), expected)) } func TestMatchFileContent(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("data", "content")) defer dir.Remove() t.Run("content matches", func(t *testing.T) { matcher := func(b []byte) CompareResult { return is.ResultSuccess } manifest := Expected(t, WithFile("data", "different", MatchFileContent(matcher))) assert.Assert(t, Equal(dir.Path(), manifest)) }) t.Run("content does not match", func(t *testing.T) { matcher := func(b []byte) CompareResult { return is.ResultFailure("data content differs from expected") } manifest := Expected(t, WithFile("data", "content", MatchFileContent(matcher))) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: /data content: data content differs from expected `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) }) } func TestMatchExtraFilesGlob(t *testing.T) { dir := NewDir(t, t.Name(), WithFile("t.go", "data"), WithFile("a.go", "data"), WithFile("conf.yml", "content", WithMode(0600))) defer dir.Remove() t.Run("matching globs", func(t *testing.T) { manifest := Expected(t, MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent), MatchFilesWithGlob("*.yml", MatchAnyFileMode, MatchAnyFileContent)) assert.Assert(t, Equal(dir.Path(), manifest)) }) t.Run("matching globs with wrong mode", func(t *testing.T) { skip.If(t, runtime.GOOS == "windows", "expect mode does not match on windows") manifest := Expected(t, MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent), MatchFilesWithGlob("*.yml", MatchAnyFileContent, WithMode(0700))) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: conf.yml mode: expected -rwx------ got -rw------- `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) }) t.Run("matching partial glob", func(t *testing.T) { manifest := Expected(t, MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent)) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: / conf.yml: unexpected file `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) }) t.Run("invalid glob", func(t *testing.T) { manifest := Expected(t, MatchFilesWithGlob("[-x]")) result := Equal(dir.Path(), manifest)() assert.Assert(t, !result.Success()) expected := fmtExpected(`directory %s does not match expected: / a.go: unexpected file conf.yml: unexpected file t.go: unexpected file a.go failed to match glob pattern: syntax error in pattern conf.yml failed to match glob pattern: syntax error in pattern t.go failed to match glob pattern: syntax error in pattern `, dir.Path()) assert.Equal(t, result.(cmpFailure).FailureMessage(), expected) }) } gotest.tools-2.3.0/fs/testdata/000077500000000000000000000000001341001157600163775ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/000077500000000000000000000000001341001157600227635ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/1000066400000000000000000000000021341001157600230360ustar00rootroot000000000000001 gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/000077500000000000000000000000001341001157600232035ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/1000066400000000000000000000000021341001157600232560ustar00rootroot000000000000001 gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/2000066400000000000000000000000021341001157600232570ustar00rootroot000000000000002 gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/b/000077500000000000000000000000001341001157600234245ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/b/1000066400000000000000000000000021341001157600234770ustar00rootroot000000000000001 gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/b/2000077700000000000000000000000001341001157600240022../2ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/b/3000077700000000000000000000000001341001157600276002/some/inexistent/linkustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test-with-symlink/a/b/40000777000000000000000000000000013410011576002357425ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test/000077500000000000000000000000001341001157600203265ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test/1000066400000000000000000000000021341001157600204010ustar00rootroot000000000000001 gotest.tools-2.3.0/fs/testdata/copy-test/a/000077500000000000000000000000001341001157600205465ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test/a/1000066400000000000000000000000021341001157600206210ustar00rootroot000000000000001 gotest.tools-2.3.0/fs/testdata/copy-test/a/2000066400000000000000000000000021341001157600206220ustar00rootroot000000000000002 gotest.tools-2.3.0/fs/testdata/copy-test/a/b/000077500000000000000000000000001341001157600207675ustar00rootroot00000000000000gotest.tools-2.3.0/fs/testdata/copy-test/a/b/1000066400000000000000000000000021341001157600210420ustar00rootroot000000000000001 gotest.tools-2.3.0/go.mod000066400000000000000000000002661341001157600152700ustar00rootroot00000000000000module gotest.tools require ( github.com/google/go-cmp v0.2.0 github.com/pkg/errors v0.8.0 github.com/spf13/pflag v1.0.3 golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d ) gotest.tools-2.3.0/go.sum000066400000000000000000000012761341001157600153170ustar00rootroot00000000000000github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d h1:/VmFqYLs4Kit6ncc9mRWBGPNlBVSusq3xA7p41/7JoY= golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gotest.tools-2.3.0/golden/000077500000000000000000000000001341001157600154265ustar00rootroot00000000000000gotest.tools-2.3.0/golden/example_test.go000066400000000000000000000005541341001157600204530ustar00rootroot00000000000000package golden_test import ( "testing" "gotest.tools/assert" "gotest.tools/golden" ) var t = &testing.T{} func ExampleAssert() { golden.Assert(t, "foo", "foo-content.golden") } func ExampleString() { assert.Assert(t, golden.String("foo", "foo-content.golden")) } func ExampleAssertBytes() { golden.AssertBytes(t, []byte("foo"), "foo-content.golden") } gotest.tools-2.3.0/golden/golden.go000066400000000000000000000101401341001157600172210ustar00rootroot00000000000000/*Package golden provides tools for comparing large mutli-line strings. Golden files are files in the ./testdata/ subdirectory of the package under test. */ package golden // import "gotest.tools/golden" import ( "bytes" "flag" "fmt" "io/ioutil" "path/filepath" "gotest.tools/assert" "gotest.tools/assert/cmp" "gotest.tools/internal/format" ) var flagUpdate = flag.Bool("test.update-golden", false, "update golden file") type helperT interface { Helper() } // Get returns the contents of the file in ./testdata func Get(t assert.TestingT, filename string) []byte { if ht, ok := t.(helperT); ok { ht.Helper() } expected, err := ioutil.ReadFile(Path(filename)) assert.NilError(t, err) return expected } // Path returns the full path to a file in ./testdata func Path(filename string) string { if filepath.IsAbs(filename) { return filename } return filepath.Join("testdata", filename) } func update(filename string, actual []byte, normalize normalize) error { if *flagUpdate { return ioutil.WriteFile(Path(filename), normalize(actual), 0644) } return nil } type normalize func([]byte) []byte func removeCarriageReturn(in []byte) []byte { return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1) } func exactBytes(in []byte) []byte { return in } // Assert compares the actual content to the expected content in the golden file. // If the `-test.update-golden` flag is set then the actual content is written // to the golden file. // Returns whether the assertion was successful (true) or not (false). // This is equivalent to assert.Check(t, String(actual, filename)) func Assert(t assert.TestingT, actual string, filename string, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } assert.Assert(t, String(actual, filename), msgAndArgs...) } // String compares actual to the contents of filename and returns success // if the strings are equal. // If the `-test.update-golden` flag is set then the actual content is written // to the golden file. // // Any \r\n substrings in actual are converted to a single \n character // before comparing it to the expected string. When updating the golden file the // normalized version will be written to the file. This allows Windows to use // the same golden files as other operating systems. func String(actual string, filename string) cmp.Comparison { return func() cmp.Result { actualBytes := removeCarriageReturn([]byte(actual)) result, expected := compare(actualBytes, filename, removeCarriageReturn) if result != nil { return result } diff := format.UnifiedDiff(format.DiffConfig{ A: string(expected), B: string(actualBytes), From: "expected", To: "actual", }) return cmp.ResultFailure("\n" + diff) } } // AssertBytes compares the actual result to the expected result in the golden // file. If the `-test.update-golden` flag is set then the actual content is // written to the golden file. // Returns whether the assertion was successful (true) or not (false). // This is equivalent to assert.Check(t, Bytes(actual, filename)) func AssertBytes( t assert.TestingT, actual []byte, filename string, msgAndArgs ...interface{}, ) { if ht, ok := t.(helperT); ok { ht.Helper() } assert.Assert(t, Bytes(actual, filename), msgAndArgs...) } // Bytes compares actual to the contents of filename and returns success // if the bytes are equal. // If the `-test.update-golden` flag is set then the actual content is written // to the golden file. func Bytes(actual []byte, filename string) cmp.Comparison { return func() cmp.Result { result, expected := compare(actual, filename, exactBytes) if result != nil { return result } msg := fmt.Sprintf("%v (actual) != %v (expected)", actual, expected) return cmp.ResultFailure(msg) } } func compare(actual []byte, filename string, normalize normalize) (cmp.Result, []byte) { if err := update(filename, actual, normalize); err != nil { return cmp.ResultFromError(err), nil } expected, err := ioutil.ReadFile(Path(filename)) if err != nil { return cmp.ResultFromError(err), nil } if bytes.Equal(expected, actual) { return cmp.ResultSuccess, nil } return nil, expected } gotest.tools-2.3.0/golden/golden_test.go000066400000000000000000000063261341001157600202730ustar00rootroot00000000000000package golden import ( "io/ioutil" "os" "path/filepath" "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" "gotest.tools/fs" ) type fakeT struct { Failed bool } func (t *fakeT) Log(...interface{}) { } func (t *fakeT) FailNow() { t.Failed = true } func (t *fakeT) Fail() { t.Failed = true } func (t *fakeT) Helper() {} func TestGoldenGetInvalidFile(t *testing.T) { fakeT := new(fakeT) Get(fakeT, "/invalid/path") assert.Assert(t, fakeT.Failed) } func TestGoldenGetAbsolutePath(t *testing.T) { file := fs.NewFile(t, "abs-test", fs.WithContent("content\n")) defer file.Remove() fakeT := new(fakeT) Get(fakeT, file.Path()) assert.Assert(t, !fakeT.Failed) } func TestGoldenGet(t *testing.T) { expected := "content\nline1\nline2" filename, clean := setupGoldenFile(t, expected) defer clean() fakeT := new(fakeT) actual := Get(fakeT, filename) assert.Assert(t, !fakeT.Failed) assert.Assert(t, cmp.DeepEqual(actual, []byte(expected))) } func TestGoldenAssertInvalidContent(t *testing.T) { filename, clean := setupGoldenFile(t, "content") defer clean() fakeT := new(fakeT) Assert(fakeT, "foo", filename) assert.Assert(t, fakeT.Failed) } func TestGoldenAssertInvalidContentUpdate(t *testing.T) { undo := setUpdateFlag() defer undo() filename, clean := setupGoldenFile(t, "content") defer clean() fakeT := new(fakeT) Assert(fakeT, "foo", filename) assert.Assert(t, !fakeT.Failed) } func TestGoldenAssert(t *testing.T) { filename, clean := setupGoldenFile(t, "foo") defer clean() fakeT := new(fakeT) Assert(fakeT, "foo", filename) assert.Assert(t, !fakeT.Failed) } func TestGoldenAssertWithCarriageReturnInActual(t *testing.T) { filename, clean := setupGoldenFile(t, "a\rfoo\nbar\n") defer clean() fakeT := new(fakeT) Assert(fakeT, "a\rfoo\r\nbar\r\n", filename) assert.Assert(t, !fakeT.Failed) } func TestGoldenAssertBytes(t *testing.T) { filename, clean := setupGoldenFile(t, "foo") defer clean() fakeT := new(fakeT) AssertBytes(fakeT, []byte("foo"), filename) assert.Assert(t, !fakeT.Failed) } func setUpdateFlag() func() { oldFlagUpdate := *flagUpdate *flagUpdate = true return func() { *flagUpdate = oldFlagUpdate } } func setupGoldenFile(t *testing.T, content string) (string, func()) { _ = os.Mkdir("testdata", 0755) f, err := ioutil.TempFile("testdata", "") assert.NilError(t, err, "fail to create test golden file") defer f.Close() // nolint: errcheck _, err = f.Write([]byte(content)) assert.NilError(t, err) return filepath.Base(f.Name()), func() { assert.NilError(t, os.Remove(f.Name())) } } func TestStringFailure(t *testing.T) { filename, clean := setupGoldenFile(t, "this is\nthe text") defer clean() result := String("this is\nnot the text", filename)() assert.Assert(t, !result.Success()) assert.Equal(t, result.(failure).FailureMessage(), ` --- expected +++ actual @@ -1,2 +1,2 @@ this is -the text +not the text `) } type failure interface { FailureMessage() string } func TestBytesFailure(t *testing.T) { filename, clean := setupGoldenFile(t, "5556") defer clean() result := Bytes([]byte("5555"), filename)() assert.Assert(t, !result.Success()) assert.Equal(t, result.(failure).FailureMessage(), `[53 53 53 53] (actual) != [53 53 53 54] (expected)`) } gotest.tools-2.3.0/icmd/000077500000000000000000000000001341001157600150725ustar00rootroot00000000000000gotest.tools-2.3.0/icmd/command.go000066400000000000000000000153621341001157600170460ustar00rootroot00000000000000/*Package icmd executes binaries and provides convenient assertions for testing the results. */ package icmd // import "gotest.tools/icmd" import ( "bytes" "fmt" "io" "os/exec" "strings" "sync" "time" "gotest.tools/assert" "gotest.tools/assert/cmp" ) type helperT interface { Helper() } // None is a token to inform Result.Assert that the output should be empty const None = "[NOTHING]" type lockedBuffer struct { m sync.RWMutex buf bytes.Buffer } func (buf *lockedBuffer) Write(b []byte) (int, error) { buf.m.Lock() defer buf.m.Unlock() return buf.buf.Write(b) } func (buf *lockedBuffer) String() string { buf.m.RLock() defer buf.m.RUnlock() return buf.buf.String() } // Result stores the result of running a command type Result struct { Cmd *exec.Cmd ExitCode int Error error // Timeout is true if the command was killed because it ran for too long Timeout bool outBuffer *lockedBuffer errBuffer *lockedBuffer } // Assert compares the Result against the Expected struct, and fails the test if // any of the expectations are not met. // // This function is equivalent to assert.Assert(t, result.Equal(exp)). func (r *Result) Assert(t assert.TestingT, exp Expected) *Result { if ht, ok := t.(helperT); ok { ht.Helper() } assert.Assert(t, r.Equal(exp)) return r } // Equal compares the result to Expected. If the result doesn't match expected // returns a formatted failure message with the command, stdout, stderr, exit code, // and any failed expectations. func (r *Result) Equal(exp Expected) cmp.Comparison { return func() cmp.Result { return cmp.ResultFromError(r.match(exp)) } } // Compare the result to Expected and return an error if they do not match. func (r *Result) Compare(exp Expected) error { return r.match(exp) } // nolint: gocyclo func (r *Result) match(exp Expected) error { errors := []string{} add := func(format string, args ...interface{}) { errors = append(errors, fmt.Sprintf(format, args...)) } if exp.ExitCode != r.ExitCode { add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode) } if exp.Timeout != r.Timeout { if exp.Timeout { add("Expected command to timeout") } else { add("Expected command to finish, but it hit the timeout") } } if !matchOutput(exp.Out, r.Stdout()) { add("Expected stdout to contain %q", exp.Out) } if !matchOutput(exp.Err, r.Stderr()) { add("Expected stderr to contain %q", exp.Err) } switch { // If a non-zero exit code is expected there is going to be an error. // Don't require an error message as well as an exit code because the // error message is going to be "exit status which is not useful case exp.Error == "" && exp.ExitCode != 0: case exp.Error == "" && r.Error != nil: add("Expected no error") case exp.Error != "" && r.Error == nil: add("Expected error to contain %q, but there was no error", exp.Error) case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error): add("Expected error to contain %q", exp.Error) } if len(errors) == 0 { return nil } return fmt.Errorf("%s\nFailures:\n%s", r, strings.Join(errors, "\n")) } func matchOutput(expected string, actual string) bool { switch expected { case None: return actual == "" default: return strings.Contains(actual, expected) } } func (r *Result) String() string { var timeout string if r.Timeout { timeout = " (timeout)" } var errString string if r.Error != nil { errString = "\nError: " + r.Error.Error() } return fmt.Sprintf(` Command: %s ExitCode: %d%s%s Stdout: %v Stderr: %v `, strings.Join(r.Cmd.Args, " "), r.ExitCode, timeout, errString, r.Stdout(), r.Stderr()) } // Expected is the expected output from a Command. This struct is compared to a // Result struct by Result.Assert(). type Expected struct { ExitCode int Timeout bool Error string Out string Err string } // Success is the default expected result. A Success result is one with a 0 // ExitCode. var Success = Expected{} // Stdout returns the stdout of the process as a string func (r *Result) Stdout() string { return r.outBuffer.String() } // Stderr returns the stderr of the process as a string func (r *Result) Stderr() string { return r.errBuffer.String() } // Combined returns the stdout and stderr combined into a single string func (r *Result) Combined() string { return r.outBuffer.String() + r.errBuffer.String() } func (r *Result) setExitError(err error) { if err == nil { return } r.Error = err r.ExitCode = processExitCode(err) } // Cmd contains the arguments and options for a process to run as part of a test // suite. type Cmd struct { Command []string Timeout time.Duration Stdin io.Reader Stdout io.Writer Dir string Env []string } // Command create a simple Cmd with the specified command and arguments func Command(command string, args ...string) Cmd { return Cmd{Command: append([]string{command}, args...)} } // RunCmd runs a command and returns a Result func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result { for _, op := range cmdOperators { op(&cmd) } result := StartCmd(cmd) if result.Error != nil { return result } return WaitOnCmd(cmd.Timeout, result) } // RunCommand runs a command with default options, and returns a result func RunCommand(command string, args ...string) *Result { return RunCmd(Command(command, args...)) } // StartCmd starts a command, but doesn't wait for it to finish func StartCmd(cmd Cmd) *Result { result := buildCmd(cmd) if result.Error != nil { return result } result.setExitError(result.Cmd.Start()) return result } // TODO: support exec.CommandContext func buildCmd(cmd Cmd) *Result { var execCmd *exec.Cmd switch len(cmd.Command) { case 1: execCmd = exec.Command(cmd.Command[0]) default: execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...) } outBuffer := new(lockedBuffer) errBuffer := new(lockedBuffer) execCmd.Stdin = cmd.Stdin execCmd.Dir = cmd.Dir execCmd.Env = cmd.Env if cmd.Stdout != nil { execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout) } else { execCmd.Stdout = outBuffer } execCmd.Stderr = errBuffer return &Result{ Cmd: execCmd, outBuffer: outBuffer, errBuffer: errBuffer, } } // WaitOnCmd waits for a command to complete. If timeout is non-nil then // only wait until the timeout. func WaitOnCmd(timeout time.Duration, result *Result) *Result { if timeout == time.Duration(0) { result.setExitError(result.Cmd.Wait()) return result } done := make(chan error, 1) // Wait for command to exit in a goroutine go func() { done <- result.Cmd.Wait() }() select { case <-time.After(timeout): killErr := result.Cmd.Process.Kill() if killErr != nil { fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr) } result.Timeout = true case err := <-done: result.setExitError(err) } return result } gotest.tools-2.3.0/icmd/command_test.go000066400000000000000000000070201341001157600200750ustar00rootroot00000000000000package icmd import ( "bytes" "errors" "os" "os/exec" "path/filepath" "runtime" "testing" "time" "gotest.tools/assert" "gotest.tools/fs" "gotest.tools/golden" "gotest.tools/internal/maint" ) var ( bindir = fs.NewDir(maint.T, "icmd-dir") binname = bindir.Join("bin-stub") + pathext() stubpath = filepath.FromSlash("./internal/stub") ) func pathext() string { if runtime.GOOS == "windows" { return ".exe" } return "" } func TestMain(m *testing.M) { exitcode := m.Run() bindir.Remove() os.Exit(exitcode) } func buildStub(t assert.TestingT) { if _, err := os.Stat(binname); err == nil { return } result := RunCommand("go", "build", "-o", binname, stubpath) result.Assert(t, Success) } func TestRunCommandSuccess(t *testing.T) { buildStub(t) result := RunCommand(binname) result.Assert(t, Success) } func TestRunCommandWithCombined(t *testing.T) { buildStub(t) result := RunCommand(binname, "-warn") result.Assert(t, Expected{}) assert.Equal(t, result.Combined(), "this is stdout\nthis is stderr\n") assert.Equal(t, result.Stdout(), "this is stdout\n") assert.Equal(t, result.Stderr(), "this is stderr\n") } func TestRunCommandWithTimeoutFinished(t *testing.T) { buildStub(t) result := RunCmd(Cmd{ Command: []string{binname, "-sleep=1ms"}, Timeout: 2 * time.Second, }) result.Assert(t, Expected{Out: "this is stdout"}) } func TestRunCommandWithTimeoutKilled(t *testing.T) { buildStub(t) command := []string{binname, "-sleep=200ms"} result := RunCmd(Cmd{Command: command, Timeout: 30 * time.Millisecond}) result.Assert(t, Expected{Timeout: true, Out: None, Err: None}) } func TestRunCommandWithErrors(t *testing.T) { buildStub(t) result := RunCommand("doesnotexists") expected := `exec: "doesnotexists": executable file not found` result.Assert(t, Expected{Out: None, Err: None, ExitCode: 127, Error: expected}) } func TestRunCommandWithStdoutNoStderr(t *testing.T) { buildStub(t) result := RunCommand(binname) result.Assert(t, Expected{Out: "this is stdout\n", Err: None}) } func TestRunCommandWithExitCode(t *testing.T) { buildStub(t) result := RunCommand(binname, "-fail=99") result.Assert(t, Expected{ ExitCode: 99, Error: "exit status 99", }) } func TestResult_Match_NotMatched(t *testing.T) { result := &Result{ Cmd: exec.Command("binary", "arg1"), ExitCode: 99, Error: errors.New("exit code 99"), outBuffer: newLockedBuffer("the output"), errBuffer: newLockedBuffer("the stderr"), Timeout: true, } exp := Expected{ ExitCode: 101, Out: "Something else", Err: None, } err := result.match(exp) assert.ErrorContains(t, err, "Failures") golden.Assert(t, err.Error(), "result-match-no-match.golden") } func newLockedBuffer(s string) *lockedBuffer { return &lockedBuffer{buf: *bytes.NewBufferString(s)} } func TestResult_Match_NotMatchedNoError(t *testing.T) { result := &Result{ Cmd: exec.Command("binary", "arg1"), outBuffer: newLockedBuffer("the output"), errBuffer: newLockedBuffer("the stderr"), } exp := Expected{ ExitCode: 101, Out: "Something else", Err: None, } err := result.match(exp) assert.ErrorContains(t, err, "Failures") golden.Assert(t, err.Error(), "result-match-no-match-no-error.golden") } func TestResult_Match_Match(t *testing.T) { result := &Result{ Cmd: exec.Command("binary", "arg1"), outBuffer: newLockedBuffer("the output"), errBuffer: newLockedBuffer("the stderr"), } exp := Expected{ Out: "the output", Err: "the stderr", } err := result.match(exp) assert.NilError(t, err) } gotest.tools-2.3.0/icmd/example_test.go000066400000000000000000000006311341001157600201130ustar00rootroot00000000000000package icmd_test import ( "testing" "gotest.tools/icmd" ) var t = &testing.T{} func ExampleRunCommand() { result := icmd.RunCommand("bash", "-c", "echo all good") result.Assert(t, icmd.Success) } func ExampleRunCmd() { result := icmd.RunCmd(icmd.Command("cat", "/does/not/exist")) result.Assert(t, icmd.Expected{ ExitCode: 1, Err: "cat: /does/not/exist: No such file or directory", }) } gotest.tools-2.3.0/icmd/exitcode.go000066400000000000000000000014001341001157600172200ustar00rootroot00000000000000package icmd import ( "os/exec" "syscall" "github.com/pkg/errors" ) // getExitCode returns the ExitStatus of a process from the error returned by // exec.Run(). If the exit status could not be parsed an error is returned. func getExitCode(err error) (int, error) { if exiterr, ok := err.(*exec.ExitError); ok { if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok { return procExit.ExitStatus(), nil } } return 0, errors.Wrap(err, "failed to get exit code") } func processExitCode(err error) (exitCode int) { if err == nil { return 0 } exitCode, exiterr := getExitCode(err) if exiterr != nil { // TODO: Fix this so we check the error's text. // we've failed to retrieve exit code, so we set it to 127 return 127 } return exitCode } gotest.tools-2.3.0/icmd/internal/000077500000000000000000000000001341001157600167065ustar00rootroot00000000000000gotest.tools-2.3.0/icmd/internal/stub/000077500000000000000000000000001341001157600176635ustar00rootroot00000000000000gotest.tools-2.3.0/icmd/internal/stub/main.go000066400000000000000000000005621341001157600211410ustar00rootroot00000000000000package main import ( "flag" "fmt" "os" "time" ) func main() { sleep := flag.Duration("sleep", 0, "Sleep") warn := flag.Bool("warn", false, "Warn") fail := flag.Int("fail", 0, "Fail with code") flag.Parse() if *sleep != 0 { time.Sleep(*sleep) } fmt.Println("this is stdout") if *warn { fmt.Fprintln(os.Stderr, "this is stderr") } os.Exit(*fail) } gotest.tools-2.3.0/icmd/ops.go000066400000000000000000000013671341001157600162310ustar00rootroot00000000000000package icmd import ( "io" "time" ) // CmdOp is an operation which modified a Cmd structure used to execute commands type CmdOp func(*Cmd) // WithTimeout sets the timeout duration of the command func WithTimeout(timeout time.Duration) CmdOp { return func(c *Cmd) { c.Timeout = timeout } } // WithEnv sets the environment variable of the command. // Each arguments are in the form of KEY=VALUE func WithEnv(env ...string) CmdOp { return func(c *Cmd) { c.Env = env } } // Dir sets the working directory of the command func Dir(path string) CmdOp { return func(c *Cmd) { c.Dir = path } } // WithStdin sets the standard input of the command to the specified reader func WithStdin(r io.Reader) CmdOp { return func(c *Cmd) { c.Stdin = r } } gotest.tools-2.3.0/icmd/testdata/000077500000000000000000000000001341001157600167035ustar00rootroot00000000000000gotest.tools-2.3.0/icmd/testdata/result-match-no-match-no-error.golden000066400000000000000000000003061341001157600257510ustar00rootroot00000000000000 Command: binary arg1 ExitCode: 0 Stdout: the output Stderr: the stderr Failures: ExitCode was 0 expected 101 Expected stdout to contain "Something else" Expected stderr to contain "[NOTHING]"gotest.tools-2.3.0/icmd/testdata/result-match-no-match.golden000066400000000000000000000004341341001157600242120ustar00rootroot00000000000000 Command: binary arg1 ExitCode: 99 (timeout) Error: exit code 99 Stdout: the output Stderr: the stderr Failures: ExitCode was 99 expected 101 Expected command to finish, but it hit the timeout Expected stdout to contain "Something else" Expected stderr to contain "[NOTHING]"gotest.tools-2.3.0/internal/000077500000000000000000000000001341001157600157725ustar00rootroot00000000000000gotest.tools-2.3.0/internal/difflib/000077500000000000000000000000001341001157600173715ustar00rootroot00000000000000gotest.tools-2.3.0/internal/difflib/LICENSE000066400000000000000000000026451341001157600204050ustar00rootroot00000000000000Copyright (c) 2013, Patrick Mezard All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. gotest.tools-2.3.0/internal/difflib/difflib.go000066400000000000000000000317211341001157600213230ustar00rootroot00000000000000/*Package difflib is a partial port of Python difflib module. Original source: https://github.com/pmezard/go-difflib This file is trimmed to only the parts used by this repository. */ package difflib // import "gotest.tools/internal/difflib" func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } // Match stores line numbers of size of match type Match struct { A int B int Size int } // OpCode identifies the type of diff type OpCode struct { Tag byte I1 int I2 int J1 int J2 int } // SequenceMatcher compares sequence of strings. The basic // algorithm predates, and is a little fancier than, an algorithm // published in the late 1980's by Ratcliff and Obershelp under the // hyperbolic name "gestalt pattern matching". The basic idea is to find // the longest contiguous matching subsequence that contains no "junk" // elements (R-O doesn't address junk). The same idea is then applied // recursively to the pieces of the sequences to the left and to the right // of the matching subsequence. This does not yield minimal edit // sequences, but does tend to yield matches that "look right" to people. // // SequenceMatcher tries to compute a "human-friendly diff" between two // sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the // longest *contiguous* & junk-free matching subsequence. That's what // catches peoples' eyes. The Windows(tm) windiff has another interesting // notion, pairing up elements that appear uniquely in each sequence. // That, and the method here, appear to yield more intuitive difference // reports than does diff. This method appears to be the least vulnerable // to synching up on blocks of "junk lines", though (like blank lines in // ordinary text files, or maybe "

" lines in HTML files). That may be // because this is the only method of the 3 that has a *concept* of // "junk" . // // Timing: Basic R-O is cubic time worst case and quadratic time expected // case. SequenceMatcher is quadratic time for the worst case and has // expected-case behavior dependent in a complicated way on how many // elements the sequences have in common; best case time is linear. type SequenceMatcher struct { a []string b []string b2j map[string][]int IsJunk func(string) bool autoJunk bool bJunk map[string]struct{} matchingBlocks []Match fullBCount map[string]int bPopular map[string]struct{} opCodes []OpCode } // NewMatcher returns a new SequenceMatcher func NewMatcher(a, b []string) *SequenceMatcher { m := SequenceMatcher{autoJunk: true} m.SetSeqs(a, b) return &m } // SetSeqs sets two sequences to be compared. func (m *SequenceMatcher) SetSeqs(a, b []string) { m.SetSeq1(a) m.SetSeq2(b) } // SetSeq1 sets the first sequence to be compared. The second sequence to be compared is // not changed. // // SequenceMatcher computes and caches detailed information about the second // sequence, so if you want to compare one sequence S against many sequences, // use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other // sequences. // // See also SetSeqs() and SetSeq2(). func (m *SequenceMatcher) SetSeq1(a []string) { if &a == &m.a { return } m.a = a m.matchingBlocks = nil m.opCodes = nil } // SetSeq2 sets the second sequence to be compared. The first sequence to be compared is // not changed. func (m *SequenceMatcher) SetSeq2(b []string) { if &b == &m.b { return } m.b = b m.matchingBlocks = nil m.opCodes = nil m.fullBCount = nil m.chainB() } func (m *SequenceMatcher) chainB() { // Populate line -> index mapping b2j := map[string][]int{} for i, s := range m.b { indices := b2j[s] indices = append(indices, i) b2j[s] = indices } // Purge junk elements m.bJunk = map[string]struct{}{} if m.IsJunk != nil { junk := m.bJunk for s := range b2j { if m.IsJunk(s) { junk[s] = struct{}{} } } for s := range junk { delete(b2j, s) } } // Purge remaining popular elements popular := map[string]struct{}{} n := len(m.b) if m.autoJunk && n >= 200 { ntest := n/100 + 1 for s, indices := range b2j { if len(indices) > ntest { popular[s] = struct{}{} } } for s := range popular { delete(b2j, s) } } m.bPopular = popular m.b2j = b2j } func (m *SequenceMatcher) isBJunk(s string) bool { _, ok := m.bJunk[s] return ok } // Find longest matching block in a[alo:ahi] and b[blo:bhi]. // // If IsJunk is not defined: // // Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where // alo <= i <= i+k <= ahi // blo <= j <= j+k <= bhi // and for all (i',j',k') meeting those conditions, // k >= k' // i <= i' // and if i == i', j <= j' // // In other words, of all maximal matching blocks, return one that // starts earliest in a, and of all those maximal matching blocks that // start earliest in a, return the one that starts earliest in b. // // If IsJunk is defined, first the longest matching block is // determined as above, but with the additional restriction that no // junk element appears in the block. Then that block is extended as // far as possible by matching (only) junk elements on both sides. So // the resulting block never matches on junk except as identical junk // happens to be adjacent to an "interesting" match. // // If no blocks match, return (alo, blo, 0). func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match { // CAUTION: stripping common prefix or suffix would be incorrect. // E.g., // ab // acab // Longest matching block is "ab", but if common prefix is // stripped, it's "a" (tied with "b"). UNIX(tm) diff does so // strip, so ends up claiming that ab is changed to acab by // inserting "ca" in the middle. That's minimal but unintuitive: // "it's obvious" that someone inserted "ac" at the front. // Windiff ends up at the same place as diff, but by pairing up // the unique 'b's and then matching the first two 'a's. besti, bestj, bestsize := alo, blo, 0 // find longest junk-free match // during an iteration of the loop, j2len[j] = length of longest // junk-free match ending with a[i-1] and b[j] j2len := map[int]int{} for i := alo; i != ahi; i++ { // look at all instances of a[i] in b; note that because // b2j has no junk keys, the loop is skipped if a[i] is junk newj2len := map[int]int{} for _, j := range m.b2j[m.a[i]] { // a[i] matches b[j] if j < blo { continue } if j >= bhi { break } k := j2len[j-1] + 1 newj2len[j] = k if k > bestsize { besti, bestj, bestsize = i-k+1, j-k+1, k } } j2len = newj2len } // Extend the best by non-junk elements on each end. In particular, // "popular" non-junk elements aren't in b2j, which greatly speeds // the inner loop above, but also means "the best" match so far // doesn't contain any junk *or* popular non-junk elements. for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) && m.a[besti-1] == m.b[bestj-1] { besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 } for besti+bestsize < ahi && bestj+bestsize < bhi && !m.isBJunk(m.b[bestj+bestsize]) && m.a[besti+bestsize] == m.b[bestj+bestsize] { bestsize += 1 } // Now that we have a wholly interesting match (albeit possibly // empty!), we may as well suck up the matching junk on each // side of it too. Can't think of a good reason not to, and it // saves post-processing the (possibly considerable) expense of // figuring out what to do with it. In the case of an empty // interesting match, this is clearly the right thing to do, // because no other kind of match is possible in the regions. for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) && m.a[besti-1] == m.b[bestj-1] { besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 } for besti+bestsize < ahi && bestj+bestsize < bhi && m.isBJunk(m.b[bestj+bestsize]) && m.a[besti+bestsize] == m.b[bestj+bestsize] { bestsize += 1 } return Match{A: besti, B: bestj, Size: bestsize} } // GetMatchingBlocks returns a list of triples describing matching subsequences. // // Each triple is of the form (i, j, n), and means that // a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in // i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are // adjacent triples in the list, and the second is not the last triple in the // list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe // adjacent equal blocks. // // The last triple is a dummy, (len(a), len(b), 0), and is the only // triple with n==0. func (m *SequenceMatcher) GetMatchingBlocks() []Match { if m.matchingBlocks != nil { return m.matchingBlocks } var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match { match := m.findLongestMatch(alo, ahi, blo, bhi) i, j, k := match.A, match.B, match.Size if match.Size > 0 { if alo < i && blo < j { matched = matchBlocks(alo, i, blo, j, matched) } matched = append(matched, match) if i+k < ahi && j+k < bhi { matched = matchBlocks(i+k, ahi, j+k, bhi, matched) } } return matched } matched := matchBlocks(0, len(m.a), 0, len(m.b), nil) // It's possible that we have adjacent equal blocks in the // matching_blocks list now. nonAdjacent := []Match{} i1, j1, k1 := 0, 0, 0 for _, b := range matched { // Is this block adjacent to i1, j1, k1? i2, j2, k2 := b.A, b.B, b.Size if i1+k1 == i2 && j1+k1 == j2 { // Yes, so collapse them -- this just increases the length of // the first block by the length of the second, and the first // block so lengthened remains the block to compare against. k1 += k2 } else { // Not adjacent. Remember the first block (k1==0 means it's // the dummy we started with), and make the second block the // new block to compare against. if k1 > 0 { nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) } i1, j1, k1 = i2, j2, k2 } } if k1 > 0 { nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) } nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0}) m.matchingBlocks = nonAdjacent return m.matchingBlocks } // GetOpCodes returns a list of 5-tuples describing how to turn a into b. // // Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple // has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the // tuple preceding it, and likewise for j1 == the previous j2. // // The tags are characters, with these meanings: // // 'r' (replace): a[i1:i2] should be replaced by b[j1:j2] // // 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case. // // 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case. // // 'e' (equal): a[i1:i2] == b[j1:j2] func (m *SequenceMatcher) GetOpCodes() []OpCode { if m.opCodes != nil { return m.opCodes } i, j := 0, 0 matching := m.GetMatchingBlocks() opCodes := make([]OpCode, 0, len(matching)) for _, m := range matching { // invariant: we've pumped out correct diffs to change // a[:i] into b[:j], and the next matching block is // a[ai:ai+size] == b[bj:bj+size]. So we need to pump // out a diff to change a[i:ai] into b[j:bj], pump out // the matching block, and move (i,j) beyond the match ai, bj, size := m.A, m.B, m.Size tag := byte(0) if i < ai && j < bj { tag = 'r' } else if i < ai { tag = 'd' } else if j < bj { tag = 'i' } if tag > 0 { opCodes = append(opCodes, OpCode{tag, i, ai, j, bj}) } i, j = ai+size, bj+size // the list of matching blocks is terminated by a // sentinel with size 0 if size > 0 { opCodes = append(opCodes, OpCode{'e', ai, i, bj, j}) } } m.opCodes = opCodes return m.opCodes } // GetGroupedOpCodes isolates change clusters by eliminating ranges with no changes. // // Return a generator of groups with up to n lines of context. // Each group is in the same format as returned by GetOpCodes(). func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { if n < 0 { n = 3 } codes := m.GetOpCodes() if len(codes) == 0 { codes = []OpCode{{'e', 0, 1, 0, 1}} } // Fixup leading and trailing groups if they show no changes. if codes[0].Tag == 'e' { c := codes[0] i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2} } if codes[len(codes)-1].Tag == 'e' { c := codes[len(codes)-1] i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)} } nn := n + n groups := [][]OpCode{} group := []OpCode{} for _, c := range codes { i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 // End the current group and start a new one whenever // there is a large range with no changes. if c.Tag == 'e' && i2-i1 > nn { group = append(group, OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}) groups = append(groups, group) group = []OpCode{} i1, j1 = max(i1, i2-n), max(j1, j2-n) } group = append(group, OpCode{c.Tag, i1, i2, j1, j2}) } if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') { groups = append(groups, group) } return groups } gotest.tools-2.3.0/internal/format/000077500000000000000000000000001341001157600172625ustar00rootroot00000000000000gotest.tools-2.3.0/internal/format/diff.go000066400000000000000000000073601341001157600205270ustar00rootroot00000000000000package format import ( "bytes" "fmt" "strings" "unicode" "gotest.tools/internal/difflib" ) const ( contextLines = 2 ) // DiffConfig for a unified diff type DiffConfig struct { A string B string From string To string } // UnifiedDiff is a modified version of difflib.WriteUnifiedDiff with better // support for showing the whitespace differences. func UnifiedDiff(conf DiffConfig) string { a := strings.SplitAfter(conf.A, "\n") b := strings.SplitAfter(conf.B, "\n") groups := difflib.NewMatcher(a, b).GetGroupedOpCodes(contextLines) if len(groups) == 0 { return "" } buf := new(bytes.Buffer) writeFormat := func(format string, args ...interface{}) { buf.WriteString(fmt.Sprintf(format, args...)) } writeLine := func(prefix string, s string) { buf.WriteString(prefix + s) } if hasWhitespaceDiffLines(groups, a, b) { writeLine = visibleWhitespaceLine(writeLine) } formatHeader(writeFormat, conf) for _, group := range groups { formatRangeLine(writeFormat, group) for _, opCode := range group { in, out := a[opCode.I1:opCode.I2], b[opCode.J1:opCode.J2] switch opCode.Tag { case 'e': formatLines(writeLine, " ", in) case 'r': formatLines(writeLine, "-", in) formatLines(writeLine, "+", out) case 'd': formatLines(writeLine, "-", in) case 'i': formatLines(writeLine, "+", out) } } } return buf.String() } // hasWhitespaceDiffLines returns true if any diff groups is only different // because of whitespace characters. func hasWhitespaceDiffLines(groups [][]difflib.OpCode, a, b []string) bool { for _, group := range groups { in, out := new(bytes.Buffer), new(bytes.Buffer) for _, opCode := range group { if opCode.Tag == 'e' { continue } for _, line := range a[opCode.I1:opCode.I2] { in.WriteString(line) } for _, line := range b[opCode.J1:opCode.J2] { out.WriteString(line) } } if removeWhitespace(in.String()) == removeWhitespace(out.String()) { return true } } return false } func removeWhitespace(s string) string { var result []rune for _, r := range s { if !unicode.IsSpace(r) { result = append(result, r) } } return string(result) } func visibleWhitespaceLine(ws func(string, string)) func(string, string) { mapToVisibleSpace := func(r rune) rune { switch r { case '\n': case ' ': return '·' case '\t': return '▷' case '\v': return '▽' case '\r': return '↵' case '\f': return '↓' default: if unicode.IsSpace(r) { return '�' } } return r } return func(prefix, s string) { ws(prefix, strings.Map(mapToVisibleSpace, s)) } } func formatHeader(wf func(string, ...interface{}), conf DiffConfig) { if conf.From != "" || conf.To != "" { wf("--- %s\n", conf.From) wf("+++ %s\n", conf.To) } } func formatRangeLine(wf func(string, ...interface{}), group []difflib.OpCode) { first, last := group[0], group[len(group)-1] range1 := formatRangeUnified(first.I1, last.I2) range2 := formatRangeUnified(first.J1, last.J2) wf("@@ -%s +%s @@\n", range1, range2) } // Convert range to the "ed" format func formatRangeUnified(start, stop int) string { // Per the diff spec at http://www.unix.org/single_unix_specification/ beginning := start + 1 // lines start numbering with one length := stop - start if length == 1 { return fmt.Sprintf("%d", beginning) } if length == 0 { beginning-- // empty ranges begin at line just before the range } return fmt.Sprintf("%d,%d", beginning, length) } func formatLines(writeLine func(string, string), prefix string, lines []string) { for _, line := range lines { writeLine(prefix, line) } // Add a newline if the last line is missing one so that the diff displays // properly. if !strings.HasSuffix(lines[len(lines)-1], "\n") { writeLine("", "\n") } } gotest.tools-2.3.0/internal/format/diff_test.go000066400000000000000000000030231341001157600215560ustar00rootroot00000000000000package format_test import ( "testing" "gotest.tools/assert" "gotest.tools/golden" "gotest.tools/internal/format" ) func TestUnifiedDiff(t *testing.T) { var testcases = []struct { name string a string b string expected string from string to string }{ { name: "empty diff", a: "a\nb\nc", b: "a\nb\nc", from: "from", to: "to", }, { name: "one diff with header", a: "a\nxyz\nc", b: "a\nb\nc", from: "from", to: "to", expected: "one-diff-with-header.golden", }, { name: "many diffs", a: "a123\nxyz\nc\nbaba\nz\nt\nj2j2\nok\nok\ndone\n", b: "a123\nxyz\nc\nabab\nz\nt\nj2j2\nok\nok\n", expected: "many-diff.golden", }, { name: "no trailing newline", a: "a123\nxyz\nc\nbaba\nz\nt\nj2j2\nok\nok\ndone\n", b: "a123\nxyz\nc\nabab\nz\nt\nj2j2\nok\nok", expected: "many-diff-no-trailing-newline.golden", }, { name: "whitespace diff", a: " something\n something\n \v\r\n", b: " something\n\tsomething\n \n", expected: "whitespace-diff.golden", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { diff := format.UnifiedDiff(format.DiffConfig{ A: testcase.a, B: testcase.b, From: testcase.from, To: testcase.to, }) if testcase.expected != "" { assert.Assert(t, golden.String(diff, testcase.expected)) return } assert.Equal(t, diff, "") }) } } gotest.tools-2.3.0/internal/format/format.go000066400000000000000000000012641341001157600211040ustar00rootroot00000000000000package format // import "gotest.tools/internal/format" import "fmt" // Message accepts a msgAndArgs varargs and formats it using fmt.Sprintf func Message(msgAndArgs ...interface{}) string { switch len(msgAndArgs) { case 0: return "" case 1: return fmt.Sprintf("%v", msgAndArgs[0]) default: return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) } } // WithCustomMessage accepts one or two messages and formats them appropriately func WithCustomMessage(source string, msgAndArgs ...interface{}) string { custom := Message(msgAndArgs...) switch { case custom == "": return source case source == "": return custom } return fmt.Sprintf("%s: %s", source, custom) } gotest.tools-2.3.0/internal/format/format_test.go000066400000000000000000000022231341001157600221370ustar00rootroot00000000000000package format_test import ( "testing" "gotest.tools/assert" "gotest.tools/internal/format" ) func TestMessage(t *testing.T) { var testcases = []struct { doc string args []interface{} expected string }{ { doc: "none", }, { doc: "single string", args: args("foo"), expected: "foo", }, { doc: "single non-string", args: args(123), expected: "123", }, { doc: "format string and args", args: args("%s %v", "a", 3), expected: "a 3", }, } for _, tc := range testcases { t.Run(tc.doc, func(t *testing.T) { assert.Equal(t, format.Message(tc.args...), tc.expected) }) } } func args(a ...interface{}) []interface{} { return a } func TestWithCustomMessage(t *testing.T) { t.Run("only custom", func(t *testing.T) { msg := format.WithCustomMessage("", "extra") assert.Equal(t, msg, "extra") }) t.Run("only source", func(t *testing.T) { msg := format.WithCustomMessage("source") assert.Equal(t, msg, "source") }) t.Run("source and custom", func(t *testing.T) { msg := format.WithCustomMessage("source", "extra") assert.Equal(t, msg, "source: extra") }) } gotest.tools-2.3.0/internal/format/testdata/000077500000000000000000000000001341001157600210735ustar00rootroot00000000000000gotest.tools-2.3.0/internal/format/testdata/many-diff-no-trailing-newline.golden000066400000000000000000000001051341001157600300130ustar00rootroot00000000000000@@ -2,10 +2,8 @@ xyz c -baba +abab z t j2j2 ok -ok -done - +ok gotest.tools-2.3.0/internal/format/testdata/many-diff.golden000066400000000000000000000001121341001157600241310ustar00rootroot00000000000000@@ -2,5 +2,5 @@ xyz c -baba +abab z t @@ -8,4 +8,3 @@ ok ok -done gotest.tools-2.3.0/internal/format/testdata/one-diff-with-header.golden000066400000000000000000000000561341001157600261540ustar00rootroot00000000000000--- from +++ to @@ -1,3 +1,3 @@ a -xyz +b c gotest.tools-2.3.0/internal/format/testdata/whitespace-diff.golden000066400000000000000000000001341341001157600253250ustar00rootroot00000000000000@@ -1,4 +1,4 @@ ··something -······something -····▽↵ +▷something +·· gotest.tools-2.3.0/internal/maint/000077500000000000000000000000001341001157600171025ustar00rootroot00000000000000gotest.tools-2.3.0/internal/maint/maint.go000066400000000000000000000010601341001157600205360ustar00rootroot00000000000000package maint // import "gotest.tools/internal/maint" import ( "fmt" "os" ) // T provides an implementation of assert.TestingT which uses os.Exit, and // fmt.Println. This implementation can be used outside of test cases to provide // assert.TestingT, for example in a TestMain. var T = t{} type t struct{} // FailNow exits with a non-zero code func (t t) FailNow() { os.Exit(1) } // Fail exits with a non-zero code func (t t) Fail() { os.Exit(2) } // Log args by printing them to stdout func (t t) Log(args ...interface{}) { fmt.Println(args...) } gotest.tools-2.3.0/internal/source/000077500000000000000000000000001341001157600172725ustar00rootroot00000000000000gotest.tools-2.3.0/internal/source/defers.go000066400000000000000000000022451341001157600210740ustar00rootroot00000000000000package source import ( "go/ast" "go/token" "github.com/pkg/errors" ) func scanToDeferLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { var matchedNode ast.Node ast.Inspect(node, func(node ast.Node) bool { switch { case node == nil || matchedNode != nil: return false case fileset.Position(node.End()).Line == lineNum: if funcLit, ok := node.(*ast.FuncLit); ok { matchedNode = funcLit return false } } return true }) debug("defer line node: %s", debugFormatNode{matchedNode}) return matchedNode } func guessDefer(node ast.Node) (ast.Node, error) { defers := collectDefers(node) switch len(defers) { case 0: return nil, errors.New("failed to expression in defer") case 1: return defers[0].Call, nil default: return nil, errors.Errorf( "ambiguous call expression: multiple (%d) defers in call block", len(defers)) } } func collectDefers(node ast.Node) []*ast.DeferStmt { var defers []*ast.DeferStmt ast.Inspect(node, func(node ast.Node) bool { if d, ok := node.(*ast.DeferStmt); ok { defers = append(defers, d) debug("defer: %s", debugFormatNode{d}) return false } return true }) return defers } gotest.tools-2.3.0/internal/source/source.go000066400000000000000000000100721341001157600211210ustar00rootroot00000000000000package source // import "gotest.tools/internal/source" import ( "bytes" "fmt" "go/ast" "go/format" "go/parser" "go/token" "os" "runtime" "strconv" "strings" "github.com/pkg/errors" ) const baseStackIndex = 1 // FormattedCallExprArg returns the argument from an ast.CallExpr at the // index in the call stack. The argument is formatted using FormatNode. func FormattedCallExprArg(stackIndex int, argPos int) (string, error) { args, err := CallExprArgs(stackIndex + 1) if err != nil { return "", err } if argPos >= len(args) { return "", errors.New("failed to find expression") } return FormatNode(args[argPos]) } // CallExprArgs returns the ast.Expr slice for the args of an ast.CallExpr at // the index in the call stack. func CallExprArgs(stackIndex int) ([]ast.Expr, error) { _, filename, lineNum, ok := runtime.Caller(baseStackIndex + stackIndex) if !ok { return nil, errors.New("failed to get call stack") } debug("call stack position: %s:%d", filename, lineNum) node, err := getNodeAtLine(filename, lineNum) if err != nil { return nil, err } debug("found node: %s", debugFormatNode{node}) return getCallExprArgs(node) } func getNodeAtLine(filename string, lineNum int) (ast.Node, error) { fileset := token.NewFileSet() astFile, err := parser.ParseFile(fileset, filename, nil, parser.AllErrors) if err != nil { return nil, errors.Wrapf(err, "failed to parse source file: %s", filename) } if node := scanToLine(fileset, astFile, lineNum); node != nil { return node, nil } if node := scanToDeferLine(fileset, astFile, lineNum); node != nil { node, err := guessDefer(node) if err != nil || node != nil { return node, err } } return nil, errors.Errorf( "failed to find an expression on line %d in %s", lineNum, filename) } func scanToLine(fileset *token.FileSet, node ast.Node, lineNum int) ast.Node { var matchedNode ast.Node ast.Inspect(node, func(node ast.Node) bool { switch { case node == nil || matchedNode != nil: return false case nodePosition(fileset, node).Line == lineNum: matchedNode = node return false } return true }) return matchedNode } // In golang 1.9 the line number changed from being the line where the statement // ended to the line where the statement began. func nodePosition(fileset *token.FileSet, node ast.Node) token.Position { if goVersionBefore19 { return fileset.Position(node.End()) } return fileset.Position(node.Pos()) } var goVersionBefore19 = func() bool { version := runtime.Version() // not a release version if !strings.HasPrefix(version, "go") { return false } version = strings.TrimPrefix(version, "go") parts := strings.Split(version, ".") if len(parts) < 2 { return false } minor, err := strconv.ParseInt(parts[1], 10, 32) return err == nil && parts[0] == "1" && minor < 9 }() func getCallExprArgs(node ast.Node) ([]ast.Expr, error) { visitor := &callExprVisitor{} ast.Walk(visitor, node) if visitor.expr == nil { return nil, errors.New("failed to find call expression") } debug("callExpr: %s", debugFormatNode{visitor.expr}) return visitor.expr.Args, nil } type callExprVisitor struct { expr *ast.CallExpr } func (v *callExprVisitor) Visit(node ast.Node) ast.Visitor { if v.expr != nil || node == nil { return nil } debug("visit: %s", debugFormatNode{node}) switch typed := node.(type) { case *ast.CallExpr: v.expr = typed return nil case *ast.DeferStmt: ast.Walk(v, typed.Call.Fun) return nil } return v } // FormatNode using go/format.Node and return the result as a string func FormatNode(node ast.Node) (string, error) { buf := new(bytes.Buffer) err := format.Node(buf, token.NewFileSet(), node) return buf.String(), err } var debugEnabled = os.Getenv("GOTESTTOOLS_DEBUG") != "" func debug(format string, args ...interface{}) { if debugEnabled { fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...) } } type debugFormatNode struct { ast.Node } func (n debugFormatNode) String() string { out, err := FormatNode(n.Node) if err != nil { return fmt.Sprintf("failed to format %s: %s", n.Node, err) } return fmt.Sprintf("(%T) %s", n.Node, out) } gotest.tools-2.3.0/internal/source/source_test.go000066400000000000000000000035031341001157600221610ustar00rootroot00000000000000package source_test // using a separate package for test to avoid circular imports with the assert // package import ( "fmt" "runtime" "strings" "testing" "gotest.tools/assert" "gotest.tools/internal/source" "gotest.tools/skip" ) func TestFormattedCallExprArg_SingleLine(t *testing.T) { msg, err := shim("not", "this", "this text") assert.NilError(t, err) assert.Equal(t, `"this text"`, msg) } func TestFormattedCallExprArg_MultiLine(t *testing.T) { msg, err := shim( "first", "second", "this text", ) assert.NilError(t, err) assert.Equal(t, `"this text"`, msg) } func TestFormattedCallExprArg_IfStatement(t *testing.T) { if msg, err := shim( "first", "second", "this text", ); true { assert.NilError(t, err) assert.Equal(t, `"this text"`, msg) } } func shim(_, _, _ string) (string, error) { return source.FormattedCallExprArg(1, 2) } func TestFormattedCallExprArg_InDefer(t *testing.T) { skip.If(t, isGoVersion18) cap := &capture{} func() { defer cap.shim("first", "second") }() assert.NilError(t, cap.err) assert.Equal(t, cap.value, `"second"`) } func isGoVersion18() bool { return strings.HasPrefix(runtime.Version(), "go1.8.") } type capture struct { value string err error } func (c *capture) shim(_, _ string) { c.value, c.err = source.FormattedCallExprArg(1, 1) } func TestFormattedCallExprArg_InAnonymousDefer(t *testing.T) { cap := &capture{} func() { fmt.Println() defer fmt.Println() defer func() { cap.shim("first", "second") }() }() assert.NilError(t, cap.err) assert.Equal(t, cap.value, `"second"`) } func TestFormattedCallExprArg_InDeferMultipleDefers(t *testing.T) { skip.If(t, isGoVersion18) cap := &capture{} func() { fmt.Println() defer fmt.Println() defer cap.shim("first", "second") }() assert.ErrorContains(t, cap.err, "ambiguous call expression") } gotest.tools-2.3.0/pkg.go000066400000000000000000000002241341001157600152640ustar00rootroot00000000000000/*Package gotesttools is a collection of packages to augment `testing` and support common patterns. */ package gotesttools // import "gotest.tools" gotest.tools-2.3.0/poll/000077500000000000000000000000001341001157600151245ustar00rootroot00000000000000gotest.tools-2.3.0/poll/check.go000066400000000000000000000016761341001157600165420ustar00rootroot00000000000000package poll import ( "net" "os" ) // Check is a function which will be used as check for the WaitOn method. type Check func(t LogT) Result // FileExists looks on filesystem and check that path exists. func FileExists(path string) Check { return func(t LogT) Result { _, err := os.Stat(path) if os.IsNotExist(err) { t.Logf("waiting on file %s to exist", path) return Continue("file %s does not exist", path) } if err != nil { return Error(err) } return Success() } } // Connection try to open a connection to the address on the // named network. See net.Dial for a description of the network and // address parameters. func Connection(network, address string) Check { return func(t LogT) Result { _, err := net.Dial(network, address) if err != nil { t.Logf("waiting on socket %s://%s to be available...", network, address) return Continue("socket %s://%s not available", network, address) } return Success() } } gotest.tools-2.3.0/poll/check_test.go000066400000000000000000000016361341001157600175750ustar00rootroot00000000000000package poll import ( "fmt" "os" "testing" "gotest.tools/assert" ) func TestWaitOnFile(t *testing.T) { fakeFilePath := "./fakefile" check := FileExists(fakeFilePath) t.Run("file does not exist", func(t *testing.T) { r := check(t) assert.Assert(t, !r.Done()) assert.Equal(t, r.Message(), fmt.Sprintf("file %s does not exist", fakeFilePath)) }) os.Create(fakeFilePath) defer os.Remove(fakeFilePath) t.Run("file exists", func(t *testing.T) { assert.Assert(t, check(t).Done()) }) } func TestWaitOnSocketWithTimeout(t *testing.T) { t.Run("connection to unavailable address", func(t *testing.T) { check := Connection("tcp", "foo.bar:55555") r := check(t) assert.Assert(t, !r.Done()) assert.Equal(t, r.Message(), "socket tcp://foo.bar:55555 not available") }) t.Run("connection to ", func(t *testing.T) { check := Connection("tcp", "google.com:80") assert.Assert(t, check(t).Done()) }) } gotest.tools-2.3.0/poll/example_test.go000066400000000000000000000016671341001157600201570ustar00rootroot00000000000000package poll_test import ( "time" "github.com/pkg/errors" "gotest.tools/poll" ) var t poll.TestingT func numOfProcesses() (int, error) { return 0, nil } func ExampleWaitOn() { desired := 10 check := func(t poll.LogT) poll.Result { actual, err := numOfProcesses() if err != nil { return poll.Error(errors.Wrap(err, "failed to get number of processes")) } if actual == desired { return poll.Success() } t.Logf("waiting on process count to be %d...", desired) return poll.Continue("number of processes is %d, not %d", actual, desired) } poll.WaitOn(t, check) } func isDesiredState() bool { return false } func getState() string { return "" } func ExampleSettingOp() { check := func(t poll.LogT) poll.Result { if isDesiredState() { return poll.Success() } return poll.Continue("state is: %s", getState()) } poll.WaitOn(t, check, poll.WithTimeout(30*time.Second), poll.WithDelay(15*time.Millisecond)) } gotest.tools-2.3.0/poll/poll.go000066400000000000000000000065611341001157600164310ustar00rootroot00000000000000/*Package poll provides tools for testing asynchronous code. */ package poll // import "gotest.tools/poll" import ( "fmt" "time" ) // TestingT is the subset of testing.T used by WaitOn type TestingT interface { LogT Fatalf(format string, args ...interface{}) } // LogT is a logging interface that is passed to the WaitOn check function type LogT interface { Log(args ...interface{}) Logf(format string, args ...interface{}) } type helperT interface { Helper() } // Settings are used to configure the behaviour of WaitOn type Settings struct { // Timeout is the maximum time to wait for the condition. Defaults to 10s. Timeout time.Duration // Delay is the time to sleep between checking the condition. Defaults to // 100ms. Delay time.Duration } func defaultConfig() *Settings { return &Settings{Timeout: 10 * time.Second, Delay: 100 * time.Millisecond} } // SettingOp is a function which accepts and modifies Settings type SettingOp func(config *Settings) // WithDelay sets the delay to wait between polls func WithDelay(delay time.Duration) SettingOp { return func(config *Settings) { config.Delay = delay } } // WithTimeout sets the timeout func WithTimeout(timeout time.Duration) SettingOp { return func(config *Settings) { config.Timeout = timeout } } // Result of a check performed by WaitOn type Result interface { // Error indicates that the check failed and polling should stop, and the // the has failed Error() error // Done indicates that polling should stop, and the test should proceed Done() bool // Message provides the most recent state when polling has not completed Message() string } type result struct { done bool message string err error } func (r result) Done() bool { return r.done } func (r result) Message() string { return r.message } func (r result) Error() error { return r.err } // Continue returns a Result that indicates to WaitOn that it should continue // polling. The message text will be used as the failure message if the timeout // is reached. func Continue(message string, args ...interface{}) Result { return result{message: fmt.Sprintf(message, args...)} } // Success returns a Result where Done() returns true, which indicates to WaitOn // that it should stop polling and exit without an error. func Success() Result { return result{done: true} } // Error returns a Result that indicates to WaitOn that it should fail the test // and stop polling. func Error(err error) Result { return result{err: err} } // WaitOn a condition or until a timeout. Poll by calling check and exit when // check returns a done Result. To fail a test and exit polling with an error // return a error result. func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { if ht, ok := t.(helperT); ok { ht.Helper() } config := defaultConfig() for _, pollOp := range pollOps { pollOp(config) } var lastMessage string after := time.After(config.Timeout) chResult := make(chan Result) for { go func() { chResult <- check(t) }() select { case <-after: if lastMessage == "" { lastMessage = "first check never completed" } t.Fatalf("timeout hit after %s: %s", config.Timeout, lastMessage) case result := <-chResult: switch { case result.Error() != nil: t.Fatalf("polling check failed: %s", result.Error()) case result.Done(): return } time.Sleep(config.Delay) lastMessage = result.Message() } } } gotest.tools-2.3.0/poll/poll_test.go000066400000000000000000000030631341001157600174620ustar00rootroot00000000000000package poll import ( "fmt" "testing" "time" "github.com/pkg/errors" "gotest.tools/assert" "gotest.tools/assert/cmp" ) type fakeT struct { failed string } func (t *fakeT) Fatalf(format string, args ...interface{}) { t.failed = fmt.Sprintf(format, args...) panic("exit wait on") } func (t *fakeT) Log(args ...interface{}) {} func (t *fakeT) Logf(format string, args ...interface{}) {} func TestWaitOn(t *testing.T) { counter := 0 end := 4 check := func(t LogT) Result { if counter == end { return Success() } counter++ return Continue("counter is at %d not yet %d", counter-1, end) } WaitOn(t, check, WithDelay(0)) assert.Equal(t, end, counter) } func TestWaitOnWithTimeout(t *testing.T) { fakeT := &fakeT{} check := func(t LogT) Result { return Continue("not done") } assert.Assert(t, cmp.Panics(func() { WaitOn(fakeT, check, WithTimeout(time.Millisecond)) })) assert.Equal(t, "timeout hit after 1ms: not done", fakeT.failed) } func TestWaitOnWithCheckTimeout(t *testing.T) { fakeT := &fakeT{} check := func(t LogT) Result { time.Sleep(1 * time.Second) return Continue("not done") } assert.Assert(t, cmp.Panics(func() { WaitOn(fakeT, check, WithTimeout(time.Millisecond)) })) assert.Equal(t, "timeout hit after 1ms: first check never completed", fakeT.failed) } func TestWaitOnWithCheckError(t *testing.T) { fakeT := &fakeT{} check := func(t LogT) Result { return Error(errors.New("broke")) } assert.Assert(t, cmp.Panics(func() { WaitOn(fakeT, check) })) assert.Equal(t, "polling check failed: broke", fakeT.failed) } gotest.tools-2.3.0/scripts/000077500000000000000000000000001341001157600156455ustar00rootroot00000000000000gotest.tools-2.3.0/scripts/binary-gty-migrate-from-testify000077500000000000000000000001531341001157600237330ustar00rootroot00000000000000#!/usr/bin/env bash exec go build -o ./dist/gty-migrate-from-testify ./assert/cmd/gty-migrate-from-testify gotest.tools-2.3.0/scripts/ci/000077500000000000000000000000001341001157600162405ustar00rootroot00000000000000gotest.tools-2.3.0/scripts/ci/test000077500000000000000000000005331341001157600171460ustar00rootroot00000000000000#!/usr/bin/env sh set -eu -o pipefail golang_version=${1-} image="cli-builder:$CIRCLE_BUILD_NUM" docker build \ --target dev-with-source \ --build-arg GOLANG_VERSION="$golang_version" \ --tag "$image" . docker run \ -e TESTTIMEOUT=30s \ --name "test-$CIRCLE_BUILD_NUM" "$image" \ bash -ec 'dep ensure; scripts/test-unit' gotest.tools-2.3.0/scripts/test-unit000077500000000000000000000003141341001157600175250ustar00rootroot00000000000000#!/usr/bin/env bash set -eu -o pipefail paths=${@:-$(go list ./... | grep -v '/vendor/')} TESTFLAGS=${TESTFLAGS-} TESTTIMEOUT=${TESTTIMEOUT-30s} go test -test.timeout "$TESTTIMEOUT" $TESTFLAGS -v $paths gotest.tools-2.3.0/skip/000077500000000000000000000000001341001157600151245ustar00rootroot00000000000000gotest.tools-2.3.0/skip/example_test.go000066400000000000000000000015541341001157600201520ustar00rootroot00000000000000package skip_test import ( "testing" "gotest.tools/skip" ) var apiVersion = "" type env struct{} func (e env) hasFeature(_ string) bool { return false } var testEnv = env{} func MissingFeature() bool { return false } var t = &testing.T{} func ExampleIf() { // --- SKIP: TestName (0.00s) // skip.go:19: MissingFeature skip.If(t, MissingFeature) // --- SKIP: TestName (0.00s) // skip.go:19: MissingFeature: coming soon skip.If(t, MissingFeature, "coming soon") } func ExampleIf_withExpression() { // --- SKIP: TestName (0.00s) // skip.go:19: apiVersion < version("v1.24") skip.If(t, apiVersion < version("v1.24")) // --- SKIP: TestName (0.00s) // skip.go:19: !textenv.hasFeature("build"): coming soon skip.If(t, !testEnv.hasFeature("build"), "coming soon") } func version(v string) string { return v } gotest.tools-2.3.0/skip/skip.go000066400000000000000000000045011341001157600164210ustar00rootroot00000000000000/*Package skip provides functions for skipping a test and printing the source code of the condition used to skip the test. */ package skip // import "gotest.tools/skip" import ( "fmt" "path" "reflect" "runtime" "strings" "gotest.tools/internal/format" "gotest.tools/internal/source" ) type skipT interface { Skip(args ...interface{}) Log(args ...interface{}) } // Result of skip function type Result interface { Skip() bool Message() string } type helperT interface { Helper() } // BoolOrCheckFunc can be a bool, func() bool, or func() Result. Other types will panic type BoolOrCheckFunc interface{} // If the condition expression evaluates to true, skip the test. // // The condition argument may be one of three types: bool, func() bool, or // func() SkipResult. // When called with a bool, the test will be skip if the condition evaluates to true. // When called with a func() bool, the test will be skip if the function returns true. // When called with a func() Result, the test will be skip if the Skip method // of the result returns true. // The skip message will contain the source code of the expression. // Extra message text can be passed as a format string with args. func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } switch check := condition.(type) { case bool: ifCondition(t, check, msgAndArgs...) case func() bool: if check() { t.Skip(format.WithCustomMessage(getFunctionName(check), msgAndArgs...)) } case func() Result: result := check() if result.Skip() { msg := getFunctionName(check) + ": " + result.Message() t.Skip(format.WithCustomMessage(msg, msgAndArgs...)) } default: panic(fmt.Sprintf("invalid type for condition arg: %T", check)) } } func getFunctionName(function interface{}) string { funcPath := runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name() return strings.SplitN(path.Base(funcPath), ".", 2)[1] } func ifCondition(t skipT, condition bool, msgAndArgs ...interface{}) { if ht, ok := t.(helperT); ok { ht.Helper() } if !condition { return } const ( stackIndex = 2 argPos = 1 ) source, err := source.FormattedCallExprArg(stackIndex, argPos) if err != nil { t.Log(err.Error()) t.Skip(format.Message(msgAndArgs...)) } t.Skip(format.WithCustomMessage(source, msgAndArgs...)) } gotest.tools-2.3.0/skip/skip_test.go000066400000000000000000000054251341001157600174660ustar00rootroot00000000000000package skip import ( "bytes" "fmt" "testing" "gotest.tools/assert" "gotest.tools/assert/cmp" ) type fakeSkipT struct { reason string logs []string } func (f *fakeSkipT) Skip(args ...interface{}) { buf := new(bytes.Buffer) for _, arg := range args { buf.WriteString(fmt.Sprintf("%s", arg)) } f.reason = buf.String() } func (f *fakeSkipT) Log(args ...interface{}) { f.logs = append(f.logs, fmt.Sprintf("%s", args[0])) } func (f *fakeSkipT) Helper() {} func version(v string) string { return v } func TestIfCondition(t *testing.T) { skipT := &fakeSkipT{} apiVersion := "v1.4" If(skipT, apiVersion < version("v1.6")) assert.Equal(t, `apiVersion < version("v1.6")`, skipT.reason) assert.Assert(t, cmp.Len(skipT.logs, 0)) } func TestIfConditionWithMessage(t *testing.T) { skipT := &fakeSkipT{} apiVersion := "v1.4" If(skipT, apiVersion < "v1.6", "see notes") assert.Equal(t, `apiVersion < "v1.6": see notes`, skipT.reason) assert.Assert(t, cmp.Len(skipT.logs, 0)) } func TestIfConditionMultiline(t *testing.T) { skipT := &fakeSkipT{} apiVersion := "v1.4" If( skipT, apiVersion < "v1.6") assert.Equal(t, `apiVersion < "v1.6"`, skipT.reason) assert.Assert(t, cmp.Len(skipT.logs, 0)) } func TestIfConditionMultilineWithMessage(t *testing.T) { skipT := &fakeSkipT{} apiVersion := "v1.4" If( skipT, apiVersion < "v1.6", "see notes") assert.Equal(t, `apiVersion < "v1.6": see notes`, skipT.reason) assert.Assert(t, cmp.Len(skipT.logs, 0)) } func TestIfConditionNoSkip(t *testing.T) { skipT := &fakeSkipT{} If(skipT, false) assert.Equal(t, "", skipT.reason) assert.Assert(t, cmp.Len(skipT.logs, 0)) } func SkipBecauseISaidSo() bool { return true } func TestIf(t *testing.T) { skipT := &fakeSkipT{} If(skipT, SkipBecauseISaidSo) assert.Equal(t, "SkipBecauseISaidSo", skipT.reason) } func TestIfWithMessage(t *testing.T) { skipT := &fakeSkipT{} If(skipT, SkipBecauseISaidSo, "see notes") assert.Equal(t, "SkipBecauseISaidSo: see notes", skipT.reason) } func TestIf_InvalidCondition(t *testing.T) { skipT := &fakeSkipT{} assert.Assert(t, cmp.Panics(func() { If(skipT, "just a string") })) } func TestIfWithSkipResultFunc(t *testing.T) { t.Run("no extra message", func(t *testing.T) { skipT := &fakeSkipT{} If(skipT, alwaysSkipWithMessage) assert.Equal(t, "alwaysSkipWithMessage: skip because I said so!", skipT.reason) }) t.Run("with extra message", func(t *testing.T) { skipT := &fakeSkipT{} If(skipT, alwaysSkipWithMessage, "also %v", 4) assert.Equal(t, "alwaysSkipWithMessage: skip because I said so!: also 4", skipT.reason) }) } func alwaysSkipWithMessage() Result { return skipResult{} } type skipResult struct{} func (s skipResult) Skip() bool { return true } func (s skipResult) Message() string { return "skip because I said so!" } gotest.tools-2.3.0/x/000077500000000000000000000000001341001157600144255ustar00rootroot00000000000000gotest.tools-2.3.0/x/doc.go000066400000000000000000000003651341001157600155250ustar00rootroot00000000000000/*Package x is a namespace for other packages. Packages under x have looser compatibility requirements. Packages in this namespace may contain backwards incompatible changes within the same major version. */ package x // import "gotest.tools/x" gotest.tools-2.3.0/x/subtest/000077500000000000000000000000001341001157600161165ustar00rootroot00000000000000gotest.tools-2.3.0/x/subtest/context.go000066400000000000000000000040611341001157600201320ustar00rootroot00000000000000/*Package subtest provides a TestContext to subtests which handles cleanup, and provides a testing.TB, and context.Context. This package was inspired by github.com/frankban/quicktest. */ package subtest // import "gotest.tools/x/subtest" import ( "context" "testing" ) type testcase struct { testing.TB ctx context.Context cleanupFuncs []cleanupFunc } type cleanupFunc func() func (tc *testcase) Ctx() context.Context { if tc.ctx == nil { var cancel func() tc.ctx, cancel = context.WithCancel(context.Background()) tc.AddCleanup(cancel) } return tc.ctx } // Cleanup runs all cleanup functions. Functions are run in the opposite order // in which they were added. Cleanup is called automatically before Run exits. func (tc *testcase) Cleanup() { for _, f := range tc.cleanupFuncs { // Defer all cleanup functions so they all run even if one calls // t.FailNow() or panics. Deferring them also runs them in reverse order. defer f() } tc.cleanupFuncs = nil } func (tc *testcase) AddCleanup(f func()) { tc.cleanupFuncs = append(tc.cleanupFuncs, f) } func (tc *testcase) Parallel() { tp, ok := tc.TB.(parallel) if !ok { panic("Parallel called with a testing.B") } tp.Parallel() } type parallel interface { Parallel() } // Run a subtest. When subtest exits, every cleanup function added with // TestContext.AddCleanup will be run. func Run(t *testing.T, name string, subtest func(t TestContext)) bool { return t.Run(name, func(t *testing.T) { tc := &testcase{TB: t} defer tc.Cleanup() subtest(tc) }) } // TestContext provides a testing.TB and a context.Context for a test case. type TestContext interface { testing.TB // AddCleanup function which will be run when before Run returns. AddCleanup(f func()) // Ctx returns a context for the test case. Multiple calls from the same subtest // will return the same context. The context is cancelled when Run // returns. Ctx() context.Context // Parallel calls t.Parallel on the testing.TB. Panics if testing.TB does // not implement Parallel. Parallel() } var _ TestContext = &testcase{} gotest.tools-2.3.0/x/subtest/context_test.go000066400000000000000000000011531341001157600211700ustar00rootroot00000000000000package subtest import ( "context" "testing" "gotest.tools/assert" ) func TestTestcase_Run_CallsCleanup(t *testing.T) { calls := []int{} var ctx context.Context Run(t, "test-run-cleanup", func(t TestContext) { cleanup := func(n int) func() { return func() { calls = append(calls, n) } } ctx = t.Ctx() t.AddCleanup(cleanup(2)) t.AddCleanup(cleanup(1)) t.AddCleanup(cleanup(0)) }) assert.DeepEqual(t, calls, []int{0, 1, 2}) assert.Equal(t, ctx.Err(), context.Canceled) } func TestTestcase_Run_Parallel(t *testing.T) { Run(t, "test-parallel", func(t TestContext) { t.Parallel() }) } gotest.tools-2.3.0/x/subtest/example_test.go000066400000000000000000000024221341001157600211370ustar00rootroot00000000000000package subtest_test import ( "io" "net/http" "strings" "testing" "gotest.tools/assert" "gotest.tools/x/subtest" ) var t = &testing.T{} func ExampleRun_tableTest() { var testcases = []struct { data io.Reader expected int }{ { data: strings.NewReader("invalid input"), expected: 400, }, { data: strings.NewReader("valid input"), expected: 200, }, } for _, tc := range testcases { subtest.Run(t, "test-service-call", func(t subtest.TestContext) { // startFakeService can shutdown using t.AddCleanup url := startFakeService(t) req, err := http.NewRequest("POST", url, tc.data) assert.NilError(t, err) req = req.WithContext(t.Ctx()) client := newClient(t) resp, err := client.Do(req) assert.NilError(t, err) assert.Equal(t, resp.StatusCode, tc.expected) }) } } func startFakeService(t subtest.TestContext) string { // t.AddCleanup(shutdown) return "url" } func newClient(T subtest.TestContext) *http.Client { return &http.Client{} } func ExampleRun_testSuite() { // do suite setup before subtests subtest.Run(t, "test-one", func(t subtest.TestContext) { assert.Equal(t, 1, 1) }) subtest.Run(t, "test-two", func(t subtest.TestContext) { assert.Equal(t, 2, 2) }) // do suite teardown after subtests }