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 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
118
119 pass
120
121
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
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
156
157 executable_path = None
158
159 steps = self._GetCompilationSteps(context)
160
161 is_execution_required = self._IsExecutionRequired()
162
163 self._MakeDirectory(context)
164
165
166
167 step_index = 1
168
169
170 for step in steps:
171
172 compiler = step.compiler
173
174
175 prefix = self._GetAnnotationPrefix() + "step_%d_" % step_index
176
177
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
184 timeout = context.get("CompilerTest.compilation_timeout", -1)
185 (status, output) \
186 = compiler.ExecuteCommand(self._GetDirectory(context),
187 command, timeout)
188
189 if output:
190 result[prefix + "output"] = result.Quote(output)
191
192 if not self._CheckOutput(context, result, prefix, output,
193 step.diagnostics):
194
195 is_execution_required = 0
196
197
198 if step.mode == Compiler.MODE_LINK:
199 desc = "Link"
200 else:
201 desc = "Compilation"
202
203
204 if not result.CheckExitStatus(prefix, desc, status,
205 step.diagnostics):
206 return
207
208
209
210 if step.mode == Compiler.MODE_LINK:
211 executable_path = os.path.join(".", step.output or "a.out")
212
213
214 step_index = step_index + 1
215
216
217 if executable_path and is_execution_required:
218 self._RunExecutable(executable_path, context, result)
219
220
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
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
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
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
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
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
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
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
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
322 prefix = self._GetAnnotationPrefix() + "execution_"
323
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
330 environment = self._GetEnvironment(context)
331
332 library_dirs = self._GetLibraryDirectories(context)
333 if library_dirs:
334 if not environment:
335 environment = dict()
336
337
338
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
355 result[prefix + "output"] = result.Quote(output)
356 self._CheckExecutableOutput(result, output)
357
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
382 compiler = self._GetCompiler(context)
383
384
385 emitted_diagnostics \
386 = compiler.ParseOutput(output, self._ignored_diagnostic_regexps)
387
388
389 missing_diagnostics = []
390
391 spurious_diagnostics = []
392
393 matched_diagnostics = []
394
395 errors_occurred = 0
396
397
398
399 for emitted_diagnostic in emitted_diagnostics:
400
401
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
408 is_expected = 0
409
410
411
412
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
421
422 for expected_diagnostic in diagnostics:
423 if expected_diagnostic not in matched_diagnostics:
424 missing_diagnostics.append(expected_diagnostic)
425
426
427 if missing_diagnostics or spurious_diagnostics:
428
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
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
447
448 return not errors_occurred
449
450
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
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
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
489 if (expected.severity and emitted.severity != expected.severity):
490 return 0
491
492 if expected.message and not re.search(expected.message,
493 emitted.message):
494 return 0
495
496
497 return 1
498
499
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
509 diagnostic_strings = map(str, diagnostics)
510
511 result[self._GetAnnotationPrefix() + annotation] \
512 = result.Quote("\n".join(diagnostic_strings))
513