github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/shared/product_spec.go (about) 1 // Copyright 2018 The WPT Dashboard Project. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package shared 6 7 import ( 8 "encoding/json" 9 "sort" 10 "strings" 11 12 mapset "github.com/deckarep/golang-set" 13 ) 14 15 // ProductSpec is a struct representing a parsed product spec string. 16 type ProductSpec struct { 17 ProductAtRevision 18 19 Labels mapset.Set 20 } 21 22 // Matches returns whether the ProductSpec matches the given run. 23 func (p ProductSpec) Matches(run TestRun) bool { 24 runLabels := run.LabelsSet() 25 return p.MatchesLabels(runLabels) && p.MatchesProductAtRevision(run.ProductAtRevision) 26 } 27 28 // MatchesProductSpec returns whether the ProductSpec matches the given ProductSpec. 29 func (p ProductSpec) MatchesProductSpec(productSpec ProductSpec) bool { 30 labels := productSpec.Labels 31 productAtRevision := productSpec.ProductAtRevision 32 return p.MatchesLabels(labels) && p.MatchesProductAtRevision(productAtRevision) 33 } 34 35 // MatchesLabels returns whether the ProductSpec's labels matches the given labels. 36 func (p ProductSpec) MatchesLabels(labels mapset.Set) bool { 37 if p.Labels != nil && p.Labels.Cardinality() > 0 { 38 if labels == nil || !p.Labels.IsSubset(labels) { 39 return false 40 } 41 } 42 return true 43 } 44 45 // MatchesProductAtRevision returns whether the spec matches the given ProductAtRevision. 46 func (p ProductSpec) MatchesProductAtRevision(productAtRevision ProductAtRevision) bool { 47 if productAtRevision.BrowserName != p.BrowserName { 48 return false 49 } 50 if !IsLatest(p.Revision) && 51 p.Revision != productAtRevision.Revision && 52 !strings.HasPrefix(productAtRevision.FullRevisionHash, p.Revision) { 53 return false 54 } 55 if p.BrowserVersion != "" { 56 // Make "6" not match "60.123" by adding trailing dots to both. 57 if !strings.HasPrefix(productAtRevision.BrowserVersion+".", p.BrowserVersion+".") { 58 return false 59 } 60 } 61 return true 62 } 63 64 // IsExperimental returns true if the product spec is restricted to experimental 65 // runs (i.e. has the label "experimental"). 66 func (p ProductSpec) IsExperimental() bool { 67 return p.Labels != nil && p.Labels.Contains(ExperimentalLabel) 68 } 69 70 // DisplayName returns a capitalized version of the product's name. 71 func (p ProductSpec) DisplayName() string { 72 switch p.BrowserName { 73 case "chrome": 74 return "Chrome" 75 case "chromium": 76 return "Chromium" 77 case "chrome_android": 78 return "ChromeAndroid" 79 case "chrome_ios": 80 return "ChromeIOS" 81 case "android_webview": 82 return "WebView" 83 case "deno": 84 return "Deno" 85 case "edge": 86 return "Edge" 87 case "firefox": 88 return "Firefox" 89 case "firefox_android": 90 return "Firefox Android" 91 case "flow": 92 return "Flow" 93 case "node.js": 94 return "Node.js" 95 case "safari": 96 return "Safari" 97 case "servo": 98 return "Servo" 99 case "wktr": 100 return "macOS WebKit" 101 case "webkitgtk": 102 return "WebKitGTK" 103 default: 104 return p.BrowserName 105 } 106 } 107 108 // ProductSpecs is a helper type for a slice of ProductSpec structs. 109 type ProductSpecs []ProductSpec 110 111 // Products gets the slice of products specified in the ProductSpecs slice. 112 func (p ProductSpecs) Products() []Product { 113 result := make([]Product, len(p)) 114 for i, spec := range p { 115 result[i] = spec.Product 116 } 117 return result 118 } 119 120 // OrDefault returns the current product specs, or the default if the set is empty. 121 func (p ProductSpecs) OrDefault() ProductSpecs { 122 if len(p) < 1 { 123 return GetDefaultProducts() 124 } 125 return p 126 } 127 128 // Strings returns the array of the ProductSpec items as their string 129 // representations. 130 func (p ProductSpecs) Strings() []string { 131 result := make([]string, len(p)) 132 for i, spec := range p { 133 result[i] = spec.String() 134 } 135 return result 136 } 137 138 func (p ProductSpec) String() string { 139 s := p.Product.String() 140 if p.Labels != nil { 141 p.Labels.Remove("") // Remove the empty label, if present. 142 if p.Labels.Cardinality() > 0 { 143 labels := make([]string, 0, p.Labels.Cardinality()) 144 for l := range p.Labels.Iter() { 145 labels = append(labels, l.(string)) 146 } 147 sort.Strings(labels) // Deterministic String() output. 148 s += "[" + strings.Join(labels, ",") + "]" 149 } 150 } 151 if !IsLatest(p.Revision) { 152 s += "@" + p.Revision 153 } 154 return s 155 } 156 157 func (p ProductSpecs) Len() int { return len(p) } 158 func (p ProductSpecs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 159 func (p ProductSpecs) Less(i, j int) bool { return p[i].String() < p[j].String() } 160 161 // MarshalJSON treats the set as an array so it can be marshalled. 162 func (p ProductSpec) MarshalJSON() ([]byte, error) { 163 return json.Marshal(p.String()) 164 } 165 166 // UnmarshalJSON parses an array so that ProductSpec can be unmarshalled. 167 func (p *ProductSpec) UnmarshalJSON(data []byte) (err error) { 168 var s string 169 if err := json.Unmarshal(data, &s); err != nil { 170 return err 171 } 172 *p, err = ParseProductSpec(s) 173 return err 174 } 175 176 // UnmarshalYAML parses an array so that ProductSpec can be unmarshalled. 177 func (p *ProductSpec) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { 178 var s string 179 if err := unmarshal(&s); err != nil { 180 return err 181 } 182 *p, err = ParseProductSpec(s) 183 return err 184 } 185 186 // MarshalYAML serializes a ProductSpec into a YAML string. 187 func (p ProductSpec) MarshalYAML() (interface{}, error) { 188 return p.String(), nil 189 }