Merge pull request #2393 from vdemeester/gotestyourself-with-tools

Update gotestyourself to gotest.tools
This commit is contained in:
Kenfe-Mickaël Laventure 2018-06-11 07:35:25 -07:00 committed by GitHub
commit 0158a6fb34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 435 additions and 526 deletions

View File

@ -32,8 +32,8 @@ import (
"github.com/containerd/fifo" "github.com/containerd/fifo"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "gotest.tools/assert/cmp"
) )
func assertHasPrefix(t *testing.T, s, prefix string) { func assertHasPrefix(t *testing.T, s, prefix string) {

View File

@ -23,7 +23,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
func TestOpenFifos(t *testing.T) { func TestOpenFifos(t *testing.T) {

View File

@ -25,9 +25,9 @@ import (
"testing" "testing"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
) )
type copySource struct { type copySource struct {

View File

@ -37,9 +37,9 @@ import (
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/testsuite" "github.com/containerd/containerd/content/testsuite"
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/gotestyourself/gotestyourself/assert"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/assert"
) )
type memoryLabelStore struct { type memoryLabelStore struct {

View File

@ -31,10 +31,10 @@ import (
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/gotestyourself/gotestyourself/assert"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"gotest.tools/assert"
) )
// ContentSuite runs a test suite on the content store given a factory function. // ContentSuite runs a test suite on the content store given a factory function.

View File

@ -23,7 +23,7 @@ import (
"time" "time"
"github.com/containerd/containerd/gc" "github.com/containerd/containerd/gc"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
func TestPauseThreshold(t *testing.T) { func TestPauseThreshold(t *testing.T) {

View File

@ -19,9 +19,9 @@ package oci
import ( import (
"testing" "testing"
"github.com/gotestyourself/gotestyourself/assert"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/assert"
) )
func TestNormalizeImageRef(t *testing.T) { func TestNormalizeImageRef(t *testing.T) {

View File

@ -20,7 +20,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
func TestLoggerContext(t *testing.T) { func TestLoggerContext(t *testing.T) {

View File

@ -36,7 +36,7 @@ import (
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
func checkLookup(t *testing.T, fsType, mntPoint, dir string) { func checkLookup(t *testing.T, fsType, mntPoint, dir string) {

View File

@ -24,7 +24,7 @@ import (
"testing" "testing"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
// Unmount unmounts a given mountPoint and sets t.Error if it fails // Unmount unmounts a given mountPoint and sets t.Error if it fails

View File

@ -20,7 +20,7 @@ import (
"testing" "testing"
"github.com/containerd/containerd/reference" "github.com/containerd/containerd/reference"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
) )
func TestRepositoryScope(t *testing.T) { func TestRepositoryScope(t *testing.T) {

View File

@ -20,8 +20,8 @@ import (
"context" "context"
"testing" "testing"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "gotest.tools/assert/cmp"
) )
func TestNewErrorsWithSamePathForRootAndState(t *testing.T) { func TestNewErrorsWithSamePathForRootAndState(t *testing.T) {

View File

@ -27,9 +27,9 @@ import (
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/pkg/errors" "github.com/pkg/errors"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
) )
type testFunc func(context.Context, *testing.T, *MetaStore) type testFunc func(context.Context, *testing.T, *MetaStore)

View File

@ -32,8 +32,8 @@ import (
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
"github.com/containerd/continuity/fs/fstest" "github.com/containerd/continuity/fs/fstest"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "gotest.tools/assert/cmp"
) )
// SnapshotterSuite runs a test suite on the snapshotter given a factory function. // SnapshotterSuite runs a test suite on the snapshotter given a factory function.

View File

@ -29,8 +29,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "gotest.tools/assert/cmp"
) )
func TestSetPositiveOomScoreAdjustment(t *testing.T) { func TestSetPositiveOomScoreAdjustment(t *testing.T) {

View File

@ -22,7 +22,6 @@ github.com/golang/protobuf v1.1.0
github.com/opencontainers/runtime-spec v1.0.1 github.com/opencontainers/runtime-spec v1.0.1
github.com/opencontainers/runc 69663f0bd4b60df09991c08812a60108003fa340 github.com/opencontainers/runc 69663f0bd4b60df09991c08812a60108003fa340
github.com/sirupsen/logrus v1.0.0 github.com/sirupsen/logrus v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
google.golang.org/grpc v1.12.0 google.golang.org/grpc v1.12.0
@ -40,7 +39,7 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577 github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16 github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
github.com/gotestyourself/gotestyourself 44dbf532bbf5767611f6f2a61bded572e337010a gotest.tools v2.1.0
github.com/google/go-cmp v0.1.0 github.com/google/go-cmp v0.1.0
github.com/containerd/cri 8bcb9a95394e8d7845da1d6a994d3ac2a86d22f0 github.com/containerd/cri 8bcb9a95394e8d7845da1d6a994d3ac2a86d22f0

View File

@ -1,33 +0,0 @@
# Go Test Yourself
A collection of packages compatible with `go test` to support common testing
patterns.
[![GoDoc](https://godoc.org/github.com/gotestyourself/gotestyourself?status.svg)](https://godoc.org/github.com/gotestyourself/gotestyourself)
[![CircleCI](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master)
[![Go Reportcard](https://goreportcard.com/badge/github.com/gotestyourself/gotestyourself)](https://goreportcard.com/report/github.com/gotestyourself/gotestyourself)
## Packages
* [assert](http://godoc.org/github.com/gotestyourself/gotestyourself/assert) -
compare values and fail the test when the comparison fails
* [env](http://godoc.org/github.com/gotestyourself/gotestyourself/env) -
test code that uses environment variables
* [fs](http://godoc.org/github.com/gotestyourself/gotestyourself/fs) -
create test files and directories
* [golden](http://godoc.org/github.com/gotestyourself/gotestyourself/golden) -
compare large multi-line strings
* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) -
execute binaries and test the output
* [poll](http://godoc.org/github.com/gotestyourself/gotestyourself/poll) -
test asynchronous code by polling until a desired state is reached
* [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) -
skip tests based on conditions
* [testsum](http://godoc.org/github.com/gotestyourself/gotestyourself/testsum) -
a program to summarize `go test` output and test failures
## Related
* [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`

View File

@ -1,50 +0,0 @@
go-difflib
==========
[![Build Status](https://travis-ci.org/pmezard/go-difflib.png?branch=master)](https://travis-ci.org/pmezard/go-difflib)
[![GoDoc](https://godoc.org/github.com/pmezard/go-difflib/difflib?status.svg)](https://godoc.org/github.com/pmezard/go-difflib/difflib)
Go-difflib is a partial port of python 3 difflib package. Its main goal
was to make unified and context diff available in pure Go, mostly for
testing purposes.
The following class and functions (and related tests) have be ported:
* `SequenceMatcher`
* `unified_diff()`
* `context_diff()`
## Installation
```bash
$ go get github.com/pmezard/go-difflib/difflib
```
### Quick Start
Diffs are configured with Unified (or ContextDiff) structures, and can
be output to an io.Writer or returned as a string.
```Go
diff := UnifiedDiff{
A: difflib.SplitLines("foo\nbar\n"),
B: difflib.SplitLines("foo\nbaz\n"),
FromFile: "Original",
ToFile: "Current",
Context: 3,
}
text, _ := GetUnifiedDiffString(diff)
fmt.Printf(text)
```
would output:
```
--- Original
+++ Current
@@ -1,3 +1,3 @@
foo
-bar
+baz
```

31
vendor/gotest.tools/README.md vendored Normal file
View File

@ -0,0 +1,31 @@
# 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/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/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`

View File

@ -8,7 +8,7 @@ comparison fails. The one difference is that Assert() will end the test executio
immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()), 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. return the value of the comparison, then proceed with the rest of the test case.
Example Usage Example usage
The example below shows assert used with some common types. The example below shows assert used with some common types.
@ -16,8 +16,8 @@ The example below shows assert used with some common types.
import ( import (
"testing" "testing"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "gotest.tools/assert/cmp"
) )
func TestEverything(t *testing.T) { func TestEverything(t *testing.T) {
@ -32,15 +32,15 @@ The example below shows assert used with some common types.
// errors // errors
assert.NilError(t, closer.Close()) assert.NilError(t, closer.Close())
assert.Assert(t, is.Error(err, "the exact error message")) assert.Error(t, err, "the exact error message")
assert.Assert(t, is.ErrorContains(err, "includes this")) assert.ErrorContains(t, err, "includes this")
assert.Assert(t, os.IsNotExist(err), "got %+v", err) assert.ErrorType(t, err, os.IsNotExist)
// complex types // complex types
assert.DeepEqual(t, result, myStruct{Name: "title"})
assert.Assert(t, is.Len(items, 3)) assert.Assert(t, is.Len(items, 3))
assert.Assert(t, len(sequence) != 0) // NotEmpty assert.Assert(t, len(sequence) != 0) // NotEmpty
assert.Assert(t, is.Contains(mapping, "key")) assert.Assert(t, is.Contains(mapping, "key"))
assert.Assert(t, is.DeepEqual(result, myStruct{Name: "title"}))
// pointers and interface // pointers and interface
assert.Assert(t, is.Nil(ref)) assert.Assert(t, is.Nil(ref))
@ -49,31 +49,30 @@ The example below shows assert used with some common types.
Comparisons Comparisons
https://godoc.org/github.com/gotestyourself/gotestyourself/assert/cmp provides Package https://godoc.org/gotest.tools/assert/cmp provides
many common comparisons. Additional comparisons can be written to compare many common comparisons. Additional comparisons can be written to compare
values in other ways. values in other ways. See the example Assert (CustomComparison).
Below is an example of a custom comparison using a regex pattern: 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.
func RegexP(value string, pattern string) func() (bool, string) {
return func() (bool, string) {
re := regexp.MustCompile(pattern)
msg := fmt.Sprintf("%q did not match pattern %q", value, pattern)
return re.MatchString(value), msg
}
}
*/ */
package assert package assert // import "gotest.tools/assert"
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"github.com/gotestyourself/gotestyourself/assert/cmp" gocmp "github.com/google/go-cmp/cmp"
"github.com/gotestyourself/gotestyourself/internal/format" "gotest.tools/assert/cmp"
"github.com/gotestyourself/gotestyourself/internal/source" "gotest.tools/internal/format"
"gotest.tools/internal/source"
) )
// BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage. // BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage.
@ -96,7 +95,7 @@ const failureMessage = "assertion failed: "
func assert( func assert(
t TestingT, t TestingT,
failer func(), failer func(),
argsFilter astExprListFilter, argSelector argSelector,
comparison BoolOrComparison, comparison BoolOrComparison,
msgAndArgs ...interface{}, msgAndArgs ...interface{},
) bool { ) bool {
@ -123,10 +122,10 @@ func assert(
t.Log(format.WithCustomMessage(failureMessage+msg+check.Error(), msgAndArgs...)) t.Log(format.WithCustomMessage(failureMessage+msg+check.Error(), msgAndArgs...))
case cmp.Comparison: case cmp.Comparison:
success = runComparison(t, argsFilter, check, msgAndArgs...) success = runComparison(t, argSelector, check, msgAndArgs...)
case func() cmp.Result: case func() cmp.Result:
success = runComparison(t, argsFilter, check, msgAndArgs...) success = runComparison(t, argSelector, check, msgAndArgs...)
default: default:
t.Log(fmt.Sprintf("invalid Comparison: %v (%T)", check, check)) t.Log(fmt.Sprintf("invalid Comparison: %v (%T)", check, check))
@ -155,6 +154,9 @@ func runCompareFunc(
} }
func logFailureFromBool(t TestingT, msgAndArgs ...interface{}) { func logFailureFromBool(t TestingT, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
const stackIndex = 3 // Assert()/Check(), assert(), formatFailureFromBool() const stackIndex = 3 // Assert()/Check(), assert(), formatFailureFromBool()
const comparisonArgPos = 1 const comparisonArgPos = 1
args, err := source.CallExprArgs(stackIndex) args, err := source.CallExprArgs(stackIndex)
@ -215,7 +217,7 @@ func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{})
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
assert(t, t.FailNow, filterExprArgsFromComparison, comparison, msgAndArgs...) assert(t, t.FailNow, argsFromComparisonCall, comparison, msgAndArgs...)
} }
// Check performs a comparison. If the comparison fails the test is marked as // Check performs a comparison. If the comparison fails the test is marked as
@ -227,7 +229,7 @@ func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) b
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
return assert(t, t.Fail, filterExprArgsFromComparison, comparison, msgAndArgs...) return assert(t, t.Fail, argsFromComparisonCall, comparison, msgAndArgs...)
} }
// NilError fails the test immediately if err is not nil. // NilError fails the test immediately if err is not nil.
@ -236,14 +238,74 @@ func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
assert(t, t.FailNow, filterExprExcludeFirst, err, msgAndArgs...) assert(t, t.FailNow, argsAfterT, err, msgAndArgs...)
} }
// Equal uses the == operator to assert two values are equal and fails the test // Equal uses the == operator to assert two values are equal and fails the test
// if they are not equal. This is equivalent to Assert(t, cmp.Equal(x, y)). // 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{}) { func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
assert(t, t.FailNow, filterExprExcludeFirst, cmp.Equal(x, y), msgAndArgs...) 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...)
} }

View File

@ -1,5 +1,5 @@
/*Package cmp provides Comparisons for Assert and Check*/ /*Package cmp provides Comparisons for Assert and Check*/
package cmp package cmp // import "gotest.tools/assert/cmp"
import ( import (
"fmt" "fmt"
@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/pmezard/go-difflib/difflib" "gotest.tools/internal/format"
) )
// Comparison is a function which compares values and returns ResultSuccess if // Comparison is a function which compares values and returns ResultSuccess if
@ -15,10 +15,12 @@ import (
// Result will contain a message about why it failed. // Result will contain a message about why it failed.
type Comparison func() Result type Comparison func() Result
// DeepEqual compares two values using https://godoc.org/github.com/google/go-cmp/cmp // DeepEqual compares two values using google/go-cmp (http://bit.do/go-cmp)
// and succeeds if the values are equal. // and succeeds if the values are equal.
// //
// The comparison can be customized using comparison Options. // 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 { func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison {
return func() (result Result) { return func() (result Result) {
defer func() { defer func() {
@ -27,7 +29,10 @@ func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison {
} }
}() }()
diff := cmp.Diff(x, y, opts...) diff := cmp.Diff(x, y, opts...)
return toResult(diff == "", "\n"+diff) if diff == "" {
return ResultSuccess
}
return multiLineDiffResult(diff)
} }
} }
@ -53,14 +58,15 @@ func toResult(success bool, msg string) Result {
return ResultFailure(msg) return ResultFailure(msg)
} }
// Equal succeeds if x == y. // Equal succeeds if x == y. See assert.Equal for full documentation.
func Equal(x, y interface{}) Comparison { func Equal(x, y interface{}) Comparison {
return func() Result { return func() Result {
switch { switch {
case x == y: case x == y:
return ResultSuccess return ResultSuccess
case isMultiLineStringCompare(x, y): case isMultiLineStringCompare(x, y):
return multiLineStringDiffResult(x.(string), y.(string)) diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)})
return multiLineDiffResult(diff)
} }
return ResultFailureTemplate(` return ResultFailureTemplate(`
{{- .Data.x}} ( {{- .Data.x}} (
@ -86,15 +92,7 @@ func isMultiLineStringCompare(x, y interface{}) bool {
return strings.Contains(strX, "\n") || strings.Contains(strY, "\n") return strings.Contains(strX, "\n") || strings.Contains(strY, "\n")
} }
func multiLineStringDiffResult(x, y string) Result { func multiLineDiffResult(diff string) Result {
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(x),
B: difflib.SplitLines(y),
Context: 3,
})
if err != nil {
return ResultFailure(fmt.Sprintf("failed to diff: %s", err))
}
return ResultFailureTemplate(` return ResultFailureTemplate(`
--- {{ with callArg 0 }}{{ formatNode . }}{{else}}{{end}} --- {{ with callArg 0 }}{{ formatNode . }}{{else}}{{end}}
+++ {{ with callArg 1 }}{{ formatNode . }}{{else}}{{end}} +++ {{ with callArg 1 }}{{ formatNode . }}{{else}}{{end}}
@ -237,3 +235,78 @@ func isNil(obj interface{}, msgFunc func(reflect.Value) string) Comparison {
return ResultFailure(fmt.Sprintf("%v (type %s) can not be nil", value, value.Type())) 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
}

View File

@ -6,7 +6,7 @@ import (
"go/ast" "go/ast"
"text/template" "text/template"
"github.com/gotestyourself/gotestyourself/internal/source" "gotest.tools/internal/source"
) )
// Result of a Comparison. // Result of a Comparison.

View File

@ -4,14 +4,14 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"github.com/gotestyourself/gotestyourself/assert/cmp" "gotest.tools/assert/cmp"
"github.com/gotestyourself/gotestyourself/internal/format" "gotest.tools/internal/format"
"github.com/gotestyourself/gotestyourself/internal/source" "gotest.tools/internal/source"
) )
func runComparison( func runComparison(
t TestingT, t TestingT,
exprFilter astExprListFilter, argSelector argSelector,
f cmp.Comparison, f cmp.Comparison,
msgAndArgs ...interface{}, msgAndArgs ...interface{},
) bool { ) bool {
@ -31,7 +31,7 @@ func runComparison(
if err != nil { if err != nil {
t.Log(err.Error()) t.Log(err.Error())
} }
message = typed.FailureMessage(filterPrintableExpr(exprFilter(args))) message = typed.FailureMessage(filterPrintableExpr(argSelector(args)))
case resultBasic: case resultBasic:
message = typed.FailureMessage() message = typed.FailureMessage()
default: default:
@ -50,9 +50,7 @@ type resultBasic interface {
FailureMessage() string FailureMessage() string
} }
type astExprListFilter func([]ast.Expr) []ast.Expr // filterPrintableExpr filters the ast.Expr slice to only include Expr that are
// filterPrintableExpr filters the ast.Expr slice to only include nodes that are
// easy to read when printed and contain relevant information to an assertion. // easy to read when printed and contain relevant information to an assertion.
// //
// Ident and SelectorExpr are included because they print nicely and the variable // Ident and SelectorExpr are included because they print nicely and the variable
@ -63,24 +61,42 @@ type astExprListFilter func([]ast.Expr) []ast.Expr
func filterPrintableExpr(args []ast.Expr) []ast.Expr { func filterPrintableExpr(args []ast.Expr) []ast.Expr {
result := make([]ast.Expr, len(args)) result := make([]ast.Expr, len(args))
for i, arg := range args { for i, arg := range args {
switch arg.(type) { if isShortPrintableExpr(arg) {
case *ast.Ident, *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr:
result[i] = arg result[i] = arg
default: continue
result[i] = nil
} }
if starExpr, ok := arg.(*ast.StarExpr); ok {
result[i] = starExpr.X
continue
}
result[i] = nil
} }
return result return result
} }
func filterExprExcludeFirst(args []ast.Expr) []ast.Expr { 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 { if len(args) < 1 {
return nil return nil
} }
return args[1:] return args[1:]
} }
func filterExprArgsFromComparison(args []ast.Expr) []ast.Expr { func argsFromComparisonCall(args []ast.Expr) []ast.Expr {
if len(args) < 1 { if len(args) < 1 {
return nil return nil
} }

View File

@ -1,27 +1,10 @@
// Package difflib is a partial port of Python difflib module. /* Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import ( Original source: https://github.com/pmezard/go-difflib
"bufio"
"bytes" This file is trimmed to only the parts used by this repository.
"fmt" */
"io" package difflib // import "gotest.tools/internal/difflib"
"strings"
)
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
@ -37,13 +20,6 @@ func max(a, b int) int {
return b return b
} }
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct { type Match struct {
A int A int
B int B int
@ -103,14 +79,6 @@ func NewMatcher(a, b []string) *SequenceMatcher {
return &m return &m
} }
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared. // Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) { func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a) m.SetSeq1(a)
@ -450,323 +418,3 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
} }
return groups return groups
} }
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// 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 -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
ws := func(s string) error {
_, err := buf.WriteString(s)
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := ws(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := ws("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(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 == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
ws := func(s string) {
_, err := buf.WriteString(s)
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
}
first, last := g[0], g[len(g)-1]
ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

View File

@ -0,0 +1,161 @@
package 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 '<27>'
}
}
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")
}
}

View File

@ -1,4 +1,4 @@
package format package format // import "gotest.tools/internal/format"
import "fmt" import "fmt"

View File

@ -1,4 +1,4 @@
package source package source // import "gotest.tools/internal/source"
import ( import (
"bytes" "bytes"
@ -68,12 +68,14 @@ func (v *scanToLineVisitor) Visit(node ast.Node) ast.Visitor {
// In golang 1.9 the line number changed from being the line where the statement // In golang 1.9 the line number changed from being the line where the statement
// ended to the line where the statement began. // ended to the line where the statement began.
func (v *scanToLineVisitor) nodePosition(node ast.Node) token.Position { func (v *scanToLineVisitor) nodePosition(node ast.Node) token.Position {
if isGOVersionBefore19() { if goVersionBefore19 {
return v.fileset.Position(node.End()) return v.fileset.Position(node.End())
} }
return v.fileset.Position(node.Pos()) return v.fileset.Position(node.Pos())
} }
var goVersionBefore19 = isGOVersionBefore19()
func isGOVersionBefore19() bool { func isGOVersionBefore19() bool {
version := runtime.Version() version := runtime.Version()
// not a release version // not a release version