text: import argparse
  import difflib
  import os
  import shutil
  import subprocess
  import sys
  import urllib2
  
  
  usage_str = ("usage: 'python check.py [options]'\n\n"
               "Runs the Binaryen test suite.")
  parser = argparse.ArgumentParser(description=usage_str)
  parser.add_argument(
      '--torture', dest='torture', action='store_true', default=True,
      help='Chooses whether to run the torture testcases. Default: true.')
  parser.add_argument(
      '--no-torture', dest='torture', action='store_false',
      help='Disables running the torture testcases.')
  parser.add_argument(
      '--only-prepare', dest='only_prepare', action='store_true', default=False,
      help='If enabled, only fetches the waterfall build. Default: false.')
  parser.add_argument(
      # Backwards compatibility
      '--only_prepare', dest='only_prepare', action='store_true', default=False,
      help='If enabled, only fetches the waterfall build. Default: false.')
  parser.add_argument(
      '--test-waterfall', dest='test_waterfall', action='store_true',
      default=False,
      help=('If enabled, fetches and tests the LLVM waterfall builds.'
            ' Default: false.'))
  parser.add_argument(
      '--no-test-waterfall', dest='test_waterfall', action='store_false',
      help='Disables downloading and testing of the LLVM waterfall builds.')
  parser.add_argument(
      '--abort-on-first-failure', dest='abort_on_first_failure',
      action='store_true', default=True,
      help=('Specifies whether to halt test suite execution on first test error.'
            ' Default: true.'))
  parser.add_argument(
      '--no-abort-on-first-failure', dest='abort_on_first_failure',
      action='store_false',
      help=('If set, the whole test suite will run to completion independent of'
            ' earlier errors.'))
  parser.add_argument(
      '--run-gcc-tests', dest='run_gcc_tests', action='store_true', default=True,
      help=('Chooses whether to run the tests that require building with native'
            ' GCC. Default: true.'))
  parser.add_argument(
      '--no-run-gcc-tests', dest='run_gcc_tests', action='store_false',
      help='If set, disables the native GCC tests.')
  
  parser.add_argument(
      '--interpreter', dest='interpreter', default='',
      help='Specifies the wasm interpreter executable to run tests on.')
  parser.add_argument(
      '--binaryen-bin', dest='binaryen_bin', default='',
      help=('Specifies a path to where the built Binaryen executables reside at.'
            ' Default: bin/ of current directory (i.e. assume an in-tree build).'
            ' If not specified, the environment variable BINARYEN_ROOT= can also'
            ' be used to adjust this.'))
  parser.add_argument(
      '--binaryen-root', dest='binaryen_root', default='',
      help=('Specifies a path to the root of the Binaryen repository tree.'
            ' Default: the directory where this file check.py resides.'))
  parser.add_argument(
      '--valgrind', dest='valgrind', default='',
      help=('Specifies a path to Valgrind tool, which will be used to validate'
            ' execution if specified. (Pass --valgrind=valgrind to search in'
            ' PATH)'))
  parser.add_argument(
      '--valgrind-full-leak-check', dest='valgrind_full_leak_check',
      action='store_true', default=False,
      help=('If specified, all unfreed (but still referenced) pointers at the'
            ' end of execution are considered memory leaks. Default: disabled.'))
  
  parser.add_argument(
      'positional_args', metavar='tests', nargs=argparse.REMAINDER,
      help='Names specific tests to run.')
  options = parser.parse_args()
  requested = options.positional_args
  
  num_failures = 0
  warnings = []
  
  
  def warn(text):
    global warnings
    warnings.append(text)
    print 'warning:', text
  
  
  # setup
  
  # Locate Binaryen build artifacts directory (bin/ by default)
  if not options.binaryen_bin:
    if os.environ.get('BINARYEN_ROOT'):
      if os.path.isdir(os.path.join(os.environ.get('BINARYEN_ROOT'), 'bin')):
        options.binaryen_bin = os.path.join(
            os.environ.get('BINARYEN_ROOT'), 'bin')
      else:
        options.binaryen_bin = os.environ.get('BINARYEN_ROOT')
    else:
      options.binaryen_bin = 'bin'
  
  # ensure BINARYEN_ROOT is set up
  os.environ['BINARYEN_ROOT'] = os.path.dirname(os.path.abspath(
      options.binaryen_bin))
  
  options.binaryen_bin = os.path.normpath(options.binaryen_bin)
  
  wasm_dis_filenames = ['wasm-dis', 'wasm-dis.exe']
  if not any(os.path.isfile(os.path.join(options.binaryen_bin, f))
             for f in wasm_dis_filenames):
    warn('Binaryen not found (or has not been successfully built to bin/ ?')
  
  # Locate Binaryen source directory if not specified.
  if not options.binaryen_root:
    path_parts = os.path.abspath(__file__).split(os.path.sep)
    options.binaryen_root = os.path.sep.join(path_parts[:-3])
  
  options.binaryen_test = os.path.join(options.binaryen_root, 'test')
  
  
  # Finds the given executable 'program' in PATH.
  # Operates like the Unix tool 'which'.
  def which(program):
    def is_exe(fpath):
      return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
    fpath, fname = os.path.split(program)
    if fpath:
      if is_exe(program):
        return program
    else:
      for path in os.environ["PATH"].split(os.pathsep):
        path = path.strip('"')
        exe_file = os.path.join(path, program)
        if is_exe(exe_file):
          return exe_file
        if '.' not in fname:
          if is_exe(exe_file + '.exe'):
            return exe_file + '.exe'
          if is_exe(exe_file + '.cmd'):
            return exe_file + '.cmd'
          if is_exe(exe_file + '.bat'):
            return exe_file + '.bat'
  
  
  WATERFALL_BUILD_DIR = os.path.join(options.binaryen_test, 'wasm-install')
  BIN_DIR = os.path.abspath(os.path.join(
      WATERFALL_BUILD_DIR, 'wasm-install', 'bin'))
  
  NATIVECC = (os.environ.get('CC') or which('mingw32-gcc') or
              which('gcc') or which('clang'))
  NATIVEXX = (os.environ.get('CXX') or which('mingw32-g++') or
              which('g++') or which('clang++'))
  NODEJS = which('nodejs') or which('node')
  MOZJS = which('mozjs')
  EMCC = which('emcc')
  
  BINARYEN_INSTALL_DIR = os.path.dirname(options.binaryen_bin)
  WASM_OPT = [os.path.join(options.binaryen_bin, 'wasm-opt')]
  WASM_AS = [os.path.join(options.binaryen_bin, 'wasm-as')]
  WASM_DIS = [os.path.join(options.binaryen_bin, 'wasm-dis')]
  ASM2WASM = [os.path.join(options.binaryen_bin, 'asm2wasm')]
  WASM2ASM = [os.path.join(options.binaryen_bin, 'wasm2asm')]
  WASM_CTOR_EVAL = [os.path.join(options.binaryen_bin, 'wasm-ctor-eval')]
  WASM_SHELL = [os.path.join(options.binaryen_bin, 'wasm-shell')]
  WASM_MERGE = [os.path.join(options.binaryen_bin, 'wasm-merge')]
  S2WASM = [os.path.join(options.binaryen_bin, 's2wasm')]
  WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')]
  WASM_METADCE = [os.path.join(options.binaryen_bin, 'wasm-metadce')]
  
  S2WASM_EXE = S2WASM[0]
  WASM_SHELL_EXE = WASM_SHELL[0]
  
  
  def wrap_with_valgrind(cmd):
    # Exit code 97 is arbitrary, used to easily detect when an error occurs that
    # is detected by Valgrind.
    valgrind = [options.valgrind, '--quiet', '--error-exitcode=97']
    if options.valgrind_full_leak_check:
      valgrind += ['--leak-check=full', '--show-leak-kinds=all']
    return valgrind + cmd
  
  
  if options.valgrind:
    WASM_OPT = wrap_with_valgrind(WASM_OPT)
    WASM_AS = wrap_with_valgrind(WASM_AS)
    WASM_DIS = wrap_with_valgrind(WASM_DIS)
    ASM2WASM = wrap_with_valgrind(ASM2WASM)
    WASM_SHELL = wrap_with_valgrind(WASM_SHELL)
    S2WASM = wrap_with_valgrind(S2WASM)
  
  os.environ['BINARYEN'] = os.getcwd()
  
  
  def get_platform():
    return {'linux2': 'linux',
            'darwin': 'mac',
            'win32': 'windows',
            'cygwin': 'windows'}[sys.platform]
  
  
  def has_shell_timeout():
    return get_platform() != 'windows' and os.system('timeout 1s pwd') == 0
  
  
  def fetch_waterfall():
    rev = open(os.path.join(options.binaryen_test, 'revision')).read().strip()
    buildername = get_platform()
    local_rev_path = os.path.join(WATERFALL_BUILD_DIR, 'local-revision')
    if os.path.exists(local_rev_path):
      with open(local_rev_path) as f:
        local_rev = f.read().strip()
      if local_rev == rev:
        return
    # fetch it
    basename = 'wasm-binaries-' + rev + '.tbz2'
    url = '/'.join(['https://storage.googleapis.com/wasm-llvm/builds',
                    buildername, rev, basename])
    print '(downloading waterfall %s: %s)' % (rev, url)
    downloaded = urllib2.urlopen(url).read().strip()
    fullname = os.path.join(options.binaryen_test, basename)
    open(fullname, 'wb').write(downloaded)
    print '(unpacking)'
    if os.path.exists(WATERFALL_BUILD_DIR):
      shutil.rmtree(WATERFALL_BUILD_DIR)
    os.mkdir(WATERFALL_BUILD_DIR)
    subprocess.check_call(['tar', '-xf', os.path.abspath(fullname)],
                          cwd=WATERFALL_BUILD_DIR)
    print '(noting local revision)'
    with open(local_rev_path, 'w') as o:
      o.write(rev + '\n')
  
  
  has_vanilla_llvm = False
  
  
  def setup_waterfall():
    # if we can use the waterfall llvm, do so
    global has_vanilla_llvm
    CLANG = os.path.join(BIN_DIR, 'clang')
    print 'trying waterfall clang at', CLANG
    try:
      subprocess.check_call([CLANG, '-v'])
      has_vanilla_llvm = True
      print '...success'
    except (OSError, subprocess.CalledProcessError) as e:
      warn('could not run vanilla LLVM from waterfall: ' + str(e) +
           ', looked for clang at ' + CLANG)
  
  
  if options.test_waterfall:
    fetch_waterfall()
    setup_waterfall()
  
  if options.only_prepare:
    print 'waterfall is fetched and setup, exiting since --only-prepare'
    sys.exit(0)
  
  # external tools
  
  try:
    if NODEJS is not None:
      subprocess.check_call(
          [NODEJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  except (OSError, subprocess.CalledProcessError):
    NODEJS = None
  if NODEJS is None:
    warn('no node found (did not check proper js form)')
  
  try:
    if MOZJS is not None:
      subprocess.check_call(
          [MOZJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  except (OSError, subprocess.CalledProcessError):
    MOZJS = None
  if MOZJS is None:
    warn('no mozjs found (did not check native wasm support nor asm.js'
         ' validation)')
  
  try:
    if EMCC is not None:
      subprocess.check_call(
          [EMCC, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  except (OSError, subprocess.CalledProcessError):
    EMCC = None
  if EMCC is None:
    warn('no emcc found (did not check non-vanilla emscripten/binaryen'
         ' integration)')
  
  has_vanilla_emcc = False
  try:
    subprocess.check_call(
        [os.path.join(options.binaryen_test, 'emscripten', 'emcc'), '--version'],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    has_vanilla_emcc = True
  except (OSError, subprocess.CalledProcessError):
    pass
  
  
  # utilities
  
  # removes a file if it exists, using any and all ways of doing so
  def delete_from_orbit(filename):
    try:
      os.unlink(filename)
    except OSError:
      pass
    if not os.path.exists(filename):
      return
    try:
      shutil.rmtree(filename, ignore_errors=True)
    except OSError:
      pass
    if not os.path.exists(filename):
      return
    try:
      import stat
      os.chmod(filename, os.stat(filename).st_mode | stat.S_IWRITE)
  
      def remove_readonly_and_try_again(func, path, exc_info):
        if not (os.stat(path).st_mode & stat.S_IWRITE):
          os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
          func(path)
        else:
          raise
      shutil.rmtree(filename, onerror=remove_readonly_and_try_again)
    except OSError:
      pass
  
  
  def fail_with_error(msg):
    global num_failures
    try:
      num_failures += 1
      raise Exception(msg)
    except Exception, e:
      print >> sys.stderr, str(e)
      if options.abort_on_first_failure:
        raise
  
  
  def fail(actual, expected):
    diff_lines = difflib.unified_diff(
        expected.split('\n'), actual.split('\n'),
        fromfile='expected', tofile='actual')
    diff_str = ''.join([a.rstrip() + '\n' for a in diff_lines])[:]
    fail_with_error("incorrect output, diff:\n\n%s" % diff_str)
  
  
  def fail_if_not_identical(actual, expected):
    if expected != actual:
      fail(actual, expected)
  
  
  def fail_if_not_contained(actual, expected):
    if expected not in actual:
      fail(actual, expected)
  
  
  if len(requested) == 0:
    tests = sorted(os.listdir(os.path.join(options.binaryen_test)))
  else:
    tests = requested[:]
  
  if not options.interpreter:
    warn('no interpreter provided (did not test spec interpreter validation)')
  
  if not has_vanilla_emcc:
    warn('no functional emcc submodule found')
  
  
  # check utilities
  
  def binary_format_check(wast, verify_final_result=True, wasm_as_args=['-g'],
                          binary_suffix='.fromBinary'):
    # checks we can convert the wast to binary and back
  
    print '     (binary format check)'
    cmd = WASM_AS + [wast, '-o', 'a.wasm'] + wasm_as_args
    print '      ', ' '.join(cmd)
    if os.path.exists('a.wasm'):
      os.unlink('a.wasm')
    subprocess.check_call(cmd, stdout=subprocess.PIPE)
    assert os.path.exists('a.wasm')
  
    cmd = WASM_DIS + ['a.wasm', '-o', 'ab.wast']
    print '      ', ' '.join(cmd)
    if os.path.exists('ab.wast'):
      os.unlink('ab.wast')
    subprocess.check_call(cmd, stdout=subprocess.PIPE)
    assert os.path.exists('ab.wast')
  
    # make sure it is a valid wast
    cmd = WASM_OPT + ['ab.wast']
    print '      ', ' '.join(cmd)
    subprocess.check_call(cmd, stdout=subprocess.PIPE)
  
    if verify_final_result:
      expected = open(wast + binary_suffix).read()
      actual = open('ab.wast').read()
      if actual != expected:
        fail(actual, expected)
  
    return 'ab.wast'
  
  
  def minify_check(wast, verify_final_result=True):
    # checks we can parse minified output
  
    print '     (minify check)'
    cmd = WASM_OPT + [wast, '--print-minified']
    print '      ', ' '.join(cmd)
    subprocess.check_call(
        WASM_OPT + [wast, '--print-minified'],
        stdout=open('a.wast', 'w'), stderr=subprocess.PIPE)
    assert os.path.exists('a.wast')
    subprocess.check_call(
        WASM_OPT + ['a.wast', '--print-minified'],
        stdout=open('b.wast', 'w'), stderr=subprocess.PIPE)
    assert os.path.exists('b.wast')
    if verify_final_result:
      expected = open('a.wast').read()
      actual = open('b.wast').read()
      if actual != expected:
        fail(actual, expected)
    if os.path.exists('a.wast'):
      os.unlink('a.wast')
    if os.path.exists('b.wast'):
      os.unlink('b.wast')
  