autospider.py (23325B)
1 #!/usr/bin/env python3 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 # You can obtain one at http://mozilla.org/MPL/2.0/. 5 6 7 import argparse 8 import json 9 import logging 10 import multiprocessing 11 import os 12 import platform 13 import shlex 14 import shutil 15 import subprocess 16 import sys 17 from collections import Counter, namedtuple 18 from logging import info 19 from os import environ as env 20 from pathlib import Path 21 from subprocess import Popen 22 from threading import Timer 23 24 Dirs = namedtuple("Dirs", ["scripts", "js_src", "source", "fetches"]) 25 26 27 def directories(pathmodule, cwd, fixup=lambda s: s): 28 scripts = pathmodule.join(fixup(cwd), fixup(pathmodule.dirname(__file__))) 29 js_src = pathmodule.abspath(pathmodule.join(scripts, "..", "..")) 30 source = pathmodule.abspath(pathmodule.join(js_src, "..", "..")) 31 mozbuild = pathmodule.abspath( 32 # os.path.expanduser does not work on Windows. 33 env.get("MOZBUILD_STATE_PATH") or pathmodule.join(Path.home(), ".mozbuild") 34 ) 35 fetches = pathmodule.abspath(env.get("MOZ_FETCHES_DIR", mozbuild)) 36 return Dirs(scripts, js_src, source, fetches) 37 38 39 def quote(s): 40 # shlex quotes for the purpose of passing to the native shell, which is cmd 41 # on Windows, and therefore will not replace backslashed paths with forward 42 # slashes. When such a path is passed to sh, the backslashes will be 43 # interpreted as escape sequences. 44 return shlex.quote(s).replace("\\", "/") 45 46 47 # Some scripts will be called with sh, which cannot use backslashed 48 # paths. So for direct subprocess.* invocation, use normal paths from 49 # DIR, but when running under the shell, use POSIX style paths. 50 DIR = directories(os.path, os.getcwd()) 51 52 AUTOMATION = bool(env.get("AUTOMATION")) 53 54 parser = argparse.ArgumentParser(description="Run a spidermonkey shell build job") 55 parser.add_argument( 56 "--verbose", 57 action="store_true", 58 default=AUTOMATION, 59 help="display additional logging info", 60 ) 61 parser.add_argument( 62 "--dep", action="store_true", help="do not clobber the objdir before building" 63 ) 64 parser.add_argument( 65 "--keep", 66 action="store_true", 67 help="do not delete the sanitizer output directory (for testing)", 68 ) 69 parser.add_argument( 70 "--platform", 71 "-p", 72 type=str, 73 metavar="PLATFORM", 74 default="", 75 help='build platform, including a suffix ("-debug" or "") used ' 76 'by buildbot to override the variant\'s "debug" setting. The platform can be ' 77 "used to specify 32 vs 64 bits.", 78 ) 79 parser.add_argument( 80 "--timeout", 81 "-t", 82 type=int, 83 metavar="TIMEOUT", 84 default=12600, 85 help="kill job after TIMEOUT seconds", 86 ) 87 parser.add_argument( 88 "--objdir", 89 type=str, 90 metavar="DIR", 91 # The real default must be set later so that OBJDIR can be 92 # relative to the srcdir. 93 default=env.get("OBJDIR"), 94 help="object directory", 95 ) 96 group = parser.add_mutually_exclusive_group() 97 group.add_argument( 98 "--optimize", 99 action="store_true", 100 help="generate an optimized build. Overrides variant setting.", 101 ) 102 group.add_argument( 103 "--no-optimize", 104 action="store_false", 105 dest="optimize", 106 help="generate a non-optimized build. Overrides variant setting.", 107 ) 108 group.set_defaults(optimize=None) 109 group = parser.add_mutually_exclusive_group() 110 group.add_argument( 111 "--debug", 112 action="store_true", 113 help="generate a debug build. Overrides variant setting.", 114 ) 115 group.add_argument( 116 "--no-debug", 117 action="store_false", 118 dest="debug", 119 help="generate a non-debug build. Overrides variant setting.", 120 ) 121 group.set_defaults(debug=None) 122 group = parser.add_mutually_exclusive_group() 123 group.add_argument( 124 "--jemalloc", 125 action="store_true", 126 dest="jemalloc", 127 help="use mozilla's jemalloc instead of the default allocator", 128 ) 129 group.add_argument( 130 "--no-jemalloc", 131 action="store_false", 132 dest="jemalloc", 133 help="use the default allocator instead of mozilla's jemalloc", 134 ) 135 group.set_defaults(jemalloc=None) 136 parser.add_argument( 137 "--run-tests", 138 "--tests", 139 type=str, 140 metavar="TESTSUITE", 141 default="", 142 help="comma-separated set of test suites to add to the variant's default set", 143 ) 144 parser.add_argument( 145 "--skip-tests", 146 "--skip", 147 type=str, 148 metavar="TESTSUITE", 149 default="", 150 help="comma-separated set of test suites to remove from the variant's default set", 151 ) 152 parser.add_argument( 153 "--build-only", 154 "--build", 155 dest="skip_tests", 156 action="store_const", 157 const="all", 158 help="only do a build, do not run any tests", 159 ) 160 parser.add_argument( 161 "--nobuild", 162 action="store_true", 163 help="Do not do a build. Rerun tests on existing build.", 164 ) 165 parser.add_argument( 166 "variant", type=str, help="type of job requested, see variants/ subdir" 167 ) 168 args = parser.parse_args() 169 170 logging.basicConfig(level=logging.INFO, format="%(message)s") 171 172 env["CPP_UNIT_TESTS_DIR_JS_SRC"] = DIR.js_src 173 if AUTOMATION and platform.system() == "Windows": 174 # build/win{32,64}/mozconfig.vs-latest uses TOOLTOOL_DIR to set VSPATH. 175 env["TOOLTOOL_DIR"] = DIR.fetches 176 177 OBJDIR = args.objdir or os.path.join(DIR.source, "obj-spider") 178 OBJDIR = os.path.abspath(OBJDIR) 179 OUTDIR = os.path.join(OBJDIR, "out") 180 MAKE = env.get("MAKE", "make") 181 PYTHON = sys.executable 182 MACH = os.path.join(DIR.source, "mach") 183 184 for d in DIR._fields: 185 info(f"DIR.{d} = {getattr(DIR, d)}") 186 187 188 def ensure_dir_exists( 189 name, clobber=True, creation_marker_filename="CREATED-BY-AUTOSPIDER" 190 ): 191 if creation_marker_filename is None: 192 marker = None 193 else: 194 marker = os.path.join(name, creation_marker_filename) 195 if clobber: 196 if ( 197 not AUTOMATION 198 and marker 199 and os.path.exists(name) 200 and not os.path.exists(marker) 201 ): 202 raise Exception( 203 "Refusing to delete objdir %s because it was not created by autospider" 204 % name 205 ) 206 shutil.rmtree(name, ignore_errors=True) 207 try: 208 os.mkdir(name) 209 if marker: 210 open(marker, "a").close() 211 except OSError: 212 if clobber: 213 raise 214 215 216 with open(os.path.join(DIR.scripts, "variants", args.variant)) as fh: 217 variant = json.load(fh) 218 219 # Some of the variants request a particular word size (eg ARM simulators). 220 word_bits = variant.get("bits") 221 222 # On Linux and Windows, we build 32- and 64-bit versions on a 64 bit 223 # host, so the caller has to specify what is desired. 224 if word_bits is None and args.platform: 225 platform_arch = args.platform.split("-")[0] 226 if platform_arch in ("win32", "linux"): 227 word_bits = 32 228 elif platform_arch in ("win64", "linux64"): 229 word_bits = 64 230 231 # Fall back to the word size of the host. 232 if word_bits is None: 233 word_bits = 64 if platform.architecture()[0] == "64bit" else 32 234 235 # Need a platform name to use as a key in variant files. 236 if args.platform: 237 variant_platform = args.platform.split("-")[0] 238 elif platform.system() == "Windows": 239 variant_platform = "win64" if word_bits == 64 else "win32" 240 elif platform.system() == "Linux": 241 variant_platform = "linux64" if word_bits == 64 else "linux" 242 elif platform.system() == "Darwin": 243 variant_platform = "macosx64" 244 else: 245 variant_platform = "other" 246 247 CONFIGURE_ARGS = variant["configure-args"] 248 249 if variant_platform in ("win32", "win64"): 250 compiler = "clang-cl" 251 else: 252 compiler = variant.get("compiler") 253 if compiler != "gcc" and "clang-plugin" not in CONFIGURE_ARGS: 254 CONFIGURE_ARGS += " --enable-clang-plugin" 255 256 if compiler == "gcc": 257 if AUTOMATION: 258 fetches = env["MOZ_FETCHES_DIR"] 259 env["CC"] = os.path.join(fetches, "gcc", "bin", "gcc") 260 env["CXX"] = os.path.join(fetches, "gcc", "bin", "g++") 261 else: 262 env["CC"] = "gcc" 263 env["CXX"] = "g++" 264 265 opt = args.optimize 266 if opt is None: 267 opt = variant.get("optimize") 268 if opt is not None: 269 CONFIGURE_ARGS += " --enable-optimize" if opt else " --disable-optimize" 270 271 opt = args.debug 272 if opt is None: 273 opt = variant.get("debug") 274 if opt is not None: 275 CONFIGURE_ARGS += " --enable-debug" if opt else " --disable-debug" 276 277 opt = args.jemalloc 278 if opt is not None: 279 CONFIGURE_ARGS += " --enable-jemalloc" if opt else " --disable-jemalloc" 280 281 # By default, we build with NSPR, even if not specified. But we actively allow 282 # builds to disable NSPR. 283 opt = variant.get("nspr") 284 if opt is None or opt: 285 CONFIGURE_ARGS += " --enable-nspr-build" 286 287 env["LD_LIBRARY_PATH"] = ":".join( 288 d 289 for d in [ 290 # for libnspr etc. 291 os.path.join(OBJDIR, "dist", "bin"), 292 # existing search path, if any 293 env.get("LD_LIBRARY_PATH"), 294 ] 295 if d is not None 296 ) 297 298 os.environ["SOURCE"] = DIR.source 299 if platform.system() == "Windows": 300 MAKE = env.get("MAKE", "mozmake") 301 302 # Configure flags, based on word length and cross-compilation 303 if word_bits == 32: 304 if platform.system() == "Windows": 305 CONFIGURE_ARGS += " --target=i686-pc-windows-msvc" 306 elif platform.system() == "Linux": 307 if not platform.machine().startswith("arm"): 308 CONFIGURE_ARGS += " --target=i686-pc-linux" 309 310 # Add SSE2 support for x86/x64 architectures. 311 if not platform.machine().startswith("arm"): 312 if platform.system() == "Windows": 313 sse_flags = "-arch:SSE2" 314 else: 315 sse_flags = "-msse -msse2 -mfpmath=sse" 316 env["CCFLAGS"] = "{} {}".format(env.get("CCFLAGS", ""), sse_flags) 317 env["CXXFLAGS"] = "{} {}".format(env.get("CXXFLAGS", ""), sse_flags) 318 elif platform.system() == "Windows": 319 CONFIGURE_ARGS += " --target=x86_64-pc-windows-msvc" 320 321 if platform.system() == "Linux" and AUTOMATION: 322 CONFIGURE_ARGS = "--enable-stdcxx-compat " + CONFIGURE_ARGS 323 324 # Timeouts. 325 ACTIVE_PROCESSES = set() 326 327 328 def killall(): 329 for proc in ACTIVE_PROCESSES: 330 proc.kill() 331 ACTIVE_PROCESSES.clear() 332 333 334 timer = Timer(args.timeout, killall) 335 timer.daemon = True 336 timer.start() 337 338 ensure_dir_exists(OBJDIR, clobber=not args.dep and not args.nobuild) 339 ensure_dir_exists(OUTDIR, clobber=not args.keep) 340 341 # Any jobs that wish to produce additional output can save them into the upload 342 # directory if there is such a thing, falling back to OBJDIR. 343 env.setdefault("MOZ_UPLOAD_DIR", OBJDIR) 344 ensure_dir_exists(env["MOZ_UPLOAD_DIR"], clobber=False, creation_marker_filename=None) 345 info("MOZ_UPLOAD_DIR = {}".format(env["MOZ_UPLOAD_DIR"])) 346 347 348 def run_command(command, check=False, **kwargs): 349 kwargs.setdefault("cwd", OBJDIR) 350 info("in directory {}, running {}".format(kwargs["cwd"], command)) 351 if platform.system() == "Windows": 352 # Windows will use cmd for the shell, which causes all sorts of 353 # problems. Use sh instead, quoting appropriately. (Use sh in all 354 # cases, not just when shell=True, because we want to be able to use 355 # paths that sh understands and cmd does not.) 356 if not isinstance(command, list): 357 if kwargs.get("shell"): 358 command = shlex.split(command) 359 else: 360 command = [command] 361 362 command = " ".join(quote(c) for c in command) 363 command = ["sh", "-c", command] 364 kwargs["shell"] = False 365 proc = Popen(command, **kwargs) 366 ACTIVE_PROCESSES.add(proc) 367 stdout, stderr = None, None 368 try: 369 stdout, stderr = proc.communicate() 370 finally: 371 ACTIVE_PROCESSES.discard(proc) 372 status = proc.wait() 373 if check and status != 0: 374 raise subprocess.CalledProcessError(status, command, output=stderr) 375 return stdout, stderr, status 376 377 378 def run_mach_command(command, check=False, **kwargs): 379 _stdout, _stderr, status = run_command( 380 [PYTHON, MACH] + command, check=check, **kwargs 381 ) 382 return status 383 384 385 # Replacement strings in environment variables. 386 REPLACEMENTS = { 387 "DIR": DIR.scripts, 388 "MOZ_FETCHES_DIR": DIR.fetches, 389 "MOZ_UPLOAD_DIR": env["MOZ_UPLOAD_DIR"], 390 "OUTDIR": OUTDIR, 391 } 392 393 # Add in environment variable settings for this variant. Normally used to 394 # modify the flags passed to the shell or to set the GC zeal mode. 395 for k, v in variant.get("env", {}).items(): 396 env[k] = v.format(**REPLACEMENTS) 397 398 # Do similar substitution for the extra_args keyed lists. 399 extra_args = {"jit-test": [], "jstests": [], "gdb": []} 400 extra_args.update(variant.get("extra-args", {})) 401 for v in extra_args.values(): 402 v[:] = [arg.format(**REPLACEMENTS) for arg in v] 403 404 if AUTOMATION: 405 # Currently only supported on linux64. 406 if platform.system() == "Linux" and word_bits == 64: 407 use_minidump = variant.get("use_minidump", True) 408 else: 409 use_minidump = False 410 else: 411 use_minidump = False 412 413 414 def resolve_path(dirs, *components): 415 if None in components: 416 return None 417 for dir in dirs: 418 path = os.path.join(dir, *components) 419 if os.path.exists(path): 420 return path 421 422 423 if use_minidump: 424 env.setdefault("MINIDUMP_SAVE_PATH", env["MOZ_UPLOAD_DIR"]) 425 426 injector_basename = { 427 "Linux": "libbreakpadinjector.so", 428 "Darwin": "breakpadinjector.dylib", 429 }.get(platform.system()) 430 431 injector_lib = resolve_path((DIR.fetches,), "injector", injector_basename) 432 stackwalk = resolve_path((DIR.fetches,), "minidump-stackwalk", "minidump-stackwalk") 433 if stackwalk is not None: 434 env.setdefault("MINIDUMP_STACKWALK", stackwalk) 435 dump_syms = resolve_path((DIR.fetches,), "dump_syms", "dump_syms") 436 if dump_syms is not None: 437 env.setdefault("DUMP_SYMS", dump_syms) 438 439 if injector_lib is None: 440 use_minidump = False 441 442 info(f"use_minidump is {use_minidump}") 443 info(" MINIDUMP_SAVE_PATH={}".format(env["MINIDUMP_SAVE_PATH"])) 444 info(f" injector lib is {injector_lib}") 445 info(" MINIDUMP_STACKWALK={}".format(env.get("MINIDUMP_STACKWALK"))) 446 447 448 mozconfig = os.path.join(DIR.source, "mozconfig.autospider") 449 CONFIGURE_ARGS += f" --prefix={quote(OBJDIR)}/dist" 450 451 # Generate a mozconfig. 452 with open(mozconfig, "w") as fh: 453 if AUTOMATION and platform.system() == "Windows": 454 fh.write('. "$topsrcdir/build/mozconfig.clang-cl"\n') 455 fh.write("ac_add_options --enable-project=js\n") 456 fh.write("ac_add_options " + CONFIGURE_ARGS + "\n") 457 fh.write("mk_add_options MOZ_OBJDIR=" + quote(OBJDIR) + "\n") 458 459 env["MOZCONFIG"] = mozconfig 460 461 if not args.nobuild: 462 # Do the build 463 run_mach_command(["build"], check=True) 464 465 if use_minidump: 466 # Convert symbols to breakpad format. 467 cmd_env = env.copy() 468 cmd_env["MOZ_SOURCE_REPO"] = "file://" + DIR.source 469 cmd_env["RUSTC_COMMIT"] = "0" 470 cmd_env["MOZ_CRASHREPORTER"] = "1" 471 cmd_env["MOZ_AUTOMATION_BUILD_SYMBOLS"] = "1" 472 run_mach_command( 473 [ 474 "build", 475 "recurse_syms", 476 ], 477 check=True, 478 env=cmd_env, 479 ) 480 481 COMMAND_PREFIX = [] 482 # On Linux, disable ASLR to make shell builds a bit more reproducible. 483 # Bug 1795718 - Disable in automation for now as call to setarch requires extra 484 # docker privileges. 485 if not AUTOMATION and subprocess.call("type setarch >/dev/null 2>&1", shell=True) == 0: 486 COMMAND_PREFIX.extend(["setarch", platform.machine(), "-R"]) 487 488 489 def run_test_command(command, **kwargs): 490 _, _, status = run_command(COMMAND_PREFIX + command, check=False, **kwargs) 491 return status 492 493 494 def run_jsapitests(args): 495 test_env = env.copy() 496 if use_minidump and platform.system() == "Linux": 497 test_env["LD_PRELOAD"] = injector_lib 498 st = run_mach_command(["jsapi-tests"], env=test_env) 499 if st < 0: 500 info = " ".join(["jsapi-tests"] + args) 501 print(f"PROCESS-CRASH | {info} | application crashed") 502 print(f"Return code: {st}") 503 return st 504 505 506 default_test_suites = frozenset(["jstests", "jittest", "jsapitests", "checks"]) 507 nondefault_test_suites = frozenset(["gdb"]) 508 all_test_suites = default_test_suites | nondefault_test_suites 509 510 test_suites = set(default_test_suites) 511 512 513 def normalize_tests(tests): 514 if "all" in tests: 515 return default_test_suites 516 return tests 517 518 519 # Override environment variant settings conditionally. 520 for k, v in variant.get("conditional-env", {}).get(variant_platform, {}).items(): 521 env[k] = v.format(**REPLACEMENTS) 522 523 # Skip any tests that are not run on this platform (or the 'all' platform). 524 test_suites -= set( 525 normalize_tests(variant.get("skip-tests", {}).get(variant_platform, [])) 526 ) 527 test_suites -= set(normalize_tests(variant.get("skip-tests", {}).get("all", []))) 528 529 # Add in additional tests for this platform (or the 'all' platform). 530 test_suites |= set( 531 normalize_tests(variant.get("extra-tests", {}).get(variant_platform, [])) 532 ) 533 test_suites |= set(normalize_tests(variant.get("extra-tests", {}).get("all", []))) 534 535 # Now adjust the variant's default test list with command-line arguments. 536 test_suites |= set(normalize_tests(args.run_tests.split(","))) 537 test_suites -= set(normalize_tests(args.skip_tests.split(","))) 538 if "all" in args.skip_tests.split(","): 539 test_suites = [] 540 541 # Bug 1557130 - Atomics tests can create many additional threads which can 542 # lead to resource exhaustion, resulting in intermittent failures. This was 543 # only seen on beefy machines (> 32 cores), so limit the number of parallel 544 # workers for now. 545 # 546 # Bug 1391877 - Windows test runs are getting mysterious timeouts when run 547 # through taskcluster, but only when running many jit-test jobs in parallel. 548 # Even at 16, some tests can overflow the paging file. 549 worker_max = multiprocessing.cpu_count() 550 jstest_workers = worker_max 551 jittest_workers = worker_max 552 if platform.system() == "Windows": 553 jstest_workers = min(worker_max, 16) 554 extra_args["jstests"].append(f"-j{jstest_workers}") 555 jittest_workers = min(worker_max, 8) 556 extra_args["jit-test"].append(f"-j{jittest_workers}") 557 print( 558 f"using {jstest_workers}/{worker_max} workers for jstests, " 559 f"{jittest_workers}/{worker_max} for jittest" 560 ) 561 562 if use_minidump: 563 # Set up later js invocations to run with the breakpad injector loaded. 564 # Originally, I intended for this to be used with LD_PRELOAD, but when 565 # cross-compiling from 64- to 32-bit, that will fail and produce stderr 566 # output when running any 64-bit commands, which breaks eg mozconfig 567 # processing. So use the --dll command line mechanism universally. 568 for suite in ("jstests", "jit-test"): 569 extra_args[suite].append(f"--args=--dll {injector_lib}") 570 571 # Report longest running tests in automation. 572 for suite in ("jstests", "jit-test"): 573 extra_args[suite].append("--show-slow") 574 575 # Always run all enabled tests, even if earlier ones failed. But return the 576 # first failed status. 577 results = [("(make-nonempty)", 0)] 578 579 if "checks" in test_suites: 580 results.append(("make check", run_test_command([MAKE, "check"]))) 581 582 if "jittest" in test_suites: 583 auto_args = [] 584 if AUTOMATION: 585 auto_args = [ 586 "--no-slow", 587 "--no-progress", 588 "--format=automation", 589 "--timeout=300", 590 "--jitflags=all", 591 ] 592 results.append(( 593 "mach jit-test", 594 run_mach_command(["jit-test", "--", *auto_args, *extra_args["jit-test"]]), 595 )) 596 if "jsapitests" in test_suites: 597 st = run_jsapitests([]) 598 if st == 0: 599 st = run_jsapitests(["--frontend-only"]) 600 results.append(("jsapi-tests", st)) 601 if "jstests" in test_suites: 602 auto_args = [] 603 if AUTOMATION: 604 auto_args = ["--no-progress", "--format=automation", "--timeout=300"] 605 results.append(( 606 "mach jstests", 607 run_mach_command(["jstests", "--", *auto_args, *extra_args["jstests"]]), 608 )) 609 if "gdb" in test_suites: 610 test_script = os.path.join(DIR.js_src, "gdb", "run-tests.py") 611 auto_args = ["-s", "-o", "--no-progress"] if AUTOMATION else [] 612 results.append(( 613 "gdb", 614 run_test_command([PYTHON, test_script, *auto_args, *extra_args["gdb"], OBJDIR]), 615 )) 616 617 # FIXME bug 1291449: This would be unnecessary if we could run msan with -mllvm 618 # -msan-keep-going, but in clang 3.8 it causes a hang during compilation. 619 if variant.get("ignore-test-failures"): 620 logging.warning("Ignoring test results %s" % (results,)) 621 results = [("ignored", 0)] 622 623 if args.variant == "msan": 624 files = filter(lambda f: f.startswith("sanitize_log."), os.listdir(OUTDIR)) 625 fullfiles = [os.path.join(OUTDIR, f) for f in files] 626 627 # Summarize results 628 sites = Counter() 629 errors = Counter() 630 for filename in fullfiles: 631 with open(os.path.join(OUTDIR, filename), "rb") as fh: 632 for line in fh: 633 m = re.match( 634 r"^SUMMARY: \w+Sanitizer: (?:data race|use-of-uninitialized-value) (.*)", # NOQA: E501 635 line.strip(), 636 ) 637 if m: 638 # Some reports include file:line:column, some just 639 # file:line. Just in case it's nondeterministic, we will 640 # canonicalize to just the line number. 641 site = re.sub(r"^(\S+?:\d+)(:\d+)* ", r"\1 ", m.group(1)) 642 sites[site] += 1 643 644 # Write a summary file and display it to stdout. 645 summary_filename = os.path.join( 646 env["MOZ_UPLOAD_DIR"], "%s_summary.txt" % args.variant 647 ) 648 with open(summary_filename, "wb") as outfh: 649 for location, count in sites.most_common(): 650 print >> outfh, "%d %s" % (count, location) 651 print(open(summary_filename, "rb").read()) 652 653 if "max-errors" in variant: 654 max_allowed = variant["max-errors"] 655 print("Found %d errors out of %d allowed" % (len(sites), max_allowed)) 656 if len(sites) > max_allowed: 657 results.append(("too many msan errors", 1)) 658 659 # Gather individual results into a tarball. Note that these are 660 # distinguished only by pid of the JS process running within each test, so 661 # given the 16-bit limitation of pids, it's totally possible that some of 662 # these files will be lost due to being overwritten. 663 command = [ 664 "tar", 665 "-C", 666 OUTDIR, 667 "-zcf", 668 os.path.join(env["MOZ_UPLOAD_DIR"], "%s.tar.gz" % args.variant), 669 ] 670 command += files 671 subprocess.call(command) 672 673 # Upload dist/bin/js as js.wasm for the WASI build. 674 if args.variant == "wasi": 675 command = [ 676 "cp", 677 os.path.join(OBJDIR, "dist/bin/js"), 678 os.path.join(env["MOZ_UPLOAD_DIR"], "js.wasm"), 679 ] 680 subprocess.call(command) 681 682 # Generate stacks from minidumps. 683 if use_minidump: 684 run_mach_command([ 685 "python", 686 "--virtualenv=build", 687 os.path.join(DIR.source, "testing/mozbase/mozcrash/mozcrash/mozcrash.py"), 688 os.getenv("TMPDIR", "/tmp"), 689 os.path.join(OBJDIR, "dist/crashreporter-symbols"), 690 ]) 691 692 for name, st in results: 693 print("exit status %d for '%s'" % (st, name)) 694 695 # Pick the "worst" exit status. SIGSEGV might give a status of -11, so use the 696 # maximum absolute value instead of just the maximum. 697 exit_status = max((st for _, st in results), key=abs) 698 699 # The exit status on Windows can be something like 2147483651 (0x80000003), 700 # which will be converted to status zero in the caller. Mask off the high bits, 701 # but if the result is zero then fall back to returning 1. 702 if exit_status & 0xFF: 703 sys.exit(exit_status & 0xFF) 704 else: 705 sys.exit(1 if exit_status else 0)