Test script no longer automatically generates hooks.
authorJustin Seyster <jseyster@cs.sunysb.edu>
Wed, 1 Sep 2010 19:34:56 +0000 (15:34 -0400)
committerJustin Seyster <jseyster@cs.sunysb.edu>
Wed, 1 Sep 2010 19:34:56 +0000 (15:34 -0400)
Instead, it is necessary to supply a user-written hooks in a separate
source file.  This makes the test case system much simpler and more
flexible.

test/run-testcase.py
test/testcase.dtd

index bf6527679ef80c5c2468634b6bba2c5a4284435d..2a5576ad7a4d50d4e72d272d37f6cdeefc119a70 100755 (executable)
@@ -50,14 +50,17 @@ class MisplacedElement(XMLDataException):
     def getMessage(self):
         return "Element <" + self.element + "> outside of <" + self.parent + ">"
 
-class BadName(XMLDataException):
-    def __init__(self, attr, val):
-        self.attr = attr
-        self.val = val
+class MisplacedCData(XMLDataException):
+    def getMessage(self):
+        return "Character data outside of <output> element"
+
+class DuplicateTag(XMLDataException):
+    def __init__(self, element, parent):
+        self.element = element
+        self.parent = parent
 
     def getMessage(self):
-        return ("Value of " + self.attr + " not a legal C function name: "
-                + self.val)
+        return "Only one <" + self.element + "> allowed in <" + self.parent + ">"
 
 class BadId(XMLDataException):
     def __init__(self, id_name, val):
@@ -68,34 +71,11 @@ class BadId(XMLDataException):
         return ("Invalid reference: No " + self.id_name + " with id "
                 + self.val)
 
-class DuplicateHook(XMLDataException):
-    def __init__(self, run_name, hook_name):
-        self.run_name = run_name
-        self.hook_name = hook_name
-
-    def getMessage(self):
-        return (self.run_name + ": Run contains two hooks with same name, "
-                + self.hook_name + ", but different arguments")
-
-# A plug-in includes a source file and the descriptions of each hook
-# that the plug-in might add a call for.
+# A plug-in includes a source file and id.
 class Plugin:
     def __init__(self, plugin_id, source):
         self.plugin_id = plugin_id
         self.source = source
-        self.hooks = {}
-
-# A hook description comprises the C function name for the hook and
-# the types for all its arguments.
-class Hook:
-    def __init__(self, name):
-        self.name = name
-        self.arg_list = []
-
-class Arg:
-    def __init__(self, arg_id, arg_type):
-        self.arg_id = arg_id
-        self.arg_type = arg_type
 
 # A run has a target source file along with a list of plug-ins that
 # should be used when compiling the target and the hooks necessary for
@@ -103,76 +83,22 @@ class Arg:
 # output that the target is supposed to produce (because of the
 # plug-in) when it runs.
 class Run:
-    def __init__(self, name, target_source):
-        self.name = name;
-        self.target_source = target_source;
+    def __init__(self, name, target_source, hooks_source):
+        self.name = name
+        self.target_source = target_source
+        self.hooks_source = hooks_source
         self.plugin_list = []
-        self.hooks = {}
-        self.output = []
-
-# A Value is one entry in the output list for a Run object.
-class Value:
-    def __init__(self, val_type, val):
-        self.val_type = val_type
-        self.val = val
-
-# Called for two hooks with the same name, return True if they have
-# _exactly_ the same arguments.
-def hooksMatch(hook1, hook2):
-    assert hook1.name == hook2.name
-
-    if len(hook1.arg_list) != len(hook2.arg_list):
-        return False
-
-    for i in range(len(hook1.arg_list)):
-        arg1 = hook1.arg_list[i]
-        arg2 = hook2.arg_list[i]
-        if arg1.arg_id != arg2.arg_id or arg1.arg_type != arg2.arg_type:
-            return False
-
-    return True
+        self.output = ""
 
 class TestcaseHandler(handler.ContentHandler):
     plugins = {}
-    current_plugin = None
-    current_hook = None
 
     run_list = []
     current_run = None
     current_call_hook = None
 
