• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
2#
3# Copyright 2021 Advanced Micro Devices, Inc.
4#
5# SPDX-License-Identifier: MIT
6#
7
8import os
9import sys
10import argparse
11import subprocess
12import shutil
13from datetime import datetime
14import tempfile
15import itertools
16import filecmp
17import multiprocessing
18import csv
19
20
21def print_red(txt, end_line=True, prefix=None):
22    if prefix:
23        print(prefix, end="")
24    print("\033[0;31m{}\033[0m".format(txt), end="\n" if end_line else " ")
25
26
27def print_yellow(txt, end_line=True, prefix=None):
28    if prefix:
29        print(prefix, end="")
30    print("\033[1;33m{}\033[0m".format(txt), end="\n" if end_line else " ")
31
32
33def print_green(txt, end_line=True, prefix=None):
34    if prefix:
35        print(prefix, end="")
36    print("\033[1;32m{}\033[0m".format(txt), end="\n" if end_line else " ")
37
38
39parser = argparse.ArgumentParser(
40    description="radeonsi tester",
41    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
42)
43parser.add_argument(
44    "--jobs",
45    "-j",
46    type=int,
47    help="Number of processes/threads to use.",
48    default=multiprocessing.cpu_count(),
49)
50
51# The path to above the mesa directory, i.e. ../../../../../..
52path_above_mesa = os.path.realpath(os.path.join(os.path.dirname(__file__), *['..'] * 6))
53
54parser.add_argument("--piglit-path", type=str, help="Path to piglit source folder.")
55parser.add_argument("--glcts-path", type=str, help="Path to GLCTS source folder.")
56parser.add_argument(
57    "--parent-path",
58    type=str,
59    help="Path to folder containing piglit/GLCTS and dEQP source folders.",
60    default=os.getenv('MAREKO_BUILD_PATH', path_above_mesa),
61)
62parser.add_argument("--verbose", "-v", action="count", default=0)
63parser.add_argument(
64    "--include-tests",
65    "-t",
66    action="append",
67    dest="include_tests",
68    default=[],
69    help="Only run the test matching this expression. This can only be a filename containing a list of failing tests to re-run.",
70)
71parser.add_argument(
72    "--baseline",
73    dest="baseline",
74    help="Folder containing expected results files",
75    default=os.path.dirname(__file__),
76)
77parser.add_argument(
78    "--no-piglit", dest="piglit", help="Disable piglit tests", action="store_false"
79)
80parser.add_argument(
81    "--no-glcts", dest="glcts", help="Disable GLCTS tests", action="store_false"
82)
83parser.add_argument(
84    "--no-escts", dest="escts", help="Disable GLES CTS tests", action="store_false"
85)
86parser.add_argument(
87    "--no-deqp", dest="deqp", help="Disable dEQP tests", action="store_false"
88)
89parser.add_argument(
90    "--no-deqp-egl",
91    dest="deqp_egl",
92    help="Disable dEQP-EGL tests",
93    action="store_false",
94)
95parser.add_argument(
96    "--no-deqp-gles2",
97    dest="deqp_gles2",
98    help="Disable dEQP-gles2 tests",
99    action="store_false",
100)
101parser.add_argument(
102    "--no-deqp-gles3",
103    dest="deqp_gles3",
104    help="Disable dEQP-gles3 tests",
105    action="store_false",
106)
107parser.add_argument(
108    "--no-deqp-gles31",
109    dest="deqp_gles31",
110    help="Disable dEQP-gles31 tests",
111    action="store_false",
112)
113parser.set_defaults(piglit=True)
114parser.set_defaults(glcts=True)
115parser.set_defaults(escts=True)
116parser.set_defaults(deqp=True)
117parser.set_defaults(deqp_egl=True)
118parser.set_defaults(deqp_gles2=True)
119parser.set_defaults(deqp_gles3=True)
120parser.set_defaults(deqp_gles31=True)
121
122parser.add_argument(
123    "output_folder",
124    nargs="?",
125    help="Output folder (logs, etc)",
126    default=os.path.join(
127        # Default is ../../../../../../test-results/datetime
128        os.path.join(path_above_mesa, 'test-results',
129                     datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
130    ),
131)
132
133available_gpus = []
134for f in os.listdir("/dev/dri/by-path"):
135    idx = f.find("-render")
136    if idx < 0:
137        continue
138    # gbm name is the full path, but DRI_PRIME expects a different
139    # format
140    available_gpus += [
141        (
142            os.path.join("/dev/dri/by-path", f),
143            f[:idx].replace(":", "_").replace(".", "_"),
144        )
145    ]
146
147parser.add_argument(
148    "--gpu",
149    type=int,
150    dest="gpu",
151    default=0,
152    help="Select GPU (0..{})".format(len(available_gpus) - 1),
153)
154parser.add_argument(
155    "--llvmpipe", dest="llvmpipe", help="Test llvmpipe", action="store_true"
156)
157parser.add_argument(
158    "--softpipe", dest="softpipe", help="Test softpipe", action="store_true"
159)
160parser.add_argument(
161    "--virgl", dest="virgl", help="Test virgl", action="store_true"
162)
163parser.add_argument(
164    "--zink", dest="zink", help="Test zink", action="store_true"
165)
166
167args = parser.parse_args(sys.argv[1:])
168piglit_path = args.piglit_path
169glcts_path = args.glcts_path
170
171if args.parent_path:
172    if args.piglit_path or args.glcts_path:
173        parser.print_help()
174        sys.exit(0)
175    piglit_path = os.path.join(args.parent_path, "piglit")
176    glcts_path = os.path.join(args.parent_path, "glcts")
177else:
178    if not args.piglit_path or not args.glcts_path:
179        parser.print_help()
180        sys.exit(0)
181
182env = os.environ.copy()
183
184if "DISPLAY" not in env:
185    print_red("DISPLAY environment variable missing.")
186    sys.exit(1)
187p = subprocess.run(
188    ["deqp-runner", "--version"], capture_output="True", check=True, env=env
189)
190for line in p.stdout.decode().split("\n"):
191    if line.find("deqp-runner") >= 0:
192        s = line.split(" ")[1].split(".")
193        if args.verbose > 1:
194            print("Checking deqp-version ({})".format(s))
195        # We want at least 0.9.0
196        if not (int(s[0]) > 0 or int(s[1]) >= 9):
197            print("Expecting deqp-runner 0.9.0+ version (got {})".format(".".join(s)))
198            sys.exit(1)
199
200if "DRI_PRIME" in env:
201    print("Don't use DRI_PRIME. Instead use --gpu N")
202    del env["DRI_PRIME"]
203
204assert "gpu" in args, "--gpu defaults to 0"
205
206gpu_device = available_gpus[args.gpu][1]
207env["DRI_PRIME"] = gpu_device
208env["WAFFLE_GBM_DEVICE"] = available_gpus[args.gpu][0]
209
210# Use piglit's glinfo to determine the GPU name
211gpu_name = "unknown"
212gpu_name_full = ""
213gfx_level = -1
214
215assert args.llvmpipe + args.softpipe + args.virgl + args.zink <= 1
216is_amd = args.llvmpipe + args.softpipe + args.virgl + args.zink == 0
217
218if args.llvmpipe:
219    env["LIBGL_ALWAYS_SOFTWARE"] = '1'
220    baseline = "../../llvmpipe/ci/llvmpipe-fails.txt"
221    flakes_list = "../../llvmpipe/ci/llvmpipe-flakes.txt"
222    skips_list = "../../llvmpipe/ci/llvmpipe-skips.txt"
223elif args.softpipe:
224    env["LIBGL_ALWAYS_SOFTWARE"] = '1'
225    env["GALLIUM_DRIVER"] = 'softpipe'
226    baseline = "../../softpipe/ci/softpipe-fails.txt"
227    flakes_list = "../../softpipe/ci/softpipe-flakes.txt"
228    skips_list = "../../softpipe/ci/softpipe-skips.txt"
229elif args.virgl:
230    env["PIGLIT_PLATFORM"] = "gbm"
231    baseline = ''
232    flakes_list = None
233    skips_list = "skips.csv"
234elif args.zink:
235    env["PIGLIT_PLATFORM"] = "gbm"
236    env["MESA_LOADER_DRIVER_OVERRIDE"] = 'zink'
237    baseline = "../../zink/ci/zink-radv-navi31-fails.txt"
238    flakes_list = "../../zink/ci/zink-radv-navi31-flakes.txt"
239    skips_list = "../../zink/ci/zink-radv-navi31-skips.txt"
240elif is_amd:
241    env["PIGLIT_PLATFORM"] = "gbm"
242    flakes_list = None # it will be determined later
243    skips_list = "skips.csv"
244else:
245    assert False
246
247if not is_amd:
248    baseline = os.path.normpath(os.path.join(os.path.dirname(__file__), baseline))
249    if flakes_list is not None:
250        flakes_list = os.path.normpath(os.path.join(os.path.dirname(__file__), flakes_list))
251
252skips_list = os.path.normpath(os.path.join(os.path.dirname(__file__), skips_list))
253env_glinfo = dict(env)
254env_glinfo["AMD_DEBUG"] = "info"
255
256try:
257    p = subprocess.run(
258        ["./glinfo"],
259        capture_output="True",
260        cwd=os.path.join(piglit_path, "bin"),
261        check=True,
262        env=env_glinfo,
263    )
264except subprocess.CalledProcessError:
265    print('piglit/bin/glinfo failed to create a GL context')
266    exit(1)
267
268renderer = None
269for line in p.stdout.decode().split("\n"):
270    if "GL_RENDER" in line:
271        line = line.split("=")[1]
272        renderer = line
273        if is_amd:
274            gpu_name_full = "(".join(line.split("(")[:-1]).strip()
275            gpu_name = line.replace("(TM)", "").split("(")[1].split(",")[1].lower().strip()
276        break
277    elif "gfx_level" in line:
278        gfx_level = int(line.split("=")[1])
279
280if renderer is None:
281    print('piglit/bin/glinfo failed to create a GL context')
282    exit(1)
283
284output_folder = args.output_folder
285if is_amd:
286    print_green("Tested GPU: '{}' ({}) {}".format(gpu_name_full, gpu_name, gpu_device))
287else:
288    print_green("Renderer: '{}'".format(renderer))
289print_green("Output folder: '{}'".format(output_folder))
290
291count = 1
292while os.path.exists(output_folder):
293    output_folder = "{}.{}".format(os.path.abspath(args.output_folder), count)
294    count += 1
295
296os.makedirs(output_folder, exist_ok=True)
297
298logfile = open(os.path.join(output_folder, "{}-run-tests.log".format(gpu_name)), "w")
299
300spin = itertools.cycle("-\\|/")
301
302def gfx_level_to_str(cl):
303    supported = ["gfx6", "gfx7", "gfx8", "gfx9", "gfx10", "gfx10_3", "gfx11", "gfx12"]
304    if 8 <= cl and cl < 8 + len(supported):
305        return supported[cl - 8]
306    return supported[-1]
307
308
309def run_cmd(args, verbosity):
310    if verbosity > 1:
311        print_yellow(
312            "| Command line argument '"
313            + " ".join(['"{}"'.format(a) for a in args])
314            + "'"
315        )
316    start = datetime.now()
317    proc = subprocess.Popen(
318        args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env
319    )
320    while True:
321        line = proc.stdout.readline().decode()
322        if verbosity > 0:
323            if "ERROR" in line:
324                print_red(line.strip(), prefix="| ")
325            else:
326                print("| " + line.strip())
327        else:
328            sys.stdout.write(next(spin))
329            sys.stdout.flush()
330            sys.stdout.write("\b")
331
332        logfile.write(line)
333
334        if proc.poll() is not None:
335            break
336    proc.wait()
337    end = datetime.now()
338
339    if verbosity == 0:
340        sys.stdout.write(" ... ")
341
342    print_yellow(
343        "Completed in {} seconds".format(int((end - start).total_seconds())),
344        prefix="└ " if verbosity > 0 else None,
345    )
346
347
348def verify_results(results):
349    with open(results) as file:
350        lines = file.readlines()
351        if len(lines) == 0:
352            return True
353        print("{} new result{}:".format(len(lines), 's' if len(lines) > 1 else ''))
354        for i in range(min(10, len(lines))):
355            print("  * ", end='')
356            if "Pass" in lines[i]:
357                print_green(lines[i][:-1])
358            else:
359                print_red(lines[i][:-1])
360        if len(lines) > 10:
361            print_yellow("...")
362        print("Full results: {}".format(results))
363
364    return False
365
366
367def parse_test_filters(include_tests, baseline):
368    cmd = []
369    for t in include_tests:
370        if t == 'baseline':
371            t = baseline
372
373        if os.path.exists(t):
374            with open(t, "r") as file:
375                for row in csv.reader(file, delimiter=","):
376                    if not row or row[0][0] == "#":
377                        continue
378                    cmd += ["-t", row[0]]
379        else:
380            cmd += ["-t", t]
381    return cmd
382
383
384def select_baseline(basepath, gfx_level, gpu_name, suffix):
385    gfx_level_str = gfx_level_to_str(gfx_level)
386
387    # select the best baseline we can find
388    # 1. exact match
389    exact = os.path.join(basepath, "{}-{}-{}.csv".format(gfx_level_str, gpu_name, suffix))
390    if os.path.exists(exact):
391        return exact
392    # 2. any baseline with the same gfx_level
393    while gfx_level >= 8:
394        gfx_level_str += '-'
395        for subdir, dirs, files in os.walk(basepath):
396            for file in files:
397                if file.find(gfx_level_str) == 0 and file.endswith("-{}.csv".format(suffix)):
398                    return os.path.join(basepath, file)
399        # No match. Try an earlier class
400        gfx_level = gfx_level - 1
401        gfx_level_str = gfx_level_to_str(gfx_level)
402
403    return exact
404
405
406if is_amd:
407    baseline = select_baseline(args.baseline, gfx_level, gpu_name, 'fail')
408    flakes_list = select_baseline(args.baseline, gfx_level, gpu_name, 'flakes')
409
410success = True
411filters_args = parse_test_filters(args.include_tests, baseline)
412flakes_args = []
413
414if os.path.exists(baseline):
415    print_yellow("Baseline: {}".format(baseline))
416
417if flakes_list is not None and os.path.exists(flakes_list):
418    print_yellow("Flakes: {}".format(flakes_list))
419    flakes_args = ["--flakes", flakes_list]
420
421print_yellow("Skips: {}".format(skips_list))
422
423# piglit test
424if args.piglit:
425    out = os.path.join(output_folder, "piglit")
426    print_yellow("Running piglit tests", args.verbose > 0)
427    cmd = [
428        "piglit-runner",
429        "run",
430        "--piglit-folder",
431        piglit_path,
432        "--profile",
433        "quick",
434        "--output",
435        out,
436        "--process-isolation",
437        "--timeout",
438        "300",
439        "--jobs",
440        str(args.jobs),
441        "--skips",
442        skips_list,
443        "--skips",
444        os.path.join(path_above_mesa, "mesa", ".gitlab-ci", "gbm-skips.txt")
445    ] + filters_args + flakes_args
446
447    if os.path.exists(baseline):
448        cmd += ["--baseline", baseline]
449
450    run_cmd(cmd, args.verbose)
451
452    if not verify_results(os.path.join(out, "failures.csv")):
453        success = False
454
455deqp_args = "-- --deqp-surface-width=256 --deqp-surface-height=256 --deqp-gl-config-name=rgba8888d24s8ms0 --deqp-visibility=hidden".split(
456    " "
457)
458
459# glcts test
460if args.glcts:
461    out = os.path.join(output_folder, "glcts")
462    print_yellow("Running  GLCTS tests", args.verbose > 0)
463    os.mkdir(os.path.join(output_folder, "glcts"))
464
465    cmd = [
466        "deqp-runner",
467        "run",
468        "--tests-per-group",
469        "100",
470        "--deqp",
471        "{}/build/external/openglcts/modules/glcts".format(glcts_path),
472    ]
473
474    if is_amd or args.zink:
475        cmd += [
476            "--caselist",
477            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl46-main.txt".format(glcts_path),
478            "--caselist",
479            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass_single/4.6.1.x/gl46-khr-single.txt".format(glcts_path),
480            "--caselist",
481            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl46-gtf-main.txt".format(glcts_path),
482        ]
483    elif args.llvmpipe:
484        cmd += [
485            "--caselist",
486            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl45-main.txt".format(glcts_path),
487            "--caselist",
488            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass_single/4.6.1.x/gl45-khr-single.txt".format(glcts_path),
489            "--caselist",
490            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl45-gtf-main.txt".format(glcts_path),
491        ]
492    elif args.virgl:
493        cmd += [
494            "--caselist",
495            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl43-main.txt".format(glcts_path),
496            "--caselist",
497            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass_single/4.6.1.x/gl43-khr-single.txt".format(glcts_path),
498            "--caselist",
499            "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl43-gtf-main.txt".format(glcts_path),
500        ]
501    elif args.softpipe:
502        # KHR-GL33.info.renderer crashes with softpipe.
503        #cmd += [
504        #    "--caselist",
505        #    "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl33-main.txt".format(glcts_path),
506        #    "--caselist",
507        #    "{}/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl33-gtf-main.txt".format(glcts_path),
508        #]
509        pass
510    else:
511        assert False
512
513    cmd += [
514        "--output",
515        out,
516        "--skips",
517        skips_list,
518        "--jobs",
519        str(args.jobs),
520        "--timeout",
521        "1000"
522    ] + filters_args + flakes_args
523
524    if os.path.exists(baseline):
525        cmd += ["--baseline", baseline]
526    cmd += deqp_args
527
528    run_cmd(cmd, args.verbose)
529
530    if not verify_results(os.path.join(out, "failures.csv")):
531        success = False
532
533# escts test
534if args.escts:
535    out = os.path.join(output_folder, "escts")
536    print_yellow("Running  ESCTS tests", args.verbose > 0)
537    os.mkdir(out)
538
539    cmd = [
540        "deqp-runner",
541        "run",
542        "--tests-per-group",
543        "100",
544        "--deqp",
545        "{}/build_es/external/openglcts/modules/glcts".format(glcts_path),
546        "--caselist",
547        "{}/external/openglcts/data/gl_cts/data/mustpass/gles/khronos_mustpass/3.2.6.x/gles2-khr-main.txt".format(
548            glcts_path
549        ),
550        "--caselist",
551        "{}/external/openglcts/data/gl_cts/data/mustpass/gles/khronos_mustpass/3.2.6.x/gles3-khr-main.txt".format(
552            glcts_path
553        ),
554        "--caselist",
555        "{}/external/openglcts/data/gl_cts/data/mustpass/gles/khronos_mustpass/3.2.6.x/gles31-khr-main.txt".format(
556            glcts_path
557        ),
558    ]
559
560    if not args.softpipe:
561        cmd += [
562            "--caselist",
563            "{}/external/openglcts/data/gl_cts/data/mustpass/gles/khronos_mustpass/3.2.6.x/gles32-khr-main.txt".format(
564                glcts_path
565            ),
566        ]
567
568    cmd += [
569        "--output",
570        out,
571        "--skips",
572        skips_list,
573        "--jobs",
574        str(args.jobs),
575        "--timeout",
576        "1000"
577    ] + filters_args + flakes_args
578
579    if os.path.exists(baseline):
580        cmd += ["--baseline", baseline]
581    cmd += deqp_args
582
583    run_cmd(cmd, args.verbose)
584
585    if not verify_results(os.path.join(out, "failures.csv")):
586        success = False
587
588if args.deqp:
589    print_yellow("Running   dEQP tests", args.verbose > 0)
590
591    # Generate a test-suite file
592    out = os.path.join(output_folder, "deqp")
593    suite_filename = os.path.join(output_folder, "deqp-suite.toml")
594    suite = open(suite_filename, "w")
595    os.mkdir(out)
596
597    deqp_tests = {
598        "egl": args.deqp_egl,
599        "gles2": args.deqp_gles2,
600        "gles3": args.deqp_gles3,
601        "gles31": args.deqp_gles31,
602    }
603
604    for k in deqp_tests:
605        if not deqp_tests[k]:
606            continue
607
608        suite.write("[[deqp]]\n")
609        suite.write(
610            'deqp = "{}"\n'.format(
611                "{}/build/modules/{subtest}/deqp-{subtest}".format(glcts_path, subtest=k)
612            )
613        )
614        suite.write(
615            'caselists = ["{}"]\n'.format(
616                "{}/external/openglcts/data/gl_cts/data/mustpass/{}/aosp_mustpass/3.2.6.x/{}-main.txt".format(glcts_path, "egl" if k == "egl" else "gles", k)
617            )
618        )
619        if os.path.exists(baseline):
620            suite.write('baseline = "{}"\n'.format(baseline))
621        suite.write('skips = ["{}"]\n'.format(skips_list))
622        suite.write("deqp_args = [\n")
623        for a in deqp_args[1:-1]:
624            suite.write('    "{}",\n'.format(a))
625        suite.write('    "{}"\n'.format(deqp_args[-1]))
626        suite.write("]\n")
627
628    suite.close()
629
630    cmd = [
631        "deqp-runner",
632        "suite",
633        "--jobs",
634        str(args.jobs),
635        "--output",
636        os.path.join(output_folder, "deqp"),
637        "--suite",
638        suite_filename,
639    ] + filters_args + flakes_args
640
641    run_cmd(cmd, args.verbose)
642
643    if not verify_results(os.path.join(out, "failures.csv")):
644        success = False
645
646sys.exit(0 if success else 1)
647