added elfdump comparison test
authorYann Rouillard <yann@pleiades.fr.eu.org>
Mon, 29 Apr 2013 23:40:33 +0000 (01:40 +0200)
committerYann Rouillard <yann@pleiades.fr.eu.org>
Mon, 29 Apr 2013 23:40:33 +0000 (01:40 +0200)
test/run_elfdump_tests.py [new file with mode: 0755]

diff --git a/test/run_elfdump_tests.py b/test/run_elfdump_tests.py
new file mode 100755 (executable)
index 0000000..3d5f2e7
--- /dev/null
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+#-------------------------------------------------------------------------------
+# test/run_elfdump_tests.py
+#
+# Automatic test runner for elftools & elfdump
+#
+# Eli Bendersky (eliben@gmail.com)
+# Yann Rouillard (yann@pleiades.fr.eu.org)
+# 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))
+
+ELFDUMP_PATH = '/usr/ccs/bin/elfdump'
+
+def discover_testfiles(rootdir):
+    """ Discover test files in the given directory. Yield them one by one.
+    """
+    for filename in os.listdir(rootdir):
+        name, ext = os.path.splitext(filename)
+        if ext == '.elf' and 'solaris' in name:
+            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 [
+            '-y']:
+        if verbose: testlog.info("..option='%s'" % option)
+        # stdouts will be a 2-element list: output of elfdump and output
+        # of scripts/elfdump.py
+        stdouts = []
+        for exe_path in [ELFDUMP_PATH, 'scripts/elfdump.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 elfdump, 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 elfdump, s2 from elftools elfdump.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 elfdump which pyelftools
+        doesn't currently implement, or silly inconsistencies in the output of
+        elfdump, 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_elfdump_lines(lines):
+        filter_out = False
+        for line in lines:
+            if not filter_out:
+                yield line
+
+    lines1 = prepare_lines(s1)
+    lines2 = prepare_lines(s2)
+
+    lines1 = list(filter_elfdump_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
+            if not ok:
+                errmsg = 'Mismatch on line #%s:\n>>%s<<\n>>%s<<\n' % (
+                    i, lines1[i], lines2[i])
+                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_elfdump_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('elfdump 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'))
+
+    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())
+