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', | |
Nick Bray
2011/11/07 23:39:09
Make this an integer. The option parser will do t
jvoung - send to chromium...
2011/11/09 00:50:54
Done. Leaving capture_output, track_cmdtime, alone
| |
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': '', | |
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 num_runs = int(GlobalSettings['num_runs']) |
140 if num_runs > 1: | |
141 run_message_extra = ' (run %d times)' % num_runs | |
142 else: | |
143 run_message_extra = '' | |
144 return '[ RUN ] nacl.%s%s' % (GlobalSettings['name'], run_message_extra) | |
Nick Bray
2011/11/07 23:39:09
It might be clearer to produce the base string and
jvoung - send to chromium...
2011/11/09 00:50:54
Done.
| |
132 | 145 |
133 def FailureMessage(total_time): | 146 def FailureMessage(total_time): |
134 return '[ FAILED ] nacl.%s (%d ms)' % (GlobalSettings['name'], | 147 return '[ FAILED ] nacl.%s (%d ms)' % (GlobalSettings['name'], |
135 total_time * 1000.0) | 148 total_time * 1000.0) |
136 | 149 |
137 def SuccessMessage(total_time): | 150 def SuccessMessage(total_time): |
138 return '[ OK ] nacl.%s (%d ms)' % (GlobalSettings['name'], | 151 return '[ OK ] nacl.%s (%d ms)' % (GlobalSettings['name'], |
139 total_time * 1000.0) | 152 total_time * 1000.0) |
140 | 153 |
141 def LogPerfResult(graph_name, trace_name, value, units): | 154 def LogPerfResult(graph_name, trace_name, value, units): |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
252 except getopt.GetoptError, err: | 265 except getopt.GetoptError, err: |
253 Print(str(err)) # will print something like 'option -a not recognized' | 266 Print(str(err)) # will print something like 'option -a not recognized' |
254 sys.exit(-1) | 267 sys.exit(-1) |
255 | 268 |
256 for o, a in opts: | 269 for o, a in opts: |
257 # strip the leading '--' | 270 # strip the leading '--' |
258 option = o[2:] | 271 option = o[2:] |
259 assert option in GlobalSettings | 272 assert option in GlobalSettings |
260 if option == 'exit_status': | 273 if option == 'exit_status': |
261 GlobalSettings[option] = a | 274 GlobalSettings[option] = a |
262 elif type(GlobalSettings[option]) == int: | 275 elif type(GlobalSettings[option]) == int: |
Nick Bray
2011/11/07 23:39:09
Check this out!
jvoung - send to chromium...
2011/11/09 00:50:54
Done =P
| |
263 GlobalSettings[option] = int(a) | 276 GlobalSettings[option] = int(a) |
264 else: | 277 else: |
265 GlobalSettings[option] = a | 278 GlobalSettings[option] = a |
266 | 279 |
267 if (sys.platform == 'win32') and (GlobalSettings['subarch'] == '64'): | 280 if (sys.platform == 'win32') and (GlobalSettings['subarch'] == '64'): |
268 GlobalPlatform = 'win64' | 281 GlobalPlatform = 'win64' |
269 else: | 282 else: |
270 GlobalPlatform = sys.platform | 283 GlobalPlatform = sys.platform |
271 | 284 |
272 # return the unprocessed options, i.e. the command | 285 # return the unprocessed options, i.e. the command |
(...skipping 30 matching lines...) Expand all Loading... | |
303 words[0] == 'qemu:' and words[1] == 'uncaught' and | 316 words[0] == 'qemu:' and words[1] == 'uncaught' and |
304 words[2] == 'target' and words[3] == 'signal'): | 317 words[2] == 'target' and words[3] == 'signal'): |
305 return -int(words[4]) | 318 return -int(words[4]) |
306 return default | 319 return default |
307 | 320 |
308 def FormatExitStatus(number): | 321 def FormatExitStatus(number): |
309 # Include the hex version because it makes the Windows error exit | 322 # Include the hex version because it makes the Windows error exit |
310 # statuses (STATUS_*) more recognisable. | 323 # statuses (STATUS_*) more recognisable. |
311 return '%i (0x%x)' % (number, number & 0xffffffff) | 324 return '%i (0x%x)' % (number, number & 0xffffffff) |
312 | 325 |
326 def PrintStdStreams(stdout, stderr): | |
327 if stdout: | |
Nick Bray
2011/11/07 23:39:09
The case in which stdout is printed has changed.
jvoung - send to chromium...
2011/11/09 00:50:54
Leaving this the same as before for now, as noted
| |
328 Banner('Stdout for %s:' % os.path.basename(GlobalSettings['name'])) | |
329 Print(stdout) | |
330 if stderr: | |
Nick Bray
2011/11/07 23:39:09
is not None?
!= ''?
It's hard to tell what your in
jvoung - send to chromium...
2011/11/09 00:50:54
Hmm... I don't remember why I did that change anym
| |
331 Banner('Stderr for %s:' % os.path.basename(GlobalSettings['name'])) | |
332 Print(stderr) | |
333 | |
313 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, | 334 def CheckExitStatus(failed, req_status, using_nacl_signal_handler, |
314 exit_status, stdout, stderr): | 335 exit_status, stdout, stderr): |
315 expected_sigtype = 'normal' | 336 expected_sigtype = 'normal' |
316 if req_status in status_map: | 337 if req_status in status_map: |
317 expected_statuses = status_map[req_status][GlobalPlatform] | 338 expected_statuses = status_map[req_status][GlobalPlatform] |
318 if using_nacl_signal_handler: | 339 if using_nacl_signal_handler: |
319 if req_status.startswith('trusted_'): | 340 if req_status.startswith('trusted_'): |
320 expected_sigtype = 'trusted' | 341 expected_sigtype = 'trusted' |
321 elif req_status.startswith('untrusted_'): | 342 elif req_status.startswith('untrusted_'): |
322 expected_sigtype = 'untrusted' | 343 expected_sigtype = 'untrusted' |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
361 expected_printed = (expected_printed_signum, expected_sigtype) | 382 expected_printed = (expected_printed_signum, expected_sigtype) |
362 actual_printed = GetNaClSignalInfoFromStderr(stderr) | 383 actual_printed = GetNaClSignalInfoFromStderr(stderr) |
363 msg = ('\nERROR: Command printed the signal info %s to stderr ' | 384 msg = ('\nERROR: Command printed the signal info %s to stderr ' |
364 'but we expected %s' % | 385 'but we expected %s' % |
365 (actual_printed, expected_printed)) | 386 (actual_printed, expected_printed)) |
366 if actual_printed != expected_printed: | 387 if actual_printed != expected_printed: |
367 Print(msg) | 388 Print(msg) |
368 failed = True | 389 failed = True |
369 | 390 |
370 if failed: | 391 if failed: |
371 if stderr is not None: | 392 PrintStdStreams(stdout, stderr) |
372 Banner('Stdout') | |
373 Print(stdout) | |
374 Banner('Stderr') | |
375 Print(stderr) | |
376 return not failed | 393 return not failed |
377 | 394 |
378 def CheckTimeBounds(total_time): | 395 def CheckTimeBounds(total_time): |
379 if GlobalSettings['time_error']: | 396 if GlobalSettings['time_error']: |
380 if total_time > GlobalSettings['time_error']: | 397 if total_time > GlobalSettings['time_error']: |
381 Print('ERROR: should have taken less than %f secs' % | 398 Print('ERROR: should have taken less than %f secs' % |
382 (GlobalSettings['time_error'])) | 399 (GlobalSettings['time_error'])) |
383 return False | 400 return False |
384 | 401 |
385 if GlobalSettings['time_warning']: | 402 if GlobalSettings['time_warning']: |
(...skipping 14 matching lines...) Expand all Loading... | |
400 actual = getter() | 417 actual = getter() |
401 if GlobalSettings['filter_regex']: | 418 if GlobalSettings['filter_regex']: |
402 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], | 419 actual = test_lib.RegexpFilterLines(GlobalSettings['filter_regex'], |
403 GlobalSettings['filter_inverse'], | 420 GlobalSettings['filter_inverse'], |
404 GlobalSettings['filter_group_only'], | 421 GlobalSettings['filter_group_only'], |
405 actual) | 422 actual) |
406 if DifferentFromGolden(actual, golden_data, stream): | 423 if DifferentFromGolden(actual, golden_data, stream): |
407 return False | 424 return False |
408 return True | 425 return True |
409 | 426 |
410 def ProcessLogOutput(stdout, stderr): | 427 def ProcessLogOutputSingle(stdout, stderr): |
411 output_processor = GlobalSettings['process_output'] | 428 output_processor = GlobalSettings['process_output_single'] |
412 if output_processor: | 429 if not output_processor: |
430 return (True, stdout, stderr) | |
431 else: | |
413 output_processor_cmd = DestringifyList(output_processor) | 432 output_processor_cmd = DestringifyList(output_processor) |
414 # Also, get the output from logout (to get NaClLog output in Windows). | 433 # Also, get the output from log_file to get NaClLog output in Windows. |
415 log_output = open(GlobalSettings['log_file']).read() | 434 log_output = open(GlobalSettings['log_file']).read() |
416 # Assume the log processor does not care about the order of the lines. | 435 # Assume the log processor does not care about the order of the lines. |
417 all_output = log_output + stdout + stderr | 436 all_output = log_output + stdout + stderr |
418 if not test_lib.RunCmdWithInput(output_processor_cmd, all_output): | 437 _, retcode, failed, new_stdout, new_stderr = \ |
438 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) | |
439 # Print the result, since we have done some processing and we need | |
440 # to have the processed data. However, if we intend to process it some | |
441 # more later via process_output_combined, do not duplicate the data here. | |
442 # Only print out the final result! | |
443 if not GlobalSettings['process_output_combined']: | |
444 PrintStdStreams(new_stdout, new_stderr) | |
445 if retcode != 0 or failed: | |
446 return (False, new_stdout, new_stderr) | |
447 else: | |
448 return (True, new_stdout, new_stderr) | |
449 | |
450 def ProcessLogOutputCombined(stdout, stderr): | |
451 output_processor = GlobalSettings['process_output_combined'] | |
452 if not output_processor: | |
453 return True | |
454 else: | |
455 output_processor_cmd = DestringifyList(output_processor) | |
456 all_output = stdout + stderr | |
457 _, retcode, failed, new_stdout, new_stderr = \ | |
458 test_lib.RunTestWithInputOutput(output_processor_cmd, all_output) | |
459 # Print the result, since we have done some processing. | |
460 PrintStdStreams(new_stdout, new_stderr) | |
461 if retcode != 0 or failed: | |
419 return False | 462 return False |
420 return True | 463 else: |
464 return True | |
421 | 465 |
422 def main(argv): | 466 def DoRun(command, stdin_data): |
423 global GlobalPlatform | 467 """ |
424 global GlobalReportStream | 468 Run the command, given stdin_data. Returns a return code (0 is good) |
425 command = ProcessOptions(argv) | 469 and optionally a captured version of stdout, stderr from the run |
426 | 470 (if the global setting capture_output is true). |
427 if GlobalSettings['report']: | 471 """ |
428 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) | 472 # Initialize stdout, stderr to indicate we have not captured |
429 | 473 # any of stdout or stderr. |
430 if not GlobalSettings['name']: | 474 stdout = '' |
431 GlobalSettings['name'] = command[0] | 475 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']): | 476 if not int(GlobalSettings['capture_output']): |
465 # We are only blurting out the stdout and stderr, not capturing it | 477 # We are only blurting out the stdout and stderr, not capturing it |
466 # for comparison, etc. | 478 # for comparison, etc. |
467 assert (not GlobalSettings['stdout_golden'] | 479 assert (not GlobalSettings['stdout_golden'] |
468 and not GlobalSettings['stderr_golden'] | 480 and not GlobalSettings['stderr_golden'] |
469 and not GlobalSettings['log_golden'] | 481 and not GlobalSettings['log_golden'] |
470 and not GlobalSettings['filter_regex'] | 482 and not GlobalSettings['filter_regex'] |
471 and not GlobalSettings['filter_inverse'] | 483 and not GlobalSettings['filter_inverse'] |
472 and not GlobalSettings['filter_group_only'] | 484 and not GlobalSettings['filter_group_only'] |
473 and not GlobalSettings['process_output'] | 485 and not GlobalSettings['process_output_single'] |
486 and not GlobalSettings['process_output_combined'] | |
474 ) | 487 ) |
475 # If python ever changes popen.stdout.read() to not risk deadlock, | 488 # If python ever changes popen.stdout.read() to not risk deadlock, |
476 # we could stream and capture, and use RunTestWithInputOutput instead. | 489 # we could stream and capture, and use RunTestWithInputOutput instead. |
477 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, | 490 (total_time, exit_status, failed) = test_lib.RunTestWithInput(command, |
478 stdin_data) | 491 stdin_data) |
479 PrintTotalTime(total_time) | 492 PrintTotalTime(total_time) |
480 if not CheckExitStatus(failed, | 493 if not CheckExitStatus(failed, |
481 GlobalSettings['exit_status'], | 494 GlobalSettings['exit_status'], |
482 GlobalSettings['using_nacl_signal_handler'], | 495 GlobalSettings['using_nacl_signal_handler'], |
483 exit_status, None, None): | 496 exit_status, None, None): |
484 Print(FailureMessage(total_time)) | 497 Print(FailureMessage(total_time)) |
485 return -1 | 498 return (-1, stdout, stderr) |
486 else: | 499 else: |
487 (total_time, exit_status, | 500 (total_time, exit_status, |
488 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( | 501 failed, stdout, stderr) = test_lib.RunTestWithInputOutput( |
489 command, stdin_data) | 502 command, stdin_data) |
490 PrintTotalTime(total_time) | 503 PrintTotalTime(total_time) |
504 # CheckExitStatus may spew stdout/stderr when there is an error. | |
505 # Otherwise, we do not spew stdout/stderr in this case (capture_output). | |
491 if not CheckExitStatus(failed, | 506 if not CheckExitStatus(failed, |
492 GlobalSettings['exit_status'], | 507 GlobalSettings['exit_status'], |
493 GlobalSettings['using_nacl_signal_handler'], | 508 GlobalSettings['using_nacl_signal_handler'], |
494 exit_status, stdout, stderr): | 509 exit_status, stdout, stderr): |
495 Print(FailureMessage(total_time)) | 510 Print(FailureMessage(total_time)) |
496 return -1 | 511 return (-1, stdout, stderr) |
497 if not CheckGoldenOutput(stdout, stderr): | 512 if not CheckGoldenOutput(stdout, stderr): |
498 Print(FailureMessage(total_time)) | 513 Print(FailureMessage(total_time)) |
499 return -1 | 514 return (-1, stdout, stderr) |
500 if not ProcessLogOutput(stdout, stderr): | 515 success, stdout, stderr = ProcessLogOutputSingle(stdout, stderr) |
501 Print(FailureMessage(total_time)) | 516 if not success: |
502 return -1 | 517 Print(FailureMessage(total_time) + ' ProcessLogOutputSingle failed!') |
518 return (-1, stdout, stderr) | |
503 | 519 |
504 if not CheckTimeBounds(total_time): | 520 if not CheckTimeBounds(total_time): |
505 Print(FailureMessage(total_time)) | 521 Print(FailureMessage(total_time)) |
506 return -1 | 522 return (-1, stdout, stderr) |
507 | 523 |
508 Print(SuccessMessage(total_time)) | 524 Print(SuccessMessage(total_time)) |
525 return (0, stdout, stderr) | |
526 | |
527 | |
528 def main(argv): | |
Nick Bray
2011/11/07 23:39:09
Capitalize
jvoung - send to chromium...
2011/11/09 00:50:54
capitalize Main? ok.
| |
529 global GlobalPlatform | |
Nick Bray
2011/11/07 23:39:09
Do not declare globals unless you assign directly
jvoung - send to chromium...
2011/11/09 00:50:54
Done.
| |
530 global GlobalReportStream | |
531 command = ProcessOptions(argv) | |
532 | |
533 if GlobalSettings['report']: | |
534 GlobalReportStream.append(open(GlobalSettings['report'], 'w')) | |
535 | |
536 if not GlobalSettings['name']: | |
537 GlobalSettings['name'] = command[0] | |
538 GlobalSettings['name'] = os.path.basename(GlobalSettings['name']) | |
539 | |
540 Print(RunMessage()) | |
541 | |
542 if GlobalSettings['osenv']: | |
543 Banner('setting environment') | |
544 env_vars = DestringifyList(GlobalSettings['osenv']) | |
545 else: | |
546 env_vars = [] | |
547 for env_var in env_vars: | |
548 key, val = env_var.split('=', 1) | |
549 Print('[%s] = [%s]' % (key, val)) | |
550 os.putenv(key, val) | |
Nick Bray
2011/11/07 23:39:09
putenv considered harmful. Assign to os.environ?
jvoung - send to chromium...
2011/11/09 00:50:54
Don't know the specifics (does it sometimes not wo
| |
551 | |
552 stdin_data = '' | |
553 if GlobalSettings['stdin']: | |
554 stdin_data = open(GlobalSettings['stdin']) | |
555 | |
556 run_under = GlobalSettings['run_under'] | |
557 if run_under: | |
558 command = run_under.split(',') + command | |
559 | |
560 Banner('running %s' % str(command)) | |
561 # print the command in copy-and-pastable fashion | |
562 print " ".join(env_vars + command) | |
563 | |
564 # Concatenate output when running multiple times (e.g., for timing). | |
565 combined_stdout = '' | |
566 combined_stderr = '' | |
567 cur_runs = 0 | |
568 num_runs = int(GlobalSettings['num_runs']) | |
569 while cur_runs < num_runs: | |
570 cur_runs += 1 | |
571 # Clear out previous log_file. | |
572 if GlobalSettings['log_file']: | |
573 try: | |
574 os.unlink(GlobalSettings['log_file']) # might not pre-exist | |
575 except OSError: | |
576 pass | |
577 ret_code, stdout, stderr = DoRun(command, stdin_data) | |
578 if ret_code != 0: | |
579 return ret_code | |
580 combined_stdout += stdout | |
581 combined_stderr += stderr | |
582 # Process the log output after all the runs. | |
583 success = ProcessLogOutputCombined(combined_stdout, combined_stderr) | |
584 if not success: | |
585 # Bogus time, since only ProcessLogOutputCombined failed. | |
586 Print(FailureMessage(0.0) + ' ProcessLogOutputCombined failed!') | |
587 return -1 | |
509 return 0 | 588 return 0 |
510 | 589 |
511 | |
512 if __name__ == '__main__': | 590 if __name__ == '__main__': |
513 retval = main(sys.argv[1:]) | 591 retval = main(sys.argv[1:]) |
514 # Add some whitepsace to make the logs easier to read. | 592 # Add some whitepsace to make the logs easier to read. |
515 sys.stdout.write('\n\n') | 593 sys.stdout.write('\n\n') |
516 sys.exit(retval) | 594 sys.exit(retval) |
517 | |
OLD | NEW |