Skip to content

Commit 09756f5

Browse files
authored
Merge pull request #33 from dmcgowan/only-os
2 parents 3a284c1 + 27058a1 commit 09756f5

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

compare.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,88 @@ func Only(platform specs.Platform) MatchComparer {
152152
return Ordered(platformVector(Normalize(platform))...)
153153
}
154154

155+
// OnlyOS returns a match comparer that matches only platforms with the same
156+
// OS, OS version, and OS features, regardless of architecture. When comparing,
157+
// it always ranks the best architecture match highest using the default
158+
// platform resolution logic.
159+
func OnlyOS(platform specs.Platform) MatchComparer {
160+
normalized := Normalize(platform)
161+
return onlyOSComparer{
162+
platform: normalized,
163+
osvM: newOSVersionMatcher(normalized),
164+
archOrder: orderedPlatformComparer{
165+
matchers: []Matcher{NewMatcher(normalized)},
166+
},
167+
}
168+
}
169+
170+
func newOSVersionMatcher(platform specs.Platform) osVerMatcher {
171+
if platform.OS == "windows" {
172+
return &windowsVersionMatcher{
173+
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
174+
}
175+
}
176+
return nil
177+
}
178+
179+
type onlyOSComparer struct {
180+
platform specs.Platform
181+
osvM osVerMatcher
182+
archOrder orderedPlatformComparer
183+
}
184+
185+
func (c onlyOSComparer) matchOS(platform specs.Platform) bool {
186+
normalized := Normalize(platform)
187+
if c.platform.OS != normalized.OS {
188+
return false
189+
}
190+
if c.osvM != nil {
191+
if !c.osvM.Match(platform.OSVersion) {
192+
return false
193+
}
194+
}
195+
if len(normalized.OSFeatures) > 0 {
196+
if len(c.platform.OSFeatures) < len(normalized.OSFeatures) {
197+
return false
198+
}
199+
j := 0
200+
for _, feature := range normalized.OSFeatures {
201+
found := false
202+
for ; j < len(c.platform.OSFeatures); j++ {
203+
if feature == c.platform.OSFeatures[j] {
204+
found = true
205+
j++
206+
break
207+
}
208+
if feature < c.platform.OSFeatures[j] {
209+
return false
210+
}
211+
}
212+
if !found {
213+
return false
214+
}
215+
}
216+
}
217+
return true
218+
}
219+
220+
func (c onlyOSComparer) Match(platform specs.Platform) bool {
221+
return c.matchOS(platform)
222+
}
223+
224+
func (c onlyOSComparer) Less(p1, p2 specs.Platform) bool {
225+
p1m := c.matchOS(p1)
226+
p2m := c.matchOS(p2)
227+
if p1m && !p2m {
228+
return true
229+
}
230+
if !p1m {
231+
return false
232+
}
233+
// Both match — rank by architecture preference
234+
return c.archOrder.Less(p1, p2)
235+
}
236+
155237
// OnlyStrict returns a match comparer for a single platform.
156238
//
157239
// Unlike Only, OnlyStrict does not match sub platforms.

compare_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,126 @@ func TestOnlyStrict(t *testing.T) {
595595
}
596596
}
597597

598+
func TestOnlyOS(t *testing.T) {
599+
for _, tc := range []struct {
600+
platform string
601+
matches map[bool][]string
602+
}{
603+
{
604+
platform: "linux/amd64",
605+
matches: map[bool][]string{
606+
true: {
607+
"linux/amd64",
608+
"linux/arm64",
609+
"linux/arm/v7",
610+
"linux/386",
611+
},
612+
false: {
613+
"windows/amd64",
614+
"darwin/arm64",
615+
},
616+
},
617+
},
618+
{
619+
platform: "windows(10.0.17763)/amd64",
620+
matches: map[bool][]string{
621+
true: {
622+
"windows/amd64",
623+
"windows/arm64",
624+
"windows(10.0.17763)/amd64",
625+
"windows(10.0.17763)/arm64",
626+
},
627+
false: {
628+
"linux/amd64",
629+
},
630+
},
631+
},
632+
{
633+
platform: "linux(+gpu)/amd64",
634+
matches: map[bool][]string{
635+
true: {
636+
"linux/arm64",
637+
"linux(+gpu)/amd64",
638+
},
639+
false: {
640+
"windows/amd64",
641+
"linux(+gpu+simd)/amd64",
642+
},
643+
},
644+
},
645+
} {
646+
testcase := tc
647+
t.Run(testcase.platform, func(t *testing.T) {
648+
p, err := Parse(testcase.platform)
649+
if err != nil {
650+
t.Fatal(err)
651+
}
652+
m := OnlyOS(p)
653+
for shouldMatch, platforms := range testcase.matches {
654+
for _, matchPlatform := range platforms {
655+
mp, err := Parse(matchPlatform)
656+
if err != nil {
657+
t.Fatal(err)
658+
}
659+
if match := m.Match(mp); shouldMatch != match {
660+
t.Errorf("OnlyOS(%q).Match(%q) should return %v, but returns %v", testcase.platform, matchPlatform, shouldMatch, match)
661+
}
662+
}
663+
}
664+
})
665+
}
666+
}
667+
668+
func TestOnlyOSLess(t *testing.T) {
669+
for _, tc := range []struct {
670+
platform string
671+
platforms []string
672+
expected []string
673+
}{
674+
{
675+
// Exact architecture match ranks first, others are unordered but before non-OS matches
676+
platform: "linux/amd64",
677+
platforms: []string{"linux/arm64", "linux/386", "linux/amd64", "windows/amd64"},
678+
expected: []string{"linux/amd64", "linux/arm64", "linux/386", "windows/amd64"},
679+
},
680+
{
681+
// Strict: only exact arch/variant match is preferred
682+
platform: "linux/arm64",
683+
platforms: []string{"linux/amd64", "linux/arm/v7", "linux/arm64", "windows/arm64"},
684+
expected: []string{"linux/arm64", "linux/amd64", "linux/arm/v7", "windows/arm64"},
685+
},
686+
{
687+
// Non-matching OS should always sort last
688+
platform: "linux/amd64",
689+
platforms: []string{"windows/amd64", "darwin/amd64", "linux/arm64", "linux/amd64"},
690+
expected: []string{"linux/amd64", "linux/arm64", "windows/amd64", "darwin/amd64"},
691+
},
692+
} {
693+
testcase := tc
694+
t.Run(testcase.platform, func(t *testing.T) {
695+
p, err := Parse(testcase.platform)
696+
if err != nil {
697+
t.Fatal(err)
698+
}
699+
mc := OnlyOS(p)
700+
platforms, err := ParseAll(testcase.platforms)
701+
if err != nil {
702+
t.Fatal(err)
703+
}
704+
sort.Slice(platforms, func(i, j int) bool {
705+
return mc.Less(platforms[i], platforms[j])
706+
})
707+
actual := make([]string, len(platforms))
708+
for i, ps := range platforms {
709+
actual[i] = FormatAll(ps)
710+
}
711+
if !reflect.DeepEqual(testcase.expected, actual) {
712+
t.Errorf("Wrong platform order:\nExpected: %#v\nActual: %#v", testcase.expected, actual)
713+
}
714+
})
715+
}
716+
}
717+
598718
func TestCompareOSFeatures(t *testing.T) {
599719
for _, tc := range []struct {
600720
platform string

0 commit comments

Comments
 (0)