Skip to content

Commit

Permalink
implement 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 a Path.Equals method to ensure all IndexSteps compare correctly.

This also allows us to implement the unknown IndexStep functionality,
which is documented that an unknown value can match any value of the
same type.
  • Loading branch information
jbardin committed Jul 5, 2019
1 parent 6fd39ad commit cacb556
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
44 changes: 44 additions & 0 deletions cty/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,50 @@ func (p Path) GetAttr(name string) Path {
return ret
}

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.Type().Equals(ov.Key.Type()) {
return false
}

// if a path element is unknown, it can match any element of the
// same type
if !pv.Key.IsKnown() || !ov.Key.IsKnown() {
continue
}

eq := pv.Key.Equals(ov.Key)

// even if the value is known, it may not be wholly known, and in
// that case it does not serve as a wildcard.
if !eq.IsKnown() || !eq.True() {
return false
}
default:
// Any invalid steps default to evaluating false.
return false
}
}
return true
}

// GetAttrPath is a convenience method to start a new Path with a GetAttrStep.
func GetAttrPath(name string) Path {
return Path{}.GetAttr(name)
Expand Down
97 changes: 97 additions & 0 deletions cty/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,100 @@ func TestPathApply(t *testing.T) {
})
}
}

func TestPathEqual(t *testing.T) {
tests := []struct {
A, B cty.Path
Equal bool
}{
{
A: nil,
B: nil,
Equal: true,
},
{
A: cty.Path{},
B: cty.Path{},
Equal: true,
},
{
A: cty.Path{nil},
B: cty.Path{cty.GetAttrStep{Name: "attr"}},
Equal: false,
},
{
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"},
},
Equal: true,
},
{
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"},
},
Equal: true,
},
{
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.IndexStep{Key: cty.StringVal("key")},
cty.GetAttrStep{Name: "attr"},
},
Equal: false,
},
{
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"},
},
Equal: false,
},
}

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 == %#v) == %t", test.A, test.B, test.Equal)
}
})
}
}

0 comments on commit cacb556

Please sign in to comment.