• 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(
57    description="radeonsi tester",
58    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
59)
60parser.add_argument(
61    "--jobs",
62    "-j",
63    type=int,
64    help="Number of processes/threads to use.",
65    default=multiprocessing.cpu_count(),
66)
67parser.add_argument("--piglit-path", type=str, help="Path to piglit source folder.")
68parser.add_argument("--glcts-path", type=str, help="Path to GLCTS source folder.")
69parser.add_argument("--deqp-path", type=str, help="Path to dEQP source folder.")
70parser.add_argument(
71    "--parent-path",
72    type=str,
73    help="Path to folder containing piglit/GLCTS and dEQP source folders.",
74    default=os.getenv("MAREKO_BUILD_PATH"),
75)
76parser.add_argument("--verbose", "-v", action="count", default=0)
77parser.add_argument(
78    "--include-tests",
79    "-t",
80    action="append",
81    dest="include_tests",
82    default=[],
83    help="Only run the test matching this expression. This can only be a filename containing a list of failing tests to re-run.",
84)
85parser.add_argument(
86    "--baseline",
87    dest="baseline",
88    help="Folder containing expected results files",
89    default=os.path.dirname(__file__),
90)
91parser.add_argument(
92    "--no-piglit", dest="piglit", help="Disable piglit tests", action="store_false"
93)
94parser.add_argument(
95    "--no-glcts", dest="glcts", help="Disable GLCTS tests", action="store_false"
96)
97parser.add_argument(
98    "--no-deqp", dest="deqp", help="Disable dEQP tests", action="store_false"
99)
100parser.add_argument(
101    "--slow", dest="slow", help="Include slowest glcts tests", action="store_true"
102)
103parser.add_argument(
104    "--no-deqp-egl",
105    dest="deqp_egl",
106    help="Disable dEQP-EGL tests",
107    action="store_false",
108)
109parser.add_argument(
110    "--no-deqp-gles2",
111    dest="deqp_gles2",
112    help="Disable dEQP-gles2 tests",
113    action="store_false",
114)
115parser.add_argument(
116    "--no-deqp-gles3",
117    dest="deqp_gles3",
118    help="Disable dEQP-gles3 tests",
119    action="store_false",
120)
121parser.add_argument(
122    "--no-deqp-gles31",
123    dest="deqp_gles31",
124    help="Disable dEQP-gles31 tests",
125    action="store_false",
126)
127parser.set_defaults(piglit=True)
128parser.set_defaults(glcts=True)
129parser.set_defaults(deqp=True)
130parser.set_defaults(deqp_egl=True)
131parser.set_defaults(deqp_gles2=True)
132parser.set_defaults(deqp_gles3=True)
133parser.set_defaults(deqp_gles31=True)
134parser.set_defaults(slow=False)
135
136parser.add_argument(
137    "output_folder",
138    nargs="?",
139    help="Output folder (logs, etc)",
140    default=os.path.join(
141        tempfile.gettempdir(), datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
142    ),
143)
144
145available_gpus = []
146for f in os.listdir("/dev/dri/by-path"):
147    idx = f.find("-render")
148    if idx < 0:
149        continue
150    # gbm name is the full path, but DRI_PRIME expects a different
151    # format
152    available_gpus += [
153        (
154            os.path.join("/dev/dri/by-path", f),
155            f[:idx].replace(":", "_").replace(".", "_"),
156        )
157    ]
158
159parser.add_argument(
160    "--gpu",
161    type=int,
162    dest="gpu",
163    default=0,
164    help="Select GPU (0..{})".format(len(available_gpus) - 1),
165)
166
167args = parser.parse_args(sys.argv[1:])
168piglit_path = args.piglit_path
169glcts_path = args.glcts_path
170deqp_path = args.deqp_path
171
172if args.parent_path:
173    if args.piglit_path or args.glcts_path or args.deqp_path:
174        parser.print_help()
175        sys.exit(0)
176    piglit_path = os.path.join(args.parent_path, "piglit")
177    glcts_path = os.path.join(args.parent_path, "glcts")
178    deqp_path = os.path.join(args.parent_path, "deqp")
179else:
180    if not args.piglit_path or not args.glcts_path or not args.deqp_path:
181        parser.print_help()
182        sys.exit(0)
183
184base = args.baseline
185skips = os.path.join(os.path.dirname(__file__), "skips.csv")
186
187env = os.environ.copy()
188
189if "DISPLAY" not in env:
190    print_red("DISPLAY environment variable missing.")
191    sys.exit(1)
192p = subprocess.run(
193    ["deqp-runner", "--version"], capture_output="True", check=True, env=env
194)
195for line in p.stdout.decode().split("\n"):
196    if line.find("deqp-runner") >= 0:
197        s = line.split(" ")[1].split(".")
198        if args.verbose > 1:
199            print("Checking deqp-version ({})".format(s))
200        # We want at least 0.9.0
201        if not (int(s[0]) > 0 or int(s[1]) >= 9):
202            print("Expecting deqp-runner 0.9.0+ version (got {})".format(".".join(s)))
203            sys.exit(1)
204
205env["PIGLIT_PLATFORM"] = "gbm"
206
207if "DRI_PRIME" in env:
208    print("Don't use DRI_PRIME. Instead use --gpu N")
209    del env["DRI_PRIME"]
210
211assert "gpu" in args, "--gpu defaults to 0"
212
213gpu_device = available_gpus[args.gpu][1]
214env["DRI_PRIME"] = gpu_device
215env["WAFFLE_GBM_DEVICE"] = available_gpus[args.gpu][0]
216
217# Use piglit's glinfo to determine the GPU name
218gpu_name = "unknown"
219gpu_name_full = ""
220gfx_level = -1
221
222env["AMD_DEBUG"] = "info"
223p = subprocess.run(
224    ["./glinfo"],
225    capture_output="True",
226    cwd=os.path.join(piglit_path, "bin"),
227    check=True,
228    env=env,
229)
230del env["AMD_DEBUG"]
231for line in p.stdout.decode().split("\n"):
232    if "GL_RENDER" in line:
233        line = line.split("=")[1]
234        gpu_name_full = "(".join(line.split("(")[:-1]).strip()
235        gpu_name = line.replace("(TM)", "").split("(")[1].split(",")[0].lower()
236        break
237    elif "gfx_level" in line:
238        gfx_level = int(line.split("=")[1])
239
240output_folder = args.output_folder
241print_green("Tested GPU: '{}' ({}) {}".format(gpu_name_full, gpu_name, gpu_device))
242print_green("Output folder: '{}'".format(output_folder))
243
244count = 1
245while os.path.exists(output_folder):
246    output_folder = "{}.{}".format(os.path.abspath(args.output_folder), count)
247    count += 1
248
249os.mkdir(output_folder)
250new_baseline_folder = os.path.join(output_folder, "new_baseline")
251os.mkdir(new_baseline_folder)
252
253logfile = open(os.path.join(output_folder, "{}-run-tests.log".format(gpu_name)), "w")
254
255spin = itertools.cycle("-\\|/")
256
257shutil.copy(skips, output_folder)
258skips = os.path.join(output_folder, "skips.csv")
259if not args.slow:
260    # Exclude these 3 tests slow tests
261    with open(skips, "a") as f:
262        print("KHR-GL46.copy_image.functional", file=f)
263        print("KHR-GL46.texture_swizzle.smoke", file=f)
264        print(
265            "KHR-GL46.tessellation_shader.tessellation_control_to_tessellation_evaluation.gl_MaxPatchVertices_Position_PointSize",
266            file=f,
267        )
268
269
270def gfx_level_to_str(cl):
271    supported = ["gfx6", "gfx7", "gfx8", "gfx9", "gfx10", "gfx10_3", "gfx11"]
272    if 8 <= cl and cl < 8 + len(supported):
273        return supported[cl - 8]
274    return supported[-1]
275
276
277def run_cmd(args, verbosity):
278    if verbosity > 1:
279        print_yellow(
280            "| Command line argument '"
281            + " ".join(['"{}"'.format(a) for a in args])
282            + "'"
283        )
284    start = datetime.now()
285    proc = subprocess.Popen(
286        args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env
287    )
288    while True:
289        line = proc.stdout.readline().decode()
290        if verbosity > 0:
291            if "ERROR" in line:
292                print_red(line.strip(), prefix="| ")
293            else:
294                print("| " + line.strip())
295        else:
296            sys.stdout.write(next(spin))
297            sys.stdout.flush()
298            sys.stdout.write("\b")
299
300        logfile.write(line)
301
302        if proc.poll() is not None:
303            break
304    proc.wait()
305    end = datetime.now()
306
307    if verbosity == 0:
308        sys.stdout.write(" ... ")
309
310    print_yellow(
311        "Completed in {} seconds".format(int((end - start).total_seconds())),
312        prefix="└ " if verbosity > 0 else None,
313    )
314
315
316def verify_results(results):
317    with open(results) as file:
318        if len(file.readlines()) == 0:
319            return True
320    print_red("New results (fails or pass). Check {}".format(results))
321    return False
322
323
324def parse_test_filters(include_tests):
325    cmd = []
326    for t in include_tests:
327        if os.path.exists(t):
328            with open(t, "r") as file:
329                for row in csv.reader(file, delimiter=","):
330                    if not row or row[0][0] == "#":
331                        continue
332                    print(row)
333                    cmd += ["-t", row[0]]
334        else:
335            cmd += ["-t", t]
336    return cmd
337
338
339def select_baseline(basepath, gfx_level, gpu_name):
340    gfx_level_str = gfx_level_to_str(gfx_level)
341
342    # select the best baseline we can find
343    # 1. exact match
344    exact = os.path.join(base, "{}-{}-fail.csv".format(gfx_level_str, gpu_name))
345    if os.path.exists(exact):
346        return exact
347    # 2. any baseline with the same gfx_level
348    while gfx_level >= 8:
349        for subdir, dirs, files in os.walk(basepath):
350            for file in files:
351                if file.find(gfx_level_str) == 0 and file.endswith("-fail.csv"):
352                    return os.path.join(base, file)
353        # No match. Try an earlier class
354        gfx_level = gfx_level - 1
355        gfx_level_str = gfx_level_to_str(gfx_level)
356
357    return exact
358
359
360filters_args = parse_test_filters(args.include_tests)
361baseline = select_baseline(base, gfx_level, gpu_name)
362flakes = os.path.join(
363    base, "{}-{}-flakes.csv".format(gfx_level_to_str(gfx_level), gpu_name)
364)
365
366if os.path.exists(baseline):
367    print_yellow("Baseline: {}\n".format(baseline), args.verbose > 0)
368if os.path.exists(flakes):
369    print_yellow("[flakes {}]\n".format(flakes), args.verbose > 0)
370
371# piglit test
372if args.piglit:
373    out = os.path.join(output_folder, "piglit")
374    new_baseline = os.path.join(
375        new_baseline_folder, "{}-piglit-quick-fail.csv".format(gpu_name)
376    )
377    print_yellow("Running piglit tests\n", args.verbose > 0)
378    cmd = [
379        "piglit-runner",
380        "run",
381        "--piglit-folder",
382        piglit_path,
383        "--profile",
384        "quick",
385        "--output",
386        out,
387        "--process-isolation",
388        "--timeout",
389        "300",
390        "--jobs",
391        str(args.jobs),
392        "--skips",
393        skips,
394    ] + filters_args
395
396    if os.path.exists(baseline):
397        cmd += ["--baseline", baseline]
398
399    if os.path.exists(flakes):
400        cmd += ["--flakes", flakes]
401
402    run_cmd(cmd, args.verbose)
403    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
404    verify_results(new_baseline)
405
406deqp_args = "-- --deqp-surface-width=256 --deqp-surface-height=256 --deqp-gl-config-name=rgba8888d24s8ms0 --deqp-visibility=hidden".split(
407    " "
408)
409
410# glcts test
411if args.glcts:
412    out = os.path.join(output_folder, "glcts")
413    new_baseline = os.path.join(
414        new_baseline_folder, "{}-glcts-fail.csv".format(gpu_name)
415    )
416    print_yellow("Running  GLCTS tests", args.verbose > 0)
417    os.mkdir(os.path.join(output_folder, "glcts"))
418
419    cmd = [
420        "deqp-runner",
421        "run",
422        "--tests-per-group",
423        "100",
424        "--deqp",
425        "{}/external/openglcts/modules/glcts".format(glcts_path),
426        "--caselist",
427        "{}/external/openglcts/modules/gl_cts/data/mustpass/gl/khronos_mustpass/4.6.1.x/gl46-master.txt".format(
428            glcts_path
429        ),
430        "--output",
431        out,
432        "--skips",
433        skips,
434        "--jobs",
435        str(args.jobs),
436        "--timeout",
437        "1000",
438    ] + filters_args
439
440    if os.path.exists(baseline):
441        cmd += ["--baseline", baseline]
442    cmd += deqp_args
443    run_cmd(cmd, args.verbose)
444    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
445    verify_results(new_baseline)
446
447if args.deqp:
448    print_yellow("Running   dEQP tests", args.verbose > 0)
449
450    # Generate a test-suite file
451    out = os.path.join(output_folder, "deqp")
452    suite_filename = os.path.join(output_folder, "deqp-suite.toml")
453    suite = open(suite_filename, "w")
454    os.mkdir(out)
455    new_baseline = os.path.join(
456        new_baseline_folder, "{}-deqp-fail.csv".format(gpu_name)
457    )
458
459    deqp_tests = {
460        "egl": args.deqp_egl,
461        "gles2": args.deqp_gles2,
462        "gles3": args.deqp_gles3,
463        "gles31": args.deqp_gles31,
464    }
465
466    for k in deqp_tests:
467        if not deqp_tests[k]:
468            continue
469
470        suite.write("[[deqp]]\n")
471        suite.write(
472            'deqp = "{}"\n'.format(
473                "{}/modules/{subtest}/deqp-{subtest}".format(deqp_path, subtest=k)
474            )
475        )
476        suite.write(
477            'caselists = ["{}"]\n'.format(
478                "{}/android/cts/master/{}-master.txt".format(deqp_path, k)
479            )
480        )
481        if os.path.exists(baseline):
482            suite.write('baseline = "{}"\n'.format(baseline))
483        suite.write('skips = ["{}"]\n'.format(skips))
484        suite.write("deqp_args = [\n")
485        for a in deqp_args[1:-1]:
486            suite.write('    "{}",\n'.format(a))
487        suite.write('    "{}"\n'.format(deqp_args[-1]))
488        suite.write("]\n")
489
490    suite.close()
491
492    cmd = [
493        "deqp-runner",
494        "suite",
495        "--jobs",
496        str(args.jobs),
497        "--output",
498        os.path.join(output_folder, "deqp"),
499        "--suite",
500        suite_filename,
501    ] + filters_args
502    run_cmd(cmd, args.verbose)
503    shutil.copy(os.path.join(out, "failures.csv"), new_baseline)
504    verify_results(new_baseline)
505