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