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:
parent
2051b286e0
commit
482c1f1ea5
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue