xref: /aosp_15_r20/external/swiftshader/tests/regres/testlist/testlist.go (revision 03ce13f70fcc45d86ee91b7ee4cab1936a95046e)
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