github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/shared/metadata.go (about) 1 // Copyright 2019 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 "strings" 9 10 "gopkg.in/yaml.v3" 11 ) 12 13 // ShowMetadataParam determines whether Metadata Information returns along 14 // with a test result query request. 15 const ShowMetadataParam = "metadataInfo" 16 17 // MetadataFileName is the name of Metadata files in the wpt-metadata repo. 18 const MetadataFileName = "META.yml" 19 20 // MetadataResults is a map from test paths to all of the links under that test path. 21 // It represents a flattened copy of the wpt-metadata repository, which has metadata 22 // sharded across as large number of files in a directory structure. 23 type MetadataResults map[string]MetadataLinks 24 25 // Metadata represents a wpt-metadata META.yml file. 26 type Metadata struct { 27 Links MetadataLinks `yaml:"links"` 28 } 29 30 // MetadataLinks is a helper type for a MetadataLink slice. 31 type MetadataLinks []MetadataLink 32 33 // MetadataLink is an item in the `links` node of a wpt-metadata 34 // META.yml file, which lists an external reference, optionally 35 // filtered by product and a specific test. 36 type MetadataLink struct { 37 Product ProductSpec `yaml:"product,omitempty" json:"product,omitempty"` 38 URL string `yaml:"url,omitempty" json:"url"` 39 Label string `yaml:"label,omitempty" json:"label,omitempty"` 40 Results []MetadataTestResult `yaml:"results" json:"results,omitempty"` 41 } 42 43 // MetadataTestResult is a filter for test results to which the Metadata link 44 // should apply. 45 type MetadataTestResult struct { 46 TestPath string `yaml:"test" json:"test,omitempty"` 47 SubtestName *string `yaml:"subtest,omitempty" json:"subtest,omitempty"` 48 Status *TestStatus `yaml:"status,omitempty" json:"status,omitempty"` 49 } 50 51 // GetMetadataResponse retrieves the response to a WPT Metadata query. Metadata 52 // is included for any product that matches a passed TestRun. Test-level 53 // metadata (i.e. that is not associated with any product) may be fetched by 54 // passing true for includeTestLevel. 55 func GetMetadataResponse(testRuns []TestRun, includeTestLevel bool, log Logger, fetcher MetadataFetcher) (MetadataResults, error) { 56 var productSpecs = make([]ProductSpec, len(testRuns)) 57 for i, run := range testRuns { 58 productSpecs[i] = ProductSpec{ProductAtRevision: run.ProductAtRevision, Labels: run.LabelsSet()} 59 } 60 61 // TODO(kyleju): Include the SHA information in API response; 62 // see https://github.com/web-platform-tests/wpt.fyi/issues/1938 63 _, metadata, err := GetMetadataByteMap(log, fetcher) 64 if err != nil { 65 return nil, err 66 } 67 68 return constructMetadataResponse(productSpecs, includeTestLevel, metadata), nil 69 } 70 71 // GetMetadataResponseOnProducts constructs the response to a WPT Metadata 72 // query, given ProductSpecs. Metdata is included for any product that matches 73 // a passed ProductSpec. Test-level metadata (i.e. that is not associated with 74 // any product) may be fetched by passing true for includeTestLevel. 75 func GetMetadataResponseOnProducts(productSpecs ProductSpecs, includeTestLevel bool, log Logger, fetcher MetadataFetcher) (MetadataResults, error) { 76 // TODO(kyleju): Include the SHA information in API response; 77 // see https://github.com/web-platform-tests/wpt.fyi/issues/1938 78 _, metadata, err := GetMetadataByteMap(log, fetcher) 79 if err != nil { 80 return nil, err 81 } 82 83 return constructMetadataResponse(productSpecs, includeTestLevel, metadata), nil 84 } 85 86 // GetMetadataByteMap collects and parses all META.yml files from 87 // the wpt-metadata repository. 88 func GetMetadataByteMap(log Logger, fetcher MetadataFetcher) (sha *string, metadata map[string]Metadata, err error) { 89 sha, metadataByteMap, err := fetcher.Fetch() 90 if err != nil { 91 log.Errorf("Error from FetchMetadata: %s", err.Error()) 92 return nil, nil, err 93 } 94 95 metadata = parseMetadata(metadataByteMap, log) 96 return sha, metadata, nil 97 } 98 99 func parseMetadata(metadataByteMap map[string][]byte, log Logger) map[string]Metadata { 100 var metadataMap = make(map[string]Metadata) 101 for path, data := range metadataByteMap { 102 var metadata Metadata 103 err := yaml.Unmarshal(data, &metadata) 104 if err != nil { 105 log.Warningf("Failed to unmarshal %s.", path) 106 continue 107 } 108 metadataMap[path] = metadata 109 } 110 return metadataMap 111 } 112 113 // addResponseLink is a helper method for constructMetadataResponse. It creates a new MetadataLink 114 // object corresponding to a specific MetadataTestResult for a given test, and adds it to a 115 // MetadataResults map. 116 func addResponseLink(fullTestName string, link MetadataLink, result MetadataTestResult, outMap MetadataResults) { 117 newLink := MetadataLink{ 118 Product: link.Product, 119 URL: link.URL, 120 Label: link.Label, 121 } 122 if result.SubtestName != nil || result.Status != nil { 123 newLink.Results = []MetadataTestResult{ 124 { 125 SubtestName: result.SubtestName, 126 Status: result.Status, 127 // TestPath is redundant (it's the map key in outMap) 128 }, 129 } 130 } 131 if _, ok := outMap[fullTestName]; !ok { 132 outMap[fullTestName] = MetadataLinks{newLink} 133 } else { 134 outMap[fullTestName] = append(outMap[fullTestName], newLink) 135 } 136 } 137 138 // constructMetadataResponse constructs the response to a WPT Metadata query, given ProductSpecs. 139 func constructMetadataResponse(productSpecs ProductSpecs, includeTestLevel bool, metadata map[string]Metadata) MetadataResults { 140 res := make(MetadataResults) 141 for folderPath, data := range metadata { 142 for i := range data.Links { 143 link := data.Links[i] 144 for _, result := range link.Results { 145 //TODO(kyleju): Concatenate test path on WPT Metadata repository instead of here. 146 fullTestName := GetWPTTestPath(folderPath, result.TestPath) 147 if link.Product.BrowserName == "" && includeTestLevel { 148 addResponseLink(fullTestName, link, result, res) 149 } 150 151 // Find any matching product for this link result (there can be at most one). 152 for _, productSpec := range productSpecs { 153 // Matches on browser type if a version is not specified. 154 if link.Product.MatchesProductSpec(productSpec) { 155 addResponseLink(fullTestName, link, result, res) 156 break 157 } 158 } 159 } 160 } 161 } 162 return res 163 } 164 165 // PrepareLinkFilter maps a MetadataResult test name to its URLs. 166 func PrepareLinkFilter(metadata MetadataResults) map[string][]string { 167 metadataMap := make(map[string][]string) 168 for test, links := range metadata { 169 for _, link := range links { 170 if link.URL == "" { 171 continue 172 } 173 if urls, ok := metadataMap[test]; !ok { 174 metadataMap[test] = []string{link.URL} 175 } else { 176 metadataMap[test] = append(urls, link.URL) 177 } 178 } 179 } 180 return metadataMap 181 } 182 183 // PrepareTestLabelFilter maps a MetadataResult test name to its labels. 184 func PrepareTestLabelFilter(metadata MetadataResults) map[string][]string { 185 metadataMap := make(map[string][]string) 186 for test, links := range metadata { 187 for _, link := range links { 188 if link.Label == "" { 189 continue 190 } 191 if labels, ok := metadataMap[test]; !ok { 192 metadataMap[test] = []string{link.Label} 193 } else { 194 metadataMap[test] = append(labels, link.Label) 195 } 196 197 } 198 } 199 return metadataMap 200 } 201 202 // GetWPTTestPath concatenates a folder path and a test name into a WPT test path. 203 func GetWPTTestPath(folderPath string, testname string) string { 204 if folderPath == "" { 205 return "/" + testname 206 } 207 return "/" + folderPath + "/" + testname 208 } 209 210 // SplitWPTTestPath splits a WPT test path into a folder path and a test name. 211 func SplitWPTTestPath(githubPath string) (string, string) { 212 if !strings.HasPrefix(githubPath, "/") { 213 return "", "" 214 } 215 216 pathArray := strings.Split(githubPath, "/")[1:] 217 if len(pathArray) == 1 { 218 return "", pathArray[0] 219 } 220 221 folderPath := strings.Join(pathArray[:len(pathArray)-1], "/") 222 testName := pathArray[len(pathArray)-1] 223 return folderPath, testName 224 } 225 226 // GetMetadataFilePath appends MetadataFileName to a Metadata folder path. 227 func GetMetadataFilePath(folderName string) string { 228 if folderName == "" { 229 return MetadataFileName 230 } 231 232 return folderName + "/" + MetadataFileName 233 }