blob: e905a351f6aed0994612a675aee4616ec3559015 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compare_test
import (
"reflect"
"testing"
"fmt"
"android.googlesource.com/platform/tools/gpu/framework/assert"
"android.googlesource.com/platform/tools/gpu/framework/compare"
)
var (
funcNil1 func()
funcNil2 func()
funcNotNil = func() { funcNil1() } // Not nil.
leafBase = &Object{0, nil}
leafEqual = &Object{0, nil}
leafDifferent = &Object{2, nil}
objectBase = &Object{0, []interface{}{leafBase, leafBase}}
objectEqual1 = &Object{0, []interface{}{leafBase, leafBase}}
objectEqual2 = &Object{0, []interface{}{leafEqual, leafBase}}
objectDifferent = &Object{0, []interface{}{leafDifferent, leafBase}}
objectInverse = &Object{0, []interface{}{leafBase, leafEqual}}
objectShort = &Object{0, []interface{}{leafBase}}
objectInvalid1 = &Object{0, []interface{}{nil}}
objectInvalid2 = &Object{0, []interface{}{nil}}
objectWithInt = &Object{0, []interface{}{0}}
objectWithString = &Object{0, []interface{}{""}}
root = compare.Path{}
noDiff = []compare.Path{}
valueBase = Value(1)
valueEqual = Value(1)
valueDifferent = Value(2)
alternate = Alternate(1)
sliceBase = []int{1, 2, 3}
sliceEqual = []int{1, 2, 3}
sliceLonger = []int{1, 2, 3, 4}
sliceDifferent = []int{1, 2, 4}
arrayBase = [3]int{1, 2, 3}
arrayEqual = [3]int{1, 2, 3}
arrayDifferent = [3]int{1, 2, 4}
structBase = Struct{1, 0.5}
structEqual = Struct{1, 0.5}
structDifferentInt = Struct{2, 0.5}
structDifferentFloat = Struct{1, 0.6}
aliasBase = Alias{1, 0.5}
otherBase = OtherStruct{1, 0.5}
mapBase = map[int]string{1: "one", 2: "two"}
mapEqual = map[int]string{1: "one", 2: "two"}
mapDifferentKeys = map[int]string{1: "one", 3: "three"}
mapDifferentValues = map[int]string{1: "one", 2: "too"}
mapDifferentLength = map[int]string{1: "one"}
mapUint = map[uint]string{1: "one", 2: "two"}
hidden1 = compare.Hidden{Value: Hide{1}}
hidden2 = compare.Hidden{Value: Hide{2}}
compareTests = []struct {
name string
a, b interface{}
diff []compare.Path
}{
// Equalities
{"nil ==", nil, nil, noDiff},
{"int ==", 1, 1, noDiff},
{"int32 ==", int32(1), int32(1), noDiff},
{"float64 ==", 0.5, 0.5, noDiff},
{"float32 ==", float32(0.5), float32(0.5), noDiff},
{"string ==", "hello", "hello", noDiff},
{"slice ==", sliceBase, sliceEqual, noDiff},
{"empty slice ==", []int{}, []int{}, noDiff},
{"nill slice ==", []int(nil), []int(nil), noDiff},
{"array ==", arrayBase, arrayEqual, noDiff},
{"struct ==", structBase, structEqual, noDiff},
{"error ==", error(nil), error(nil), noDiff},
{"map ==", mapBase, mapEqual, noDiff},
{"empty map ==", map[int]int{}, map[int]int{}, noDiff},
{"nil map ==", map[int]int(nil), map[int]int(nil), noDiff},
{"func ==", funcNil1, funcNil2, noDiff},
{"leaf ==", leafBase, leafEqual, noDiff},
{"object ==", objectBase, objectBase, noDiff},
{"object pointer ==", objectBase, objectEqual1, noDiff},
{"object value ==", objectBase, objectEqual2, noDiff},
{"object inverse ==", objectEqual2, objectInverse, noDiff},
{"hidden==", Hide{1}, Hide{1}, noDiff},
{"pointer ==", &valueBase, &valueEqual, noDiff},
{"invalid", objectInvalid1, objectInvalid2, noDiff},
{"custom 1", Special(1), Special(1), noDiff},
{"custom 3", Special(1), Special(3), noDiff},
{"special struct custom 1", SpecialStruct{false, 1}, SpecialStruct{false, 1}, noDiff},
{"special struct custom 3", SpecialStruct{false, 1}, SpecialStruct{false, 3}, noDiff},
// Inequalities
{"int !=", 1, 2, []compare.Path{root.Diff(1, 2)}},
{"int32 !=", int32(1), int32(2), []compare.Path{root.Diff(int32(1), int32(2))}},
{"float64 !=", 0.5, 0.6, []compare.Path{root.Diff(0.5, 0.6)}},
{"float32 !=", float32(0.5), float32(0.6), []compare.Path{root.Diff(float32(0.5), float32(0.6))}},
{"string !=", "hello", "hey", []compare.Path{root.Diff("hello", "hey")}},
{"slice.longer", sliceBase, sliceLonger, []compare.Path{
root.Length(sliceBase, sliceLonger).Diff(3, 4),
root.Index(3, sliceBase, sliceLonger).Missing(compare.Missing, 4),
}},
{"slice.shorter", sliceLonger, sliceBase, []compare.Path{
root.Index(3, sliceLonger, sliceBase).Missing(4, compare.Missing),
root.Length(sliceLonger, sliceBase).Diff(4, 3),
}},
{"slice.value", sliceBase, sliceDifferent, []compare.Path{
root.Index(2, sliceBase, sliceDifferent).Diff(3, 4),
}},
{"array !=", arrayBase, arrayDifferent, []compare.Path{
root.Index(2, arrayBase, arrayDifferent).Diff(3, 4),
}},
{"struct.float !=", structBase, structDifferentFloat, []compare.Path{
root.Member("Float", structBase, structDifferentFloat).Diff(float32(0.5), float32(0.6)),
}},
{"struct.int !=", structBase, structDifferentInt, []compare.Path{
root.Member("Int", structBase, structDifferentInt).Diff(1, 2),
}},
{"map.keys !=", mapBase, mapDifferentKeys, []compare.Path{
root.Entry(2, mapBase, mapDifferentKeys).Missing("two", compare.Missing),
root.Entry(3, mapBase, mapDifferentKeys).Missing(compare.Missing, "three"),
}},
{"map.values !=", mapBase, mapDifferentValues, []compare.Path{
root.Entry(2, mapBase, mapDifferentValues).Diff("two", "too"),
}},
{"len(map) 1 != 2", mapDifferentLength, mapBase, []compare.Path{
root.Length(mapDifferentLength, mapBase).Diff(1, 2),
root.Entry(2, mapDifferentLength, mapBase).Missing(compare.Missing, "two"),
}},
{"len(map) 2 != 1", mapBase, mapDifferentLength, []compare.Path{
root.Length(mapBase, mapDifferentLength).Diff(2, 1),
root.Entry(2, mapBase, mapDifferentLength).Missing("two", compare.Missing),
}},
{"nil != int", nil, 1, []compare.Path{
root.Nil(nil, 1),
}},
{"int != nil", 1, nil, []compare.Path{
root.Nil(1, nil),
}},
{"nil != func", funcNil1, funcNotNil, []compare.Path{{}}},
{"func != func", funcNotNil, funcNotNil, []compare.Path{{}}},
{"leaf !=", leafBase, leafDifferent, []compare.Path{
root.Member("Value", leafBase, leafDifferent).Diff(0, 2),
}},
{"object 1 !=", objectBase, objectDifferent, []compare.Path{
root.Member("Children", objectBase, objectDifferent).
Index(0, objectBase.Children, objectDifferent.Children).
Member("Value", objectBase.Children[0], objectDifferent.Children[0]).
Diff(0, 2),
}},
{"object 2 !=", objectBase, objectShort, []compare.Path{
root.Member("Children", objectBase, objectShort).
Index(1, objectBase.Children, objectShort.Children).
Missing(objectBase.Children[1], compare.Missing),
root.Member("Children", objectBase, objectShort).
Length(objectBase.Children, objectShort.Children).
Diff(2, 1),
}},
{"invalid reference", objectInvalid1, objectShort, []compare.Path{
root.Member("Children", objectInvalid1, objectShort).
Index(0, objectInvalid1.Children, objectShort.Children).
Nil(nil, leafBase),
}},
{"invalid value", objectShort, objectInvalid1, []compare.Path{
root.Member("Children", objectShort, objectInvalid1).
Index(0, objectShort.Children, objectInvalid1.Children).
Nil(leafBase, nil),
}},
{"int aliases", valueBase, alternate, []compare.Path{
root.Type(valueBase, alternate).Diff(reflect.TypeOf(valueBase), reflect.TypeOf(alternate)),
}},
{"empty slice != nil slice", []int{}, []int(nil), []compare.Path{
root.Nil([]int{}, nil),
}},
{"empty map != nil map", map[int]int{}, map[int]int(nil), []compare.Path{
root.Nil(map[int]int{}, nil),
}},
{"hidden!=", Hide{1}, Hide{2}, []compare.Path{
root.Diff(hidden1, hidden2),
}},
{"pointer!=", &valueBase, &valueDifferent, []compare.Path{
root.Diff(&valueBase, &valueDifferent),
}},
{"custom", Special(1), Special(2), []compare.Path{
root.Diff(Special(1), Special(2)),
}},
{"not so special struct", SpecialStruct{true, 1}, SpecialStruct{true, 3}, []compare.Path{
root.Member("Value", SpecialStruct{true, 1}, SpecialStruct{true, 3}).
Diff(Special(1), Special(3)),
}},
// Mismatched types
{"int != float64", 1, 1.0, []compare.Path{
root.Type(1, 1.0).Diff(reflect.TypeOf(0), reflect.TypeOf(0.0)),
}},
{"int32 != int64", int32(1), int64(1), []compare.Path{
root.Type(int32(1), int64(1)).Diff(reflect.TypeOf(int32(0)), reflect.TypeOf(int64(0))),
}},
{"float64 != string", 0.5, "hello", []compare.Path{
root.Type(0.5, "hello").Diff(reflect.TypeOf(0.0), reflect.TypeOf("")),
}},
{"slice != array", sliceBase, arrayBase, []compare.Path{
root.Type(sliceBase, arrayBase).Diff(reflect.TypeOf(sliceBase), reflect.TypeOf(arrayBase)),
}},
{"int != string", objectWithInt, objectWithString, []compare.Path{
root.Member("Children", objectWithInt, objectWithString).
Index(0, objectWithInt.Children, objectWithString.Children).
Type(objectWithInt.Children[0], objectWithString.Children[0]).
Diff(reflect.TypeOf(0), reflect.TypeOf("")),
}},
{"struct != alias", structBase, aliasBase, []compare.Path{
root.Type(structBase, aliasBase).Diff(reflect.TypeOf(Struct{}), reflect.TypeOf(Alias{})),
}},
{"struct != other", structBase, otherBase, []compare.Path{
root.Type(structBase, otherBase).Diff(reflect.TypeOf(Struct{}), reflect.TypeOf(OtherStruct{})),
}},
{"map[uint]string != map[int]string", mapBase, mapUint, []compare.Path{
root.Type(mapBase, mapUint).Diff(reflect.TypeOf(mapBase), reflect.TypeOf(mapUint)),
}},
}
stringTests = []struct {
diff compare.Path
expect string
}{
{root,
""},
{root.Diff(0, 0),
"«0» != «0»"},
{root.Member("Field", 0, 0).Diff(0, 0),
"«0» != «0» for v.Field"},
{root.Length(0, 0).Diff(0, 0),
"«0» != «0» for v·length"},
{root.Type(0, 0).Diff(0, 0),
"«0» != «0» for v·type"},
{root.Nil(0, 0),
"nil «0» != «0»"},
{root.Index(2, 0, 0).Diff(0, 0),
"«0» != «0» for v[2]"},
{root.Missing(0, 0),
"key «0» != «0»"},
{root.Entry("MapKey", 0, 0).Diff(0, 0),
"«0» != «0» for v[MapKey]"},
{root.Member("Slice", 0, 0).Entry(9, 0, 0).Entry("Key", 0, 0).Type(0, 0).Diff(0, 0),
"«0» != «0» for v.Slice[9][Key]·type"},
{root.Diff(hidden1, compare.Missing),
"«⚠ Hidden compare_test.Hide{private:1}» != «⚠ Missing»"},
}
)
type Struct struct {
Int int
Float float32
}
type OtherStruct struct {
Int int
Float float32
}
type Alias Struct
type Object struct {
Value int
Children []interface{}
}
type Hide struct {
private int
}
type Value int
type Alternate int
type Special int
func compareSpecial(t compare.Comparator, reference, value Special) {
if reference&0x01 != value&0x01 {
t.AddDiff(reference, value)
}
}
type SpecialStruct struct {
NotSoSpecial bool
Value Special
}
func compareSpecialStruct(t compare.Comparator, reference, value SpecialStruct) compare.Action {
if reference.NotSoSpecial {
if reference.Value != value.Value {
t.With(t.Path.Member("Value", reference, value)).AddDiff(reference.Value, value.Value)
}
return compare.Done
}
return compare.Fallback
}
func init() {
compare.Register(compareSpecial)
compare.Register(compareSpecialStruct)
}
func TestDeepEqual(t *testing.T) {
ctx := assert.Context(t)
for _, test := range compareTests {
got := compare.DeepEqual(test.a, test.b)
assert.For(ctx, test.name).That(got).Equals(len(test.diff) == 0)
}
}
func TestDiff(t *testing.T) {
ctx := assert.Context(t)
for _, test := range compareTests {
if len(test.diff) > 0 && len(test.diff[0]) == 0 {
continue
}
got := compare.Diff(test.a, test.b, len(test.diff)+2)
assert.For(ctx, test.name).ThatSlice(got).DeepEquals(test.diff)
}
}
func TestDiffLimit(t *testing.T) {
ctx := assert.Context(t)
for _, test := range compareTests {
got := compare.Diff(test.a, test.b, 1)
expect := test.diff
if len(expect) > 0 {
expect = expect[:1]
if len(expect[0]) == 0 {
continue
}
}
assert.For(ctx, test.name).ThatSlice(got).DeepEquals(expect)
}
}
func TestStrings(t *testing.T) {
ctx := assert.Context(t)
for _, test := range stringTests {
got := fmt.Sprint(test.diff)
assert.With(ctx).That(got).Equals(test.expect)
}
}
func TestPanic(t *testing.T) {
ctx := assert.Context(t)
p := struct{}{}
defer func() {
err := recover()
assert.For(ctx, "Should panic").That(err).Equals(p)
}()
compare.Compare(1, 0, func(compare.Path) {
panic(p)
})
ctx.Error().Log("Expected compare.DeepEqual to panic")
}
func TestRegisterNonFunc(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).That(err).Equals("Invalid function int")
}()
compare.Register(0)
ctx.Error().Log("Expected compare.Register to panic")
}
func TestRegisterWrongArgCount(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).That(err).Equals("Compare functions must have 3 args, got func(int, int)")
}()
compare.Register(func(reference, value int) {})
ctx.Error().Log("Expected compare.Register to panic")
}
func TestRegisterInvalidComparatorArg(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).That(err).Equals("First argument must be compare.Comparator, got int")
}()
compare.Register(func(c, reference, value int) {})
ctx.Error().Log("Expected compare.Register to panic")
}
func TestRegisterInvalidComparatorReturn(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).ThatString(err).Contains("Compare functions must")
}()
compare.Register(func(compare.Comparator, unique, unique) string { return "" })
ctx.Error().Log("Expected compare.Register to panic")
}
func TestRegisterNonSymetric(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).That(err).Equals("Comparison arguments must be of the same type, got int and uint8")
}()
compare.Register(func(c compare.Comparator, reference int, value byte) {})
ctx.Error().Log("Expected compare.Register to panic")
}
type registerTestType int
func TestRegisterAlreadyRegistered(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).That(err).Equals("compare_test.registerTestType to compare_test.registerTestType already registered")
}()
compare.Register(func(c compare.Comparator, reference, value registerTestType) {})
compare.Register(func(c compare.Comparator, reference, value registerTestType) {})
ctx.Error().Log("Expected compare.Register to panic")
}
func TestCustomDiffFallback(t *testing.T) {
ctx := assert.Context(t)
custom := compare.Custom{}
for _, test := range compareTests {
if len(test.diff) > 0 && len(test.diff[0]) == 0 {
continue
}
got := custom.Diff(test.a, test.b, len(test.diff)+2)
assert.For(ctx, test.name).That(got).DeepEquals(test.diff)
}
}
type unique bool
func TestCustomDiff(t *testing.T) {
ctx := assert.Context(t)
custom := compare.Custom{}
got := custom.Diff(unique(false), unique(true), 1)
diff := []compare.Path{root.Diff(unique(false), unique(true))}
assert.For(ctx, "pre-register").ThatSlice(got).DeepEquals(diff)
custom.Register(func(compare.Comparator, unique, unique) {})
got = custom.Diff(unique(false), unique(true), 1)
assert.For(ctx, "pre-register").ThatSlice(got).DeepEquals(noDiff)
}
func TestInvalidCustomAction(t *testing.T) {
ctx := assert.Context(t)
defer func() {
err := recover()
assert.With(ctx).ThatString(err).Contains("Unknown action -1")
}()
custom := compare.Custom{}
custom.Register(func(compare.Comparator, unique, unique) compare.Action { return compare.Action(-1) })
custom.Diff(unique(false), unique(true), 1)
ctx.Error().Log("Expected custom.Diff to panic")
}