Skip to content

Commit 54c1ef4

Browse files
authored
Merge pull request #20 from dmcgowan/match-features
Match and Compare platforms with OSFeatures
2 parents b42036f + 1b8cf34 commit 54c1ef4

File tree

3 files changed

+156
-3
lines changed

3 files changed

+156
-3
lines changed

‎compare.go‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,20 @@ func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool
213213
return true
214214
}
215215
if p1m || p2m {
216+
if p1m && p2m {
217+
// Prefer one with most matching features
218+
if len(p1.OSFeatures) != len(p2.OSFeatures) {
219+
return len(p1.OSFeatures) > len(p2.OSFeatures)
220+
}
221+
}
216222
return false
217223
}
218224
}
225+
if len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0 {
226+
p1.OSFeatures = nil
227+
p2.OSFeatures = nil
228+
return c.Less(p1, p2)
229+
}
219230
return false
220231
}
221232

@@ -242,9 +253,20 @@ func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
242253
p2m = true
243254
}
244255
if p1m && p2m {
245-
return false
256+
if len(p1.OSFeatures) != len(p2.OSFeatures) {
257+
return len(p1.OSFeatures) > len(p2.OSFeatures)
258+
}
259+
break
246260
}
247261
}
262+
263+
// If neither match and has features, strip features and compare
264+
if !p1m && !p2m && (len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0) {
265+
p1.OSFeatures = nil
266+
p2.OSFeatures = nil
267+
return c.Less(p1, p2)
268+
}
269+
248270
// If one matches, and the other does, sort match first
249271
return p1m && !p2m
250272
}

‎compare_test.go‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package platforms
1818

1919
import (
20+
"reflect"
21+
"sort"
2022
"testing"
2123
)
2224

@@ -592,3 +594,94 @@ func TestOnlyStrict(t *testing.T) {
592594
})
593595
}
594596
}
597+
598+
func TestCompareOSFeatures(t *testing.T) {
599+
for _, tc := range []struct {
600+
platform string
601+
platforms []string
602+
expected []string
603+
}{
604+
{
605+
"linux/amd64",
606+
[]string{"windows/amd64", "linux/amd64", "linux(+other)/amd64", "linux/arm64"},
607+
[]string{"linux/amd64", "linux(+other)/amd64", "windows/amd64", "linux/arm64"},
608+
},
609+
{
610+
"linux(+none)/amd64",
611+
[]string{"windows/amd64", "linux/amd64", "linux/arm64", "linux(+other)/amd64"},
612+
[]string{"linux/amd64", "linux(+other)/amd64", "windows/amd64", "linux/arm64"},
613+
},
614+
{
615+
"linux(+other)/amd64",
616+
[]string{"windows/amd64", "linux/amd64", "linux/arm64", "linux(+other)/amd64"},
617+
[]string{"linux(+other)/amd64", "linux/amd64", "windows/amd64", "linux/arm64"},
618+
},
619+
{
620+
"linux(+af+other+zf)/amd64",
621+
[]string{"windows/amd64", "linux/amd64", "linux/arm64", "linux(+other)/amd64"},
622+
[]string{"linux(+other)/amd64", "linux/amd64", "windows/amd64", "linux/arm64"},
623+
},
624+
{
625+
"linux(+f1+f2)/amd64",
626+
[]string{"linux/amd64", "linux(+f2)/amd64", "linux(+f1)/amd64", "linux(+f1+f2)/amd64"},
627+
[]string{"linux(+f1+f2)/amd64", "linux(+f2)/amd64", "linux(+f1)/amd64", "linux/amd64"},
628+
},
629+
{
630+
// This test should likely fail and be updated when os version is considered for linux
631+
"linux(7.2+other)/amd64",
632+
[]string{"linux/amd64", "linux(+other)/amd64", "linux(7.1)/amd64", "linux(7.2+other)/amd64"},
633+
[]string{"linux(+other)/amd64", "linux(7.2+other)/amd64", "linux/amd64", "linux(7.1)/amd64"},
634+
},
635+
} {
636+
testcase := tc
637+
t.Run(testcase.platform, func(t *testing.T) {
638+
t.Parallel()
639+
p, err := Parse(testcase.platform)
640+
if err != nil {
641+
t.Fatal(err)
642+
}
643+
644+
for _, stc := range []struct {
645+
name string
646+
mc MatchComparer
647+
}{
648+
{
649+
name: "only",
650+
mc: Only(p),
651+
},
652+
{
653+
name: "only strict",
654+
mc: OnlyStrict(p),
655+
},
656+
{
657+
name: "ordered",
658+
mc: Ordered(p),
659+
},
660+
{
661+
name: "any",
662+
mc: Any(p),
663+
},
664+
} {
665+
mc := stc.mc
666+
testcase := testcase
667+
t.Run(stc.name, func(t *testing.T) {
668+
p, err := ParseAll(testcase.platforms)
669+
if err != nil {
670+
t.Fatal(err)
671+
}
672+
sort.Slice(p, func(i, j int) bool {
673+
return mc.Less(p[i], p[j])
674+
})
675+
actual := make([]string, len(p))
676+
for i, ps := range p {
677+
actual[i] = FormatAll(ps)
678+
}
679+
680+
if !reflect.DeepEqual(testcase.expected, actual) {
681+
t.Errorf("Wrong platform order:\nExpected: %#v\nActual: %#v", testcase.expected, actual)
682+
}
683+
})
684+
}
685+
})
686+
}
687+
}

