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': None, |
| 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': None, |
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 |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
303 words[0] == 'qemu:' and words[1] == 'uncaught' and | 311 words[0] == 'qemu:' and words[1] == 'uncaught' and |
304 words[2] == 'target' and words[3] == 'signal'): | 312 words[2] == 'target' and words[3] == 'signal'): |
305 return -int(words[4]) | 313 return -int(words[4]) |
306 return default | 314 return default |
307 | 315 |
308 def FormatExitStatus(number): | 316 def FormatExitStatus(number): |
309 # Include the hex version because it makes the Windows error exit | 317 # Include the hex version because it makes the Windows error exit |
310 # statuses (STATUS_*) more recognisable. | 318 # statuses (STATUS_*) more recognisable. |
311 return '%i (0x%x)' % (number, number & 0xffffffff) | 319 return '%i (0x%x)' % (number, number & 0xffffffff) |
312 | 320 |
| 321 def PrintStdStreams(stdout, stderr): |
| 322 if stderr is not None: |
| 323 Banner('Stdout for %s:' % os.path.basename(GlobalSettings['name'])) |
| 324 Print(stdout) |
| 325 Banner('Stderr for %s:' % os.path.basename(GlobalSettings['name'])) |
| 326 Print(stderr) |
| 327 |
313 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, | 328 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, |
314 exit_status, stdout, stderr): | 329 exit_status, stdout, stderr): |
315 expected_sigtype = 'normal' | 330 expected_sigtype = 'normal' |
316 if req_status in status_map: | 331 if req_status in status_map: |
317 expected_statuses = status_map[req_status][GlobalPlatform] | 332 expected_statuses = status_map[req_status][GlobalPlatform] |
318 if using_nacl_signal_handler: | 333 if using_nacl_signal_handler: |
319 if req_status.startswith('trusted_'): | 334 if req_status.startswith('trusted_'): |
320 expected_sigtype = 'trusted' | 335 expected_sigtype = 'trusted' |
321 elif req_status.startswith('untrusted_'): | 336 elif req_status.startswith('untrusted_'): |
322 expected_sigtype = 'untrusted' | 337 expected_sigtype = 'untrusted' |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
361 expected_printed = (expected_printed_signum, expected_sigtype) | 376 expected_printed = (expected_printed_signum, expected_sigtype) |
362 actual_printed = GetNaClSignalInfoFromStderr(stderr) | 377 actual_printed = GetNaClSignalInfoFromStderr(stderr) |
363 msg = ('\nERROR: Command printed the signal info %s to stderr ' | 378 msg = ('\nERROR: Command printed the signal info %s to stderr ' |
364 'but we expected %s' % | 379 'but we expected %s' % |
365 (actual_printed, expected_printed)) | 380 (actual_printed, expected_printed)) |
366 if actual_printed != expected_printed: | 381 if actual_printed != expected_printed: |
367 Print(msg) | 382 Print(msg) |
368 failed = True | 383 failed = True |
369 | 384 |
370 if failed: | 385 if failed: |
371 if stderr is not None: | 386 PrintStdStreams(stdout, stderr) |
372 Banner('Stdout') | |
373 Print(stdout) | |
374 Banner('Stderr') | |
375 Print(stderr) | |
376 return not failed | 387 return not failed |
377 | 388 |
378 def CheckTimeBounds(total_time): | 389 def CheckTimeBounds(total_time): |
379 if GlobalSettings['time_error']: | 390 if GlobalSettings['time_error']: |
380 if total_time > GlobalSettings['time_error']: | 391 if total_time > GlobalSettings['time_error']: |
381 Print('ERROR: should have taken less than %f secs' % | 392 Print('ERROR: should have taken less than %f secs' % |
382 (GlobalSettings['time_error'])) | 393 (GlobalSettings['time_error'])) |
383 return False | 394 return False |
384 | 395 |
385 if GlobalSettings['time_warning']: | 396 if GlobalSettings['time_warning']: |
(...skipping 14 matching lines...) Expand all Loading... |
400 actual = getter() | 411 actual = getter() |
401 if GlobalSettings['filter_regex']: | 412 if GlobalSettings['filter_regex']: |
402 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], | 413 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], |
403 GlobalSettings['filter_inverse'], | 414 GlobalSettings['filter_inverse'], |
404 GlobalSettings['filter_group_only'], | 415 GlobalSettings['filter_group_only'], |
405 actual) | 416 actual) |
406 if DifferentFromGolden(actual, golden_data, stream): | 417 if DifferentFromGolden(actual, golden_data, stream): |
407 return False | 418 return False |
408 return True | 419 return True |
409 | 420 |
410 def ProcessLogOutput(stdout, stderr): | 421 def ProcessLogOutputSingle(stdout, stderr): |
411 output_processor = GlobalSettings['process_output'] | 422 output_processor = GlobalSettings['process_output_single'] |
412 if output_processor: | 423 if output_processor is None: |
| 424 return (True, stdout, stderr) |
| 425 else: |
413 output_processor_cmd = DestringifyList(output_processor) | 426 output_processor_cmd = DestringifyList(output_processor) |
414 # Also, get the output from logout (to get NaClLog output in Windows). | 427 # Also, get the output from log_file to get NaClLog output in Windows. |
415 log_output = open(GlobalSettings['log_file']).read() | 428 log_output = open(GlobalSettings['log_file']).read() |
416 # Assume the log processor does not care about the order of the lines. | 429 # Assume the log processor does not care about the order of the lines. |
417 all_output = log_output + stdout + stderr | 430 all_output = log_output + stdout + stderr |
418 if not test_lib.RunCmdWithInput(output_processor_cmd, all_output): | 431 _, retcode, failed, new_stdout, new_stderr = \ |
| 432 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) |
| 433 # Print the result, since we have done some processing and we need |
| 434 # to have the processed data. However, if we intend to process it some |
| 435 # more later via process_output_combined, do not duplicate the data here. |
| 436 # Only print out the final result! |
| 437 if not GlobalSettings['process_output_combined']: |
| 438 PrintStdStreams(new_stdout, new_stderr) |
| 439 if retcode != 0 or failed: |
| 440 return (False, new_stdout, new_stderr) |
| 441 else: |
| 442 return (True, new_stdout, new_stderr) |
| 443 |
| 444 def ProcessLogOutputCombined(stdout, stderr): |
| 445 output_processor = GlobalSettings['process_output_combined'] |
| 446 if output_processor is None: |
| 447 return True |
| 448 else: |
| 449 output_processor_cmd = DestringifyList(output_processor) |
| 450 all_output = stdout + stderr |
| 451 _, retcode, failed, new_stdout, new_stderr = \ |
| 452 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) |
| 453 # Print the result, since we have done some processing. |
| 454 PrintStdStreams(new_stdout, new_stderr) |
| 455 if retcode != 0 or failed: |
419 return False | 456 return False |
420 return True | 457 else: |
| 458 return True |
421 | 459 |
422 def main(argv): | 460 def DoRun(command, stdin_data): |
423 global GlobalPlatform | 461 """ |
424 global GlobalReportStream | 462 Run the command, given stdin_data. Returns a return code (0 is good) |
425 command = ProcessOptions(argv) | 463 and optionally a captured version of stdout, stderr from the run |
426 | 464 (if the global setting capture_output is true). |
427 if GlobalSettings['report']: | 465 """ |
428 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) | 466 # Initialize stdout, stderr to indicate we have not captured |
429 | 467 # any of stdout or stderr. |
430 if not GlobalSettings['name']: | 468 stdout = '' |
431 GlobalSettings['name'] = command[0] | 469 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']): | 470 if not int(GlobalSettings['capture_output']): |
465 # We are only blurting out the stdout and stderr, not capturing it | 471 # We are only blurting out the stdout and stderr, not capturing it |
466 # for comparison, etc. | 472 # for comparison, etc. |
467 assert (not GlobalSettings['stdout_golden'] | 473 assert (not GlobalSettings['stdout_golden'] |
468 and not GlobalSettings['stderr_golden'] | 474 and not GlobalSettings['stderr_golden'] |
469 and not GlobalSettings['log_golden'] | 475 and not GlobalSettings['log_golden'] |
470 and not GlobalSettings['filter_regex'] | 476 and not GlobalSettings['filter_regex'] |
471 and not GlobalSettings['filter_inverse'] | 477 and not GlobalSettings['filter_inverse'] |
472 and not GlobalSettings['filter_group_only'] | 478 and not GlobalSettings['filter_group_only'] |
473 and not GlobalSettings['process_output'] | 479 and not GlobalSettings['process_output_single'] |
| 480 and not GlobalSettings['process_output_combined'] |
474 ) | 481 ) |
475 # If python ever changes popen.stdout.read() to not risk deadlock, | 482 # If python ever changes popen.stdout.read() to not risk deadlock, |
476 # we could stream and capture, and use RunTestWithInputOutput instead. | 483 # we could stream and capture, and use RunTestWithInputOutput instead. |
477 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, | 484 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, |
478 stdin_data) | 485 stdin_data) |
479 PrintTotalTime(total_time) | 486 PrintTotalTime(total_time) |
480 if not CheckExitStatus(failed, | 487 if not CheckExitStatus(failed, |
481 GlobalSettings['exit_status'], | 488 GlobalSettings['exit_status'], |
482 GlobalSettings['using_nacl_signal_handler'], | 489 GlobalSettings['using_nacl_signal_handler'], |
483 exit_status, None, None): | 490 exit_status, None, None): |
484 Print(FailureMessage(total_time)) | 491 Print(FailureMessage(total_time)) |
485 return -1 | 492 return (-1, stdout, stderr) |
486 else: | 493 else: |
487 (total_time, exit_status, | 494 (total_time, exit_status, |
488 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( | 495 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( |
489 command, stdin_data) | 496 command, stdin_data) |
490 PrintTotalTime(total_time) | 497 PrintTotalTime(total_time) |
| 498 # CheckExitStatus may spew stdout/stderr when there is an error. |
| 499 # Otherwise, we do not spew stdout/stderr in this case (capture_output). |
491 if not CheckExitStatus(failed, | 500 if not CheckExitStatus(failed, |
492 GlobalSettings['exit_status'], | 501 GlobalSettings['exit_status'], |
493 GlobalSettings['using_nacl_signal_handler'], | 502 GlobalSettings['using_nacl_signal_handler'], |
494 exit_status, stdout, stderr): | 503 exit_status, stdout, stderr): |
495 Print(FailureMessage(total_time)) | 504 Print(FailureMessage(total_time)) |
496 return -1 | 505 return (-1, stdout, stderr) |
497 if not CheckGoldenOutput(stdout, stderr): | 506 if not CheckGoldenOutput(stdout, stderr): |
498 Print(FailureMessage(total_time)) | 507 Print(FailureMessage(total_time)) |
499 return -1 | 508 return (-1, stdout, stderr) |
500 if not ProcessLogOutput(stdout, stderr): | 509 success, stdout, stderr = ProcessLogOutputSingle(stdout, stderr) |
501 Print(FailureMessage(total_time)) | 510 if not success: |
502 return -1 | 511 Print(FailureMessage(total_time) + ' ProcessLogOutputSingle failed!') |
| 512 return (-1, stdout, stderr) |
503 | 513 |
504 if not CheckTimeBounds(total_time): | 514 if not CheckTimeBounds(total_time): |
505 Print(FailureMessage(total_time)) | 515 Print(FailureMessage(total_time)) |
506 return -1 | 516 return (-1, stdout, stderr) |
507 | 517 |
508 Print(SuccessMessage(total_time)) | 518 Print(SuccessMessage(total_time)) |
| 519 return (0, stdout, stderr) |
| 520 |
| 521 |
| 522 def Main(argv): |
| 523 command = ProcessOptions(argv) |
| 524 |
| 525 if GlobalSettings['report']: |
| 526 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) |
| 527 |
| 528 if not GlobalSettings['name']: |
| 529 GlobalSettings['name'] = command[0] |
| 530 GlobalSettings['name'] = os.path.basename(GlobalSettings['name']) |
| 531 |
| 532 Print(RunMessage()) |
| 533 num_runs = GlobalSettings['num_runs'] |
| 534 if num_runs > 1: |
| 535 Print(' (running %d times)' % num_runs) |
| 536 |
| 537 if GlobalSettings['osenv']: |
| 538 Banner('setting environment') |
| 539 env_vars = DestringifyList(GlobalSettings['osenv']) |
| 540 else: |
| 541 env_vars = [] |
| 542 for env_var in env_vars: |
| 543 key, val = env_var.split('=', 1) |
| 544 Print('[%s] = [%s]' % (key, val)) |
| 545 os.environ[key] = val |
| 546 |
| 547 stdin_data = '' |
| 548 if GlobalSettings['stdin']: |
| 549 stdin_data = open(GlobalSettings['stdin']) |
| 550 |
| 551 run_under = GlobalSettings['run_under'] |
| 552 if run_under: |
| 553 command = run_under.split(',') + command |
| 554 |
| 555 Banner('running %s' % str(command)) |
| 556 # print the command in copy-and-pastable fashion |
| 557 print ' '.join(env_vars + command) |
| 558 |
| 559 # Concatenate output when running multiple times (e.g., for timing). |
| 560 combined_stdout = '' |
| 561 combined_stderr = '' |
| 562 cur_runs = 0 |
| 563 num_runs = GlobalSettings['num_runs'] |
| 564 while cur_runs < num_runs: |
| 565 cur_runs += 1 |
| 566 # Clear out previous log_file. |
| 567 if GlobalSettings['log_file']: |
| 568 try: |
| 569 os.unlink(GlobalSettings['log_file']) # might not pre-exist |
| 570 except OSError: |
| 571 pass |
| 572 ret_code, stdout, stderr = DoRun(command, stdin_data) |
| 573 if ret_code != 0: |
| 574 return ret_code |
| 575 combined_stdout += stdout |
| 576 combined_stderr += stderr |
| 577 # Process the log output after all the runs. |
| 578 success = ProcessLogOutputCombined(combined_stdout, combined_stderr) |
| 579 if not success: |
| 580 # Bogus time, since only ProcessLogOutputCombined failed. |
| 581 Print(FailureMessage(0.0) + ' ProcessLogOutputCombined failed!') |
| 582 return -1 |
509 return 0 | 583 return 0 |
510 | 584 |
511 | |
512 if __name__ == '__main__': | 585 if __name__ == '__main__': |
513 retval = main(sys.argv[1:]) | 586 retval = Main(sys.argv[1:]) |
514 # Add some whitepsace to make the logs easier to read. | 587 # Add some whitepsace to make the logs easier to read. |
515 sys.stdout.write('\n\n') | 588 sys.stdout.write('\n\n') |
516 sys.exit(retval) | 589 sys.exit(retval) |
517 | |
OLD | NEW |