-    current_value_arg_id = None
-    current_value_cdata = ""
-
-    def isAllowedInC(self, token):
-        if not re.match('[a-zA-Z_][0-9a-zA-Z_]*', token):
-            return False
-        elif token == 'if':
-            return False
-        elif token == 'else':
-            return False
-        elif token == 'for':
-            return False
-        elif token == 'do':
-            return False
-        elif token == 'while':
-            return False
-        elif token == 'void':
-            return False
-        elif token == 'unsigned':
-            return False
-        elif token == 'short':
-            return False
-        elif token == 'long':
-            return False
-        elif token == 'int':
-            return False
-        elif token == 'float':
-            return False
-        elif token == 'double':
-            return False
-        else:
-            return True
+    current_cdata = ""
+    in_output = False
 
     def startTestcase(self, attrs):
         self.name = attrs.get('name')
@@ -188,50 +114,9 @@ class TestcaseHandler(handler.ContentHandler):
         if source is None:
             raise MissingAttr("plugin", "source")
 
-        self.current_plugin = Plugin(plugin_id, source)
-
-    def endPlugin(self):
-        self.plugins[self.current_plugin.plugin_id] = (self.current_plugin)
-        self.current_plugin = None
-
-    def startHook(self, attrs):
-        if self.current_hook is not None:
-            raise MisplacedElement("hook", "plugin");
-
-        name = attrs.get('name')
-        if name is None:
-            raise MissingAttr("hook", "name")
-        elif not self.isAllowedInC(name):
-            raise BadName("name", name)
-
-        self.current_hook = Hook(name)
-
-    def endHook(self):
-        if self.current_plugin is None:
-            raise MisplacedElement("hook", "plugin")
-
-        self.current_plugin.hooks[self.current_hook.name] = self.current_hook
-        self.current_hook = None
-
-    def startArg(self, attrs):
-        if self.current_hook is None:
-            raise MisplacedElement("arg", "hook")
-
-        arg_id = attrs.get('id')
-        if arg_id is None:
-            raise MissingAttr("arg", "id")
-
-        arg_type = attrs.get('type')
-        if arg_type is None:
-            raise MissingAttr("arg", "type")
-
-        arg = Arg(arg_id, arg_type)
-        self.current_hook.arg_list.append(arg)
+        self.plugins[plugin_id] = Plugin(plugin_id, source)
 
     def startRun(self, attrs):
-        if self.current_plugin is not None:
-            raise MisplacedElement("run", "testcase")
-
         run_name = attrs.get('name')
         if run_name is None:
             raise MissingAttr("run", "name")
@@ -240,7 +125,11 @@ class TestcaseHandler(handler.ContentHandler):
         if target_source is None:
             raise MissingAttr("run", "target")
 
-        self.current_run = Run(run_name, target_source)
+        hooks_source = attrs.get('hooks')
+        if hooks_source is None:
+            raise MissingAttr("run", "hooks")
+
+        self.current_run = Run(run_name, target_source, hooks_source)
 
     def endRun(self):
         self.run_list.append(self.current_run)
@@ -259,80 +148,25 @@ class TestcaseHandler(handler.ContentHandler):
         except KeyError as e:
             raise BadId("plugin", plugin_id)
 
-        # Add all of this plug-in's hooks to the run.
-        for name, hook in plugin.hooks.iteritems():
-            dup_hook = None
-            try:
-                dup_hook = self.current_run.hooks[name]
-            except KeyError as e:
-                pass
-
-            if dup_hook is None:
-                # We don't have any hooks with this name.
-                self.current_run.hooks[name] = hook
-            else:
-                # There is already a hook with this name.  Make sure
-                # it matches.
-                if not hooksMatch(hook, dup_hook):
-                    raise DuplicateHook(self.current_run.name, name)
-
         # And the plug-in itself!
         self.current_run.plugin_list.append(plugin)
 
-    def startCall(self, attrs):
-        if self.current_run is None or self.current_call_hook is not None:
-            raise MisplacedElement("call", "run")
+    def startOutput(self, attrs):
+        if (self.current_run is None):
+            raise MisplacedElement("output", "run")
 
