diff --git a/cmp/compare.go b/cmp/compare.go index 7278609..eb84d01 100644 --- a/cmp/compare.go +++ b/cmp/compare.go @@ -35,7 +35,7 @@ import ( "github.com/google/go-cmp/cmp/internal/value" ) -// BUG: Maps with keys containing NaN values cannot be properly compared due to +// BUG(dsnet): Maps with keys containing NaN values cannot be properly compared due to // the reflection package's inability to retrieve such entries. Equal will panic // anytime it comes across a NaN key, but this behavior may change. // diff --git a/cmp/compare_test.go b/cmp/compare_test.go index 1fb6955..c26ac3f 100644 --- a/cmp/compare_test.go +++ b/cmp/compare_test.go @@ -7,6 +7,7 @@ package cmp_test import ( "bytes" "crypto/md5" + "encoding/json" "fmt" "io" "math" @@ -408,7 +409,9 @@ root: } func transformerTests() []test { - const label = "Transformer/" + type JSON string + + const label = "Transformer" return []test{{ label: label, @@ -469,6 +472,57 @@ func transformerTests() []test { λ({int}): -: "string" +: 1`, + }, { + label: label, + x: JSON(`{ + "firstName": "John", + "lastName": "Smith", + "age": 25, + "isAlive": true, + "address": { + "city": "Los Angeles", + "postalCode": "10021-3100", + "state": "CA", + "streetAddress": "21 2nd Street" + }, + "phoneNumbers": [{ + "type": "home", + "number": "212 555-4321" + },{ + "type": "office", + "number": "646 555-4567" + },{ + "number": "123 456-7890", + "type": "mobile" + }], + "children": [] + }`), + y: JSON(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":25, + "address":{"streetAddress":"21 2nd Street","city":"New York", + "state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home", + "number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{ + "type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`), + opts: []cmp.Option{ + cmp.Transformer("ParseJSON", func(s JSON) (m map[string]interface{}) { + if err := json.Unmarshal([]byte(s), &m); err != nil { + panic(err) + } + return m + }), + }, + wantDiff: ` +ParseJSON({cmp_test.JSON})["address"]["city"]: + -: "Los Angeles" + +: "New York" +ParseJSON({cmp_test.JSON})["address"]["state"]: + -: "CA" + +: "NY" +ParseJSON({cmp_test.JSON})["phoneNumbers"][0]["number"]: + -: "212 555-4321" + +: "212 555-1234" +ParseJSON({cmp_test.JSON})["spouse"]: + -: + +: interface {}(nil)`, }} } diff --git a/cmp/internal/value/format.go b/cmp/internal/value/format.go index abaeca8..9b27152 100644 --- a/cmp/internal/value/format.go +++ b/cmp/internal/value/format.go @@ -13,10 +13,6 @@ import ( "unicode/utf8" ) -// formatFakePointers controls whether to substitute pointer addresses with nil. -// This is used for deterministic testing. -var formatFakePointers = false - var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() // Format formats the value v as a string. @@ -27,7 +23,7 @@ var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() // * Prints a nil-slice as being nil, not empty // * Prints map entries in deterministic order func Format(v reflect.Value, useStringer bool) string { - return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, nil) + return formatAny(v, formatConfig{useStringer, true, true, true}, nil) } type formatConfig struct { diff --git a/cmp/internal/value/format_test.go b/cmp/internal/value/format_test.go index 6498854..b56380f 100644 --- a/cmp/internal/value/format_test.go +++ b/cmp/internal/value/format_test.go @@ -80,10 +80,12 @@ func TestFormat(t *testing.T) { want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}", }} - formatFakePointers = true - defer func() { formatFakePointers = false }() for i, tt := range tests { - got := Format(reflect.ValueOf(tt.in), true) + // Intentionally retrieve the value through an unexported field to + // ensure the format logic does not depend on read-write access + // to the reflect.Value. + v := reflect.ValueOf(struct{ x interface{} }{tt.in}).Field(0) + got := formatAny(v, formatConfig{useStringer: true, printType: true, followPointers: true}, nil) if got != tt.want { t.Errorf("test %d, Format():\ngot %q\nwant %q", i, got, tt.want) } diff --git a/cmp/path.go b/cmp/path.go index 0c2eb33..76d73f7 100644 --- a/cmp/path.go +++ b/cmp/path.go @@ -150,13 +150,12 @@ func (pa Path) GoString() string { ssPost = append(ssPost, ")") continue case *typeAssertion: - // Elide type assertions immediately following a transform to - // prevent overly verbose path printouts. - // Some transforms return interface{} because of Go's lack of - // generics, but typically take in and return the exact same - // concrete type. Other times, the transform creates an anonymous - // struct, which will be very verbose to print. - if _, ok := nextStep.(*transform); ok { + // As a special-case, elide type assertions on anonymous types + // since they are typically generated dynamically and can be very + // verbose. For example, some transforms return interface{} because + // of Go's lack of generics, but typically take in and return the + // exact same concrete type. + if s.Type().PkgPath() == "" { continue } }