Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Native Client Authors. All rights reserved. | 2 # Copyright (c) 2011 The Native Client Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 | 6 |
| 7 """Simple testing harness for running commands and checking expected output. | 7 """Simple testing harness for running commands and checking expected output. |
| 8 | 8 |
| 9 This harness is used instead of shell scripts to ensure windows compatibility | 9 This harness is used instead of shell scripts to ensure windows compatibility |
| 10 | 10 |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 98 | 98 |
| 99 # This option must be '1' for the output to be captured, for checking | 99 # This option must be '1' for the output to be captured, for checking |
| 100 # against golden files, special exit_status signals, etc. | 100 # against golden files, special exit_status signals, etc. |
| 101 # When this option is '0', stdout and stderr will be streamed out. | 101 # When this option is '0', stdout and stderr will be streamed out. |
| 102 'capture_output': '1', | 102 'capture_output': '1', |
| 103 | 103 |
| 104 'filter_regex': None, | 104 'filter_regex': None, |
| 105 'filter_inverse': False, | 105 'filter_inverse': False, |
| 106 'filter_group_only': False, | 106 'filter_group_only': False, |
| 107 | 107 |
| 108 # Script for processing output along with its arguments. | 108 # Number of times a test is run. |
| 109 'process_output': '', | 109 # This is useful for getting multiple samples for time perf tests. |
| 110 'num_runs': 1, | |
| 111 | |
| 112 # Scripts for processing output along with its arguments. | |
| 113 # This script is given the output of a single run. | |
| 114 'process_output_single': '', | |
|
Nick Bray
2011/11/11 22:42:39
Would None be a better default? (See other uses.)
jvoung - send to chromium...
2011/11/11 23:28:27
I'll Dones it.
| |
| 115 # This script is given the concatenated output of all |num_runs|, after | |
| 116 # having been filtered by |process_output_single| for individual runs. | |
| 117 'process_output_combined': '', | |
| 110 | 118 |
| 111 'time_warning': 0, | 119 'time_warning': 0, |
| 112 'time_error': 0, | 120 'time_error': 0, |
| 113 | 121 |
| 114 'run_under': None, | 122 'run_under': None, |
| 115 } | 123 } |
| 116 | 124 |
| 117 def StringifyList(lst): | 125 def StringifyList(lst): |
| 118 return ','.join(lst) | 126 return ','.join(lst) |
| 119 | 127 |
| 120 def DestringifyList(lst): | 128 def DestringifyList(lst): |
| 121 # BUG(robertm): , is a legitimate character for an environment variable | 129 # BUG(robertm): , is a legitimate character for an environment variable |
| 122 # value. | 130 # value. |
| 123 return lst.split(',') | 131 return lst.split(',') |
| 124 | 132 |
| 125 # The following messages match gtest's formatting. This improves log | 133 # The following messages match gtest's formatting. This improves log |
| 126 # greppability for people who primarily work on Chrome. It also allows | 134 # greppability for people who primarily work on Chrome. It also allows |
| 127 # gtest-specific hooks on the buildbots to fire. | 135 # gtest-specific hooks on the buildbots to fire. |
| 128 # The buildbots expect test names in the format "suite_name.test_name", so we | 136 # The buildbots expect test names in the format "suite_name.test_name", so we |
| 129 # prefix the test name with a bogus suite name (nacl). | 137 # prefix the test name with a bogus suite name (nacl). |
| 130 def RunMessage(): | 138 def RunMessage(): |
| 131 return '[ RUN ] nacl.%s' % GlobalSettings['name'] | 139 msg = '[ RUN ] nacl.%s' % GlobalSettings['name'] |
| 140 num_runs = GlobalSettings['num_runs'] | |
| 141 if num_runs > 1: | |
| 142 msg += ' (run %d times)' % num_runs | |
|
Nick Bray
2011/11/11 22:42:39
Actually... you might want to put this info on its
jvoung - send to chromium...
2011/11/11 23:28:27
Done.
| |
| 143 return msg | |
| 132 | 144 |
| 133 def FailureMessage(total_time): | 145 def FailureMessage(total_time): |
| 134 return '[ FAILED ] nacl.%s (%d ms)' % (GlobalSettings['name'], | 146 return '[ FAILED ] nacl.%s (%d ms)' % (GlobalSettings['name'], |
| 135 total_time * 1000.0) | 147 total_time * 1000.0) |
| 136 | 148 |
| 137 def SuccessMessage(total_time): | 149 def SuccessMessage(total_time): |
| 138 return '[ OK ] nacl.%s (%d ms)' % (GlobalSettings['name'], | 150 return '[ OK ] nacl.%s (%d ms)' % (GlobalSettings['name'], |
| 139 total_time * 1000.0) | 151 total_time * 1000.0) |
| 140 | 152 |
| 141 def LogPerfResult(graph_name, trace_name, value, units): | 153 def LogPerfResult(graph_name, trace_name, value, units): |
| (...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 303 words[0] == 'qemu:' and words[1] == 'uncaught' and | 315 words[0] == 'qemu:' and words[1] == 'uncaught' and |
| 304 words[2] == 'target' and words[3] == 'signal'): | 316 words[2] == 'target' and words[3] == 'signal'): |
| 305 return -int(words[4]) | 317 return -int(words[4]) |
| 306 return default | 318 return default |
| 307 | 319 |
| 308 def FormatExitStatus(number): | 320 def FormatExitStatus(number): |
| 309 # Include the hex version because it makes the Windows error exit | 321 # Include the hex version because it makes the Windows error exit |
| 310 # statuses (STATUS_*) more recognisable. | 322 # statuses (STATUS_*) more recognisable. |
| 311 return '%i (0x%x)' % (number, number & 0xffffffff) | 323 return '%i (0x%x)' % (number, number & 0xffffffff) |
| 312 | 324 |
| 325 def PrintStdStreams(stdout, stderr): | |
| 326 if stderr is not None: | |
| 327 Banner('Stdout for %s:' % os.path.basename(GlobalSettings['name'])) | |
| 328 Print(stdout) | |
| 329 Banner('Stderr for %s:' % os.path.basename(GlobalSettings['name'])) | |
| 330 Print(stderr) | |
| 331 | |
| 313 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, | 332 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, |
| 314 exit_status, stdout, stderr): | 333 exit_status, stdout, stderr): |
| 315 expected_sigtype = 'normal' | 334 expected_sigtype = 'normal' |
| 316 if req_status in status_map: | 335 if req_status in status_map: |
| 317 expected_statuses = status_map[req_status][GlobalPlatform] | 336 expected_statuses = status_map[req_status][GlobalPlatform] |
| 318 if using_nacl_signal_handler: | 337 if using_nacl_signal_handler: |
| 319 if req_status.startswith('trusted_'): | 338 if req_status.startswith('trusted_'): |
| 320 expected_sigtype = 'trusted' | 339 expected_sigtype = 'trusted' |
| 321 elif req_status.startswith('untrusted_'): | 340 elif req_status.startswith('untrusted_'): |
| 322 expected_sigtype = 'untrusted' | 341 expected_sigtype = 'untrusted' |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 361 expected_printed = (expected_printed_signum, expected_sigtype) | 380 expected_printed = (expected_printed_signum, expected_sigtype) |
| 362 actual_printed = GetNaClSignalInfoFromStderr(stderr) | 381 actual_printed = GetNaClSignalInfoFromStderr(stderr) |
| 363 msg = ('\nERROR: Command printed the signal info %s to stderr ' | 382 msg = ('\nERROR: Command printed the signal info %s to stderr ' |
| 364 'but we expected %s' % | 383 'but we expected %s' % |
| 365 (actual_printed, expected_printed)) | 384 (actual_printed, expected_printed)) |
| 366 if actual_printed != expected_printed: | 385 if actual_printed != expected_printed: |
| 367 Print(msg) | 386 Print(msg) |
| 368 failed = True | 387 failed = True |
| 369 | 388 |
| 370 if failed: | 389 if failed: |
| 371 if stderr is not None: | 390 PrintStdStreams(stdout, stderr) |
| 372 Banner('Stdout') | |
| 373 Print(stdout) | |
| 374 Banner('Stderr') | |
| 375 Print(stderr) | |
| 376 return not failed | 391 return not failed |
| 377 | 392 |
| 378 def CheckTimeBounds(total_time): | 393 def CheckTimeBounds(total_time): |
| 379 if GlobalSettings['time_error']: | 394 if GlobalSettings['time_error']: |
| 380 if total_time > GlobalSettings['time_error']: | 395 if total_time > GlobalSettings['time_error']: |
| 381 Print('ERROR: should have taken less than %f secs' % | 396 Print('ERROR: should have taken less than %f secs' % |
| 382 (GlobalSettings['time_error'])) | 397 (GlobalSettings['time_error'])) |
| 383 return False | 398 return False |
| 384 | 399 |
| 385 if GlobalSettings['time_warning']: | 400 if GlobalSettings['time_warning']: |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 400 actual = getter() | 415 actual = getter() |
| 401 if GlobalSettings['filter_regex']: | 416 if GlobalSettings['filter_regex']: |
| 402 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], | 417 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], |
| 403 GlobalSettings['filter_inverse'], | 418 GlobalSettings['filter_inverse'], |
| 404 GlobalSettings['filter_group_only'], | 419 GlobalSettings['filter_group_only'], |
| 405 actual) | 420 actual) |
| 406 if DifferentFromGolden(actual, golden_data, stream): | 421 if DifferentFromGolden(actual, golden_data, stream): |
| 407 return False | 422 return False |
| 408 return True | 423 return True |
| 409 | 424 |
| 410 def ProcessLogOutput(stdout, stderr): | 425 def ProcessLogOutputSingle(stdout, stderr): |
| 411 output_processor = GlobalSettings['process_output'] | 426 output_processor = GlobalSettings['process_output_single'] |
| 412 if output_processor: | 427 if not output_processor: |
| 428 return (True, stdout, stderr) | |
| 429 else: | |
| 413 output_processor_cmd = DestringifyList(output_processor) | 430 output_processor_cmd = DestringifyList(output_processor) |
| 414 # Also, get the output from logout (to get NaClLog output in Windows). | 431 # Also, get the output from log_file to get NaClLog output in Windows. |
| 415 log_output = open(GlobalSettings['log_file']).read() | 432 log_output = open(GlobalSettings['log_file']).read() |
| 416 # Assume the log processor does not care about the order of the lines. | 433 # Assume the log processor does not care about the order of the lines. |
| 417 all_output = log_output + stdout + stderr | 434 all_output = log_output + stdout + stderr |
| 418 if not test_lib.RunCmdWithInput(output_processor_cmd, all_output): | 435 _, retcode, failed, new_stdout, new_stderr = \ |
| 436 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) | |
| 437 # Print the result, since we have done some processing and we need | |
| 438 # to have the processed data. However, if we intend to process it some | |
| 439 # more later via process_output_combined, do not duplicate the data here. | |
| 440 # Only print out the final result! | |
| 441 if not GlobalSettings['process_output_combined']: | |
| 442 PrintStdStreams(new_stdout, new_stderr) | |
| 443 if retcode != 0 or failed: | |
| 444 return (False, new_stdout, new_stderr) | |
| 445 else: | |
| 446 return (True, new_stdout, new_stderr) | |
| 447 | |
| 448 def ProcessLogOutputCombined(stdout, stderr): | |
| 449 output_processor = GlobalSettings['process_output_combined'] | |
| 450 if not output_processor: | |
| 451 return True | |
| 452 else: | |
| 453 output_processor_cmd = DestringifyList(output_processor) | |
| 454 all_output = stdout + stderr | |
| 455 _, retcode, failed, new_stdout, new_stderr = \ | |
| 456 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) | |
| 457 # Print the result, since we have done some processing. | |
| 458 PrintStdStreams(new_stdout, new_stderr) | |
| 459 if retcode != 0 or failed: | |
| 419 return False | 460 return False |
| 420 return True | 461 else: |
| 462 return True | |
| 421 | 463 |
| 422 def main(argv): | 464 def DoRun(command, stdin_data): |
| 423 global GlobalPlatform | 465 """ |
| 424 global GlobalReportStream | 466 Run the command, given stdin_data. Returns a return code (0 is good) |
| 425 command = ProcessOptions(argv) | 467 and optionally a captured version of stdout, stderr from the run |
| 426 | 468 (if the global setting capture_output is true). |
| 427 if GlobalSettings['report']: | 469 """ |
| 428 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) | 470 # Initialize stdout, stderr to indicate we have not captured |
| 429 | 471 # any of stdout or stderr. |
| 430 if not GlobalSettings['name']: | 472 stdout = '' |
| 431 GlobalSettings['name'] = command[0] | 473 stderr = '' |
| 432 GlobalSettings['name'] = os.path.basename(GlobalSettings['name']) | |
| 433 | |
| 434 Print(RunMessage()) | |
| 435 | |
| 436 if GlobalSettings['osenv']: | |
| 437 Banner('setting environment') | |
| 438 env_vars = DestringifyList(GlobalSettings['osenv']) | |
| 439 else: | |
| 440 env_vars = [] | |
| 441 for env_var in env_vars: | |
| 442 key, val = env_var.split('=', 1) | |
| 443 Print('[%s] = [%s]' % (key, val)) | |
| 444 os.putenv(key, val) | |
| 445 | |
| 446 stdin_data = '' | |
| 447 if GlobalSettings['stdin']: | |
| 448 stdin_data = open(GlobalSettings['stdin']) | |
| 449 | |
| 450 if GlobalSettings['log_file']: | |
| 451 try: | |
| 452 os.unlink(GlobalSettings['log_file']) # might not pre-exist | |
| 453 except OSError: | |
| 454 pass | |
| 455 | |
| 456 run_under = GlobalSettings['run_under'] | |
| 457 if run_under: | |
| 458 command = run_under.split(',') + command | |
| 459 | |
| 460 Banner('running %s' % str(command)) | |
| 461 # print the command in copy-and-pastable fashion | |
| 462 print " ".join(env_vars + command) | |
| 463 | |
| 464 if not int(GlobalSettings['capture_output']): | 474 if not int(GlobalSettings['capture_output']): |
| 465 # We are only blurting out the stdout and stderr, not capturing it | 475 # We are only blurting out the stdout and stderr, not capturing it |
| 466 # for comparison, etc. | 476 # for comparison, etc. |
| 467 assert (not GlobalSettings['stdout_golden'] | 477 assert (not GlobalSettings['stdout_golden'] |
| 468 and not GlobalSettings['stderr_golden'] | 478 and not GlobalSettings['stderr_golden'] |
| 469 and not GlobalSettings['log_golden'] | 479 and not GlobalSettings['log_golden'] |
| 470 and not GlobalSettings['filter_regex'] | 480 and not GlobalSettings['filter_regex'] |
| 471 and not GlobalSettings['filter_inverse'] | 481 and not GlobalSettings['filter_inverse'] |
| 472 and not GlobalSettings['filter_group_only'] | 482 and not GlobalSettings['filter_group_only'] |
| 473 and not GlobalSettings['process_output'] | 483 and not GlobalSettings['process_output_single'] |
| 484 and not GlobalSettings['process_output_combined'] | |
| 474 ) | 485 ) |
| 475 # If python ever changes popen.stdout.read() to not risk deadlock, | 486 # If python ever changes popen.stdout.read() to not risk deadlock, |
| 476 # we could stream and capture, and use RunTestWithInputOutput instead. | 487 # we could stream and capture, and use RunTestWithInputOutput instead. |
| 477 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, | 488 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, |
| 478 stdin_data) | 489 stdin_data) |
| 479 PrintTotalTime(total_time) | 490 PrintTotalTime(total_time) |
| 480 if not CheckExitStatus(failed, | 491 if not CheckExitStatus(failed, |
| 481 GlobalSettings['exit_status'], | 492 GlobalSettings['exit_status'], |
| 482 GlobalSettings['using_nacl_signal_handler'], | 493 GlobalSettings['using_nacl_signal_handler'], |
| 483 exit_status, None, None): | 494 exit_status, None, None): |
| 484 Print(FailureMessage(total_time)) | 495 Print(FailureMessage(total_time)) |
| 485 return -1 | 496 return (-1, stdout, stderr) |
| 486 else: | 497 else: |
| 487 (total_time, exit_status, | 498 (total_time, exit_status, |
| 488 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( | 499 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( |
| 489 command, stdin_data) | 500 command, stdin_data) |
| 490 PrintTotalTime(total_time) | 501 PrintTotalTime(total_time) |
| 502 # CheckExitStatus may spew stdout/stderr when there is an error. | |
| 503 # Otherwise, we do not spew stdout/stderr in this case (capture_output). | |
| 491 if not CheckExitStatus(failed, | 504 if not CheckExitStatus(failed, |
| 492 GlobalSettings['exit_status'], | 505 GlobalSettings['exit_status'], |
| 493 GlobalSettings['using_nacl_signal_handler'], | 506 GlobalSettings['using_nacl_signal_handler'], |
| 494 exit_status, stdout, stderr): | 507 exit_status, stdout, stderr): |
| 495 Print(FailureMessage(total_time)) | 508 Print(FailureMessage(total_time)) |
| 496 return -1 | 509 return (-1, stdout, stderr) |
| 497 if not CheckGoldenOutput(stdout, stderr): | 510 if not CheckGoldenOutput(stdout, stderr): |
| 498 Print(FailureMessage(total_time)) | 511 Print(FailureMessage(total_time)) |
| 499 return -1 | 512 return (-1, stdout, stderr) |
| 500 if not ProcessLogOutput(stdout, stderr): | 513 success, stdout, stderr = ProcessLogOutputSingle(stdout, stderr) |
| 501 Print(FailureMessage(total_time)) | 514 if not success: |
| 502 return -1 | 515 Print(FailureMessage(total_time) + ' ProcessLogOutputSingle failed!') |
| 516 return (-1, stdout, stderr) | |
| 503 | 517 |
| 504 if not CheckTimeBounds(total_time): | 518 if not CheckTimeBounds(total_time): |
| 505 Print(FailureMessage(total_time)) | 519 Print(FailureMessage(total_time)) |
| 506 return -1 | 520 return (-1, stdout, stderr) |
| 507 | 521 |
| 508 Print(SuccessMessage(total_time)) | 522 Print(SuccessMessage(total_time)) |
| 523 return (0, stdout, stderr) | |
| 524 | |
| 525 | |
| 526 def Main(argv): | |
| 527 command = ProcessOptions(argv) | |
| 528 | |
| 529 if GlobalSettings['report']: | |
| 530 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) | |
| 531 | |
| 532 if not GlobalSettings['name']: | |
| 533 GlobalSettings['name'] = command[0] | |
| 534 GlobalSettings['name'] = os.path.basename(GlobalSettings['name']) | |
| 535 | |
| 536 Print(RunMessage()) | |
| 537 | |
| 538 if GlobalSettings['osenv']: | |
| 539 Banner('setting environment') | |
| 540 env_vars = DestringifyList(GlobalSettings['osenv']) | |
| 541 else: | |
| 542 env_vars = [] | |
| 543 for env_var in env_vars: | |
| 544 key, val = env_var.split('=', 1) | |
| 545 Print('[%s] = [%s]' % (key, val)) | |
| 546 os.environ[key] = val | |
| 547 | |
| 548 stdin_data = '' | |
| 549 if GlobalSettings['stdin']: | |
| 550 stdin_data = open(GlobalSettings['stdin']) | |
| 551 | |
| 552 run_under = GlobalSettings['run_under'] | |
| 553 if run_under: | |
| 554 command = run_under.split(',') + command | |
| 555 | |
| 556 Banner('running %s' % str(command)) | |
| 557 # print the command in copy-and-pastable fashion | |
| 558 print " ".join(env_vars + command) | |
| 559 | |
| 560 # Concatenate output when running multiple times (e.g., for timing). | |
| 561 combined_stdout = '' | |
| 562 combined_stderr = '' | |
| 563 cur_runs = 0 | |
| 564 num_runs = GlobalSettings['num_runs'] | |
| 565 while cur_runs < num_runs: | |
| 566 cur_runs += 1 | |
| 567 # Clear out previous log_file. | |
| 568 if GlobalSettings['log_file']: | |
| 569 try: | |
| 570 os.unlink(GlobalSettings['log_file']) # might not pre-exist | |
| 571 except OSError: | |
| 572 pass | |
| 573 ret_code, stdout, stderr = DoRun(command, stdin_data) | |
| 574 if ret_code != 0: | |
| 575 return ret_code | |
| 576 combined_stdout += stdout | |
| 577 combined_stderr += stderr | |
| 578 # Process the log output after all the runs. | |
| 579 success = ProcessLogOutputCombined(combined_stdout, combined_stderr) | |
| 580 if not success: | |
| 581 # Bogus time, since only ProcessLogOutputCombined failed. | |
| 582 Print(FailureMessage(0.0) + ' ProcessLogOutputCombined failed!') | |
| 583 return -1 | |
| 509 return 0 | 584 return 0 |
| 510 | 585 |
| 511 | |
| 512 if __name__ == '__main__': | 586 if __name__ == '__main__': |
| 513 retval = main(sys.argv[1:]) | 587 retval = Main(sys.argv[1:]) |
| 514 # Add some whitepsace to make the logs easier to read. | 588 # Add some whitepsace to make the logs easier to read. |
| 515 sys.stdout.write('\n\n') | 589 sys.stdout.write('\n\n') |
| 516 sys.exit(retval) | 590 sys.exit(retval) |
| 517 | |
| OLD | NEW |