Package qm :: Package test :: Package classes :: Module compiler_test
[hide private]
[frames] | no frames]

Source Code for Module qm.test.classes.compiler_test

  1  ######################################################################## 
  2  # 
  3  # File:   compiler_test.py 
  4  # Author: Mark Mitchell 
  5  # Date:   12/11/2001 
  6  # 
  7  # Contents: 
  8  #   CompilerTest 
  9  # 
 10  # Copyright (c) 2001, 2002 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  from   compiler import * 
 17  from   qm.test.result import * 
 18  from   qm.test.test import * 
 19  import os, dircache 
 20   
 21  ######################################################################## 
 22  # Classes 
 23  ######################################################################## 
 24   
25 -class CompilationStep:
26 """A single compilation step.""" 27
28 - def __init__(self, compiler, mode, files, options = [], ldflags = [], 29 output = None , diagnostics = []):
30 """Construct a new 'CompilationStep'. 31 32 'compiler' -- A Compiler object. 33 34 'mode' -- As for 'Compiler.Compile'. 35 36 'files' -- As for 'Compiler.Compile'. 37 38 'options' -- As for 'Compiler.Compile'. 39 40 'ldflags' -- As for 'Compiler.Compile'. 41 42 'output' -- As for 'Compiler.Compile'. 43 44 'diagnostics' -- A sequence of 'Diagnostic' instances 45 indicating diagnostic messages that are expected from this 46 compilation step.""" 47 48 self.compiler = compiler 49 self.mode = mode 50 self.files = files 51 self.options = options 52 self.ldflags = ldflags 53 self.output = output 54 self.diagnostics = diagnostics
55 56 57
58 -class CompilerBase:
59 """A 'CompilerBase' is used by compilation test and resource clases.""" 60
61 - def _GetDirectory(self, context):
62 """Get the name of the directory in which to run. 63 64 'context' -- A 'Context' giving run-time parameters to the 65 test. 66 67 'returns' -- The name of the directory in which this test or 68 resource will execute.""" 69 70 if context.has_key("CompilerTest.scratch_dir"): 71 return os.path.join(context["CompilerTest.scratch_dir"], 72 self.GetId()) 73 else: 74 return os.path.join(".", "build", self.GetId())
75 76
77 - def _MakeDirectory(self, context):
78 """Create a directory in which to place generated files. 79 80 'context' -- A 'Context' giving run-time parameters to the 81 test. 82 83 returns -- The name of the directory.""" 84 85 # Get the directory name. 86 directory = self._GetDirectory(context) 87 # Create it. 88 if not os.path.exists(directory): 89 os.makedirs(directory) 90 return directory
91 92
93 - def _RemoveDirectory(self, context, result):
94 """Remove the directory in which generated files are placed. 95 96 'result' -- The 'Result' of the test or resource. If the 97 'result' indicates success, the directory is removed. 98 Otherwise, the directory is left behind to allow investigation 99 of the reasons behind the test failure.""" 100 101 def removedir(directory, dir = True): 102 for n in dircache.listdir(directory): 103 name = os.path.join(directory, n) 104 if os.path.isfile(name): 105 os.remove(name) 106 elif os.path.isdir(name): 107 removedir(name) 108 if dir: os.rmdir(directory)
109 110 cleanup = context.GetBoolean("CompilerTest.cleanup_executable", True) 111 if result.GetOutcome() == Result.PASS and cleanup: 112 try: 113 directory = self._GetDirectory(context) 114 removedir(directory, False) 115 os.removedirs(directory) 116 except: 117 # If the directory cannot be removed, that is no 118 # reason for the test to fail. 119 pass
120 121
122 - def _GetObjectFileName(self, source_file_name, object_extension):
123 """Return the default object file name for 'source_file_name'. 124 125 'source_file_name' -- A string giving the name of a source 126 file. 127 128 'object_extension' -- The extension used for object files. 129 130 returns -- The name of the object file that will be created by 131 compiling 'source_file_name'.""" 132 133 basename = os.path.basename(source_file_name) 134 return os.path.splitext(basename)[0] + object_extension
135 136 137
138 -class CompilerTest(Test, CompilerBase):
139 """A 'CompilerTest' tests a compiler.""" 140 141 _ignored_diagnostic_regexps = () 142 """A sequence of regular expressions matching diagnostics to ignore.""" 143
144 - def Run(self, context, result):
145 """Run the test. 146 147 'context' -- A 'Context' giving run-time parameters to the 148 test. 149 150 'result' -- A 'Result' object. The outcome will be 151 'Result.PASS' when this method is called. The 'result' may be 152 modified by this method to indicate outcomes other than 153 'Result.PASS' or to add annotations.""" 154 155 # If an executable is generated, executable_path will contain 156 # the generated path. 157 executable_path = None 158 # See what we need to run this test. 159 steps = self._GetCompilationSteps(context) 160 # See if we need to run this test. 161 is_execution_required = self._IsExecutionRequired() 162 # Create the temporary build directory. 163 self._MakeDirectory(context) 164 165 # Keep track of which compilation step we are performing so 166 # that we can annotate the result appropriately. 167 step_index = 1 168 169 # Perform each of the compilation steps. 170 for step in steps: 171 # Get the compiler to use for this test. 172 compiler = step.compiler 173 174 # Compute a prefix for the result annotations. 175 prefix = self._GetAnnotationPrefix() + "step_%d_" % step_index 176 177 # Get the compilation command. 178 command = compiler.GetCompilationCommand(step.mode, step.files, 179 step.options, 180 step.ldflags, 181 step.output) 182 result[prefix + "command"] = result.Quote(' '.join(command)) 183 # Run the compiler. 184 timeout = context.get("CompilerTest.compilation_timeout", -1) 185 (status, output) \ 186 = compiler.ExecuteCommand(self._GetDirectory(context), 187 command, timeout) 188 # Annotate the result with the output. 189 if output: 190 result[prefix + "output"] = result.Quote(output) 191 # Make sure that the output is OK. 192 if not self._CheckOutput(context, result, prefix, output, 193 step.diagnostics): 194 # If there were errors, do not try to run the program. 195 is_execution_required = 0 196 197 # Check the output status. 198 if step.mode == Compiler.MODE_LINK: 199 desc = "Link" 200 else: 201 desc = "Compilation" 202 # If step.diagnostics is non-empty, a non-zero status 203 # is not considered a failure. 204 if not result.CheckExitStatus(prefix, desc, status, 205 step.diagnostics): 206 return 207 208 # If this compilation generated an executable, remember 209 # that fact. 210 if step.mode == Compiler.MODE_LINK: 211 executable_path = os.path.join(".", step.output or "a.out") 212 213 # We're on to the next step. 214 step_index = step_index + 1 215 216 # Execute the generated program, if appropriate. 217 if executable_path and is_execution_required: 218 self._RunExecutable(executable_path, context, result)
219 220
221 - def _GetCompiler(self, context):
222 """Return the 'Compiler' to use. 223 224 'context' -- The 'Context' in which this test is being 225 executed.""" 226 227 raise NotImplementedError
228 229
230 - def _GetCompilationSteps(self, context):
231 """Return the compilation steps for this test. 232 233 'context' -- The 'Context' in which this test is being 234 executed. 235 236 returns -- A sequence of 'CompilationStep' objects.""" 237 238 raise NotImplementedError
239 240
241 - def _GetTarget(self, context):
242 """Returns a target for the executable to be run on. 243 244 'context' -- The Context in which this test is being executed. 245 246 returns -- A Host to run the executable on.""" 247 248 raise NotImplementedError
249 250
251 - def _IsExecutionRequired(self):
252 """Returns true if the generated executable should be run. 253 254 returns -- True if the generated executable should be run.""" 255 256 return 0
257 258
259 - def _GetExecutableArguments(self):
260 """Returns the arguments to the generated executable. 261 262 returns -- A list of strings, to be passed as argumensts to 263 the generated executable.""" 264 265 return []
266 267
269 """Returns true if the executable must exit with code zero. 270 271 returns -- True if the generated executable (if any) must exit 272 with code zero. Note that the executable will not be run at 273 all (and so the return value of this function will be ignored) 274 if '_IsExecutionRequired' does not return true.""" 275 276 return True
277 278
279 - def _GetAnnotationPrefix(self):
280 """Return the prefix to use for result annotations. 281 282 returns -- The prefix to use for result annotations.""" 283 284 return "CompilerTest."
285 286
287 - def _GetEnvironment(self, context):
288 """Return the environment to use for test execution. 289 290 returns -- The environment dictionary to use for test execution.""" 291 292 293 return None
294 295
296 - def _GetLibraryDirectories(self, context):
297 """Returns the directories to search for libraries. 298 299 'context' -- A 'Context' giving run-time parameters to the 300 test. 301 302 returns -- A sequence of strings giving the paths to the 303 directories to search for libraries.""" 304 305 return context.get("CompilerTest.library_dirs", "").split()
306 307
308 - def _RunExecutable(self, path, context, result):
309 """Run an executable generated by the compiler. 310 311 'path' -- The path to the generated executable. 312 313 'context' -- A 'Context' giving run-time parameters to the 314 test. 315 316 'result' -- A 'Result' object. The outcome will be 317 'Result.PASS' when this method is called. The 'result' may be 318 modified by this method to indicate outcomes other than 319 'Result.PASS' or to add annotations.""" 320 321 # Compute the result annotation prefix. 322 prefix = self._GetAnnotationPrefix() + "execution_" 323 # Record the command line. 324 path = os.path.join(self._GetDirectory(context), path) 325 arguments = self._GetExecutableArguments() 326 result[prefix + "command"] \ 327 = "<tt>" + path + " " + " ".join(arguments) + "</tt>" 328 329 # Compute the environment. 330 environment = self._GetEnvironment(context) 331 332 library_dirs = self._GetLibraryDirectories(context) 333 if library_dirs: 334 if not environment: 335 environment = dict() 336 # Update LD_LIBRARY_PATH. On IRIX 6, this variable 337 # goes by other names, so we update them too. It is 338 # harmless to do this on other systems. 339 for variable in ['LD_LIBRARY_PATH', 340 'LD_LIBRARYN32_PATH', 341 'LD_LIBRARYN64_PATH']: 342 old_path = environment.get(variable) 343 new_path = ':'.join(library_dirs) 344 if old_path and new_path: 345 new_path = new_path + ':' + old_path 346 environment[variable] = new_path 347 348 target = self._GetTarget(context) 349 timeout = context.get("CompilerTest.execution_timeout", -1) 350 status, output = target.UploadAndRun(path, 351 arguments, 352 environment, 353 timeout) 354 # Record the output. 355 result[prefix + "output"] = result.Quote(output) 356 self._CheckExecutableOutput(result, output) 357 # Check the output status. 358 result.CheckExitStatus(prefix, "Executable", status, 359 not self._MustExecutableExitSuccessfully())
360 361
362 - def _CheckOutput(self, context, result, prefix, output, diagnostics):
363 """Check that the 'output' contains appropriate diagnostics. 364 365 'context' -- The 'Context' for the test that is being 366 executed. 367 368 'result' -- The 'Result' of the test. 369 370 'prefix' -- A string giving the prefix for any annotations to 371 be added to the 'result'. 372 373 'output' -- A string giving the output of the compiler. 374 375 'diagnostics' -- The diagnostics that are expected for the 376 compilation. 377 378 returns -- True if there were no errors so severe as to 379 prevent execution of the test.""" 380 381 # Get the compiler to use to parse the output. 382 compiler = self._GetCompiler(context) 383 384 # Parse the output. 385 emitted_diagnostics \ 386 = compiler.ParseOutput(output, self._ignored_diagnostic_regexps) 387 388 # Diagnostics that were not emitted, but should have been. 389 missing_diagnostics = [] 390 # Diagnostics that were emitted, but should not have been. 391 spurious_diagnostics = [] 392 # Expected diagnostics that have been matched. 393 matched_diagnostics = [] 394 # Keep track of any errors. 395 errors_occurred = 0 396 397 # Loop through the emitted diagnostics, trying to match each 398 # with an expected diagnostic. 399 for emitted_diagnostic in emitted_diagnostics: 400 # If the emitted diagnostic is an internal compiler error, 401 # then the test failed. (The compiler crashed.) 402 if emitted_diagnostic.severity == 'internal_error': 403 result.Fail("The compiler issued an internal error.") 404 return 0 405 if emitted_diagnostic.severity == "error": 406 errors_occurred = 1 407 # Assume that the emitted diagnostic is unexpected. 408 is_expected = 0 409 # Loop through the expected diagnostics, trying to find 410 # one that matches the emitted diagnostic. A single 411 # emitted diagnostic might match more than one expected 412 # diagnostic, so we can not break out of the loop early. 413 for expected_diagnostic in diagnostics: 414 if self._IsDiagnosticExpected(emitted_diagnostic, 415 expected_diagnostic): 416 matched_diagnostics.append(expected_diagnostic) 417 is_expected = 1 418 if not is_expected: 419 spurious_diagnostics.append(emitted_diagnostic) 420 # Any expected diagnostics for which there was no 421 # corresponding emitted diagnostic are missing diagnostics. 422 for expected_diagnostic in diagnostics: 423 if expected_diagnostic not in matched_diagnostics: 424 missing_diagnostics.append(expected_diagnostic) 425 426 # If there were missing or spurious diagnostics, the test failed. 427 if missing_diagnostics or spurious_diagnostics: 428 # Compute a succint description of what went wrong. 429 if missing_diagnostics and spurious_diagnostics: 430 result.Fail("Missing and spurious diagnostics.") 431 elif missing_diagnostics: 432 result.Fail("Missing diagnostics.") 433 else: 434 result.Fail("Spurious diagnostics.") 435 436 # Add annotations showing the problem. 437 if spurious_diagnostics: 438 self._DiagnosticsToString(result, 439 "spurious_diagnostics", 440 spurious_diagnostics) 441 if missing_diagnostics: 442 self._DiagnosticsToString(result, 443 "missing_diagnostics", 444 missing_diagnostics) 445 446 # If errors occurred, there is no point in trying to run 447 # the executable. 448 return not errors_occurred
449 450
451 - def _CheckExecutableOutput(self, result, output):
452 """Checks the output from the generated executable. 453 454 'result' -- The 'Result' object for this test. 455 456 'output' -- The output generated by the executable. 457 458 If the output is unsatisfactory, 'result' is modified 459 appropriately.""" 460 461 pass
462 463
464 - def _IsDiagnosticExpected(self, emitted, expected):
465 """Returns true if 'emitted' matches 'expected'. 466 467 'emitted' -- A 'Diagnostic emitted by the compiler. 468 469 'expected' -- A 'Diagnostic' indicating an expectation about a 470 diagnostic to be emitted by the compiler. 471 472 returns -- True if the 'emitted' was expected by the 473 'expected'.""" 474 475 # If the source positions do not match, there is no match. 476 if expected.source_position: 477 exsp = expected.source_position 478 emsp = emitted.source_position 479 480 if exsp.line and emsp.line != exsp.line: 481 return 0 482 if (exsp.file and (os.path.basename(emsp.file) 483 != os.path.basename(exsp.file))): 484 return 0 485 if exsp.column and emsp.column != exsp.column: 486 return 0 487 488 # If the severities do not match, there is no match. 489 if (expected.severity and emitted.severity != expected.severity): 490 return 0 491 # If the messages do not match, there is no match. 492 if expected.message and not re.search(expected.message, 493 emitted.message): 494 return 0 495 496 # There's a match. 497 return 1
498 499
500 - def _DiagnosticsToString(self, result, annotation, diagnostics):
501 """Return a string representing the 'diagnostics'. 502 503 'diagnostics' -- A sequence of 'Diagnostic' instances. 504 505 returns -- A string representing the 'Diagnostic's, with one 506 diagnostic message per line.""" 507 508 # Compute the string representation of each diagnostic. 509 diagnostic_strings = map(str, diagnostics) 510 # Insert a newline between each string. 511 result[self._GetAnnotationPrefix() + annotation] \ 512 = result.Quote("\n".join(diagnostic_strings))
513