run-testcase.py can check the output of an automatic header file.
authorJustin Seyster <jseyster@cs.sunysb.edu>
Mon, 6 Sep 2010 20:58:19 +0000 (16:58 -0400)
committerJustin Seyster <jseyster@cs.sunysb.edu>
Mon, 6 Sep 2010 20:58:19 +0000 (16:58 -0400)
test/run-testcase.py
test/testcase.dtd

index 85d939f7be37a1b97e763edd5cab1173fbb9c3c3..ae58e01af8a714c53489c76d31763a7fcb7b602f 100755 (executable)
@@ -106,6 +106,10 @@ class ExtraOutput(TestProgramException):
         return ("Test program printed unexpected additional output:\n"
                 + self.extra)
 
+class NoAutoheader(TestProgramException):
+    def getMessage(self):
+        return "Test plug-in did not generate an automatic header file."
+
 # A plug-in includes a source file and id.
 class Plugin:
     def __init__(self, plugin_id, source):
@@ -124,6 +128,7 @@ class Run:
         self.hooks_source = hooks_source
         self.plugin_list = []
         self.output = ""
+        self.prototypes = None
 
 class TestcaseHandler(handler.ContentHandler):
     plugins = {}
@@ -203,6 +208,23 @@ class TestcaseHandler(handler.ContentHandler):
         self.current_run.output = self.current_cdata
         self.in_output = False
 
+    def startPrototypes(self, attrs):
+        if (self.current_run is None):
+            raise MisplacedElement("prototypes", "run")
+
+        if (self.current_run.prototypes is not None):
+            raise DuplicateTag("prototypes", "run")
+
+        # We're only interested in the cdata, which we can't get until
+        # the endPrototypes event.
+        self.current_cdata = ""
+        self.in_output = True
+
+    def endPrototypes(self):
+        assert self.current_run is not None
+        self.current_run.prototypes = self.current_cdata
+        self.in_output = False
+
     # Parsing with SAX is simple but tedious.  There's a start
     # function for each tag and an end function for some tags.  They
     # just stash the data they find into a relevant data structure
@@ -218,12 +240,16 @@ class TestcaseHandler(handler.ContentHandler):
             self.startUsing(attrs)
         elif name == 'output':
             self.startOutput(attrs)
+        elif name == 'prototypes':
+            self.startPrototypes(attrs)
 
     def endElement(self, name):
         if name == 'run':
             self.endRun()
         elif name == 'output':
             self.endOutput()
+        elif name == 'prototypes':
+            self.endPrototypes()
 
     def characters(self, chars):
         if not self.in_output and re.search(r"\S", chars):
@@ -286,8 +312,8 @@ def runGCC(args, compile_fail_msg):
 # file (which will be in working_dir).  On failure, the return value
 # is None.
 def compilePlugin(working_dir, plugin_id, plugin_base_name, plugin_source):
-    plugin_lib_name = '{0:s}/{1:s}.so.1.0.0'.format(working_dir,
-                                                    plugin_base_name)
+    plugin_lib_name = '{0:s}/{1:s}.so'.format(working_dir,
+                                              plugin_base_name)
 
     plugin_source = formatSourceFile(plugin_source)
     include_flag = '-I{0:s}/src'.format(gcc_interaspect_src)
@@ -316,13 +342,22 @@ def compilePlugin(working_dir, plugin_id, plugin_base_name, plugin_source):
 # hooks_source: The C source file with the advice functions.  This
 # source is compiled without any plug-ins.
 #
-# plugin_libs: A list of .so plug-in files that will be used to
-# compile the target program.
+# autoheader_file: The file name where InterAspect should generate its
+# header.  This name will be passed to the plug-in, which has the
+# option of using it to call aop_write_c_header().
+#
+# plugin_names: An ordered list of the names of plug-ins that will be
+# used when compiling the target plug-in.
+#
+# plugin_libs: A mapping from plug-in names to the actual .so files.
+# There needs to be an entry in this dictionary for each plug-in in
+# plugin_names.
 #
 # On success, compileTestcase returns the path to the final test
 # executable (which will be in working_dir).  On failure, the return
 # value is None.
-def compileTestcase(working_dir, target_source, hooks_source, plugin_libs):
+def compileTestcase(working_dir, target_source, hooks_source, autoheader_file,
+                    plugin_names, plugin_libs):
     hooks_source = formatSourceFile(hooks_source)
     test_include = '-I{0:s}/test'.format(gcc_interaspect_src)
     hook_o_file = working_dir + '/hooks.o'
@@ -343,7 +378,19 @@ def compileTestcase(working_dir, target_source, hooks_source, plugin_libs):
     # Compile the target itself.
     target_source = formatSourceFile(target_source)
     target_o_file = working_dir + '/target.o'
