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  }