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

Source Code for Module qm.test.classes.dg_test

  1  ######################################################################## 
  2  # 
  3  # File:   dg_test.py 
  4  # Author: Mark Mitchell 
  5  # Date:   04/17/2003 
  6  # 
  7  # Contents: 
  8  #   DGTest 
  9  # 
 10  # Copyright (c) 2003 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  ######################################################################## 
 17  # Imports 
 18  ######################################################################## 
 19   
 20  from   dejagnu_test import DejaGNUTest 
 21  import fnmatch 
 22  import os 
 23  from   qm.test.result import Result 
 24  from   qm.fields import BooleanField 
 25  import re 
 26   
 27  ######################################################################## 
 28  # Classes 
 29  ######################################################################## 
 30   
31 -class DGTest(DejaGNUTest):
32 """A 'DGTest' is a test using the DejaGNU 'dg' driver. 33 34 This test class emulates the 'dg.exp' source file in the DejaGNU 35 distribution.""" 36
37 - class DGException(Exception):
38 """The exception class raised by 'DGTest'. 39 40 When a 'DGTest' method detects an error situation, it raises 41 an exception of this type.""" 42 43 pass
44 45 46 47 __dg_command_regexp \ 48 = re.compile(r"{[ \t]+dg-([-a-z]+)[ \t]+(.*)[ \t]+}[^}]*$") 49 """A regular expression matching commands embedded in the source file.""" 50 51 # The values of these constants have been chosen so that they 52 # match the valid values for the 'dg-do' command. 53 KIND_PREPROCESS = "preprocess" 54 KIND_COMPILE = "compile" 55 KIND_ASSEMBLE = "assemble" 56 KIND_LINK = "link" 57 KIND_RUN = "run" 58 59 keep_output = BooleanField(default_value=False, 60 description="""True if the output file should be retained 61 after the test is complete. Otherwise, it is removed.""") 62 63 _default_kind = KIND_COMPILE 64 """The default test kind. 65 66 This value can be overridden by a 'dg-do' command in the test file.""" 67 68 __test_kinds = ( 69 KIND_PREPROCESS, 70 KIND_COMPILE, 71 KIND_ASSEMBLE, 72 KIND_LINK, 73 KIND_RUN 74 ) 75 """The kinds of tests supported by 'dg.exp'.""" 76 77 __DIAG_BOGUS = "bogus" 78 __DIAG_ERROR = "error" 79 __DIAG_WARNING = "warning" 80 81 __diagnostic_descriptions = { 82 __DIAG_ERROR : "errors", 83 __DIAG_WARNING : "warnings", 84 __DIAG_BOGUS : "bogus messages", 85 "build" : "build failure", 86 } 87 """A map from dg diagnostic kinds to descriptive strings.""" 88
89 - def _RunDGTest(self, tool_flags, default_options, context, result, 90 path = None, 91 default_kind = None, 92 keep_output = None):
93 """Run a 'dg' test. 94 95 'tool_flags' -- A list of strings giving a set of options to be 96 provided to the tool being tested. 97 98 'default_options' -- A list of strings giving a default set of 99 options to be provided to the tool being tested. These options 100 can be overridden by an embedded 'dg-options' command in the 101 test itself. 102 103 'context' -- The 'Context' in which this test is running. 104 105 'result' -- The 'Result' of the test execution. 106 107 'path' -- The path to the test file. If 'None', the main test 108 file path is used. 109 110 'default_kind' -- The kind of test to perform. If this value 111 is 'None', then 'self._default_kind' is used. 112 113 'keep_output' -- True if the output file should be retained 114 after the test is complete. Otherwise, it is removed. 115 116 This function emulates 'dg-test'.""" 117 118 # Intialize. 119 if default_kind is None: 120 default_kind = self._default_kind 121 self._kind = default_kind 122 self._selected = None 123 self._expectation = None 124 self._options = list(default_options) 125 self._diagnostics = [] 126 self._excess_errors_expected = False 127 self._final_commands = [] 128 # Iterate through the test looking for embedded commands. 129 line_num = 0 130 if not path: 131 path = self._GetSourcePath() 132 root = self.GetDatabase().GetRoot() 133 if path.startswith(root): 134 self._name = path[len(root) + 1:] 135 else: 136 # We prepend "./" for output compatibility with DejaGNU. 137 self._name = os.path.join(".", os.path.basename(path)) 138 for l in open(path).xreadlines(): 139 line_num += 1 140 m = self.__dg_command_regexp.search(l) 141 if m: 142 f = getattr(self, "_DG" + m.group(1).replace("-", "_")) 143 args = self._ParseTclWords(m.group(2), 144 { "srcdir" : root }) 145 f(line_num, args, context) 146 147 # If this test does not need to be run on this target, stop. 148 if self._selected == 0: 149 self._RecordDejaGNUOutcome(result, 150 self.UNSUPPORTED, 151 self._name) 152 return 153 154 # Run the tool being tested and process its output. 155 file = self._RunDGToolPortion(path, tool_flags, context, result) 156 157 # Run the executable generated (if applicable). 158 self._RunDGExecutePortion(file, context, result) 159 160 # Run dg-final tests. 161 for c, a in self._final_commands: 162 self._ExecuteFinalCommand(c, a, context, result) 163 164 # For backward compatibility the function parameter overrides 165 # the extension argument, if defined. 166 do_keep_output = keep_output is None and self.keep_output or keep_output 167 # Remove the output file. 168 if not do_keep_output: 169 try: 170 os.remove(file) 171 except: 172 pass
173
174 - def _RunDGToolPortion(self, path, tool_flags, context, result):
175 """Perform the tool-running portions of a DG test. 176 177 Calls '_RunTool' and processes its output. 178 179 returns -- The filename of the generated file.""" 180 181 # Run the tool being tested. 182 output, file = self._RunTool(path, self._kind, 183 tool_flags + self._options, 184 context, 185 result) 186 187 # Check to see if the right diagnostic messages appeared. 188 # This algorithm takes time proportional to the number of 189 # lines in the output times the number of expected 190 # diagnostics. One could do much better, but DejaGNU does 191 # not. 192 for l, k, x, p, c in self._diagnostics: 193 # Remove all occurrences of this diagnostic from the 194 # output. 195 if l is not None: 196 ldesc = "%d" % l 197 l = ":%s:" % ldesc 198 else: 199 ldesc = "" 200 l = ldesc 201 output, matched = re.subn(r"(?m)^.+" + l + r".*(" + p + r").*$", 202 "", output) 203 # Record an appropriate test outcome. 204 message = ("%s %s (test for %s, line %s)" 205 % (self._name, c, 206 self.__diagnostic_descriptions[k], ldesc)) 207 if matched: 208 if k == self.__DIAG_BOGUS: 209 outcome = self.FAIL 210 else: 211 outcome = self.PASS 212 else: 213 if k == self.__DIAG_BOGUS: 214 outcome = self.PASS 215 else: 216 outcome = self.FAIL 217 218 self._RecordDejaGNUOutcome(result, outcome, message, x) 219 220 # Remove tool-specific messages that can be safely ignored. 221 output = self._PruneOutput(output) 222 223 # Remove leading blank lines. 224 output = re.sub(r"^\n+", "", output) 225 # If there's any output left, the test fails. 226 message = self._name + " (test for excess errors)" 227 if self._excess_errors_expected: 228 expected = self.FAIL 229 else: 230 expected = self.PASS 231 if output != "": 232 self._RecordDejaGNUOutcome(result, self.FAIL, 233 message, expected) 234 result["DGTest.excess_errors"] = result.Quote(output) 235 else: 236 self._RecordDejaGNUOutcome(result, self.PASS, 237 message, expected) 238 239 return file
240 241
242 - def _RunDGExecutePortion(self, file, context, result):
243 """Perform the executable-running portions of a DG test. 244 245 If this is a "run" test, runs the executable generated by the 246 tool and checks its output.""" 247 248 # Run the generated program. 249 if self._kind == "run": 250 if not os.path.exists(file): 251 message = (self._name 252 + " compilation failed to produce executable") 253 self._RecordDejaGNUOutcome(result, self.WARNING, message) 254 else: 255 outcome = self._RunTargetExecutable(context, result, file) 256 # Add an annotation indicating what happened. 257 message = self._name + " execution test" 258 self._RecordDejaGNUOutcome(result, outcome, message, 259 self._expectation)
260 261
262 - def _ExecuteFinalCommand(self, command, args, context, result):
263 """Run a command specified with 'dg-final'. 264 265 'command' -- A string giving the name of the command. 266 267 'args' -- A list of strings giving the arguments (if any) to 268 that command. 269 270 'context' -- The 'Context' in which this test is running. 271 272 'result' -- The 'Result' of this test.""" 273 274 raise self.DGException, \ 275 'dg-final command \"%s\" is not implemented' % command
276 277
278 - def _PruneOutput(self, output):
279 """Remove unintersting messages from 'output'. 280 281 'output' -- A string giving the output from the tool being 282 tested. 283 284 returns -- A modified version of 'output'. This modified 285 version does not contain tool output messages that are 286 irrelevant for testing purposes.""" 287 288 raise NotImplementedError
289 290
291 - def _RunTool(self, path, kind, options, context, result):
292 """Run the tool being tested. 293 294 'path' -- The path to the test file. 295 296 'kind' -- The kind of test to perform. 297 298 'options' -- A list of strings giving command-line options to 299 provide to the tool. 300 301 'context' -- The 'Context' for the test execution. 302 303 'result' -- The QMTest 'Result' for the test. 304 305 returns -- A pair '(output, file)' where 'output' consists of 306 any messages produced by the compiler, and 'file' is the name 307 of the file produced by the compilation, if any.""" 308 309 raise NotImplementedError
310 311
312 - def _DGdo(self, line_num, args, context):
313 """Emulate the 'dg-do' command. 314 315 'line_num' -- The line number at which the command was found. 316 317 'args' -- The arguments to the command, as a list of 318 strings. 319 320 'context' -- The 'Context' in which the test is running.""" 321 322 if len(args) > 2: 323 self._Error("dg-do: too many arguments") 324 325 if len(args) >= 2: 326 code = self._ParseTargetSelector(args[1], context) 327 if code == "S": 328 self._selected = 1 329 elif code == "N": 330 if self._selected != 1: 331 self._selected = 0 332 elif code == "F": 333 self._expectation = Result.FAIL 334 else: 335 self._selected = 1 336 self._expectation = Result.PASS 337 338 kind = args[0] 339 if kind not in self.__test_kinds: 340 self._Error("dg-do: syntax error") 341 342 self._kind = kind
343 344
345 - def _DGfinal(self, line_num, args, context):
346 """Emulate the 'dg-final' command. 347 348 'line_num' -- The line number at which the command was found. 349 350 'args' -- The arguments to the command, as a list of 351 strings. 352 353 'context' -- The 'Context' in which the test is running.""" 354 355 if len(args) > 1: 356 self._Error("dg-final: too many arguments") 357 358 words = self._ParseTclWords(args[0]) 359 self._final_commands.append((words[0], words[1:]))
360 361
362 - def _DGoptions(self, line_num, args, context):
363 """Emulate the 'dg-options' command. 364 365 'line_num' -- The line number at which the command was found. 366 367 'args' -- The arguments to the command, as a list of 368 strings. 369 370 'context' -- The 'Context' in which the test is running.""" 371 372 if len(args) > 2: 373 self._Error("'dg-options': too many arguments") 374 375 if len(args) >= 2: 376 code = self._ParseTargetSelector(args[1], context) 377 if code == "S": 378 self._options = self._ParseTclWords(args[0]) 379 elif code != "N": 380 self._Error("'dg-options': 'xfail' not allowed here") 381 else: 382 self._options = self._ParseTclWords(args[0])
383 384
385 - def _DGbogus(self, line_num, args, context):
386 """Emulate the 'dg-bogus' command. 387 388 'line_num' -- The number at which the command was found. 389 390 'args' -- The arguments to the command, as a list of 391 strings. 392 393 'context' -- The 'Context' in which the test is running.""" 394 395 self.__ExpectDiagnostic(self.__DIAG_BOGUS, line_num, args, context)
396 397
398 - def _DGwarning(self, line_num, args, context):
399 """Emulate the 'dg-warning' command. 400 401 'line_num' -- The number at which the command was found. 402 403 'args' -- The arguments to the command, as a list of 404 strings. 405 406 'context' -- The 'Context' in which the test is running.""" 407 408 self.__ExpectDiagnostic(self.__DIAG_WARNING, line_num, args, context)
409 410
411 - def _DGerror(self, line_num, args, context):
412 """Emulate the 'dg-error' command. 413 414 'line_num' -- The number at which the command was found. 415 416 'args' -- The arguments to the command, as a list of 417 strings. 418 419 'context' -- The 'Context' in which the test is running.""" 420 421 self.__ExpectDiagnostic(self.__DIAG_ERROR, line_num, args, context)
422 423
424 - def _DGexcess_errors(self, line_num, args, context):
425 """Emulate the 'dg-excess-errors' command. 426 427 'line_num' -- The line number at which the command was found. 428 429 'args' -- The arguments to the command, as a list of 430 strings. 431 432 'context' -- The 'Context' in which the test is running.""" 433 434 if len(args) > 2: 435 self._Error("'dg-excess-errors': too many arguments") 436 437 if len(args) >= 2: 438 code = self._ParseTargetSelector(args[1], context) 439 if code in ("F", "S"): 440 self._excess_errors_expected = True 441 else: 442 self._excess_errors_expected = True
443 444
445 - def __ExpectDiagnostic(self, kind, line_num, args, context):
446 """Register an expected diagnostic. 447 448 'kind' -- The kind of diagnostic expected. 449 450 'line_num' -- The number at which the command was found. 451 452 'args' -- The arguments to the command, as a list of 453 strings. 454 455 'context' -- The 'Context' in which the test is running.""" 456 457 if len(args) > 4: 458 self._Error("'dg-" + kind + "': too many arguments") 459 460 if len(args) >= 4: 461 l = args[3] 462 if l == "0": 463 line_num = None 464 elif l != ".": 465 line_num = int(args[3]) 466 467 # Parse the target selector, if any. 468 expectation = self.PASS 469 if len(args) >= 3: 470 code = self._ParseTargetSelector(args[2], context) 471 if code == "N": 472 return 473 if code == "F": 474 expectation = self.FAIL 475 476 if len(args) >= 2: 477 comment = args[1] 478 else: 479 comment = "" 480 481 self._diagnostics.append((line_num, kind, expectation, 482 args[0], comment))
483 484
485 - def _ParseTargetSelector(self, selector, context):
486 """Parse the target 'selector'. 487 488 'selector' -- A target selector. 489 490 'context' -- The 'Context' in which the test is running. 491 492 returns -- For a 'target' selector, 'S' if this test should be 493 run, or 'N' if it should not. For an 'xfail' selector, 'F' if 494 the test is expected to fail; 'P' if if not. 495 496 This function emulates dg-process-target.""" 497 498 # Split the selector into words. In the DejaGNU code, this 499 # operation is accomplished by treating the string as Tcl 500 # list. 501 words = selector.split() 502 # Check the first word. 503 if words[0] != "target" and words[0] != "xfail": 504 raise QMException, "Invalid selector." 505 # The rest of the selector is a space-separate list of 506 # patterns. See if any of them are matched by the current 507 # target platform. 508 target = self._GetTarget(context) 509 match = 0 510 for p in words[1:]: 511 if (p == "native" and self._IsNative(context) 512 or fnmatch.fnmatch(target, p)): 513 match = 1 514 break 515 516 if words[0] == "target": 517 if match: 518 return "S" 519 else: 520 return "N" 521 else: 522 if match: 523 return "F" 524 else: 525 return "P"
526