text: #!/usr/bin/env python
  #-------------------------------------------------------------------------------
  # test/run_readelf_tests.py
  #
  # Automatic test runner for elftools & readelf
  #
  # Eli Bendersky (eliben@gmail.com)
  # This code is in the public domain
  #-------------------------------------------------------------------------------
  import os, sys
  import re
  from difflib import SequenceMatcher
  from optparse import OptionParser
  import logging
  import platform
  from utils import setup_syspath; setup_syspath()
  from utils import run_exe, is_in_rootdir, dump_output_to_temp_files
  
  
  # Create a global logger object
  #
  testlog = logging.getLogger('run_tests')
  testlog.setLevel(logging.DEBUG)
  testlog.addHandler(logging.StreamHandler(sys.stdout))
  
  # Set the path for calling readelf. We carry our own version of readelf around,
  # because binutils tend to change its output even between daily builds of the
  # same minor release and keeping track is a headache.
  READELF_PATH = 'test/external_tools/readelf'
  if not os.path.exists(READELF_PATH):
      READELF_PATH = 'readelf'
  
  def discover_testfiles(rootdir):
      """ Discover test files in the given directory. Yield them one by one.
      """
      for filename in os.listdir(rootdir):
          _, ext = os.path.splitext(filename)
          if ext == '.elf':
              yield os.path.join(rootdir, filename)
  
  
  def run_test_on_file(filename, verbose=False):
      """ Runs a test on the given input filename. Return True if all test
          runs succeeded.
      """
      success = True
      testlog.info("Test file '%s'" % filename)
      for option in [
              '-e', '-d', '-s', '-r', '-x.text', '-p.shstrtab', '-V',
              '--debug-dump=info', '--debug-dump=decodedline',
              '--debug-dump=frames', '--debug-dump=frames-interp']:
          if verbose: testlog.info("..option='%s'" % option)
          # stdouts will be a 2-element list: output of readelf and output
          # of scripts/readelf.py
          stdouts = []
          for exe_path in [READELF_PATH, 'scripts/readelf.py']:
              args = [option, filename]
              if verbose: testlog.info("....executing: '%s %s'" % (
                  exe_path, ' '.join(args)))
              rc, stdout = run_exe(exe_path, args)
              if rc != 0:
                  testlog.error("@@ aborting - '%s' returned '%s'" % (exe_path, rc))
                  return False
              stdouts.append(stdout)
          if verbose: testlog.info('....comparing output...')
          rc, errmsg = compare_output(*stdouts)
          if rc:
              if verbose: testlog.info('.......................SUCCESS')
          else:
              success = False
              testlog.info('.......................FAIL')
              testlog.info('....for option "%s"' % option)
              testlog.info('....Output #1 is readelf, Output #2 is pyelftools')
              testlog.info('@@ ' + errmsg)
              dump_output_to_temp_files(testlog, *stdouts)
      return success
  
  
  def compare_output(s1, s2):
      """ Compare stdout strings s1 and s2.
          s1 is from readelf, s2 from elftools readelf.py
          Return pair success, errmsg. If comparison succeeds, success is True
          and errmsg is empty. Otherwise success is False and errmsg holds a
          description of the mismatch.
  
          Note: this function contains some rather horrible hacks to ignore
          differences which are not important for the verification of pyelftools.
          This is due to some intricacies of binutils's readelf which pyelftools
          doesn't currently implement, features that binutils doesn't support,
          or silly inconsistencies in the output of readelf, which I was reluctant
          to replicate. Read the documentation for more details.
      """
      def prepare_lines(s):
          return [line for line in s.lower().splitlines() if line.strip() != '']
      def filter_readelf_lines(lines):
          filter_out = False
          for line in lines:
              if 'of the .eh_frame section' in line:
                  filter_out = True
              elif 'of the .debug_frame section' in line:
                  filter_out = False
              if not filter_out:
                  if not line.startswith('unknown: length'):
                      yield line
  
      lines1 = prepare_lines(s1)
      lines2 = prepare_lines(s2)
  
      lines1 = list(filter_readelf_lines(lines1))
  
      flag_after_symtable = False
  
      if len(lines1) != len(lines2):
          return False, 'Number of lines different: %s vs %s' % (
                  len(lines1), len(lines2))
  
      for i in range(len(lines1)):
          if 'symbol table' in lines1[i]:
              flag_after_symtable = True
  
          # Compare ignoring whitespace
          lines1_parts = lines1[i].split()
          lines2_parts = lines2[i].split()
  
          if ''.join(lines1_parts) != ''.join(lines2_parts):
              ok = False
  
              try:
                  # Ignore difference in precision of hex representation in the
                  # last part (i.e. 008f3b vs 8f3b)
                  if (''.join(lines1_parts[:-1]) == ''.join(lines2_parts[:-1]) and
                      int(lines1_parts[-1], 16) == int(lines2_parts[-1], 16)):
                      ok = True
              except ValueError:
                  pass
  
              sm = SequenceMatcher()
              sm.set_seqs(lines1[i], lines2[i])
              changes = sm.get_opcodes()
              if flag_after_symtable:
                  # Detect readelf's adding @ with lib and version after
                  # symbol name.
                  if (    len(changes) == 2 and changes[1][0] == 'delete' and
                          lines1[i][changes[1][1]] == '@'):
                      ok = True
              elif 'at_const_value' in lines1[i]:
                  # On 32-bit machines, readelf doesn't correctly represent
                  # some boundary LEB128 numbers
                  val = lines2_parts[-1]
                  num2 = int(val, 16 if val.startswith('0x') else 10)
                  if num2 <= -2**31 and '32' in platform.architecture()[0]:
                      ok = True
              elif 'os/abi' in lines1[i]:
                  if 'unix - gnu' in lines1[i] and 'unix - linux' in lines2[i]:
                      ok = True
              elif (  'unknown at value' in lines1[i] and
                      'dw_at_apple' in lines2[i]):
                  ok = True
              else:
                  for s in ('t (tls)', 'l (large)'):
                      if s in lines1[i] or s in lines2[i]:
                          ok = True
                          break
              if not ok:
                  errmsg = 'Mismatch on line #%s:\n>>%s<<\n>>%s<<\n (%r)' % (
                      i, lines1[i], lines2[i], changes)
                  return False, errmsg
      return True, ''
  
  
  def main():
      if not is_in_rootdir():
          testlog.error('Error: Please run me from the root dir of pyelftools!')
          return 1
  
      optparser = OptionParser(
          usage='usage: %prog [options] [file] [file] ...',
          prog='run_readelf_tests.py')
      optparser.add_option('-V', '--verbose',
          action='store_true', dest='verbose',
          help='Verbose output')
      options, args = optparser.parse_args()
  
      if options.verbose:
          testlog.info('Running in verbose mode')
          testlog.info('Python executable = %s' % sys.executable)
          testlog.info('readelf path = %s' % READELF_PATH)
          testlog.info('Given list of files: %s' % args)
  
      # If file names are given as command-line arguments, only these files
      # are taken as inputs. Otherwise, autodiscovery is performed.
      #
      if len(args) > 0:
          filenames = args
      else:
          filenames = list(discover_testfiles('test/testfiles_for_readelf'))
  
      success = True
      for filename in filenames:
          if success:
              success = success and run_test_on_file(
                                      filename,
                                      verbose=options.verbose)
  
      if success:
          testlog.info('\nConclusion: SUCCESS')
          return 0
      else:
          testlog.info('\nConclusion: FAIL')
          return 1
  
  
  if __name__ == '__main__':
      sys.exit(main())
  
  