This commit is contained in:
Audrius Butkevicius 2016-03-06 20:32:10 +00:00 committed by Jakob Borg
parent dd9a4e044a
commit a8ffde6f21
12 changed files with 641 additions and 51 deletions

View File

@ -12,11 +12,11 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/protocol"
)
@ -65,8 +65,8 @@ func TestDefaultValues(t *testing.T) {
cfg := New(device1)
if !reflect.DeepEqual(cfg.Options, expected) {
t.Errorf("Default config differs;\n E: %#v\n A: %#v", expected, cfg.Options)
if diff, equal := messagediff.PrettyDiff(cfg.Options, expected); !equal {
t.Errorf("Default config differs. Diff:\n%s", diff)
}
}
@ -133,14 +133,14 @@ func TestDeviceConfig(t *testing.T) {
if cfg.Version != CurrentVersion {
t.Errorf("%d: Incorrect version %d != %d", i, cfg.Version, CurrentVersion)
}
if !reflect.DeepEqual(cfg.Folders, expectedFolders) {
t.Errorf("%d: Incorrect Folders\n A: %#v\n E: %#v", i, cfg.Folders, expectedFolders)
if diff, equal := messagediff.PrettyDiff(cfg.Folders, expectedFolders); !equal {
t.Errorf("%d: Incorrect Folders. Diff:\n%s", i, diff)
}
if !reflect.DeepEqual(cfg.Devices, expectedDevices) {
t.Errorf("%d: Incorrect Devices\n A: %#v\n E: %#v", i, cfg.Devices, expectedDevices)
if diff, equal := messagediff.PrettyDiff(cfg.Devices, expectedDevices); !equal {
t.Errorf("%d: Incorrect Devices. Diff:\n%s", i, diff)
}
if !reflect.DeepEqual(cfg.Folders[0].DeviceIDs(), expectedDeviceIDs) {
t.Errorf("%d: Incorrect DeviceIDs\n A: %#v\n E: %#v", i, cfg.Folders[0].DeviceIDs(), expectedDeviceIDs)
if diff, equal := messagediff.PrettyDiff(cfg.Folders[0].DeviceIDs(), expectedDeviceIDs); !equal {
t.Errorf("%d: Incorrect DeviceIDs. Diff:\n%s", i, diff)
}
}
}
@ -153,8 +153,8 @@ func TestNoListenAddress(t *testing.T) {
expected := []string{""}
actual := cfg.Options().ListenAddress
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Unexpected ListenAddress %#v", actual)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("Unexpected ListenAddress. Diff:\n%s", diff)
}
}
@ -197,8 +197,8 @@ func TestOverriddenValues(t *testing.T) {
t.Error(err)
}
if !reflect.DeepEqual(cfg.Options(), expected) {
t.Errorf("Overridden config differs;\n E: %#v\n A: %#v", expected, cfg.Options())
if diff, equal := messagediff.PrettyDiff(cfg.Options(), expected); !equal {
t.Errorf("Overridden config differs. Diff:\n%s", diff)
}
}
@ -231,8 +231,8 @@ func TestDeviceAddressesDynamic(t *testing.T) {
}
actual := cfg.Devices()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Devices differ;\n E: %#v\n A: %#v", expected, actual)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("Devices differ. Diff:\n%s", diff)
}
}
@ -268,8 +268,8 @@ func TestDeviceCompression(t *testing.T) {
}
actual := cfg.Devices()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Devices differ;\n E: %#v\n A: %#v", expected, actual)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("Devices differ. Diff:\n%s", diff)
}
}
@ -302,8 +302,8 @@ func TestDeviceAddressesStatic(t *testing.T) {
}
actual := cfg.Devices()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Devices differ;\n E: %#v\n A: %#v", expected, actual)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("Devices differ. Diff:\n%s", diff)
}
}
@ -325,8 +325,8 @@ func TestVersioningConfig(t *testing.T) {
"foo": "bar",
"baz": "quux",
}
if !reflect.DeepEqual(vc.Params, expected) {
t.Errorf("vc.Params differ;\n E: %#v\n A: %#v", expected, vc.Params)
if diff, equal := messagediff.PrettyDiff(vc.Params, expected); !equal {
t.Errorf("vc.Params differ. Diff:\n%s", diff)
}
}
@ -447,8 +447,8 @@ func TestNewSaveLoad(t *testing.T) {
t.Error(err)
}
if !reflect.DeepEqual(cfg.Raw(), cfg2.Raw()) {
t.Errorf("Configs are not equal;\n E: %#v\n A: %#v", cfg.Raw(), cfg2.Raw())
if diff, equal := messagediff.PrettyDiff(cfg.Raw(), cfg2.Raw()); !equal {
t.Errorf("Configs are not equal. Diff:\n%s", diff)
}
os.Remove(path)

View File

@ -10,10 +10,10 @@ import (
"bytes"
"fmt"
"os"
"reflect"
"sort"
"testing"
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/protocol"
)
@ -532,8 +532,9 @@ func TestListDropFolder(t *testing.T) {
// Check that we have both folders and their data is in the global list
expectedFolderList := []string{"test0", "test1"}
if actualFolderList := ldb.ListFolders(); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
t.Fatalf("FolderList mismatch\nE: %v\nA: %v", expectedFolderList, actualFolderList)
actualFolderList := ldb.ListFolders()
if diff, equal := messagediff.PrettyDiff(actualFolderList, expectedFolderList); !equal {
t.Fatalf("FolderList mismatch. Diff:\n%s", diff)
}
if l := len(globalList(s0)); l != 3 {
t.Errorf("Incorrect global length %d != 3 for s0", l)
@ -547,8 +548,9 @@ func TestListDropFolder(t *testing.T) {
db.DropFolder(ldb, "test1")
expectedFolderList = []string{"test0"}
if actualFolderList := ldb.ListFolders(); !reflect.DeepEqual(actualFolderList, expectedFolderList) {
t.Fatalf("FolderList mismatch\nE: %v\nA: %v", expectedFolderList, actualFolderList)
actualFolderList = ldb.ListFolders()
if diff, equal := messagediff.PrettyDiff(actualFolderList, expectedFolderList); !equal {
t.Fatalf("FolderList mismatch. Diff:\n%s", diff)
}
if l := len(globalList(s0)); l != 3 {
t.Errorf("Incorrect global length %d != 3 for s0", l)

View File

@ -8,8 +8,9 @@ package model
import (
"fmt"
"reflect"
"testing"
"github.com/d4l3k/messagediff"
)
func TestJobQueue(t *testing.T) {
@ -126,36 +127,36 @@ func TestBringToFront(t *testing.T) {
q.Push("f4", 0, 0)
_, queued := q.Jobs()
if !reflect.DeepEqual(queued, []string{"f1", "f2", "f3", "f4"}) {
t.Errorf("Incorrect order %v at start", queued)
if diff, equal := messagediff.PrettyDiff(queued, []string{"f1", "f2", "f3", "f4"}); !equal {
t.Errorf("Order does not match. Diff:\n%s", diff)
}
q.BringToFront("f1") // corner case: does nothing
_, queued = q.Jobs()
if !reflect.DeepEqual(queued, []string{"f1", "f2", "f3", "f4"}) {
t.Errorf("Incorrect order %v", queued)
if diff, equal := messagediff.PrettyDiff(queued, []string{"f1", "f2", "f3", "f4"}); !equal {
t.Errorf("Order does not match. Diff:\n%s", diff)
}
q.BringToFront("f3")
_, queued = q.Jobs()
if !reflect.DeepEqual(queued, []string{"f3", "f1", "f2", "f4"}) {
t.Errorf("Incorrect order %v", queued)
if diff, equal := messagediff.PrettyDiff(queued, []string{"f3", "f1", "f2", "f4"}); !equal {
t.Errorf("Order does not match. Diff:\n%s", diff)
}
q.BringToFront("f2")
_, queued = q.Jobs()
if !reflect.DeepEqual(queued, []string{"f2", "f3", "f1", "f4"}) {
t.Errorf("Incorrect order %v", queued)
if diff, equal := messagediff.PrettyDiff(queued, []string{"f2", "f3", "f1", "f4"}); !equal {
t.Errorf("Order does not match. Diff:\n%s", diff)
}
q.BringToFront("f4") // corner case: last element
_, queued = q.Jobs()
if !reflect.DeepEqual(queued, []string{"f4", "f2", "f3", "f1"}) {
t.Errorf("Incorrect order %v", queued)
if diff, equal := messagediff.PrettyDiff(queued, []string{"f4", "f2", "f3", "f1"}); !equal {
t.Errorf("Order does not match. Diff:\n%s", diff)
}
}
@ -175,7 +176,7 @@ func TestShuffle(t *testing.T) {
}
t.Logf("%v", queued)
if !reflect.DeepEqual(queued, []string{"f1", "f2", "f3", "f4"}) {
if _, equal := messagediff.PrettyDiff(queued, []string{"f1", "f2", "f3", "f4"}); !equal {
// The queue was shuffled
return
}
@ -199,8 +200,8 @@ func TestSortBySize(t *testing.T) {
}
expected := []string{"f4", "f1", "f3", "f2"}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("SortSmallestFirst(): %#v != %#v", actual, expected)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("SortSmallestFirst() diff:\n%s", diff)
}
q.SortLargestFirst()
@ -211,8 +212,8 @@ func TestSortBySize(t *testing.T) {
}
expected = []string{"f2", "f3", "f1", "f4"}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("SortLargestFirst(): %#v != %#v", actual, expected)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("SortLargestFirst() diff:\n%s", diff)
}
}
@ -231,8 +232,8 @@ func TestSortByAge(t *testing.T) {
}
expected := []string{"f4", "f1", "f3", "f2"}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("SortOldestFirst(): %#v != %#v", actual, expected)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("SortOldestFirst() diff:\n%s", diff)
}
q.SortNewestFirst()
@ -243,8 +244,8 @@ func TestSortByAge(t *testing.T) {
}
expected = []string{"f2", "f3", "f1", "f4"}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("SortNewestFirst(): %#v != %#v", actual, expected)
if diff, equal := messagediff.PrettyDiff(actual, expected); !equal {
t.Errorf("SortNewestFirst() diff:\n%s", diff)
}
}

View File

@ -80,6 +80,7 @@ func TestInWritableDirWindowsRemove(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Chmod("testdata/windows/ro/readonlynew", 0700)
defer os.RemoveAll("testdata")
create := func(name string) error {
@ -123,6 +124,7 @@ func TestInWritableDirWindowsRename(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.Chmod("testdata/windows/ro/readonlynew", 0700)
defer os.RemoveAll("testdata")
create := func(name string) error {

View File

@ -13,13 +13,13 @@ import (
"io"
"os"
"path/filepath"
"reflect"
"runtime"
rdebug "runtime/debug"
"sort"
"sync"
"testing"
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
@ -120,8 +120,8 @@ func TestWalk(t *testing.T) {
sort.Sort(fileList(tmp))
files := fileList(tmp).testfiles()
if !reflect.DeepEqual(files, testdata) {
t.Errorf("Walk returned unexpected data\nExpected: %v\nActual: %v", testdata, files)
if diff, equal := messagediff.PrettyDiff(files, testdata); !equal {
t.Errorf("Walk returned unexpected data. Diff:\n%s", diff)
}
}

22
vendor/github.com/d4l3k/messagediff/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Tristan Rice
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

65
vendor/github.com/d4l3k/messagediff/README.md generated vendored Normal file
View File

@ -0,0 +1,65 @@
# messagediff [![Build Status](https://travis-ci.org/d4l3k/messagediff.svg?branch=master)](https://travis-ci.org/d4l3k/messagediff) [![Coverage Status](https://coveralls.io/repos/github/d4l3k/messagediff/badge.svg?branch=master)](https://coveralls.io/github/d4l3k/messagediff?branch=master) [![GoDoc](https://godoc.org/github.com/d4l3k/messagediff?status.svg)](https://godoc.org/github.com/d4l3k/messagediff)
A library for doing diffs of arbitrary Golang structs.
If the unsafe package is available messagediff will diff unexported fields in
addition to exported fields. This is primarily used for testing purposes as it
allows for providing informative error messages.
## Example Usage
In a normal file:
```go
package main
import "github.com/d4l3k/messagediff"
type someStruct struct {
A, b int
C []int
}
func main() {
a := someStruct{1, 2, []int{1}}
b := someStruct{1, 3, []int{1, 2}}
diff, equal := messagediff.PrettyDiff(a, b)
/*
diff =
`added: .C[1] = 2
modified: .b = 3`
equal = false
*/
}
```
In a test:
```go
import "github.com/d4l3k/messagediff"
...
type someStruct struct {
A, b int
C []int
}
func TestSomething(t *testing.T) {
want := someStruct{1, 2, []int{1}}
got := someStruct{1, 3, []int{1, 2}}
if diff, equal := messagediff.PrettyDiff(want, got); !equal {
t.Errorf("Something() = %#v\n%s", got, diff)
}
}
```
See the `DeepDiff` function for using the diff results programmatically.
## License
Copyright (c) 2015 [Tristan Rice](https://fn.lc) <rice@fn.lc>
messagediff is licensed under the MIT license. See the LICENSE file for more information.
bypass.go and bypasssafe.go are borrowed from
[go-spew](https://github.com/davecgh/go-spew) and have a seperate copyright
notice.

151
vendor/github.com/d4l3k/messagediff/bypass.go generated vendored Normal file
View File

@ -0,0 +1,151 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine and "-tags disableunsafe"
// is not added to the go build command line.
// +build !appengine,!disableunsafe
package messagediff
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
var (
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
// internal reflect.Value fields. These values are valid before golang
// commit ecccf07e7f9d which changed the format. The are also valid
// after commit 82f48826c6c7 which changed the format again to mirror
// the original format. Code in the init function updates these offsets
// as necessary.
offsetPtr = uintptr(ptrSize)
offsetScalar = uintptr(0)
offsetFlag = uintptr(ptrSize * 2)
// flagKindWidth and flagKindShift indicate various bits that the
// reflect package uses internally to track kind information.
//
// flagRO indicates whether or not the value field of a reflect.Value is
// read-only.
//
// flagIndir indicates whether the value field of a reflect.Value is
// the actual data or a pointer to the data.
//
// These values are valid before golang commit 90a7c3c86944 which
// changed their positions. Code in the init function updates these
// flags as necessary.
flagKindWidth = uintptr(5)
flagKindShift = uintptr(flagKindWidth - 1)
flagRO = uintptr(1 << 0)
flagIndir = uintptr(1 << 1)
)
func init() {
// Older versions of reflect.Value stored small integers directly in the
// ptr field (which is named val in the older versions). Versions
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
// scalar for this purpose which unfortunately came before the flag
// field, so the offset of the flag field is different for those
// versions.
//
// This code constructs a new reflect.Value from a known small integer
// and checks if the size of the reflect.Value struct indicates it has
// the scalar field. When it does, the offsets are updated accordingly.
vv := reflect.ValueOf(0xf00)
if unsafe.Sizeof(vv) == (ptrSize * 4) {
offsetScalar = ptrSize * 2
offsetFlag = ptrSize * 3
}
// Commit 90a7c3c86944 changed the flag positions such that the low
// order bits are the kind. This code extracts the kind from the flags
// field and ensures it's the correct type. When it's not, the flag
// order has been changed to the newer format, so the flags are updated
// accordingly.
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
upfv := *(*uintptr)(upf)
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
flagKindShift = 0
flagRO = 1 << 5
flagIndir = 1 << 6
// Commit adf9b30e5594 modified the flags to separate the
// flagRO flag into two bits which specifies whether or not the
// field is embedded. This causes flagIndir to move over a bit
// and means that flagRO is the combination of either of the
// original flagRO bit and the new bit.
//
// This code detects the change by extracting what used to be
// the indirect bit to ensure it's set. When it's not, the flag
// order has been changed to the newer format, so the flags are
// updated accordingly.
if upfv&flagIndir == 0 {
flagRO = 3 << 5
flagIndir = 1 << 7
}
}
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
indirects := 1
vt := v.Type()
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
if rvf&flagIndir != 0 {
vt = reflect.PtrTo(v.Type())
indirects++
} else if offsetScalar != 0 {
// The value is in the scalar field when it's not one of the
// reference types.
switch vt.Kind() {
case reflect.Uintptr:
case reflect.Chan:
case reflect.Func:
case reflect.Map:
case reflect.Ptr:
case reflect.UnsafePointer:
default:
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
offsetScalar)
}
}
pv := reflect.NewAt(vt, upv)
rv = pv
for i := 0; i < indirects; i++ {
rv = rv.Elem()
}
return rv
}

37
vendor/github.com/d4l3k/messagediff/bypasssafe.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
// Copyright (c) 2015 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when either the code is running on Google App Engine or "-tags disableunsafe"
// is added to the go build command line.
// +build appengine disableunsafe
package messagediff
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

188
vendor/github.com/d4l3k/messagediff/messagediff.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
package messagediff
import (
"fmt"
"reflect"
"sort"
"strings"
)
// PrettyDiff does a deep comparison and returns the nicely formated results.
func PrettyDiff(a, b interface{}) (string, bool) {
d, equal := DeepDiff(a, b)
var dstr []string
for path, added := range d.Added {
dstr = append(dstr, fmt.Sprintf("added: %s = %#v\n", path.String(), added))
}
for path, removed := range d.Removed {
dstr = append(dstr, fmt.Sprintf("removed: %s = %#v\n", path.String(), removed))
}
for path, modified := range d.Modified {
dstr = append(dstr, fmt.Sprintf("modified: %s = %#v\n", path.String(), modified))
}
sort.Strings(dstr)
return strings.Join(dstr, ""), equal
}
// DeepDiff does a deep comparison and returns the results.
func DeepDiff(a, b interface{}) (*Diff, bool) {
d := newdiff()
return d, diff(a, b, nil, d)
}
func newdiff() *Diff {
return &Diff{
Added: make(map[*Path]interface{}),
Removed: make(map[*Path]interface{}),
Modified: make(map[*Path]interface{}),
}
}
func diff(a, b interface{}, path Path, d *Diff) bool {
aVal := reflect.ValueOf(a)
bVal := reflect.ValueOf(b)
if !aVal.IsValid() && !bVal.IsValid() {
// Both are nil.
return true
}
if !aVal.IsValid() || !bVal.IsValid() {
// One is nil and the other isn't.
d.Modified[&path] = b
return false
}
if aVal.Type() != bVal.Type() {
d.Modified[&path] = b
return false
}
kind := aVal.Type().Kind()
equal := true
switch kind {
case reflect.Array, reflect.Slice:
aLen := aVal.Len()
bLen := bVal.Len()
for i := 0; i < min(aLen, bLen); i++ {
localPath := append(path, SliceIndex(i))
if eq := diff(aVal.Index(i).Interface(), bVal.Index(i).Interface(), localPath, d); !eq {
equal = false
}
}
if aLen > bLen {
for i := bLen; i < aLen; i++ {
localPath := append(path, SliceIndex(i))
d.Removed[&localPath] = aVal.Index(i).Interface()
equal = false
}
} else if aLen < bLen {
for i := aLen; i < bLen; i++ {
localPath := append(path, SliceIndex(i))
d.Added[&localPath] = bVal.Index(i).Interface()
equal = false
}
}
case reflect.Map:
for _, key := range aVal.MapKeys() {
aI := aVal.MapIndex(key)
bI := bVal.MapIndex(key)
localPath := append(path, MapKey{key.Interface()})
if !bI.IsValid() {
d.Removed[&localPath] = aI.Interface()
equal = false
} else if eq := diff(aI.Interface(), bI.Interface(), localPath, d); !eq {
equal = false
}
}
for _, key := range bVal.MapKeys() {
aI := aVal.MapIndex(key)
if !aI.IsValid() {
bI := bVal.MapIndex(key)
localPath := append(path, MapKey{key.Interface()})
d.Added[&localPath] = bI.Interface()
equal = false
}
}
case reflect.Struct:
typ := aVal.Type()
for i := 0; i < typ.NumField(); i++ {
index := []int{i}
field := typ.FieldByIndex(index)
localPath := append(path, StructField(field.Name))
aI := unsafeReflectValue(aVal.FieldByIndex(index)).Interface()
bI := unsafeReflectValue(bVal.FieldByIndex(index)).Interface()
if eq := diff(aI, bI, localPath, d); !eq {
equal = false
}
}
case reflect.Ptr:
aVal = aVal.Elem()
bVal = bVal.Elem()
if !aVal.IsValid() && !bVal.IsValid() {
// Both are nil.
equal = true
} else if !aVal.IsValid() || !bVal.IsValid() {
// One is nil and the other isn't.
d.Modified[&path] = b
equal = false
} else {
equal = diff(aVal.Interface(), bVal.Interface(), path, d)
}
default:
if reflect.DeepEqual(a, b) {
equal = true
} else {
d.Modified[&path] = b
equal = false
}
}
return equal
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Diff represents a change in a struct.
type Diff struct {
Added, Removed, Modified map[*Path]interface{}
}
// Path represents a path to a changed datum.
type Path []PathNode
func (p Path) String() string {
var out string
for _, n := range p {
out += n.String()
}
return out
}
// PathNode represents one step in the path.
type PathNode interface {
String() string
}
// StructField is a path element representing a field of a struct.
type StructField string
func (n StructField) String() string {
return fmt.Sprintf(".%s", string(n))
}
// MapKey is a path element representing a key of a map.
type MapKey struct {
Key interface{}
}
func (n MapKey) String() string {
return fmt.Sprintf("[%#v]", n.Key)
}
// SliceIndex is a path element representing a index of a slice.
type SliceIndex int
func (n SliceIndex) String() string {
return fmt.Sprintf("[%d]", n)
}

116
vendor/github.com/d4l3k/messagediff/messagediff_test.go generated vendored Normal file
View File

@ -0,0 +1,116 @@
package messagediff
import (
"testing"
"time"
)
type testStruct struct {
A, b int
C []int
}
func TestPrettyDiff(t *testing.T) {
testData := []struct {
a, b interface{}
diff string
equal bool
}{
{
true,
false,
"modified: = false\n",
false,
},
{
true,
0,
"modified: = 0\n",
false,
},
{
[]int{0, 1, 2},
[]int{0, 1, 2, 3},
"added: [3] = 3\n",
false,
},
{
[]int{0, 1, 2, 3},
[]int{0, 1, 2},
"removed: [3] = 3\n",
false,
},
{
[]int{0},
[]int{1},
"modified: [0] = 1\n",
false,
},
{
&[]int{0},
&[]int{1},
"modified: [0] = 1\n",
false,
},
{
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 4, "c": 3},
"added: [\"c\"] = 3\nmodified: [\"b\"] = 4\nremoved: [\"a\"] = 1\n",
false,
},
{
testStruct{1, 2, []int{1}},
testStruct{1, 3, []int{1, 2}},
"added: .C[1] = 2\nmodified: .b = 3\n",
false,
},
{
nil,
nil,
"",
true,
},
{
&time.Time{},
nil,
"modified: = <nil>\n",
false,
},
{
time.Time{},
time.Time{},
"",
true,
},
{
time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC),
time.Time{},
"modified: .loc = (*time.Location)(nil)\nmodified: .sec = 0\n",
false,
},
}
for i, td := range testData {
diff, equal := PrettyDiff(td.a, td.b)
if diff != td.diff {
t.Errorf("%d. PrettyDiff(%#v, %#v) diff = %#v; not %#v", i, td.a, td.b, diff, td.diff)
}
if equal != td.equal {
t.Errorf("%d. PrettyDiff(%#v, %#v) equal = %#v; not %#v", i, td.a, td.b, equal, td.equal)
}
}
}
func TestPathString(t *testing.T) {
testData := []struct {
in Path
want string
}{{
Path{StructField("test"), SliceIndex(1), MapKey{"blue"}, MapKey{12.3}},
".test[1][\"blue\"][12.3]",
}}
for i, td := range testData {
if out := td.in.String(); out != td.want {
t.Errorf("%d. %#v.String() = %#v; not %#v", i, td.in, out, td.want)
}
}
}

6
vendor/manifest vendored
View File

@ -25,6 +25,12 @@
"revision": "b6e0c321c9b5b28ba5ee21e828323e4b982c6976",
"branch": "master"
},
{
"importpath": "github.com/d4l3k/messagediff",
"repository": "https://github.com/d4l3k/messagediff",
"revision": "bf29d7cd9038386a5b4a22e2d73c8fb20ae14602",
"branch": "master"
},
{
"importpath": "github.com/golang/snappy",
"repository": "https://github.com/golang/snappy",