‎platforms.go‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ type Matcher interface {
142142
// functionality.
143143
//
144144
// Applications should opt to use `Match` over directly parsing specifiers.
145+
//
146+
// For OSFeatures, this matcher will match if the platform to match has
147+
// OSFeatures which are a subset of the OSFeatures of the platform
148+
// provided to NewMatcher.
145149
func NewMatcher(platform specs.Platform) Matcher {
146150
m := &matcher{
147151
Platform: Normalize(platform),
@@ -177,10 +181,39 @@ type matcher struct {
177181

178182
func (m *matcher) Match(platform specs.Platform) bool {
179183
normalized := Normalize(platform)
180-
return m.OS == normalized.OS &&
184+
if m.OS == normalized.OS &&
181185
m.Architecture == normalized.Architecture &&
182186
m.Variant == normalized.Variant &&
183-
m.matchOSVersion(platform)
187+
m.matchOSVersion(platform) {
188+
if len(normalized.OSFeatures) == 0 {
189+
return true
190+
}
191+
if len(m.OSFeatures) >= len(normalized.OSFeatures) {
192+
// Ensure that normalized.OSFeatures is a subset of
193+
// m.OSFeatures
194+
j := 0
195+
for _, feature := range normalized.OSFeatures {
196+
found := false
197+
for ; j < len(m.OSFeatures); j++ {
198+
if feature == m.OSFeatures[j] {
199+
found = true
200+
j++
201+
break
202+
}
203+
// Since both lists are ordered, if the feature is less
204+
// than what is seen, it is not in the list
205+
if feature < m.OSFeatures[j] {
206+
return false
207+
}
208+
}
209+
if !found {
210+
return false
211+
}
212+
}
213+
return true
214+
}
215+
}
216+
return false
184217
}
185218

186219
func (m *matcher) matchOSVersion(platform specs.Platform) bool {
@@ -350,6 +383,11 @@ func FormatAll(platform specs.Platform) string {
350383
func Normalize(platform specs.Platform) specs.Platform {
351384
platform.OS = normalizeOS(platform.OS)
352385
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
386+
if len(platform.OSFeatures) > 0 {
387+
platform.OSFeatures = slices.Clone(platform.OSFeatures)
388+
slices.Sort(platform.OSFeatures)
389+
platform.OSFeatures = slices.Compact(platform.OSFeatures)
390+
}
353391

354392
return platform
355393
}

0 commit comments

Comments
 (0)