Replace gotestyourself by gotest.tools

github.com/gotestyourself/gotestyourself moved to gotest.tools with
version 2.0.0. Moving to that one, bumping it to v2.1.0.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2018-06-08 18:23:38 +02:00
parent aac3124794
commit dd9478a1f7
No known key found for this signature in database
GPG Key ID: 083CC6FD6EB699A3
29 changed files with 1034 additions and 548 deletions

View File

@ -29,7 +29,7 @@ github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
github.com/googleapis/gnostic e4f56557df6250e1945ee6854f181ce4e1c2c646 github.com/googleapis/gnostic e4f56557df6250e1945ee6854f181ce4e1c2c646
github.com/gorilla/context v1.1 github.com/gorilla/context v1.1
github.com/gorilla/mux v1.1 github.com/gorilla/mux v1.1
github.com/gotestyourself/gotestyourself cf3a5ab914a2efa8bc838d09f5918c1d44d02909 gotest.tools v2.1.0
github.com/go-openapi/jsonpointer 46af16f9f7b149af66e5d1bd010e3574dc06de98 github.com/go-openapi/jsonpointer 46af16f9f7b149af66e5d1bd010e3574dc06de98
github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272
github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff
@ -56,7 +56,6 @@ github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc 4fc53a81fb7c994640722ac585fa9ca548971871 github.com/opencontainers/runc 4fc53a81fb7c994640722ac585fa9ca548971871
github.com/peterbourgon/diskv 5f041e8faa004a95c88a202771f4cc3e991971e6 github.com/peterbourgon/diskv 5f041e8faa004a95c88a202771f4cc3e991971e6
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8 github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8

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) {
@ -49,12 +49,20 @@ 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. See the example Assert (CustomComparison). 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 package assert // import "gotest.tools/assert"
import ( import (
"fmt" "fmt"
@ -62,9 +70,9 @@ import (
"go/token" "go/token"
gocmp "github.com/google/go-cmp/cmp" gocmp "github.com/google/go-cmp/cmp"
"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"
) )
// BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage. // BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage.
@ -234,7 +242,17 @@ func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
} }
// 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()
@ -242,8 +260,12 @@ func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
assert(t, t.FailNow, argsAfterT, cmp.Equal(x, y), msgAndArgs...) assert(t, t.FailNow, argsAfterT, cmp.Equal(x, y), msgAndArgs...)
} }
// DeepEqual uses https://github.com/google/go-cmp/cmp to assert two values // DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are
// are equal and fails the test if they are not equal. // 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)). // This is equivalent to Assert(t, cmp.DeepEqual(x, y)).
func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) { func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
@ -276,7 +298,7 @@ func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interf
// //
// Expected can be one of: // Expected can be one of:
// a func(error) bool which returns true if the error is the expected type, // a func(error) bool which returns true if the error is the expected type,
// an instance of a struct of 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 pointer to an interface the error is expected to implement,
// a reflect.Type of the expected struct or interface. // a reflect.Type of the expected struct or interface.
// //

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}}
@ -242,7 +240,7 @@ func isNil(obj interface{}, msgFunc func(reflect.Value) string) Comparison {
// //
// Expected can be one of: // Expected can be one of:
// a func(error) bool which returns true if the error is the expected type, // a func(error) bool which returns true if the error is the expected type,
// an instance of a struct of 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 pointer to an interface the error is expected to implement,
// a reflect.Type of the expected struct or interface. // a reflect.Type of the expected struct or interface.
func ErrorType(err error, expected interface{}) Comparison { func ErrorType(err error, expected interface{}) Comparison {
@ -261,7 +259,7 @@ func ErrorType(err error, expected interface{}) Comparison {
expectedType := reflect.TypeOf(expected) expectedType := reflect.TypeOf(expected)
switch { switch {
case expectedType.Kind() == reflect.Struct: case expectedType.Kind() == reflect.Struct, isPtrToStruct(expectedType):
return cmpErrorTypeEqualType(err, expectedType) return cmpErrorTypeEqualType(err, expectedType)
case isPtrToInterface(expectedType): case isPtrToInterface(expectedType):
return cmpErrorTypeImplementsType(err, expectedType.Elem()) return cmpErrorTypeImplementsType(err, expectedType.Elem())
@ -308,3 +306,7 @@ func cmpErrorTypeImplementsType(err error, expectedType reflect.Type) Result {
func isPtrToInterface(typ reflect.Type) bool { func isPtrToInterface(typ reflect.Type) bool {
return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Interface 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,9 +4,9 @@ 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(

View File

@ -1,13 +1,14 @@
/*Package env provides functions to test code that read environment variables /*Package env provides functions to test code that read environment variables
or the current working directory. or the current working directory.
*/ */
package env package env // import "gotest.tools/env"
import ( import (
"os" "os"
"strings" "strings"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
"gotest.tools/x/subtest"
) )
type helperT interface { type helperT interface {
@ -23,7 +24,7 @@ func Patch(t assert.TestingT, key, value string) func() {
} }
oldValue, ok := os.LookupEnv(key) oldValue, ok := os.LookupEnv(key)
assert.NilError(t, os.Setenv(key, value)) assert.NilError(t, os.Setenv(key, value))
return func() { cleanup := func() {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
@ -33,6 +34,10 @@ func Patch(t assert.TestingT, key, value string) func() {
} }
assert.NilError(t, os.Setenv(key, oldValue)) 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 // PatchAll sets the environment to env, and returns a function which will
@ -47,7 +52,7 @@ func PatchAll(t assert.TestingT, env map[string]string) func() {
for key, value := range env { for key, value := range env {
assert.NilError(t, os.Setenv(key, value), "setenv %s=%s", key, value) assert.NilError(t, os.Setenv(key, value), "setenv %s=%s", key, value)
} }
return func() { cleanup := func() {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
@ -56,6 +61,10 @@ func PatchAll(t assert.TestingT, env map[string]string) func() {
assert.NilError(t, os.Setenv(key, oldVal), "setenv %s=%s", key, oldVal) 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 // ToMap takes a list of strings in the format returned by os.Environ() and
@ -89,10 +98,14 @@ func ChangeWorkingDir(t assert.TestingT, dir string) func() {
cwd, err := os.Getwd() cwd, err := os.Getwd()
assert.NilError(t, err) assert.NilError(t, err)
assert.NilError(t, os.Chdir(dir)) assert.NilError(t, os.Chdir(dir))
return func() { cleanup := func() {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
assert.NilError(t, os.Chdir(cwd)) assert.NilError(t, os.Chdir(cwd))
} }
if tc, ok := t.(subtest.TestContext); ok {
tc.AddCleanup(cleanup)
}
return cleanup
} }

View File

@ -1,17 +1,20 @@
/*Package fs provides tools for creating and working with temporary files and /*Package fs provides tools for creating temporary files, and testing the
directories. contents and structure of a directory.
*/ */
package fs package fs // import "gotest.tools/fs"
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
"gotest.tools/x/subtest"
) )
// Path objects return their filesystem path. Both File and Dir implement Path. // 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 { type Path interface {
Path() string Path() string
Remove() Remove()
@ -45,6 +48,9 @@ func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File {
for _, op := range ops { for _, op := range ops {
assert.NilError(t, op(file)) assert.NilError(t, op(file))
} }
if tc, ok := t.(subtest.TestContext); ok {
tc.AddCleanup(file.Remove)
}
return file return file
} }
@ -77,6 +83,9 @@ func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir {
for _, op := range ops { for _, op := range ops {
assert.NilError(t, op(dir)) assert.NilError(t, op(dir))
} }
if tc, ok := t.(subtest.TestContext); ok {
tc.AddCleanup(dir.Remove)
}
return dir return dir
} }

129
vendor/gotest.tools/fs/manifest.go vendored Normal file
View File

@ -0,0 +1,129 @@
package 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
}
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
}
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,
}, 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)
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)
return &file{
resource: newResourceFromInfo(info),
content: readCloser,
}, err
}

30
vendor/gotest.tools/fs/manifest_unix.go vendored Normal file
View File

@ -0,0 +1,30 @@
// +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
}

View File

@ -0,0 +1,22 @@
package 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
}

View File

@ -1,32 +1,73 @@
package fs package fs
import ( import (
"bytes"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/pkg/errors"
) )
// PathOp is a function which accepts a Path to perform some operation 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 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 // WithContent writes content to a file at Path
func WithContent(content string) PathOp { func WithContent(content string) PathOp {
return func(path Path) error { return func(path Path) error {
return ioutil.WriteFile(path.Path(), []byte(content), 0644) 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 // WithBytes write bytes to a file at Path
func WithBytes(raw []byte) PathOp { func WithBytes(raw []byte) PathOp {
return func(path Path) error { return func(path Path) error {
return ioutil.WriteFile(path.Path(), raw, 0644) 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 // AsUser changes ownership of the file system object at Path
func AsUser(uid, gid int) PathOp { func AsUser(uid, gid int) PathOp {
return func(path Path) error { 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) return os.Chown(path.Path(), uid, gid)
} }
} }
@ -34,6 +75,11 @@ func AsUser(uid, gid int) PathOp {
// WithFile creates a file in the directory at path with content // WithFile creates a file in the directory at path with content
func WithFile(filename, content string, ops ...PathOp) PathOp { func WithFile(filename, content string, ops ...PathOp) PathOp {
return func(path Path) error { 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)) fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil { if err := createFile(fullpath, content); err != nil {
return err return err
@ -43,12 +89,22 @@ func WithFile(filename, content string, ops ...PathOp) PathOp {
} }
func createFile(fullpath string, content string) error { func createFile(fullpath string, content string) error {
return ioutil.WriteFile(fullpath, []byte(content), 0644) return ioutil.WriteFile(fullpath, []byte(content), defaultFileMode)
} }
// WithFiles creates all the files in the directory at path with their content // WithFiles creates all the files in the directory at path with their content
func WithFiles(files map[string]string) PathOp { func WithFiles(files map[string]string) PathOp {
return func(path Path) error { 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 { for filename, content := range files {
fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename)) fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil { if err := createFile(fullpath, content); err != nil {
@ -62,6 +118,9 @@ func WithFiles(files map[string]string) PathOp {
// FromDir copies the directory tree from the source path into the new Dir // FromDir copies the directory tree from the source path into the new Dir
func FromDir(source string) PathOp { func FromDir(source string) PathOp {
return func(path Path) error { return func(path Path) error {
if _, ok := path.(manifestDirectory); ok {
return errors.New("use manifest.FromDir")
}
return copyDirectory(source, path.Path()) return copyDirectory(source, path.Path())
} }
} }
@ -69,9 +128,15 @@ func FromDir(source string) PathOp {
// WithDir creates a subdirectory in the directory at path. Additional PathOp // WithDir creates a subdirectory in the directory at path. Additional PathOp
// can be used to modify the subdirectory // can be used to modify the subdirectory
func WithDir(name string, ops ...PathOp) PathOp { func WithDir(name string, ops ...PathOp) PathOp {
const defaultMode = 0755
return func(path Path) error { 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)) fullpath := filepath.Join(path.Path(), filepath.FromSlash(name))
err := os.MkdirAll(fullpath, 0755) err := os.MkdirAll(fullpath, defaultMode)
if err != nil { if err != nil {
return err return err
} }
@ -91,6 +156,10 @@ func applyPathOps(path Path, ops []PathOp) error {
// WithMode sets the file mode on the directory or file at path // WithMode sets the file mode on the directory or file at path
func WithMode(mode os.FileMode) PathOp { func WithMode(mode os.FileMode) PathOp {
return func(path Path) error { return func(path Path) error {
if m, ok := path.(manifestResource); ok {
m.SetMode(mode)
return nil
}
return os.Chmod(path.Path(), mode) return os.Chmod(path.Path(), mode)
} }
} }
@ -112,6 +181,7 @@ func copyDirectory(source, dest string) error {
} }
continue continue
} }
// TODO: handle symlinks
if err := copyFile(sourcePath, destPath); err != nil { if err := copyFile(sourcePath, destPath); err != nil {
return err return err
} }
@ -134,6 +204,9 @@ func copyFile(source, dest string) error {
// the other functions in this package. // the other functions in this package.
func WithSymlink(path, target string) PathOp { func WithSymlink(path, target string) PathOp {
return func(root Path) error { 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)) return os.Symlink(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path))
} }
} }
@ -145,6 +218,9 @@ func WithSymlink(path, target string) PathOp {
// the other functions in this package. // the other functions in this package.
func WithHardlink(path, target string) PathOp { func WithHardlink(path, target string) PathOp {
return func(root Path) error { return func(root Path) error {
if _, ok := root.(manifestDirectory); ok {
return errors.New("WithHardlink yet implemented for manifests")
}
return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path)) return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path))
} }
} }
@ -153,6 +229,9 @@ func WithHardlink(path, target string) PathOp {
// at path. // at path.
func WithTimestamps(atime, mtime time.Time) PathOp { func WithTimestamps(atime, mtime time.Time) PathOp {
return func(root Path) error { return func(root Path) error {
if _, ok := root.(manifestDirectory); ok {
return errors.New("WithTimestamp yet implemented for manifests")
}
return os.Chtimes(root.Path(), atime, mtime) return os.Chtimes(root.Path(), atime, mtime)
} }
} }

151
vendor/gotest.tools/fs/path.go vendored Normal file
View File

@ -0,0 +1,151 @@
package 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) 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),
}
}
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
}
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
}
// 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
}

215
vendor/gotest.tools/fs/report.go vendored Normal file
View File

@ -0,0 +1,215 @@
package fs
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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 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 !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)
if x.target != y.target {
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
for _, name := range sortedKeys(x.items) {
if name == anyFile {
continue
}
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 _, ok := x.items[anyFile]; !ok {
for _, name := range sortedKeys(y.items) {
if _, ok := x.items[name]; !ok {
yEntry := y.items[name]
p = append(p, existenceProblem(name, "unexpected %s", yEntry.Type()))
}
}
}
if len(p) > 0 {
f = append(f, failure{path: path, problems: p})
}
return f
}
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
}
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()
}

View File

@ -2,7 +2,7 @@
Golden files are files in the ./testdata/ subdirectory of the package under test. Golden files are files in the ./testdata/ subdirectory of the package under test.
*/ */
package golden package golden // import "gotest.tools/golden"
import ( import (
"bytes" "bytes"
@ -11,9 +11,9 @@ import (
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
"github.com/gotestyourself/gotestyourself/assert/cmp" "gotest.tools/assert/cmp"
"github.com/pmezard/go-difflib/difflib" "gotest.tools/internal/format"
) )
var flagUpdate = flag.Bool("test.update-golden", false, "update golden file") var flagUpdate = flag.Bool("test.update-golden", false, "update golden file")
@ -62,16 +62,11 @@ func exactBytes(in []byte) []byte {
// to the golden file. // to the golden file.
// Returns whether the assertion was successful (true) or not (false). // Returns whether the assertion was successful (true) or not (false).
// This is equivalent to assert.Check(t, String(actual, filename)) // This is equivalent to assert.Check(t, String(actual, filename))
// func Assert(t assert.TestingT, actual string, filename string, msgAndArgs ...interface{}) {
// Deprecated: In a future version this function will change to use assert.Assert
// instead of assert.Check to be consistent with other assert functions.
// Use assert.Check(t, String(actual, filename) if you want to preserve the
// current behaviour.
func Assert(t assert.TestingT, actual string, filename string, msgAndArgs ...interface{}) bool {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
return assert.Check(t, String(actual, filename), msgAndArgs...) assert.Assert(t, String(actual, filename), msgAndArgs...)
} }
// String compares actual to the contents of filename and returns success // String compares actual to the contents of filename and returns success
@ -90,16 +85,12 @@ func String(actual string, filename string) cmp.Comparison {
if result != nil { if result != nil {
return result return result
} }
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ diff := format.UnifiedDiff(format.DiffConfig{
A: difflib.SplitLines(string(expected)), A: string(expected),
B: difflib.SplitLines(string(actualBytes)), B: string(actualBytes),
FromFile: "expected", From: "expected",
ToFile: "actual", To: "actual",
Context: 3,
}) })
if err != nil {
return cmp.ResultFromError(err)
}
return cmp.ResultFailure("\n" + diff) return cmp.ResultFailure("\n" + diff)
} }
} }
@ -109,21 +100,16 @@ func String(actual string, filename string) cmp.Comparison {
// written to the golden file. // written to the golden file.
// Returns whether the assertion was successful (true) or not (false) // Returns whether the assertion was successful (true) or not (false)
// This is equivalent to assert.Check(t, Bytes(actual, filename)) // This is equivalent to assert.Check(t, Bytes(actual, filename))
//
// Deprecated: In a future version this function will change to use assert.Assert
// instead of assert.Check to be consistent with other assert functions.
// Use assert.Check(t, Bytes(actual, filename) if you want to preserve the
// current behaviour.
func AssertBytes( func AssertBytes(
t assert.TestingT, t assert.TestingT,
actual []byte, actual []byte,
filename string, filename string,
msgAndArgs ...interface{}, msgAndArgs ...interface{},
) bool { ) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
} }
return assert.Check(t, Bytes(actual, filename), msgAndArgs...) assert.Assert(t, Bytes(actual, filename), msgAndArgs...)
} }
// Bytes compares actual to the contents of filename and returns success // Bytes compares actual to the contents of filename and returns success

