From 66e2eb31b7cbe7bf0aa7ddd87c5c7e5819bb3804 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 5 Jul 2019 13:25:29 -0400 Subject: [PATCH] implement Path.HasPrefix and Path.Equals Paths comparisons done with reflect.DeepEqual may erroneously return false for numeric IndexSteps, due to the differences in the internal values of the big.Float. Add Path.Equals method to ensure all IndexSteps compare correctly. Add Path.HasPrefix to test that one path is a prefix of another. --- cty/path.go | 42 +++++++++++++++ cty/path_test.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/cty/path.go b/cty/path.go index bf1a7c15..b3144495 100644 --- a/cty/path.go +++ b/cty/path.go @@ -71,6 +71,48 @@ func (p Path) GetAttr(name string) Path { return ret } +// Equals compares 2 Paths for exact equality. +func (p Path) Equals(other Path) bool { + if len(p) != len(other) { + return false + } + + for i := range p { + pv := p[i] + switch pv := pv.(type) { + case GetAttrStep: + ov, ok := other[i].(GetAttrStep) + if !ok || pv != ov { + return false + } + case IndexStep: + ov, ok := other[i].(IndexStep) + if !ok { + return false + } + + if !pv.Key.RawEquals(ov.Key) { + return false + } + default: + // Any invalid steps default to evaluating false. + return false + } + } + + return true + +} + +// HasPrefix determines if the path p contains the provided prefix. +func (p Path) HasPrefix(prefix Path) bool { + if len(prefix) > len(p) { + return false + } + + return p[:len(prefix)].Equals(prefix) +} + // GetAttrPath is a convenience method to start a new Path with a GetAttrStep. func GetAttrPath(name string) Path { return Path{}.GetAttr(name) diff --git a/cty/path_test.go b/cty/path_test.go index 9c2472af..4db9cfee 100644 --- a/cty/path_test.go +++ b/cty/path_test.go @@ -123,3 +123,135 @@ func TestPathApply(t *testing.T) { }) } } + +func TestPathEquals(t *testing.T) { + tests := []struct { + A, B cty.Path + Equal bool + Prefix bool + }{ + { + A: nil, + B: nil, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{}, + B: cty.Path{}, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{nil}, + B: cty.Path{cty.GetAttrStep{Name: "attr"}}, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("key")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)})}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.StringVal("known")})}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.StringVal("known")}, + }, + Prefix: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.UnknownVal(cty.String)}, + }, + Prefix: true, + Equal: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberFloatVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + Equal: true, + Prefix: true, + }, + { + A: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(1)}, + cty.GetAttrStep{Name: "attr"}, + }, + B: cty.Path{ + cty.GetAttrStep{Name: "attr"}, + cty.IndexStep{Key: cty.NumberIntVal(0)}, + cty.GetAttrStep{Name: "attr"}, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%d-%#v", i, test.A), func(t *testing.T) { + if test.Equal != test.A.Equals(test.B) { + t.Fatalf("%#v.Equals(%#v) != %t", test.A, test.B, test.Equal) + } + if test.Prefix != test.A.HasPrefix(test.B) { + t.Fatalf("%#v.HasPrefix(%#v) != %t", test.A, test.B, test.Prefix) + } + }) + } +}