-    cmd_args = ['-fplugin={0:s}'.format(lib) for lib in plugin_libs]
+    cmd_args = []
+    for name in plugin_names:
+        lib = plugin_libs[name];
+        assert lib is not None
+
+        # An argument specifying the plug-in to GCC.
+        cmd_args += ['-fplugin={0:s}'.format(lib)]
+
+        # An argument specifying the name of the autogenerated header
+        # file to the plug-in.
+        cmd_args += ['-fplugin-arg-{0:s}-header={1:s}'
+                     .format(name, autoheader_file)]
+
     cmd_args += ['-Wall', '-Werror', '-c', '-o', target_o_file, target_source]
     result = runGCC(cmd_args, "Fatal -- Failed to compile target source:")
     if not result:
@@ -358,15 +405,10 @@ def compileTestcase(working_dir, target_source, hooks_source, plugin_libs):
 
     return executable        
 
-# Run the given process (which is the test program) and check that its
-# output matches expected output.  If the run fails or if the output
-# does not match, checkRun raises a TestProgramException.
-def checkRun(test_proc, expected_output):
-    (actual_output, _) = test_proc.communicate()
-
-    if (test_proc.returncode != 0):
-        raise ErrorReturnCode()
-
+# Check if the output of the test case matches the expected output.
+# If it does not, raise an exception with an error message explaining
+# the mismatch.
+def checkRun(actual_output, expected_output):
     actual_array = actual_output.strip().splitlines()
     expected_array = expected_output.strip().splitlines()
 
@@ -382,13 +424,44 @@ def checkRun(test_proc, expected_output):
     if (len(actual_array) > len(expected_array)):
         raise ExtraOutput(actual_array[len(expected_array)].strip())
 
+def checkHeader(autoheader_file, expected_prototypes):
+    header_output = ""
+
+    try:
+        filehandle = None  # So close does not crash when open fails.
+        filehandle = open(autoheader_file, 'r')
+
+        # We want to read in all the lines that come after BEGIN
+        # PROTOTYPES until we see the #endif the closes the header
+        # guard..
+        in_prototypes = False
+        for line in filehandle:
+            if (re.search(r"BEGIN PROTOTYPES", line)):
+                in_prototypes = True
+                continue
+            elif (re.search(r"endif", line)):
+                break
+
+            if (in_prototypes):
+                header_output += line
+
+    except IOError as e:
+        sys.stderr.write('{0:s}: {1:s}\n'.format(autoheader_file, e.strerror))
+        raise NoAutoheader()
+    finally:
+        if (filehandle is not None):
+            filehandle.close()
+
+    checkRun(header_output.strip(), expected_prototypes)
+
 # Same as doRun but takes a temporary working directory (to place
 # compiled objects) as an input.
 def doRunInTempDir(run, tmp_dir):
     print "  Run:", run.name
 
     # Compile all the plug-ins for this test.
-    plugin_libs = []
+    plugin_names = []
+    plugin_libs = {}
     for i in range(len(run.plugin_list)):
         plugin = run.plugin_list[i]
         plugin_base_name = 'test_plugin_{0:d}'.format(i + 1)
@@ -396,10 +469,13 @@ def doRunInTempDir(run, tmp_dir):
                                         plugin_base_name, plugin.source)
         if plugin_lib_name is None:
             return False
-        plugin_libs.append(plugin_lib_name)
+        plugin_names.append(plugin_base_name)
+        plugin_libs[plugin_base_name] = plugin_lib_name
 
+    autoheader_file = tmp_dir + '/autoheader.h'
     test_executable = compileTestcase(tmp_dir, run.target_source,
-                                      run.hooks_source, plugin_libs)
+                                      run.hooks_source, autoheader_file,
+                                      plugin_names, plugin_libs)
 
     if test_executable is None:
         return False
@@ -414,8 +490,15 @@ def doRunInTempDir(run, tmp_dir):
         sys.exit(1)
 
     # ... and run it.
+    (test_output, _) = test_proc.communicate()
+    if (test_proc.returncode != 0):
+        raise ErrorReturnCode()
+
+    # Check the output
     try:
-        checkRun(test_proc, run.output)
+        checkRun(test_output, run.output)
+        if (run.prototypes is not None):
+            checkHeader(autoheader_file, run.prototypes)
     except TestProgramException as e:
         print e.getMessage()
         return False
index 0a1741ec30a19a4479c589aa5d859795b7e48b2c..27222af76961321a4ab12e95333ed8b952f16263 100644 (file)
@@ -1,8 +1,9 @@
 <!ELEMENT testcase (plugin+, run+)>
 <!ELEMENT plugin EMPTY>
-<!ELEMENT run (using*, output)>
+<!ELEMENT run (using*, output, prototypes?)>
 <!ELEMENT using EMPTY>
 <!ELEMENT output (#PCDATA)>
+<!ELEMENT prototypes (#PCDATA)>
 
 <!ATTLIST testcase
   name   CDATA #REQUIRED