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 if result.GetOutcome() == Result.PASS: 111 try: 112 directory = self._GetDirectory(context) 113 removedir(directory, False) 114 os.removedirs(directory) 115 except: 116 # If the directory cannot be removed, that is no 117 # reason for the test to fail. 118 pass
119 120
121 - def _GetObjectFileName(self, source_file_name, object_extension):
122 """Return the default object file name for 'source_file_name'. 123 124 'source_file_name' -- A string giving the name of a source 125 file. 126 127 'object_extension' -- The extension used for object files. 128 129 returns -- The name of the object file that will be created by 130 compiling 'source_file_name'.""" 131 132 basename = os.path.basename(source_file_name) 133 return os.path.splitext(basename)[0] + object_extension
134 135 136
137 -class CompilerTest(Test, CompilerBase):
138 """A 'CompilerTest' tests a compiler.""" 139 140 _ignored_diagnostic_regexps = () 141 """A sequence of regular expressions matching diagnostics to ignore.""" 142
143 - def Run(self, context, result):
144 """Run the test. 145 146 'context' -- A 'Context' giving run-time parameters to the 147 test. 148 149 'result' -- A 'Result' object. The outcome will be 150 'Result.PASS' when this method is called. The 'result' may be 151 modified by this method to indicate outcomes other than 152 'Result.PASS' or to add annotations.""" 153 154 # If an executable is generated, executable_path will contain 155 # the generated path. 156 executable_path = None 157 # See what we need to run this test. 158 steps = self._GetCompilationSteps(context) 159 # See if we need to run this test. 160 is_execution_required = self._IsExecutionRequired() 161 # Create the temporary build directory. 162 self._MakeDirectory(context) 163 164 # Keep track of which compilation step we are performing so 165 # that we can annotate the result appropriately. 166 step_index = 1 167 168 # Perform each of the compilation steps. 169 for step in steps: 170 # Get the compiler to use for this test. 171 compiler = step.compiler 172 173 # Compute a prefix for the result annotations. 174 prefix = self._GetAnnotationPrefix() + "step_%d_" % step_index 175 176 # Get the compilation command. 177 command = compiler.GetCompilationCommand(step.mode, step.files, 178 step.options, 179 step.ldflags, 180 step.output) 181 result[prefix + "command"] = result.Quote(' '.join(command)) 182 # Run the compiler. 183 timeout = context.get("CompilerTest.compilation_timeout", -1) 184 (status, output) \ 185 = compiler.ExecuteCommand(self._GetDirectory(context), 186 command, timeout) 187 # Annotate the result with the output. 188 if output: 189 result[prefix + "output"] = result.Quote(output) 190 # Make sure that the output is OK. 191 if not self._CheckOutput(context, result, prefix, output, 192 step.diagnostics): 193 # If there were errors, do not try to run the program. 194 is_execution_required = 0 195 196 # Check the output status. 197 if step.mode == Compiler.MODE_LINK: 198 desc = "Link" 199 else: 200 desc = "Compilation" 201 # If step.diagnostics is non-empty, a non-zero status 202 # is not considered a failure. 203 if not result.CheckExitStatus(prefix, desc, status, 204 step.diagnostics): 205 return 206 207 # If this compilation generated an executable, remember 208 # that fact. 209 if step.mode == Compiler.MODE_LINK: 210 executable_path = os.path.join(".", step.output or "a.out") 211 212 # We're on to the next step. 213 step_index = step_index + 1 214 215 # Execute the generated program, if appropriate. 216 if executable_path and is_execution_required: 217 self._RunExecutable(executable_path, context, result)
218 219
220 - def _GetCompiler(self, context):
221 """Return the 'Compiler' to use. 222 223 'context' -- The 'Context' in which this test is being 224 executed.""" 225 226 raise NotImplementedError
227 228
229 - def _GetCompilationSteps(self, context):
230 """Return the compilation steps for this test. 231 232 'context' -- The 'Context' in which this test is being 233 executed. 234 235 returns -- A sequence of 'CompilationStep' objects.""" 236 237 raise NotImplementedError
238 239
240 - def _GetTarget(self, context):
241 """Returns a target for the executable to be run on. 242 243 'context' -- The Context in which this test is being executed. 244 245 returns -- A Host to run the executable on.""" 246 247 raise NotImplementedError
248 249
250 - def _IsExecutionRequired(self):
251 """Returns true if the generated executable should be run. 252 253 returns -- True if the generated executable should be run.""" 254 255 return 0
256 257
258 - def _GetExecutableArguments(self):
259 """Returns the arguments to the generated executable. 260 261 returns -- A list of strings, to be passed as argumensts to 262 the generated executable.""" 263 264 return []
265 266
268 """Returns true if the executable must exit with code zero. 269 270 returns -- True if the generated executable (if any) must exit 271 with code zero. Note that the executable will not be run at 272 all (and so the return value of this function will be ignored) 273 if '_IsExecutionRequired' does not return true.""" 274 275 return True
276 277
278 - def _GetAnnotationPrefix(self):
279 """Return the prefix to use for result annotations. 280 281 returns -- The prefix to use for result annotations.""" 282 283 return "CompilerTest."
284 285
286 - def _GetLibraryDirectories(self, context):
287 """Returns the directories to search for libraries. 288 289 'context' -- A 'Context' giving run-time parameters to the 290 test. 291 292 returns -- A sequence of strings giving the paths to the 293 directories to search for libraries.""" 294 295 return context.get("CompilerTest.library_dirs", "").split()
296 297
298 - def _RunExecutable(self, path, context, result):
299 """Run an executable generated by the compiler. 300 301 'path' -- The path to the generated executable. 302 303 'context' -- A 'Context' giving run-time parameters to the 304 test. 305 306 'result' -- A 'Result' object. The outcome will be 307 'Result.PASS' when this method is called. The 'result' may be 308 modified by this method to indicate outcomes other than 309 'Result.PASS' or to add annotations.""" 310 311 # Compute the result annotation prefix. 312 prefix = self._GetAnnotationPrefix() + "execution_" 313 # Record the command line. 314 path = os.path.join(self._GetDirectory(context), path) 315 arguments = self._GetExecutableArguments() 316 result[prefix + "command"] \ 317 = "<tt>" + path + " " + " ".join(arguments) + "</tt>" 318 319 # Compute the environment. 320 library_dirs = self._GetLibraryDirectories(context) 321 if library_dirs: 322 # Update LD_LIBRARY_PATH. On IRIX 6, this variable 323 # goes by other names, so we update them too. It is 324 # harmless to do this on other systems. 325 for variable in ['LD_LIBRARY_PATH', 326 'LD_LIBRARYN32_PATH', 327 'LD_LIBRARYN64_PATH']: 328 old_path = environment.get(variable) 329 new_path = ':'.join(self._library_dirs) 330 if old_path and new_path: 331 new_path = new_path + ':' + old_path 332 environment[variable] = new_path 333 else: 334 # Use the default values. 335 environment = None 336 337 target = self._GetTarget(context) 338 timeout = context.get("CompilerTest.execution_timeout", -1) 339 status, output = target.UploadAndRun(path, 340 arguments, 341 environment, 342 timeout) 343 # Record the output. 344 result[prefix + "output"] = result.Quote(output) 345 self._CheckExecutableOutput(result, output) 346 # Check the output status. 347 result.CheckExitStatus(prefix, "Executable", status, 348 not self._MustExecutableExitSuccessfully())
349 350
351 - def _CheckOutput(self, context, result, prefix, output, diagnostics):
352 """Check that the 'output' contains appropriate diagnostics. 353 354 'context' -- The 'Context' for the test that is being 355 executed. 356 357 'result' -- The 'Result' of the test. 358 359 'prefix' -- A string giving the prefix for any annotations to 360 be added to the 'result'. 361 362 'output' -- A string giving the output of the compiler. 363 364 'diagnostics' -- The diagnostics that are expected for the 365 compilation. 366 367 returns -- True if there were no errors so severe as to 368 prevent execution of the test.""" 369 370 # Get the compiler to use to parse the output. 371 compiler = self._GetCompiler(context) 372 373 # Parse the output. 374 emitted_diagnostics \ 375 = compiler.ParseOutput(output, self._ignored_diagnostic_regexps) 376 377 # Diagnostics that were not emitted, but should have been. 378 missing_diagnostics = [] 379 # Diagnostics that were emitted, but should not have been. 380 spurious_diagnostics = [] 381 # Expected diagnostics that have been matched. 382 matched_diagnostics = [] 383 # Keep track of any errors. 384 errors_occurred = 0 385 386 # Loop through the emitted diagnostics, trying to match each 387 # with an expected diagnostic. 388 for emitted_diagnostic in emitted_diagnostics: 389 # If the emitted diagnostic is an internal compiler error, 390 # then the test failed. (The compiler crashed.) 391 if emitted_diagnostic.severity == 'internal_error': 392 result.Fail("The compiler issued an internal error.") 393 return 0 394 if emitted_diagnostic.severity == "error": 395 errors_occurred = 1 396 # Assume that the emitted diagnostic is unexpected. 397 is_expected = 0 398 # Loop through the expected diagnostics, trying to find 399 # one that matches the emitted diagnostic. A single 400 # emitted diagnostic might match more than one expected 401 # diagnostic, so we can not break out of the loop early. 402 for expected_diagnostic in diagnostics: 403 if self._IsDiagnosticExpected(emitted_diagnostic, 404 expected_diagnostic): 405 matched_diagnostics.append(expected_diagnostic) 406 is_expected = 1 407 if not is_expected: 408 spurious_diagnostics.append(emitted_diagnostic) 409 # Any expected diagnostics for which there was no 410 # corresponding emitted diagnostic are missing diagnostics. 411 for expected_diagnostic in diagnostics: 412 if expected_diagnostic not in matched_diagnostics: 413 missing_diagnostics.append(expected_diagnostic) 414 415 # If there were missing or spurious diagnostics, the test failed. 416 if missing_diagnostics or spurious_diagnostics: 417 # Compute a succint description of what went wrong. 418 if missing_diagnostics and spurious_diagnostics: 419 result.Fail("Missing and spurious diagnostics.") 420 elif missing_diagnostics: 421 result.Fail("Missing diagnostics.") 422 else: 423 result.Fail("Spurious diagnostics.") 424 425 # Add annotations showing the problem. 426 if spurious_diagnostics: 427 self._DiagnosticsToString(result, 428 "spurious_diagnostics", 429 spurious_diagnostics) 430 if missing_diagnostics: 431 self._DiagnosticsToString(result, 432 "missing_diagnostics", 433 missing_diagnostics) 434 435 # If errors occurred, there is no point in trying to run 436 # the executable. 437 return not errors_occurred
438 439
440 - def _CheckExecutableOutput(self, result, output):
441 """Checks the output from the generated executable. 442 443 'result' -- The 'Result' object for this test. 444 445 'output' -- The output generated by the executable. 446 447 If the output is unsatisfactory, 'result' is modified 448 appropriately.""" 449 450 pass
451 452
453 - def _IsDiagnosticExpected(self, emitted, expected):
454 """Returns true if 'emitted' matches 'expected'. 455 456 'emitted' -- A 'Diagnostic emitted by the compiler. 457 458 'expected' -- A 'Diagnostic' indicating an expectation about a 459 diagnostic to be emitted by the compiler. 460 461 returns -- True if the 'emitted' was expected by the 462 'expected'.""" 463 464 # If the source positions do not match, there is no match. 465 if expected.source_position: 466 exsp = expected.source_position 467 emsp = emitted.source_position 468 469 if exsp.line and emsp.line != exsp.line: 470 return 0 471 if (exsp.file and (os.path.basename(emsp.file) 472 != os.path.basename(exsp.file))): 473 return 0 474 if exsp.column and emsp.column != exsp.column: 475 return 0 476 477 # If the severities do not match, there is no match. 478 if (expected.severity and emitted.severity != expected.severity): 479 return 0 480 # If the messages do not match, there is no match. 481 if expected.message and not re.search(expected.message, 482 emitted.message): 483 return 0 484 485 # There's a match. 486 return 1
487 488
489 - def _DiagnosticsToString(self, result, annotation, diagnostics):
490 """Return a string representing the 'diagnostics'. 491 492 'diagnostics' -- A sequence of 'Diagnostic' instances. 493 494 returns -- A string representing the 'Diagnostic's, with one 495 diagnostic message per line.""" 496 497 # Compute the string representation of each diagnostic. 498 diagnostic_strings = map(str, diagnostics) 499 # Insert a newline between each string. 500 result[self._GetAnnotationPrefix() + annotation] \ 501 = result.Quote("\n".join(diagnostic_strings))
502