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