1#!/usr/bin/python 2# Copyright (c) 2012 The Native Client Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6 7"""Simple testing harness for running commands and checking expected output. 8 9This harness is used instead of shell scripts to ensure windows compatibility 10 11""" 12 13from __future__ import print_function 14 15import getopt 16import os 17import pipes 18import re 19import sys 20 21# local imports 22import test_lib 23 24GlobalPlatform=None # for pychecker, initialized in ProcessOptions 25GlobalSettings = {} 26 27 28def Print(message): 29 print(message) 30 31 32def Banner(message): 33 Print('=' * 70) 34 print(message) 35 print('=' * 70) 36 37 38def DifferentFromGolden(actual, golden, output_type): 39 """Compares actual output against golden output. 40 41 If there are any differences, output an error message (to stdout) with 42 appropriate banners. 43 44 Args: 45 actual: actual output from the program under test, as a single 46 string. 47 48 golden: expected output from the program under test, as a single 49 string. 50 51 output_type: the name / title for the output type being compared. 52 Used in banner output. 53 54 Returns: 55 True when there is a difference, False otherwise. 56 """ 57 58 diff = list(test_lib.DiffStringsIgnoringWhiteSpace(golden, actual)) 59 diff = '\n'.join(diff) 60 if diff: 61 Banner('Error %s diff found' % output_type) 62 Print(diff) 63 Banner('Potential New Golden Output') 64 Print(actual) 65 return True 66 return False 67 68 69def ResetGlobalSettings(): 70 global GlobalSettings 71 GlobalSettings = { 72 'exit_status': 0, 73 'using_nacl_signal_handler': False, 74 75 # When declares_exit_status is set, we read the expected exit 76 # status from stderr. We look for a line of the form 77 # "** intended_exit_status=X". This allows crash tests to 78 # declare their expected exit status close to where the crash is 79 # generated, rather than in a Scons file. It reduces the risk 80 # that the test passes accidentally by crashing during setup. 81 'declares_exit_status': False, 82 83 # List of environment variables to set. 84 'osenv': '', 85 86 'arch': None, 87 'subarch': None, 88 89 # An environment description that should include all factors that may 90 # affect tracked performance. Used to compare different environments. 91 'perf_env_description': None, 92 93 'name': None, 94 'output_stamp': None, 95 96 'stdin': None, 97 'log_file': None, 98 99 'stdout_golden': None, 100 'stderr_golden': None, 101 'log_golden': None, 102 103 # This option must be '1' for the output to be captured, for checking 104 # against golden files, special exit_status signals, etc. 105 # When this option is '0', stdout and stderr will be streamed out. 106 'capture_output': '1', 107 # This option must be '1' for the stderr to be captured with stdout 108 # (it's ignored if capture_output == 0). If this option is '0' only 109 # stdout will be captured and stderr will be streamed out. 110 'capture_stderr': '1', 111 112 'filter_regex': None, 113 'filter_inverse': False, 114 'filter_group_only': False, 115 116 # Number of times a test is run. 117 # This is useful for getting multiple samples for time perf tests. 118 'num_runs': 1, 119 120 # Scripts for processing output along with its arguments. 121 # This script is given the output of a single run. 122 'process_output_single': None, 123 # This script is given the concatenated output of all |num_runs|, after 124 # having been filtered by |process_output_single| for individual runs. 125 'process_output_combined': None, 126 127 'time_warning': 0, 128 'time_error': 0, 129 130 'run_under': None, 131 } 132 133def StringifyList(lst): 134 return ','.join(lst) 135 136def DestringifyList(lst): 137 # BUG(robertm): , is a legitimate character for an environment variable 138 # value. 139 return lst.split(',') 140 141# The following messages match gtest's formatting. This improves log 142# greppability for people who primarily work on Chrome. It also allows 143# gtest-specific hooks on the buildbots to fire. 144# The buildbots expect test names in the format "suite_name.test_name", so we 145# prefix the test name with a bogus suite name (nacl). 146def RunMessage(): 147 return '[ RUN ] %s' % (GlobalSettings['name'],) 148 149def FailureMessage(total_time): 150 return '[ FAILED ] %s (%d ms)' % (GlobalSettings['name'], 151 total_time * 1000.0) 152 153def SuccessMessage(total_time): 154 return '[ OK ] %s (%d ms)' % (GlobalSettings['name'], 155 total_time * 1000.0) 156 157def LogPerfResult(graph_name, trace_name, value, units): 158 # NOTE: This RESULT message is parsed by Chrome's perf graph generator. 159 Print('RESULT %s: %s= %s %s' % 160 (graph_name, trace_name, value, units)) 161 162 163# On POSIX systems, exit() codes are 8-bit. You cannot use exit() to 164# make it look like the process was killed by a signal. Instead, 165# NaCl's signal handler encodes the signal number into the exit() code 166# by returning with exit(-signum) or equivalently, exit((-signum) & 0xff). 167def IndirectSignal(signum): 168 return (-signum) & 0xff 169 170# Windows exit codes that indicate unhandled exceptions. 171STATUS_ACCESS_VIOLATION = 0xc0000005 172STATUS_ILLEGAL_INSTRUCTION = 0xc000001d 173STATUS_PRIVILEGED_INSTRUCTION = 0xc0000096 174STATUS_FLOAT_DIVIDE_BY_ZERO = 0xc000008e 175STATUS_INTEGER_DIVIDE_BY_ZERO = 0xc0000094 176 177# Python's wrapper for GetExitCodeProcess() treats the STATUS_* values 178# as negative, although the unsigned values are used in headers and 179# are more widely recognised. 180def MungeWindowsErrorExit(num): 181 return num - 0x100000000 182 183# If a crash occurs in x86-32 untrusted code on Windows, the kernel 184# apparently gets confused about the cause. It fails to take %cs into 185# account when examining the faulting instruction, so it looks at the 186# wrong instruction, so we could get either of the errors below. 187# See http://code.google.com/p/nativeclient/issues/detail?id=1689 188win32_untrusted_crash_exit = [ 189 MungeWindowsErrorExit(STATUS_ACCESS_VIOLATION), 190 MungeWindowsErrorExit(STATUS_PRIVILEGED_INSTRUCTION)] 191 192win32_sigfpe = [ 193 MungeWindowsErrorExit(STATUS_FLOAT_DIVIDE_BY_ZERO), 194 MungeWindowsErrorExit(STATUS_INTEGER_DIVIDE_BY_ZERO), 195 ] 196 197# We patch Windows' KiUserExceptionDispatcher on x86-64 to terminate 198# the process safely when untrusted code crashes. We get the exit 199# code associated with the HLT instruction. 200win64_exit_via_ntdll_patch = [ 201 MungeWindowsErrorExit(STATUS_PRIVILEGED_INSTRUCTION)] 202 203 204# Mach exception code for Mac OS X. 205EXC_BAD_ACCESS = 1 206 207 208# 32-bit processes on Mac OS X return SIGBUS in most of the cases where Linux 209# returns SIGSEGV, except for actual x86 segmentation violations. 64-bit 210# processes on Mac OS X behave differently. 211status_map = { 212 'sigtrap' : { 213 'linux2': [-5], # SIGTRAP 214 'darwin': [-5], # SIGTRAP 215 }, 216 'trusted_sigabrt' : { 217 'linux2': [-6], # SIGABRT 218 'mac32': [-6], # SIGABRT 219 'mac64': [-6], # SIGABRT 220 # On Windows, NaClAbort() exits using the HLT instruction. 221 'win32': [MungeWindowsErrorExit(STATUS_PRIVILEGED_INSTRUCTION)], 222 'win64': [MungeWindowsErrorExit(STATUS_PRIVILEGED_INSTRUCTION)], 223 }, 224 'naclabort_coverage' : { 225 # This case is here because NaClAbort() behaves differently when 226 # code coverage is enabled. 227 # This is not used on Windows. 228 'linux2': [IndirectSignal(6)], # SIGABRT 229 'mac32': [IndirectSignal(6)], # SIGABRT 230 'mac64': [IndirectSignal(6)], # SIGABRT 231 }, 232 'sigpipe': { 233 # This is not used on Windows because Windows does not have an 234 # equivalent of SIGPIPE. 235 'linux2': [-13], # SIGPIPE 236 'mac32': [-13], # SIGPIPE 237 'mac64': [-13], # SIGPIPE 238 }, 239 'untrusted_sigsegv': { 240 'linux2': [-11], # SIGSEGV 241 'mac32': [-11], # SIGSEGV 242 'mac64': [-11], # SIGSEGV 243 'win32': win32_untrusted_crash_exit, 244 'win64': win64_exit_via_ntdll_patch, 245 }, 246 'untrusted_sigill' : { 247 'linux2': [-4], # SIGILL 248 'mac32': [-4], # SIGILL 249 'mac64': [-4], # SIGILL 250 'win32': win32_untrusted_crash_exit, 251 'win64': win64_exit_via_ntdll_patch, 252 }, 253 'untrusted_sigfpe' : { 254 'linux2': [-8], # SIGFPE 255 'mac32': [-8], # SIGFPE 256 'mac64': [-8], # SIGFPE 257 'win32': win32_sigfpe, 258 'win64': win64_exit_via_ntdll_patch, 259 }, 260 'untrusted_segfault': { 261 'linux2': [-11], # SIGSEGV 262 'mac32': [-10], # SIGBUS 263 'mac64': [-10], # SIGBUS 264 'mach_exception': EXC_BAD_ACCESS, 265 'win32': win32_untrusted_crash_exit, 266 'win64': win64_exit_via_ntdll_patch, 267 }, 268 'untrusted_sigsegv_or_equivalent': { 269 'linux2': [-11], # SIGSEGV 270 'mac32': [-11], # SIGSEGV 271 'mac64': [-10], # SIGBUS 272 'win32': win32_untrusted_crash_exit, 273 'win64': win64_exit_via_ntdll_patch, 274 }, 275 'trusted_segfault': { 276 'linux2': [-11], # SIGSEGV 277 'mac32': [-10], # SIGBUS 278 'mac64': [-11], # SIGSEGV 279 'mach_exception': EXC_BAD_ACCESS, 280 'win32': [MungeWindowsErrorExit(STATUS_ACCESS_VIOLATION)], 281 'win64': [MungeWindowsErrorExit(STATUS_ACCESS_VIOLATION)], 282 }, 283 'trusted_sigsegv_or_equivalent': { 284 'linux2': [-11], # SIGSEGV 285 'mac32': [-11], # SIGSEGV 286 'mac64': [-11], # SIGSEGV 287 'win32': [], 288 'win64': [], 289 }, 290 # This is like 'untrusted_segfault', but without the 'untrusted_' 291 # prefix which marks the status type as expecting a 292 # gracefully-printed exit message from nacl_signal_common.c. This 293 # is a special case because we use different methods for writing 294 # the exception stack frame on different platforms. On Mac and 295 # Windows, NaCl uses a system call which will detect unwritable 296 # pages, so the exit status appears as an unhandled fault from 297 # untrusted code. On Linux, NaCl's signal handler writes the 298 # frame directly, so the exit status comes from getting a SIGSEGV 299 # inside the SIGSEGV handler. 300 'unwritable_exception_stack': { 301 'linux2': [-11], # SIGSEGV 302 'mac32': [-10], # SIGBUS 303 'mac64': [-10], # SIGBUS 304 'win32': win32_untrusted_crash_exit, 305 'win64': win64_exit_via_ntdll_patch, 306 }, 307 # Expectations for __builtin_trap(), which is compiled to different 308 # instructions by the GCC and LLVM toolchains. The exact exit status 309 # does not matter too much, as long as it's a crash and not a graceful 310 # exit via exit() or _exit(). We want __builtin_trap() to trigger the 311 # debugger or a crash reporter. 312 'untrusted_builtin_trap': { 313 'linux2': [-4, -5, -11], 314 'mac32': [-4, -10, -11], 315 'mac64': [-4, -10, -11], 316 'win32': win32_untrusted_crash_exit + 317 [MungeWindowsErrorExit(STATUS_ILLEGAL_INSTRUCTION)], 318 'win64': win64_exit_via_ntdll_patch, 319 }, 320 } 321 322 323def ProcessOptions(argv): 324 global GlobalPlatform 325 326 """Process command line options and return the unprocessed left overs.""" 327 ResetGlobalSettings() 328 try: 329 opts, args = getopt.getopt(argv, '', [x + '=' for x in GlobalSettings]) 330 except getopt.GetoptError, err: 331 Print(str(err)) # will print something like 'option -a not recognized' 332 sys.exit(1) 333 334 for o, a in opts: 335 # strip the leading '--' 336 option = o[2:] 337 assert option in GlobalSettings 338 if option == 'exit_status': 339 GlobalSettings[option] = a 340 elif type(GlobalSettings[option]) == int: 341 GlobalSettings[option] = int(a) 342 else: 343 GlobalSettings[option] = a 344 345 if (sys.platform == 'win32') and (GlobalSettings['subarch'] == '64'): 346 GlobalPlatform = 'win64' 347 elif (sys.platform == 'darwin'): 348 # mac32, mac64 349 GlobalPlatform = 'mac' + GlobalSettings['subarch'] 350 else: 351 GlobalPlatform = sys.platform 352 353 # return the unprocessed options, i.e. the command 354 return args 355 356 357# Parse output for signal type and number 358# 359# The '** Signal' output is from the nacl signal handler code. 360# 361# Since it is possible for there to be an output race with another 362# thread, or additional output due to atexit functions, we scan the 363# output in reverse order for the signal signature. 364def GetNaClSignalInfoFromStderr(stderr): 365 lines = stderr.splitlines() 366 367 # Scan for signal msg in reverse order 368 for curline in reversed(lines): 369 match = re.match('\*\* (Signal|Mach exception) (\d+) from ' 370 '(trusted|untrusted) code', curline) 371 if match is not None: 372 return match.group(0) 373 return None 374 375def GetQemuSignalFromStderr(stderr, default): 376 for line in reversed(stderr.splitlines()): 377 # Look for 'qemu: uncaught target signal XXX'. 378 words = line.split() 379 if (len(words) > 4 and 380 words[0] == 'qemu:' and words[1] == 'uncaught' and 381 words[2] == 'target' and words[3] == 'signal'): 382 return -int(words[4]) 383 return default 384 385def FormatExitStatus(number): 386 # Include the hex version because it makes the Windows error exit 387 # statuses (STATUS_*) more recognisable. 388 return '%i (0x%x)' % (number, number & 0xffffffff) 389 390def FormatResult((exit_status, printed_status)): 391 return 'exit status %s and signal info %r' % ( 392 FormatExitStatus(exit_status), printed_status) 393 394def PrintStdStreams(stdout, stderr): 395 if stderr is not None: 396 Banner('Stdout for %s:' % os.path.basename(GlobalSettings['name'])) 397 Print(stdout) 398 Banner('Stderr for %s:' % os.path.basename(GlobalSettings['name'])) 399 Print(stderr) 400 401def GetIntendedExitStatuses(stderr): 402 statuses = [] 403 for line in stderr.splitlines(): 404 match = re.match(r'\*\* intended_exit_status=(.*)$', line) 405 if match is not None: 406 statuses.append(match.group(1)) 407 return statuses 408 409def CheckExitStatus(failed, req_status, using_nacl_signal_handler, 410 exit_status, stdout, stderr): 411 if GlobalSettings['declares_exit_status']: 412 assert req_status == 0 413 intended_statuses = GetIntendedExitStatuses(stderr) 414 if len(intended_statuses) == 0: 415 Print('\nERROR: Command returned exit status %s but did not output an ' 416 'intended_exit_status line to stderr - did it exit too early?' 417 % FormatExitStatus(exit_status)) 418 return False 419 elif len(intended_statuses) != 1: 420 Print('\nERROR: Command returned exit status %s but produced ' 421 'multiple intended_exit_status lines (%s)' 422 % (FormatExitStatus(exit_status), ', '.join(intended_statuses))) 423 return False 424 else: 425 req_status = intended_statuses[0] 426 427 expected_sigtype = 'normal' 428 if req_status in status_map: 429 expected_statuses = status_map[req_status][GlobalPlatform] 430 if using_nacl_signal_handler: 431 if req_status.startswith('trusted_'): 432 expected_sigtype = 'trusted' 433 elif req_status.startswith('untrusted_'): 434 expected_sigtype = 'untrusted' 435 else: 436 expected_statuses = [int(req_status)] 437 438 expected_printed_status = None 439 if expected_sigtype == 'normal': 440 expected_results = [(status, None) for status in expected_statuses] 441 else: 442 if sys.platform == 'darwin': 443 # Mac OS X 444 default = '<mach_exception field missing for %r>' % req_status 445 expected_printed_status = '** Mach exception %s from %s code' % ( 446 status_map.get(req_status, {}).get('mach_exception', default), 447 expected_sigtype) 448 expected_results = [(status, expected_printed_status) 449 for status in expected_statuses] 450 else: 451 # Linux 452 assert sys.platform != 'win32' 453 454 def MapStatus(status): 455 # Expected value should be a signal number, negated. 456 assert status < 0, status 457 expected_printed_signum = -status 458 expected_printed_status = '** Signal %d from %s code' % ( 459 expected_printed_signum, 460 expected_sigtype) 461 return (IndirectSignal(expected_printed_signum), 462 expected_printed_status) 463 464 expected_results = [MapStatus(status) for status in expected_statuses] 465 466 # If an uncaught signal occurs under QEMU (on ARM), the exit status 467 # contains the signal number, mangled as per IndirectSignal(). We 468 # extract the unadulterated signal number from QEMU's log message in 469 # stderr instead. If we are not using QEMU, or no signal is raised 470 # under QEMU, this is a no-op. 471 if stderr is not None: 472 exit_status = GetQemuSignalFromStderr(stderr, exit_status) 473 474 if using_nacl_signal_handler and stderr is not None: 475 actual_printed_status = GetNaClSignalInfoFromStderr(stderr) 476 else: 477 actual_printed_status = None 478 479 actual_result = (exit_status, actual_printed_status) 480 msg = '\nERROR: Command returned: %s\n' % FormatResult(actual_result) 481 msg += 'but we expected: %s' % '\n or: '.join(FormatResult(r) 482 for r in expected_results) 483 if actual_result not in expected_results: 484 Print(msg) 485 failed = True 486 return not failed 487 488def CheckTimeBounds(total_time): 489 if GlobalSettings['time_error']: 490 if total_time > GlobalSettings['time_error']: 491 Print('ERROR: should have taken less than %f secs' % 492 (GlobalSettings['time_error'])) 493 return False 494 495 if GlobalSettings['time_warning']: 496 if total_time > GlobalSettings['time_warning']: 497 Print('WARNING: should have taken less than %f secs' % 498 (GlobalSettings['time_warning'])) 499 return True 500 501def CheckGoldenOutput(stdout, stderr): 502 for (stream, getter) in [ 503 ('stdout', lambda: stdout), 504 ('stderr', lambda: stderr), 505 ('log', lambda: open(GlobalSettings['log_file']).read()), 506 ]: 507 golden = stream + '_golden' 508 if GlobalSettings[golden]: 509 golden_data = open(GlobalSettings[golden]).read() 510 actual = getter() 511 if GlobalSettings['filter_regex']: 512 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], 513 GlobalSettings['filter_inverse'], 514 GlobalSettings['filter_group_only'], 515 actual) 516 if DifferentFromGolden(actual, golden_data, stream): 517 return False 518 return True 519 520def ProcessLogOutputSingle(stdout, stderr): 521 output_processor = GlobalSettings['process_output_single'] 522 if output_processor is None: 523 return (True, stdout, stderr) 524 else: 525 output_processor_cmd = DestringifyList(output_processor) 526 # Also, get the output from log_file to get NaClLog output in Windows. 527 log_output = open(GlobalSettings['log_file']).read() 528 # Assume the log processor does not care about the order of the lines. 529 all_output = log_output + stdout + stderr 530 _, retcode, failed, new_stdout, new_stderr = \ 531 test_lib.RunTestWithInputOutput( 532 output_processor_cmd, all_output, 533 timeout=GlobalSettings['time_error']) 534 # Print the result, since we have done some processing and we need 535 # to have the processed data. However, if we intend to process it some 536 # more later via process_output_combined, do not duplicate the data here. 537 # Only print out the final result! 538 if not GlobalSettings['process_output_combined']: 539 PrintStdStreams(new_stdout, new_stderr) 540 if retcode != 0 or failed: 541 return (False, new_stdout, new_stderr) 542 else: 543 return (True, new_stdout, new_stderr) 544 545def ProcessLogOutputCombined(stdout, stderr): 546 output_processor = GlobalSettings['process_output_combined'] 547 if output_processor is None: 548 return True 549 else: 550 output_processor_cmd = DestringifyList(output_processor) 551 all_output = stdout + stderr 552 _, retcode, failed, new_stdout, new_stderr = \ 553 test_lib.RunTestWithInputOutput( 554 output_processor_cmd, all_output, 555 timeout=GlobalSettings['time_error']) 556 # Print the result, since we have done some processing. 557 PrintStdStreams(new_stdout, new_stderr) 558 if retcode != 0 or failed: 559 return False 560 else: 561 return True 562 563def DoRun(command, stdin_data): 564 """ 565 Run the command, given stdin_data. Returns a return code (0 is good) 566 and optionally a captured version of stdout, stderr from the run 567 (if the global setting capture_output is true). 568 """ 569 # Initialize stdout, stderr to indicate we have not captured 570 # any of stdout or stderr. 571 stdout = '' 572 stderr = '' 573 if not int(GlobalSettings['capture_output']): 574 # We are only blurting out the stdout and stderr, not capturing it 575 # for comparison, etc. 576 assert (not GlobalSettings['stdout_golden'] 577 and not GlobalSettings['stderr_golden'] 578 and not GlobalSettings['log_golden'] 579 and not GlobalSettings['filter_regex'] 580 and not GlobalSettings['filter_inverse'] 581 and not GlobalSettings['filter_group_only'] 582 and not GlobalSettings['process_output_single'] 583 and not GlobalSettings['process_output_combined'] 584 ) 585 # If python ever changes popen.stdout.read() to not risk deadlock, 586 # we could stream and capture, and use RunTestWithInputOutput instead. 587 (total_time, exit_status, failed) = test_lib.RunTestWithInput( 588 command, stdin_data, 589 timeout=GlobalSettings['time_error']) 590 if not CheckExitStatus(failed, 591 GlobalSettings['exit_status'], 592 GlobalSettings['using_nacl_signal_handler'], 593 exit_status, None, None): 594 Print(FailureMessage(total_time)) 595 return (1, stdout, stderr) 596 else: 597 (total_time, exit_status, 598 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( 599 command, stdin_data, int(GlobalSettings['capture_stderr']), 600 timeout=GlobalSettings['time_error']) 601 # CheckExitStatus may spew stdout/stderr when there is an error. 602 # Otherwise, we do not spew stdout/stderr in this case (capture_output). 603 if not CheckExitStatus(failed, 604 GlobalSettings['exit_status'], 605 GlobalSettings['using_nacl_signal_handler'], 606 exit_status, stdout, stderr): 607 PrintStdStreams(stdout, stderr) 608 Print(FailureMessage(total_time)) 609 return (1, stdout, stderr) 610 if not CheckGoldenOutput(stdout, stderr): 611 Print(FailureMessage(total_time)) 612 return (1, stdout, stderr) 613 success, stdout, stderr = ProcessLogOutputSingle(stdout, stderr) 614 if not success: 615 Print(FailureMessage(total_time) + ' ProcessLogOutputSingle failed!') 616 return (1, stdout, stderr) 617 618 if not CheckTimeBounds(total_time): 619 Print(FailureMessage(total_time)) 620 return (1, stdout, stderr) 621 622 Print(SuccessMessage(total_time)) 623 return (0, stdout, stderr) 624 625 626def DisableCrashDialog(): 627 """ 628 Disable Windows' crash dialog box, which pops up when a process exits with 629 an unhandled fault. This causes the process to hang on the Buildbots. We 630 duplicate this function from SConstruct because ErrorMode flags are 631 overwritten in scons due to race conditions. See bug 632 https://code.google.com/p/nativeclient/issues/detail?id=2968 633 """ 634 if sys.platform == 'win32': 635 import win32api 636 import win32con 637 # The double call is to preserve existing flags, as discussed at 638 # http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx 639 new_flags = win32con.SEM_NOGPFAULTERRORBOX 640 existing_flags = win32api.SetErrorMode(new_flags) 641 win32api.SetErrorMode(existing_flags | new_flags) 642 643 644def Main(argv): 645 DisableCrashDialog() 646 command = ProcessOptions(argv) 647 648 if not GlobalSettings['name']: 649 GlobalSettings['name'] = command[0] 650 GlobalSettings['name'] = os.path.basename(GlobalSettings['name']) 651 652 Print(RunMessage()) 653 num_runs = GlobalSettings['num_runs'] 654 if num_runs > 1: 655 Print(' (running %d times)' % num_runs) 656 657 if GlobalSettings['osenv']: 658 Banner('setting environment') 659 env_vars = DestringifyList(GlobalSettings['osenv']) 660 else: 661 env_vars = [] 662 for env_var in env_vars: 663 key, val = env_var.split('=', 1) 664 Print('[%s] = [%s]' % (key, val)) 665 os.environ[key] = val 666 667 stdin_data = '' 668 if GlobalSettings['stdin']: 669 stdin_data = open(GlobalSettings['stdin']) 670 671 run_under = GlobalSettings['run_under'] 672 if run_under: 673 command = run_under.split(',') + command 674 675 # print the command in copy-and-pastable fashion 676 print(' '.join(pipes.quote(arg) for arg in env_vars + command)) 677 678 # Concatenate output when running multiple times (e.g., for timing). 679 combined_stdout = '' 680 combined_stderr = '' 681 cur_runs = 0 682 num_runs = GlobalSettings['num_runs'] 683 while cur_runs < num_runs: 684 cur_runs += 1 685 # Clear out previous log_file. 686 if GlobalSettings['log_file']: 687 try: 688 os.unlink(GlobalSettings['log_file']) # might not pre-exist 689 except OSError: 690 pass 691 ret_code, stdout, stderr = DoRun(command, stdin_data) 692 if ret_code != 0: 693 return ret_code 694 combined_stdout += stdout 695 combined_stderr += stderr 696 # Process the log output after all the runs. 697 success = ProcessLogOutputCombined(combined_stdout, combined_stderr) 698 if not success: 699 # Bogus time, since only ProcessLogOutputCombined failed. 700 Print(FailureMessage(0.0) + ' ProcessLogOutputCombined failed!') 701 return 1 702 if GlobalSettings['output_stamp'] is not None: 703 # Create an empty stamp file to indicate success. 704 fh = open(GlobalSettings['output_stamp'], 'w') 705 fh.close() 706 return 0 707 708if __name__ == '__main__': 709 retval = Main(sys.argv[1:]) 710 # Add some whitepsace to make the logs easier to read. 711 sys.stdout.write('\n\n') 712 sys.exit(retval) 713