• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright 2021 Advanced Micro Devices, Inc.
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the "Software"),
7# to deal in the Software without restriction, including without limitation
8# on the rights to use, copy, modify, merge, publish, distribute, sub
9# license, and/or sell copies of the Software, and to permit persons to whom
10# the Software is furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice (including the next
13# paragraph) shall be included in all copies or substantial portions of the
14# Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
19# THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
20# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22# USE OR OTHER DEALINGS IN THE SOFTWARE.
23#
24
25import os
26import sys
27import argparse
28import subprocess
29import shutil
30from datetime import datetime
31import tempfile
32import itertools
33import filecmp
34import multiprocessing
35import csv
36
37
38def print_red(txt, end_line=True, prefix=None):
39    if prefix:
40        print(prefix, end="")
41    print("\033[0;31m{}\033[0m".format(txt), end="\n" if end_line else " ")
42
43
44def print_yellow(txt, end_line=True, prefix=None):
45    if prefix:
46        print(prefix, end="")
47    print("\033[1;33m{}\033[0m".format(txt), end="\n" if end_line else " ")
48
49
50def print_green(txt, end_line=True, prefix=None):
51    if prefix:
52        print(prefix, end="")
53    print("\033[1;32m{}\033[0m".format(txt), end="\n" if end_line else " ")
54
55
56parser = argparse.ArgumentParser(description="radeonsi tester", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
57parser.add_argument(
58    "--jobs",
59    "-j",
60    type=int,
61    help="Number of processes/threads to use.",
62    default=multiprocessing.cpu_count(),
63)
64parser.add_argument("--piglit-path", type=str, help="Path to piglit source folder.")
65parser.add_argument("--glcts-path", type=str, help="Path to GLCTS source folder.")
66parser.add_argument("--deqp-path", type=str, help="Path to dEQP source folder.")
67parser.add_argument(
68    "--parent-path",
69    type=str,
70    help="Path to folder containing piglit/GLCTS and dEQP source folders.",
71    default=os.getenv("MAREKO_BUILD_PATH"),
72)
73parser.add_argument("--verbose", "-v", action="count", default=0)
74parser.add_argument(
75    "--include-tests",
76    "-t",
77    action="append",
78    dest="include_tests",
79    default=[],
80    help="Only run the test matching this expression. This can only be a filename containing a list of failing tests to re-run.",
81)
82parser.add_argument(
83    "--baseline",
84    dest="baseline",
85    help="Folder containing expected results files",
86    default=os.path.dirname(__file__))
87parser.add_argument(
88    "--no-piglit", dest="piglit", help="Disable piglit tests", action="store_false"
89)
90parser.add_argument(
91    "--no-glcts", dest="glcts", help="Disable GLCTS tests", action="store_false"
92)
93parser.add_argument(
94    "--no-deqp", dest="deqp", help="Disable dEQP tests", action="store_false"
95)
96parser.add_argument(
97    "--no-deqp-egl",
98    dest="deqp_egl",
99    help="Disable dEQP-EGL tests",
100    action="store_false",
101)
102parser.add_argument(
103    "--no-deqp-gles2",
104    dest="deqp_gles2",
105    help="Disable dEQP-gles2 tests",
106    action="store_false",
107)
108parser.add_argument(
109    "--no-deqp-gles3",
110    dest="deqp_gles3",
111    help="Disable dEQP-gles3 tests",
112    action="store_false",
113)
114parser.add_argument(
115    "--no-deqp-gles31",
116    dest="deqp_gles31",
117    help="Disable dEQP-gles31 tests",
118    action="store_false",
119)
120parser.set_defaults(piglit=True)
121parser.set_defaults(glcts=True)
122parser.set_defaults(deqp=True)
123parser.set_defaults(deqp_egl=True)
124parser.set_defaults(deqp_gles2=True)
125parser.set_defaults(deqp_gles3=True)
126parser.set_defaults(deqp_gles31=True)
127
128parser.add_argument(
129    "output_folder",
130    nargs="?",
131    help="Output folder (logs, etc)",
132    default=os.path.join(tempfile.gettempdir(), datetime.now().strftime('%Y-%m-%d-%H-%M-%S')))
133
134available_gpus = []
135for f in os.listdir("/dev/dri/by-path"):
136    idx = f.find("-render")
137    if idx < 0:
138        continue
139    # gbm name is the full path, but DRI_PRIME expects a different
140    # format
141    available_gpus += [(os.path.join("/dev/dri/by-path", f),
142                        f[:idx].replace(':', '_').replace('.', '_'))]
143
144if len(available_gpus) > 1:
145    parser.add_argument('--gpu', type=int, dest="gpu", default=0, help='Select GPU (0..{})'.format(len(available_gpus) - 1))
146
147args = parser.parse_args(sys.argv[1:])
148piglit_path = args.piglit_path
149glcts_path = args.glcts_path
150deqp_path = args.deqp_path
151
152if args.parent_path:
153    if args.piglit_path or args.glcts_path or args.deqp_path:
154        parser.print_help()
155        sys.exit(0)
156    piglit_path = os.path.join(args.parent_path, "piglit")
157    glcts_path = os.path.join(args.parent_path, "glcts")
158    deqp_path = os.path.join(args.parent_path, "deqp")
159else:
160    if not args.piglit_path or not args.glcts_path or not args.deqp_path:
161        parser.print_help()
162        sys.exit(0)
163
164base = args.baseline
165skips = os.path.join(os.path.dirname(__file__), "skips.csv")
166
167env = os.environ.copy()
168
169if "DISPLAY" not in env:
170    print_red("DISPLAY environment variable missing.")
171    sys.exit(1)
172p = subprocess.run(
173    ["deqp-runner", "--version"],
174    capture_output="True",
175    check=True,
176    env=env
177)
178for line in p.stdout.decode().split("\n"):
179    if line.find("deqp-runner") >= 0:
180        s = line.split(" ")[1].split(".")
181        if args.verbose > 1:
182            print("Checking deqp-version ({})".format(s))
183        # We want at least 0.9.0
184        if not (int(s[0]) > 0 or int(s[1]) >= 9):
185            print("Expecting deqp-runner 0.9.0+ version (got {})".format(".".join(s)))
186            sys.exit(1)
187
188env["PIGLIT_PLATFORM"] = "gbm"
189
190if "DRI_PRIME" in env:
191    print("Don't use DRI_PRIME. Instead use --gpu N")
192    del env["DRI_PRIME"]
193if "gpu" in args:
194    env["DRI_PRIME"] = available_gpus[args.gpu][1]
195    env["WAFFLE_GBM_DEVICE"] = available_gpus[args.gpu][0]
196
197# Use piglit's glinfo to determine the GPU name
198gpu_name = "unknown"
199gpu_name_full = ""
200
201p = subprocess.run(
202    ["./glinfo"],
203    capture_output="True",
204    cwd=os.path.join(piglit_path, "bin"),
205    check=True,
206    env=env
207)
208for line in p.stdout.decode().split("\n"):
209    if "GL_RENDER" in line:
210        line = line.split("=")[1]
211        gpu_name_full = '('.join(line.split("(")[:-1]).strip()
212        gpu_name = line.replace("(TM)", "").split("(")[1].split(",")[0].lower()
213        break
214
215output_folder = args.output_folder
216print_green("Tested GPU: '{}' ({})".format(gpu_name_full, gpu_name))
217print_green("Output folder: '{}'".format(output_folder))
218
219count = 1
220while os.path.exists(output_folder):
221    output_folder = "{}.{}".format(os.path.abspath(args.output_folder), count)
222    count += 1
223
224os.mkdir(output_folder)
225new_baseline_folder = os.path.join(output_folder, "new_baseline")
226os.mkdir(new_baseline_folder)
227
228logfile = open(os.path.join(output_folder, "{}-run-tests.log".format(gpu_name)), "w")
229
230spin = itertools.cycle("-\\|/")
231
232
233def run_cmd(args, verbosity):
234    if verbosity > 1:
235        print_yellow(
236            "| Command line argument '"
237            + " ".join(['"{}"'.format(a) for a in args])
238            + "'"
239        )
240    start = datetime.now()
241    proc = subprocess.Popen(
242        args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env
243    )
244    while True:
245        line = proc.stdout.readline().decode()
246        if verbosity > 0:
247            if "ERROR" in line:
248                print_red(line.strip(), prefix="| ")
249            else:
250                print("| " + line.strip())
251        else:
252            sys.stdout.write(next(spin))
253            sys.stdout.flush()
254            sys.stdout.write("\b")
255
256        logfile.write(line)
257
258        if proc.poll() is not None:
259            break
260    proc.wait()
261    end = datetime.now()
262
263    if verbosity == 0:
264        sys.stdout.write(" ... ")
265
266    print_yellow(
267        "Completed in {} seconds".format(int((end - start).total_seconds())),
268        prefix="└ " if verbosity > 0 else None,
269    )
270
271
272def verify_results(baseline1, baseline2):
273    # We're not using baseline1 because piglit-runner/deqp-runner already are:
274    #  - if no baseline, baseline2 will contain the list of failures
275    #  - if there's a baseline, baseline2 will contain the diff
276    # So in both cases, an empty baseline2 files means a successful run
277    if len(open(baseline2, "r").readlines()) != 0:
278        print_red("New errors. Check {}".format(baseline2))
279        return False
280    return True
281
282
283def parse_test_filters(include_tests):
284    cmd = []
285    for t in include_tests:
286        if os.path.exists(t):
287            with open(t, "r") as file:
288                for row in csv.reader(file, delimiter=","):
289                    cmd += ["-t", row[0]]
290        else:
291            cmd += ["-t", t]
292    return cmd
293
294
295filters_args = parse_test_filters(args.include_tests)
296
297# piglit test
298if args.piglit:
299    out = os.path.join(output_folder, "piglit")
300    baseline = os.path.join(base, "{}-piglit-quick-fail.csv".format(gpu_name))
301    new_baseline = os.path.join(
302        new_baseline_folder, "{}-piglit-quick-fail.csv".format(gpu_name)
303    )
304    print_yellow("Running piglit tests", args.verbose > 0)
305    cmd = [
306        "piglit-runner",
307        "run",
308        "--piglit-folder",
309        piglit_path,
310        "--profile",
311        "quick",
312        "--output",
313        out,
314        "--process-isolation",
315        "--timeout",
316        "300",
317        "--jobs",
318        str(args.jobs),
319        "--skips",
320        skips,
321    ] + filters_args
322
323    if os.path.exists(baseline):
324        cmd += ["--baseline", baseline]
325        print_yellow("[baseline {}]".format(baseline), args.verbose > 0)
326    run_cmd(cmd, args.verbose)
327    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
328    verify_results(baseline, new_baseline)
329
330deqp_args = "-- --deqp-surface-width=256 --deqp-surface-height=256 --deqp-gl-config-name=rgba8888d24s8ms0 --deqp-visibility=hidden".split(
331    " "
332)
333
334# glcts test
335if args.glcts:
336    out = os.path.join(output_folder, "glcts")
337    baseline = os.path.join(base, "{}-glcts-fail.csv".format(gpu_name))
338    new_baseline = os.path.join(
339        new_baseline_folder, "{}-glcts-fail.csv".format(gpu_name)
340    )
341    print_yellow("Running  GLCTS tests", args.verbose > 0)
342    os.mkdir(os.path.join(output_folder, "glcts"))
343
344    cmd = [
345        "deqp-runner",
346        "run",
347        "--deqp",
348        "{}/external/openglcts/modules/glcts".format(glcts_path),
349        "--caselist",
350        "{}/external/openglcts/modules/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl46-master.txt".format(
351            glcts_path
352        ),
353        "--output",
354        out,
355        "--skips",
356        skips,
357        "--jobs",
358        str(args.jobs),
359        "--timeout",
360        "1000",
361    ] + filters_args
362
363    if os.path.exists(baseline):
364        cmd += ["--baseline", baseline]
365        print_yellow("[baseline {}]".format(baseline), args.verbose > 0)
366    cmd += deqp_args
367    run_cmd(cmd, args.verbose)
368    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
369    verify_results(baseline, new_baseline)
370
371if args.deqp:
372    print_yellow("Running   dEQP tests", args.verbose > 0)
373
374    # Generate a test-suite file
375    out = os.path.join(output_folder, "deqp")
376    suite_filename = os.path.join(output_folder, "deqp-suite.toml")
377    suite = open(suite_filename, "w")
378    os.mkdir(out)
379    baseline = os.path.join(base, "{}-deqp-fail.csv".format(gpu_name))
380    new_baseline = os.path.join(
381        new_baseline_folder, "{}-deqp-fail.csv".format(gpu_name)
382    )
383
384    if os.path.exists(baseline):
385        print_yellow("[baseline {}]".format(baseline), args.verbose > 0)
386
387    deqp_tests = {
388        "egl": args.deqp_egl,
389        "gles2": args.deqp_gles2,
390        "gles3": args.deqp_gles3,
391        "gles31": args.deqp_gles31,
392    }
393
394    for k in deqp_tests:
395        if not deqp_tests[k]:
396            continue
397
398        suite.write("[[deqp]]\n")
399        suite.write(
400            'deqp = "{}"\n'.format(
401                "{}/modules/{subtest}/deqp-{subtest}".format(deqp_path, subtest=k)
402            )
403        )
404        suite.write(
405            'caselists = ["{}"]\n'.format(
406                "{}/android/cts/master/{}-master.txt".format(deqp_path, k)
407            )
408        )
409        if os.path.exists(baseline):
410            suite.write('baseline = "{}"\n'.format(baseline))
411        suite.write('skips = ["{}"]\n'.format(skips))
412        suite.write("deqp_args = [\n")
413        for a in deqp_args[1:-1]:
414            suite.write('    "{}",\n'.format(a))
415        suite.write('    "{}"\n'.format(deqp_args[-1]))
416        suite.write("]\n")
417
418    suite.close()
419
420    cmd = [
421        "deqp-runner",
422        "suite",
423        "--jobs",
424        str(args.jobs),
425        "--output",
426        os.path.join(output_folder, "deqp"),
427        "--suite",
428        suite_filename,
429    ] + filters_args
430    run_cmd(cmd, args.verbose)
431    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
432    verify_results(baseline, new_baseline)
433