-        hook_name = attrs.get('name')
-        if hook_name is None:
-            raise MissingAttr("call", "name")
+        if (self.current_run.output != ""):
+            raise DuplicateTag("output", "run")
 
-        try:
-            hook = self.current_run.hooks[hook_name]
-        except KeyError as e:
-            raise BadId("hook", hook_name)
-
-        self.current_call_hook = hook
-
-        output_val = Value('Hook', hook_name)
-        self.current_run.output.append(output_val)
-
-    def endCall(self):
-        self.current_call_hook = None
-
-    def startValue(self, attrs):
-        if (self.current_call_hook is None
-            or self.current_value_arg_id is not None):
-            raise MisplacedElement("value", "call")
-
-        arg_id = attrs.get('id')
-        if arg_id is None:
-            raise MissingAttr("value", "id")
-
-        # We can't examine the cdata until the endValue event.  Stash
-        # the argument name until then.
-        self.current_value_arg_id = arg_id
-        self.current_value_cdata = ""
-
-    def endValue(self):
-        assert self.current_call_hook is not None
-        assert self.current_value_arg_id is not None
+        # We're only interested in the cdata, which we can't get until
+        # the endOutput event.
+        self.current_cdata = ""
+        self.in_output = True
 
-        # Find the arg with the given arg id.  These arguments are not
-        # stored in an associative array because their order is
-        # important.  These lists are way too small to justify the
-        # overhead of a separate index.
-        arg = None
-        for arg_it in self.current_call_hook.arg_list:
-            if self.current_value_arg_id == arg_it.arg_id:
-                arg = arg_it
-        if arg is None:
-            raise BadId("arg", self.current_value_arg_id)
-
-        new_value = Value(arg.arg_type, self.current_value_cdata)
-        self.current_run.output.append(new_value)
-
-        self.current_value_arg_id = None
+    def endOutput(self):
+        assert self.current_run is not None
+        self.current_run.output = 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
@@ -343,62 +177,24 @@ class TestcaseHandler(handler.ContentHandler):
             self.startTestcase(attrs)
         elif name == 'plugin':
             self.startPlugin(attrs)
-        elif name == 'hook':
-            self.startHook(attrs)
-        elif name == 'arg':
-            self.startArg(attrs)
         elif name == 'run':
             self.startRun(attrs)
         elif name == 'using':
             self.startUsing(attrs)
-        elif name == 'call':
-            self.startCall(attrs)
-        elif name == 'value':
-            self.startValue(attrs)
+        elif name == 'output':
+            self.startOutput(attrs)
 
     def endElement(self, name):
-        if name == 'plugin':
-            self.endPlugin()
-        elif name == 'hook':
-            self.endHook()
-        elif name == 'run':
+        if name == 'run':
             self.endRun()
-        elif name == 'call':
-            self.endCall()
-        elif name == 'value':
-            self.endValue()
+        elif name == 'output':
+            self.endOutput()
 
     def characters(self, chars):
-        if self.current_value_arg_id is not None:
-            self.current_value_cdata += chars
-
-def getCheckFormat(c_type):
-    c_type = c_type.strip()
-    if re.match(r".*\bchar\s+\*", c_type):
-        return r"string: %s\n"
-    if re.match(r".*\*$", c_type):
-        return r"pointer: %p\n"
-    elif c_type == 'int':
-        return r"int: %d\n"
-    else:
-        return None
+        if not self.in_output and re.search(r"\S", chars):
+            raise MisplacedCData()
 
-# Print the C prototype and function body for the given plug-in hook
-# to the given stream.
-def printHook(stream, hook):
-    params = ['{0:s} arg{1:d}'.format(hook.arg_list[i].arg_type, i) for i in
-              range(len(hook.arg_list))]
-    param_text = ', '.join(params)
-
-    stream.write('void {0:s}({1:s})\n'.format(hook.name, param_text))
-    stream.write('{\n');
-    stream.write('  check_printf("hook: {0:s}\\n");\n'.format(hook.name))
-    for i in range(len(hook.arg_list)):
-        arg = hook.arg_list[i]
-        check_format = getCheckFormat(arg.arg_type)
-        if check_format is not None:
-            stream.write('  check_printf("{0:s}", arg{1:d});\n'.format(check_format, i))
-    stream.write('}\n');
+        self.current_cdata += chars
 
 # Run GCC with the given arguments.  On failure, print an appropriate
 # error and return False.
