Audrius Butkevicius 9 年之前
父節點
當前提交
a8ffde6f21

+ 23 - 23
lib/config/config_test.go

@@ -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)

+ 7 - 5
lib/db/set_test.go

@@ -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)

+ 21 - 20
lib/model/queue_test.go

@@ -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)
 	}
 }
 

+ 2 - 0
lib/osutil/osutil_test.go

@@ -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 {

+ 3 - 3
lib/scanner/walk_test.go

@@ -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 - 0
vendor/github.com/d4l3k/messagediff/LICENSE

@@ -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 - 0
vendor/github.com/d4l3k/messagediff/README.md

@@ -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) <[email protected]>
+
+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 - 0
vendor/github.com/d4l3k/messagediff/bypass.go

@@ -0,0 +1,151 @@
+// Copyright (c) 2015 Dave Collins <[email protected]>
+//
+// 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 - 0
vendor/github.com/d4l3k/messagediff/bypasssafe.go

@@ -0,0 +1,37 @@
+// Copyright (c) 2015 Dave Collins <[email protected]>
+//
+// 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 - 0
vendor/github.com/d4l3k/messagediff/messagediff.go

@@ -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 - 0
vendor/github.com/d4l3k/messagediff/messagediff_test.go

@@ -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 - 0
vendor/manifest

@@ -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",