1// Copyright 2019 The SwiftShader Authors. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package testlist provides utilities for handling test lists. 16package testlist 17 18import ( 19 "bytes" 20 "crypto/sha1" 21 "encoding/gob" 22 "encoding/hex" 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "path/filepath" 27 "sort" 28 "strings" 29) 30 31// API is an enumerator of graphics APIs. 32type API string 33 34// Graphics APIs. 35const ( 36 EGL = API("egl") 37 GLES2 = API("gles2") 38 GLES3 = API("gles3") 39 Vulkan = API("vulkan") 40) 41 42// Group is a list of tests to be run for a single API. 43type Group struct { 44 Name string 45 File string 46 API API 47 Tests []string 48} 49 50// Load loads the test list file and appends all tests to the Group. 51func (g *Group) Load() error { 52 return g.LoadFile(g.File) 53} 54 55func (g *Group) LoadFile(file string) error { 56 dir, _ := filepath.Split(file) 57 tests, err := ioutil.ReadFile(file) 58 if err != nil { 59 return fmt.Errorf("failed to read '%s': %w", file, err) 60 } 61 for _, line := range strings.Split(string(tests), "\n") { 62 line = strings.TrimSpace(line) 63 // The test list file can contain references to other .txt files 64 // containing the individual tests. 65 if strings.HasSuffix(line, ".txt") { 66 g.LoadFile(filepath.Join(dir, line)) 67 } else if line != "" && !strings.HasPrefix(line, "#") { 68 g.Tests = append(g.Tests, line) 69 } 70 } 71 sort.Strings(g.Tests) 72 return nil 73} 74 75// Filter returns a new Group that contains only tests that match the predicate. 76func (g Group) Filter(pred func(string) bool) Group { 77 out := Group{ 78 Name: g.Name, 79 File: g.File, 80 API: g.API, 81 } 82 for _, test := range g.Tests { 83 if pred(test) { 84 out.Tests = append(out.Tests, test) 85 } 86 } 87 return out 88} 89 90// Limit returns a new Group that contains a maximum of limit tests. 91func (g Group) Limit(limit int) Group { 92 out := Group{ 93 Name: g.Name, 94 File: g.File, 95 API: g.API, 96 Tests: g.Tests, 97 } 98 if len(g.Tests) > limit { 99 out.Tests = g.Tests[:limit] 100 } 101 return out 102} 103 104// Lists is the full list of tests to be run. 105type Lists []Group 106 107// Filter returns a new Lists that contains only tests that match the predicate. 108func (l Lists) Filter(pred func(string) bool) Lists { 109 out := Lists{} 110 for _, group := range l { 111 filtered := group.Filter(pred) 112 if len(filtered.Tests) > 0 { 113 out = append(out, filtered) 114 } 115 } 116 return out 117} 118 119// Hash returns a SHA1 hash of the set of tests. 120func (l Lists) Hash() string { 121 h := sha1.New() 122 if err := gob.NewEncoder(h).Encode(l); err != nil { 123 panic(fmt.Errorf("failed to encode testlist to produce hash: %w", err)) 124 } 125 return hex.EncodeToString(h.Sum(nil)) 126} 127 128// Load loads the test list json file and returns the full set of tests. 129func Load(root, jsonPath string) (Lists, error) { 130 root, err := filepath.Abs(root) 131 if err != nil { 132 return nil, fmt.Errorf("failed to get absolute path of '%s': %w", root, err) 133 } 134 135 jsonPath, err = filepath.Abs(jsonPath) 136 if err != nil { 137 return nil, fmt.Errorf("failed to get absolute path of '%s': %w", jsonPath, err) 138 } 139 140 i, err := ioutil.ReadFile(jsonPath) 141 if err != nil { 142 return nil, fmt.Errorf("failed to read test list from '%s': %w", jsonPath, err) 143 } 144 145 var jsonGroups []struct { 146 Name string 147 API string 148 TestFile string `json:"tests"` 149 } 150 if err := json.NewDecoder(bytes.NewReader(i)).Decode(&jsonGroups); err != nil { 151 return nil, fmt.Errorf("failed to parse '%s': %w", jsonPath, err) 152 } 153 154 dir := filepath.Dir(jsonPath) 155 156 out := make(Lists, len(jsonGroups)) 157 for i, jsonGroup := range jsonGroups { 158 group := Group{ 159 Name: jsonGroup.Name, 160 File: filepath.Join(dir, jsonGroup.TestFile), 161 API: API(jsonGroup.API), 162 } 163 if err := group.Load(); err != nil { 164 return nil, err 165 } 166 167 // Make the path relative before displaying it to the world. 168 relPath, err := filepath.Rel(root, group.File) 169 if err != nil { 170 return nil, fmt.Errorf("failed to get relative path for '%s': %w", group.File, err) 171 } 172 group.File = relPath 173 174 out[i] = group 175 } 176 177 return out, nil 178} 179 180// Status is an enumerator of test results. 181type Status string 182 183const ( 184 // Pass is the status of a successful test. 185 Pass = Status("PASS") 186 // Fail is the status of a failed test. 187 Fail = Status("FAIL") 188 // Timeout is the status of a test that failed to complete in the alloted 189 // time. 190 Timeout = Status("TIMEOUT") 191 // Crash is the status of a test that crashed. 192 Crash = Status("CRASH") 193 // Unimplemented is the status of a test that failed with UNIMPLEMENTED(). 194 Unimplemented = Status("UNIMPLEMENTED") 195 // Unsupported is the status of a test that failed with UNSUPPORTED(). 196 Unsupported = Status("UNSUPPORTED") 197 // Unreachable is the status of a test that failed with UNREACHABLE(). 198 Unreachable = Status("UNREACHABLE") 199 // Assert is the status of a test that failed with ASSERT() or ASSERT_MSG(). 200 Assert = Status("ASSERT") 201 // Abort is the status of a test that failed with ABORT(). 202 Abort = Status("ABORT") 203 // NotSupported is the status of a test feature not supported by the driver. 204 NotSupported = Status("NOT_SUPPORTED") 205 // CompatibilityWarning is the status passing test with a warning. 206 CompatibilityWarning = Status("COMPATIBILITY_WARNING") 207 // QualityWarning is the status passing test with a warning. 208 QualityWarning = Status("QUALITY_WARNING") 209 // InternalError is the status of a test that failed on an API usage error. 210 InternalError = Status("INTERNAL_ERROR") 211 // Unknown is the status of a test that had a result that could not be parsed. 212 Unknown = Status("INTERNAL_ERROR") 213) 214 215// Statuses is the full list of status types 216var Statuses = []Status{ 217 Pass, 218 Fail, 219 Timeout, 220 Crash, 221 Unimplemented, 222 Unsupported, 223 Unreachable, 224 Assert, 225 Abort, 226 NotSupported, 227 CompatibilityWarning, 228 QualityWarning, 229 InternalError, 230 Unknown, 231} 232 233// Failing returns true if the task status requires fixing. 234func (s Status) Failing() bool { 235 switch s { 236 case Fail, Timeout, Crash, Unimplemented, Unreachable, Assert, Abort, InternalError: 237 return true 238 case Unsupported: 239 // This may seem surprising that this should be a failure, however these 240 // should not be reached, as dEQP should not be using features that are 241 // not advertised. 242 return true 243 default: 244 return false 245 } 246} 247 248// Passing returns true if the task status is considered a pass. 249func (s Status) Passing() bool { 250 switch s { 251 case Pass, CompatibilityWarning, QualityWarning: 252 return true 253 default: 254 return false 255 } 256} 257 258// FilePathWithStatus returns the path to the test list file with the status 259// appended before the file extension. 260func FilePathWithStatus(listPath string, status Status) string { 261 ext := filepath.Ext(listPath) 262 name := listPath[:len(listPath)-len(ext)] 263 return name + "-" + string(status) + ext 264} 265