View File

@ -1,6 +1,6 @@
/*Package icmd executes binaries and provides convenient assertions for testing the results. /*Package icmd executes binaries and provides convenient assertions for testing the results.
*/ */
package icmd package icmd // import "gotest.tools/icmd"
import ( import (
"bytes" "bytes"
@ -11,8 +11,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/gotestyourself/gotestyourself/assert" "gotest.tools/assert"
"github.com/gotestyourself/gotestyourself/assert/cmp" "gotest.tools/assert/cmp"
) )
type helperT interface { type helperT interface {
@ -228,6 +228,7 @@ func StartCmd(cmd Cmd) *Result {
return result return result
} }
// TODO: support exec.CommandContext
func buildCmd(cmd Cmd) *Result { func buildCmd(cmd Cmd) *Result {
var execCmd *exec.Cmd var execCmd *exec.Cmd
switch len(cmd.Command) { switch len(cmd.Command) {

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"

View File

@ -1,6 +1,6 @@
/*Package poll provides tools for testing asynchronous code. /*Package poll provides tools for testing asynchronous code.
*/ */
package poll package poll // import "gotest.tools/poll"
import ( import (
"fmt" "fmt"
@ -25,15 +25,15 @@ type helperT interface {
// Settings are used to configure the behaviour of WaitOn // Settings are used to configure the behaviour of WaitOn
type Settings struct { type Settings struct {
// Timeout is the maximum time to wait for the condition. Defaults to 10s // Timeout is the maximum time to wait for the condition. Defaults to 10s.
Timeout time.Duration Timeout time.Duration
// Delay is the time to sleep between checking the condition. Detaults to // Delay is the time to sleep between checking the condition. Defaults to
// 1ms // 100ms.
Delay time.Duration Delay time.Duration
} }
func defaultConfig() *Settings { func defaultConfig() *Settings {
return &Settings{Timeout: 10 * time.Second, Delay: time.Millisecond} return &Settings{Timeout: 10 * time.Second, Delay: 100 * time.Millisecond}
} }
// SettingOp is a function which accepts and modifies Settings // SettingOp is a function which accepts and modifies Settings

View File

@ -1,6 +1,7 @@
/*Package skip provides functions for skipping based on a condition. /*Package skip provides functions for skipping a test and printing the source code
of the condition used to skip the test.
*/ */
package skip package skip // import "gotest.tools/skip"
import ( import (
"fmt" "fmt"
@ -9,8 +10,8 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/gotestyourself/gotestyourself/internal/format" "gotest.tools/internal/format"
"github.com/gotestyourself/gotestyourself/internal/source" "gotest.tools/internal/source"
) )
type skipT interface { type skipT interface {
@ -25,9 +26,10 @@ type helperT interface {
// BoolOrCheckFunc can be a bool or func() bool, other types will panic // BoolOrCheckFunc can be a bool or func() bool, other types will panic
type BoolOrCheckFunc interface{} type BoolOrCheckFunc interface{}
// If skips the test if the check function returns true. The skip message will // If the condition expression evaluates to true, or the condition function returns
// contain the name of the check function. Extra message text can be passed as a // true, skip the test.
// format string with args // 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{}) { func If(t skipT, condition BoolOrCheckFunc, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()
@ -49,18 +51,6 @@ func getFunctionName(function func() bool) string {
return strings.SplitN(path.Base(funcPath), ".", 2)[1] return strings.SplitN(path.Base(funcPath), ".", 2)[1]
} }
// IfCondition skips the test if the condition is true. The skip message will
// contain the source of the expression passed as the condition. Extra message
// text can be passed as a format string with args.
//
// Deprecated: Use If() which now accepts bool arguments
func IfCondition(t skipT, condition bool, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
ifCondition(t, condition, msgAndArgs...)
}
func ifCondition(t skipT, condition bool, msgAndArgs ...interface{}) { func ifCondition(t skipT, condition bool, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok { if ht, ok := t.(helperT); ok {
ht.Helper() ht.Helper()

View File

@ -0,0 +1,81 @@
/*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{}