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