1
2
3
4
5
6
7
8
9
10
11
12
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
23
24
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
59 """A 'CompilerBase' is used by compilation test and resource clases."""
60
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
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
86 directory = self._GetDirectory(context)
87
88 if not os.path.exists(directory):
89 os.makedirs(directory)
90 return directory
91
92
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
117
118 pass
119
120
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
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
155
156 executable_path = None
157
158 steps = self._GetCompilationSteps(context)
159
160 is_execution_required = self._IsExecutionRequired()
161
162 self._MakeDirectory(context)
163
164
165
166 step_index = 1
167
168
169 for step in steps:
170
171 compiler = step.compiler
172
173
174 prefix = self._GetAnnotationPrefix() + "step_%d_" % step_index
175
176
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
183 timeout = context.get("CompilerTest.compilation_timeout", -1)
184 (status, output) \
185 = compiler.ExecuteCommand(self._GetDirectory(context),
186 command, timeout)
187
188 if output:
189 result[prefix + "output"] = result.Quote(output)
190
191 if not self._CheckOutput(context, result, prefix, output,
192 step.diagnostics):
193
194 is_execution_required = 0
195
196
197 if step.mode == Compiler.MODE_LINK:
198 desc = "Link"
199 else:
200 desc = "Compilation"
201
202
203 if not result.CheckExitStatus(prefix, desc, status,
204 step.diagnostics):
205 return
206
207
208
209 if step.mode == Compiler.MODE_LINK:
210 executable_path = os.path.join(".", step.output or "a.out")
211
212
213 step_index = step_index + 1
214
215
216 if executable_path and is_execution_required:
217 self._RunExecutable(executable_path, context, result)
218
219
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
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
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
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
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
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
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
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
312 prefix = self._GetAnnotationPrefix() + "execution_"
313
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
320 library_dirs = self._GetLibraryDirectories(context)
321 if library_dirs:
322
323
324
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
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
344 result[prefix + "output"] = result.Quote(output)
345 self._CheckExecutableOutput(result, output)
346
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
371 compiler = self._GetCompiler(context)
372
373
374 emitted_diagnostics \
375 = compiler.ParseOutput(output, self._ignored_diagnostic_regexps)
376
377
378 missing_diagnostics = []
379
380 spurious_diagnostics = []
381
382 matched_diagnostics = []
383
384 errors_occurred = 0
385
386
387
388 for emitted_diagnostic in emitted_diagnostics:
389
390
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
397 is_expected = 0
398
399
400
401
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
410
411 for expected_diagnostic in diagnostics:
412 if expected_diagnostic not in matched_diagnostics:
413 missing_diagnostics.append(expected_diagnostic)
414
415
416 if missing_diagnostics or spurious_diagnostics:
417
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
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
436
437 return not errors_occurred
438
439
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
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
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
478 if (expected.severity and emitted.severity != expected.severity):
479 return 0
480
481 if expected.message and not re.search(expected.message,
482 emitted.message):
483 return 0
484
485
486 return 1
487
488
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
498 diagnostic_strings = map(str, diagnostics)
499
500 result[self._GetAnnotationPrefix() + annotation] \
501 = result.Quote("\n".join(diagnostic_strings))
502