@@ -462,11 +258,11 @@ def compilePlugin(working_dir, plugin_id, plugin_base_name, plugin_source):
 
     return plugin_lib_name
 
-# Compile the testcase instrumentation target, along with
-# auto-generated hook functions.  Several compilation files get
-# created in the given working directory.  It is the caller's
-# responsibility to delete these files.  No files will be left over in
-# other directories, though.
+# Compile the testcase instrumentation target, along with supplied
+# hook functions.  Several compilation files get created in the given
+# working directory.  It is the caller's responsibility to delete
+# these files.  No files will be left over in other directories,
+# though.
 #
 # working_dir: A temporary directory to store intermediate files and
 # the resulting executable.
@@ -474,25 +270,20 @@ def compilePlugin(working_dir, plugin_id, plugin_base_name, plugin_source):
 # target_source: The C source file for the target program (the program
 # that we intend to instrument with the test plug-ins).
 #
+# 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.
 #
 # 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, plugin_libs, hooks):
-    # Create a C file with the necessary plug-in hooks and compile it.
-    hook_file_name = working_dir + '/hooks.c'
-    hook_file = open(hook_file_name, 'w');
-    hook_file.write('#include "test-driver.h"\n')
-    for name, hook in hooks.iteritems():
-        printHook(hook_file, hook)
-    hook_file.close()
-
+def compileTestcase(working_dir, target_source, hooks_source, plugin_libs):
     test_include = '-I{0:s}/test'.format(gcc_interaspect_src)
     hook_o_file = working_dir + '/hooks.o'
     cmd_args = ['-Wall', '-Werror', test_include, '-c', '-o', hook_o_file,
-                hook_file_name]
+                hooks_source]
     result = runGCC(cmd_args, "Fatal -- Failed to compile plug-in hooks:")
     if not result:
         return None
@@ -542,8 +333,8 @@ def doRun(run):
             return False
         plugin_libs.append(plugin_lib_name)
 
-    test_executable = compileTestcase(tmp_dir, run.target_source, plugin_libs,
-                                      run.hooks)
+    test_executable = compileTestcase(tmp_dir, run.target_source,
+                                      run.hooks_source, plugin_libs)
 
     if test_executable is not None:
         test_proc = subprocess.Popen([test_executable])
index 324d78637a4d6bf68ff0ba6d7854e961de86854d..0a1741ec30a19a4479c589aa5d859795b7e48b2c 100644 (file)
@@ -1,11 +1,8 @@
 <!ELEMENT testcase (plugin+, run+)>
-<!ELEMENT plugin (hook+)>
-<!ELEMENT run (using*, call+)>
-<!ELEMENT hook (arg*)>
+<!ELEMENT plugin EMPTY>
+<!ELEMENT run (using*, output)>
 <!ELEMENT using EMPTY>
-<!ELEMENT call (value*)>
-<!ELEMENT arg EMPTY>
-<!ELEMENT value (#PCDATA)>
+<!ELEMENT output (#PCDATA)>
 
 <!ATTLIST testcase
   name   CDATA #REQUIRED
 <!ATTLIST run
   name   CDATA #REQUIRED
   target CDATA #REQUIRED
->
-<!ATTLIST hook
-  name   CDATA #REQUIRED
+  hooks  CDATA #REQUIRED
 >
 <!ATTLIST using
   plugin CDATA #REQUIRED
 >
-<!ATTLIST call
-  name   CDATA #REQUIRED
->
-<!ATTLIST arg
-  id     CDATA #REQUIRED
-  type   CDATA #REQUIRED
->
-<!ATTLIST value
-  id     CDATA #REQUIRED
->