core: ResourceAddress.Less for sorting resource addresses

Lexicographic sorting by the string form produces the wrong result because
[9] sorts after [10], so this custom comparison function takes that into
account and compares each portion separately to get a more intuitive
result.
This commit is contained in:
Martin Atkins 2017-06-21 17:43:56 -07:00
parent 2051b286e0
commit 482c1f1ea5
2 changed files with 137 additions and 0 deletions

View File

@ -329,6 +329,58 @@ func (addr *ResourceAddress) Equals(raw interface{}) bool {
modeMatch modeMatch
} }
// Less returns true if and only if the receiver should be sorted before
// the given address when presenting a list of resource addresses to
// an end-user.
//
// This sort uses lexicographic sorting for most components, but uses
// numeric sort for indices, thus causing index 10 to sort after
// index 9, rather than after index 1.
func (addr *ResourceAddress) Less(other *ResourceAddress) bool {
switch {
case len(addr.Path) < len(other.Path):
return true
case !reflect.DeepEqual(addr.Path, other.Path):
// If the two paths are the same length but don't match, we'll just
// cheat and compare the string forms since it's easier than
// comparing all of the path segments in turn.
addrStr := addr.String()
otherStr := other.String()
return addrStr < otherStr
case addr.Mode == config.DataResourceMode && other.Mode != config.DataResourceMode:
return true
case addr.Type < other.Type:
return true
case addr.Name < other.Name:
return true
case addr.Index < other.Index:
// Since "Index" is -1 for an un-indexed address, this also conveniently
// sorts unindexed addresses before indexed ones, should they both
// appear for some reason.
return true
case other.InstanceTypeSet && !addr.InstanceTypeSet:
return true
case addr.InstanceType < other.InstanceType:
// InstanceType is actually an enum, so this is just an arbitrary
// sort based on the enum numeric values, and thus not particularly
// meaningful.
return true
default:
return false
}
}
func ParseResourceIndex(s string) (int, error) { func ParseResourceIndex(s string) (int, error) {
if s == "" { if s == "" {
return -1, nil return -1, nil

View File

@ -1221,3 +1221,88 @@ func TestResourceAddressMatchesConfig(t *testing.T) {
}) })
} }
} }
func TestResourceAddressLess(t *testing.T) {
tests := []struct {
A string
B string
Want bool
}{
{
"foo.bar",
"module.baz.foo.bar",
true,
},
{
"module.baz.foo.bar",
"module.baz.foo.bar",
false,
},
{
"module.baz.foo.bar",
"module.boz.foo.bar",
true,
},
{
"a.b",
"b.c",
true,
},
{
"a.b",
"a.c",
true,
},
{
"a.b[9]",
"a.b[10]",
true,
},
{
"a.b",
"a.b.deposed",
true,
},
{
"a.b.tainted",
"a.b.deposed",
true,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) {
addrA, err := ParseResourceAddress(test.A)
if err != nil {
t.Fatal(err)
}
addrB, err := ParseResourceAddress(test.B)
if err != nil {
t.Fatal(err)
}
got := addrA.Less(addrB)
invGot := addrB.Less(addrA)
if got != test.Want {
t.Errorf(
"wrong result\ntest: %s < %s\ngot: %#v\nwant: %#v",
test.A, test.B, got, test.Want,
)
}
if test.A != test.B { // inverse test doesn't apply when equal
if invGot != !test.Want {
t.Errorf(
"wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v",
test.B, test.A, invGot, !test.Want,
)
}
} else {
if invGot != test.Want {
t.Errorf(
"wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v",
test.B, test.A, invGot, test.Want,
)
}
}
})
}
}