Skip to content

Commit

Permalink
implement Path.HasPrefix and Path.Equals
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jbardin committed Jul 8, 2019
1 parent 6fd39ad commit 2c62c22
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
42 changes: 42 additions & 0 deletions cty/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
131 changes: 131 additions & 0 deletions cty/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,134 @@ 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,
},
{
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)", test.A, test.B)
}
})
}
}

0 comments on commit 2c62c22

Please sign in to comment.