From 0dadb45967fb52fa5088a7e547a008870a29908f Mon Sep 17 00:00:00 2001 From: Justin Seyster Date: Mon, 6 Sep 2010 16:58:19 -0400 Subject: [PATCH] run-testcase.py can check the output of an automatic header file. --- test/run-testcase.py | 121 ++++++++++++++++++++++++++++++++++++------- test/testcase.dtd | 3 +- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/test/run-testcase.py b/test/run-testcase.py index 85d939f..ae58e01 100755 --- a/test/run-testcase.py +++ b/test/run-testcase.py @@ -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 diff --git a/test/testcase.dtd b/test/testcase.dtd index 0a1741e..27222af 100644 --- a/test/testcase.dtd +++ b/test/testcase.dtd @@ -1,8 +1,9 @@ - + +