Skip to content

Commit

Permalink
Add AcyclicTransformer helper
Browse files Browse the repository at this point in the history
Add AcyclicTransformer helper, which wraps Transformer with a FilterPath
that ensures that the transformer cannot be recursively applied upon
the output of itself. Adjust documentation of Transformer to suggest usage
of AcyclicTransformer if necessary.

Fixes #77
  • Loading branch information
dsnet committed Mar 26, 2018
1 parent 0627e44 commit aad4e8f
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 0 deletions.
33 changes: 33 additions & 0 deletions cmp/cmpopts/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,33 @@ func TestOptions(t *testing.T) {
},
wantEqual: true,
reason: "equal because all Ignore options can be composed together",
}, {
label: "AcyclicTransformer",
x: "a\nb\nc\nd",
y: "a\nb\nd\nd",
opts: []cmp.Option{
AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
},
wantEqual: false,
reason: "not equal because 3rd line differs, but should not recurse infinitely",
}, {
label: "AcyclicTransformer",
x: []string{"foo", "Bar", "BAZ"},
y: []string{"Foo", "BAR", "baz"},
opts: []cmp.Option{
AcyclicTransformer("", func(s string) string { return strings.ToUpper(s) }),
},
wantEqual: true,
reason: "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
}, {
label: "AcyclicTransformer",
x: "this is a sentence",
y: "this is a sentence",
opts: []cmp.Option{
AcyclicTransformer("", func(s string) []string { return strings.Fields(s) }),
},
wantEqual: true,
reason: "equal because acyclic transformer splits on any contiguous whitespace",
}}

for _, tt := range tests {
Expand Down Expand Up @@ -938,6 +965,12 @@ func TestPanic(t *testing.T) {
fnc: IgnoreUnexported,
args: args(Foo1{}, struct{ x, X int }{}),
reason: "input may be named or unnamed structs",
}, {
label: "AcyclicTransformer",
fnc: AcyclicTransformer,
args: args("", "not a func"),
wantPanic: "invalid transformer function",
reason: "AcyclicTransformer has same input requirements as Transformer",
}}

for _, tt := range tests {
Expand Down
35 changes: 35 additions & 0 deletions cmp/cmpopts/xform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package cmpopts

import (
"github.com/google/go-cmp/cmp"
)

type xformFilter struct{ xform cmp.Option }

func (xf xformFilter) filter(p cmp.Path) bool {
for _, ps := range p {
if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform {
return false
}
}
return true
}

// AcyclicTransformer returns a Transformer with a filter applied that ensures
// that the transformer cannot be recursively applied upon its own output.
//
// An example use case is a transformer that splits a string by lines:
// AcyclicTransformer("SplitLines", func(s string) []string{
// return strings.Split(s, "\n")
// })
//
// Had this been an unfiltered Transformer instead, this would result in an
// infinite cycle converting a string to []string to [][]string and so on.
func AcyclicTransformer(name string, f interface{}) cmp.Option {
xf := xformFilter{cmp.Transformer(name, f)}
return cmp.FilterPath(xf.filter, xf.xform)
}
3 changes: 3 additions & 0 deletions cmp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ func (invalid) apply(s *state, _, _ reflect.Value) {
// input and output types are the same), an implicit filter is added such that
// a transformer is applicable only if that exact transformer is not already
// in the tail of the Path since the last non-Transform step.
// For situations where the implicit filter is still insufficient,
// consider using cmpopts.AcyclicTransformer, which adds a filter
// to prevent the transformer from being recursively applied upon itself.
//
// The name is a user provided label that is used as the Transform.Name in the
// transformation PathStep. If empty, an arbitrary name is used.
Expand Down

0 comments on commit aad4e8f

Please sign in to comment.