Skip to content

Commit fa0e642

Browse files
committed
add entity sort functions
Signed-off-by: perdasilva <[email protected]>
1 parent 64814ac commit fa0e642

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package sort
2+
3+
import (
4+
"strings"
5+
6+
"github.com/operator-framework/deppy/pkg/deppy/input"
7+
8+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
9+
)
10+
11+
// ByChannelAndVersion is an entity sort function that orders the entities in
12+
// package, channel (default channel at the head), and inverse version (higher versions on top)
13+
// if a property does not exist for one of the entities, the one missing the property is pushed down
14+
// if both entities are missing the same property they are ordered by id
15+
func ByChannelAndVersion(entity1 *input.Entity, entity2 *input.Entity) bool {
16+
e1 := entity.NewBundleEntity(entity1)
17+
e2 := entity.NewBundleEntity(entity2)
18+
19+
// first sort package lexical order
20+
pkgOrder := packageOrder(e1, e2)
21+
if pkgOrder != 0 {
22+
return pkgOrder < 0
23+
}
24+
25+
// todo(perdasilva): handle default channel in ordering once it is being exposed by the entity
26+
channelOrder := channelOrder(e1, e2)
27+
if channelOrder != 0 {
28+
return channelOrder < 0
29+
}
30+
31+
// order version from highest to lowest (favor the latest release)
32+
versionOrder := versionOrder(e1, e2)
33+
return versionOrder > 0
34+
}
35+
36+
func compareErrors(err1 error, err2 error) int {
37+
if err1 != nil && err2 == nil {
38+
return 1
39+
}
40+
41+
if err1 == nil && err2 != nil {
42+
return -1
43+
}
44+
return 0
45+
}
46+
47+
func packageOrder(e1, e2 *entity.BundleEntity) int {
48+
name1, err1 := e1.PackageName()
49+
name2, err2 := e2.PackageName()
50+
errComp := compareErrors(err1, err2)
51+
if errComp != 0 {
52+
return errComp
53+
}
54+
return strings.Compare(name1, name2)
55+
}
56+
57+
func channelOrder(e1, e2 *entity.BundleEntity) int {
58+
channelProperties1, err1 := e1.ChannelProperties()
59+
channelProperties2, err2 := e2.ChannelProperties()
60+
errComp := compareErrors(err1, err2)
61+
if errComp != 0 {
62+
return errComp
63+
}
64+
if channelProperties1.Priority != channelProperties2.Priority {
65+
return channelProperties1.Priority - channelProperties2.Priority
66+
}
67+
return strings.Compare(channelProperties1.ChannelName, channelProperties2.ChannelName)
68+
}
69+
70+
func versionOrder(e1, e2 *entity.BundleEntity) int {
71+
ver1, err1 := e1.Version()
72+
ver2, err2 := e2.Version()
73+
errComp := compareErrors(err1, err2)
74+
if errComp != 0 {
75+
// the sign gets inverted because version is sorted
76+
// from highest to lowest
77+
return -1 * errComp
78+
}
79+
return ver1.Compare(*ver2)
80+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package sort_test
2+
3+
import (
4+
"sort"
5+
"testing"
6+
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
"github.com/operator-framework/deppy/pkg/deppy/input"
10+
"github.com/operator-framework/operator-registry/alpha/property"
11+
12+
sort2 "github.com/operator-framework/operator-controller/internal/resolution/variable_sources/utils/sort"
13+
)
14+
15+
func TestSort(t *testing.T) {
16+
RegisterFailHandler(Fail)
17+
RunSpecs(t, "Sort Suite")
18+
}
19+
20+
var _ = Describe("Sort", func() {
21+
Describe("ByChannelAndVersion", func() {
22+
It("should order entities by package name", func() {
23+
e1 := input.NewEntity("test1", map[string]string{
24+
property.TypePackage: `{"packageName": "package1", "version": "1.0.0"}`,
25+
})
26+
e2 := input.NewEntity("test2", map[string]string{
27+
property.TypePackage: `{"packageName": "package2", "version": "1.0.0"}`,
28+
})
29+
e3 := input.NewEntity("test3", map[string]string{
30+
property.TypePackage: `{"packageName": "package3", "version": "1.0.0"}`,
31+
})
32+
entities := []*input.Entity{e2, e3, e1}
33+
34+
sort.Slice(entities, func(i, j int) bool {
35+
return sort2.ByChannelAndVersion(entities[i], entities[j])
36+
})
37+
38+
Expect(entities[0]).To(Equal(e1))
39+
Expect(entities[1]).To(Equal(e2))
40+
Expect(entities[2]).To(Equal(e3))
41+
})
42+
43+
It("should order entities by channel name", func() {
44+
e1 := input.NewEntity("test1", map[string]string{
45+
property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`,
46+
property.TypeChannel: `{"channelName":"stableA","priority":0}`,
47+
})
48+
e2 := input.NewEntity("test2", map[string]string{
49+
property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`,
50+
property.TypeChannel: `{"channelName":"stableB","priority":0}`,
51+
})
52+
e3 := input.NewEntity("test3", map[string]string{
53+
property.TypePackage: `{"packageName": "package", "version": "1.0.0"}`,
54+
property.TypeChannel: `{"channelName":"stableC","priority":0}`,
55+
})
56+
entities := []*input.Entity{e2, e3, e1}
57+
58+
sort.Slice(entities, func(i, j int) bool {
59+
return sort2.ByChannelAndVersion(entities[i], entities[j])
60+
})
61+
62+
Expect(entities[0]).To(Equal(e1))
63+
Expect(entities[1]).To(Equal(e2))
64+
Expect(entities[2]).To(Equal(e3))
65+
})
66+
67+
It("should order entities by version number (highest first)", func() {
68+
e1 := input.NewEntity("test1", map[string]string{
69+
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
70+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
71+
})
72+
e2 := input.NewEntity("test2", map[string]string{
73+
property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`,
74+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
75+
})
76+
e3 := input.NewEntity("test3", map[string]string{
77+
property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`,
78+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
79+
})
80+
entities := []*input.Entity{e2, e3, e1}
81+
82+
sort.Slice(entities, func(i, j int) bool {
83+
return sort2.ByChannelAndVersion(entities[i], entities[j])
84+
})
85+
86+
Expect(entities[0]).To(Equal(e3))
87+
Expect(entities[1]).To(Equal(e2))
88+
Expect(entities[2]).To(Equal(e1))
89+
})
90+
91+
It("should order entities by version number (highest first) and channel priority (lower value -> higher priority)", func() {
92+
e1 := input.NewEntity("test1", map[string]string{
93+
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
94+
property.TypeChannel: `{"channelName":"beta","priority":1}`,
95+
})
96+
e2 := input.NewEntity("test2", map[string]string{
97+
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
98+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
99+
})
100+
e3 := input.NewEntity("test3", map[string]string{
101+
property.TypePackage: `{"packageName": "mypackage", "version": "2.0.0"}`,
102+
property.TypeChannel: `{"channelName":"beta","priority":1}`,
103+
})
104+
e4 := input.NewEntity("test4", map[string]string{
105+
property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`,
106+
property.TypeChannel: `{"channelName":"beta","priority":1}`,
107+
})
108+
entities := []*input.Entity{e2, e3, e1, e4}
109+
110+
sort.Slice(entities, func(i, j int) bool {
111+
return sort2.ByChannelAndVersion(entities[i], entities[j])
112+
})
113+
114+
Expect(entities[0]).To(Equal(e2))
115+
Expect(entities[1]).To(Equal(e4))
116+
Expect(entities[2]).To(Equal(e3))
117+
Expect(entities[3]).To(Equal(e1))
118+
})
119+
120+
It("should order entities missing a property after those that have it", func() {
121+
e1 := input.NewEntity("test1", map[string]string{
122+
property.TypePackage: `{"packageName": "mypackage", "version": "1.0.0"}`,
123+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
124+
})
125+
e2 := input.NewEntity("test2", map[string]string{
126+
property.TypePackage: `{"packageName": "mypackage"}`,
127+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
128+
})
129+
e3 := input.NewEntity("test3", map[string]string{
130+
property.TypePackage: `{"packageName": "mypackage", "version": "3.0.0"}`,
131+
property.TypeChannel: `{"channelName":"stable","priority":0}`,
132+
})
133+
e4 := input.NewEntity("test4", map[string]string{})
134+
entities := []*input.Entity{e2, e3, e1, e4}
135+
136+
sort.Slice(entities, func(i, j int) bool {
137+
return sort2.ByChannelAndVersion(entities[i], entities[j])
138+
})
139+
140+
Expect(entities[0]).To(Equal(e3))
141+
Expect(entities[1]).To(Equal(e1))
142+
Expect(entities[2]).To(Equal(e2))
143+
Expect(entities[3]).To(Equal(e4))
144+
})
145+
})
146+
147+
})

0 commit comments

Comments
 (0)