Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(78)

Side by Side Diff: trunk/test/lib/TestCommon.py

Issue 160351: Test infrastructure for end-to-end tests of GYP generators, and... (Closed) Base URL: http://gyp.googlecode.com/svn/
Patch Set: '' Created 11 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « trunk/test/lib/TestCmd.py ('k') | trunk/test/lib/TestGyp.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Name: svn:eol-style
+ LF
OLDNEW
(Empty)
1 """
2 TestCommon.py: a testing framework for commands and scripts
3 with commonly useful error handling
4
5 The TestCommon module provides a simple, high-level interface for writing
6 tests of executable commands and scripts, especially commands and scripts
7 that interact with the file system. All methods throw exceptions and
8 exit on failure, with useful error messages. This makes a number of
9 explicit checks unnecessary, making the test scripts themselves simpler
10 to write and easier to read.
11
12 The TestCommon class is a subclass of the TestCmd class. In essence,
13 TestCommon is a wrapper that handles common TestCmd error conditions in
14 useful ways. You can use TestCommon directly, or subclass it for your
15 program and add additional (or override) methods to tailor it to your
16 program's specific needs. Alternatively, the TestCommon class serves
17 as a useful example of how to define your own TestCmd subclass.
18
19 As a subclass of TestCmd, TestCommon provides access to all of the
20 variables and methods from the TestCmd module. Consequently, you can
21 use any variable or method documented in the TestCmd module without
22 having to explicitly import TestCmd.
23
24 A TestCommon environment object is created via the usual invocation:
25
26 import TestCommon
27 test = TestCommon.TestCommon()
28
29 You can use all of the TestCmd keyword arguments when instantiating a
30 TestCommon object; see the TestCmd documentation for details.
31
32 Here is an overview of the methods and keyword arguments that are
33 provided by the TestCommon class:
34
35 test.must_be_writable('file1', ['file2', ...])
36
37 test.must_contain('file', 'required text\n')
38
39 test.must_contain_all_lines(output, lines, ['title', find])
40
41 test.must_contain_any_line(output, lines, ['title', find])
42
43 test.must_exist('file1', ['file2', ...])
44
45 test.must_match('file', "expected contents\n")
46
47 test.must_not_be_writable('file1', ['file2', ...])
48
49 test.must_not_contain_any_line(output, lines, ['title', find])
50
51 test.must_not_exist('file1', ['file2', ...])
52
53 test.run(options = "options to be prepended to arguments",
54 stdout = "expected standard output from the program",
55 stderr = "expected error output from the program",
56 status = expected_status,
57 match = match_function)
58
59 The TestCommon module also provides the following variables
60
61 TestCommon.python_executable
62 TestCommon.exe_suffix
63 TestCommon.obj_suffix
64 TestCommon.shobj_prefix
65 TestCommon.shobj_suffix
66 TestCommon.lib_prefix
67 TestCommon.lib_suffix
68 TestCommon.dll_prefix
69 TestCommon.dll_suffix
70
71 """
72
73 # Copyright 2000, 2001, 2002, 2003, 2004 Steven Knight
74 # This module is free software, and you may redistribute it and/or modify
75 # it under the same terms as Python itself, so long as this copyright message
76 # and disclaimer are retained in their original form.
77 #
78 # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
79 # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
80 # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
81 # DAMAGE.
82 #
83 # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
84 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
85 # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
86 # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
87 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
88
89 __author__ = "Steven Knight <knight at baldmt dot com>"
90 __revision__ = "TestCommon.py 0.36.D001 2009/07/24 08:45:26 knight"
91 __version__ = "0.36"
92
93 import copy
94 import os
95 import os.path
96 import stat
97 import string
98 import sys
99 import types
100 import UserList
101
102 from TestCmd import *
103 from TestCmd import __all__
104
105 __all__.extend([ 'TestCommon',
106 'exe_suffix',
107 'obj_suffix',
108 'shobj_prefix',
109 'shobj_suffix',
110 'lib_prefix',
111 'lib_suffix',
112 'dll_prefix',
113 'dll_suffix',
114 ])
115
116 # Variables that describe the prefixes and suffixes on this system.
117 if sys.platform == 'win32':
118 exe_suffix = '.exe'
119 obj_suffix = '.obj'
120 shobj_suffix = '.obj'
121 shobj_prefix = ''
122 lib_prefix = ''
123 lib_suffix = '.lib'
124 dll_prefix = ''
125 dll_suffix = '.dll'
126 elif sys.platform == 'cygwin':
127 exe_suffix = '.exe'
128 obj_suffix = '.o'
129 shobj_suffix = '.os'
130 shobj_prefix = ''
131 lib_prefix = 'lib'
132 lib_suffix = '.a'
133 dll_prefix = ''
134 dll_suffix = '.dll'
135 elif string.find(sys.platform, 'irix') != -1:
136 exe_suffix = ''
137 obj_suffix = '.o'
138 shobj_suffix = '.o'
139 shobj_prefix = ''
140 lib_prefix = 'lib'
141 lib_suffix = '.a'
142 dll_prefix = 'lib'
143 dll_suffix = '.so'
144 elif string.find(sys.platform, 'darwin') != -1:
145 exe_suffix = ''
146 obj_suffix = '.o'
147 shobj_suffix = '.os'
148 shobj_prefix = ''
149 lib_prefix = 'lib'
150 lib_suffix = '.a'
151 dll_prefix = 'lib'
152 dll_suffix = '.dylib'
153 elif string.find(sys.platform, 'sunos') != -1:
154 exe_suffix = ''
155 obj_suffix = '.o'
156 shobj_suffix = '.os'
157 shobj_prefix = 'so_'
158 lib_prefix = 'lib'
159 lib_suffix = '.a'
160 dll_prefix = 'lib'
161 dll_suffix = '.dylib'
162 else:
163 exe_suffix = ''
164 obj_suffix = '.o'
165 shobj_suffix = '.os'
166 shobj_prefix = ''
167 lib_prefix = 'lib'
168 lib_suffix = '.a'
169 dll_prefix = 'lib'
170 dll_suffix = '.so'
171
172 def is_List(e):
173 return type(e) is types.ListType \
174 or isinstance(e, UserList.UserList)
175
176 def is_writable(f):
177 mode = os.stat(f)[stat.ST_MODE]
178 return mode & stat.S_IWUSR
179
180 def separate_files(flist):
181 existing = []
182 missing = []
183 for f in flist:
184 if os.path.exists(f):
185 existing.append(f)
186 else:
187 missing.append(f)
188 return existing, missing
189
190 if os.name == 'posix':
191 def _failed(self, status = 0):
192 if self.status is None or status is None:
193 return None
194 return _status(self) != status
195 def _status(self):
196 return self.status
197 elif os.name == 'nt':
198 def _failed(self, status = 0):
199 return not (self.status is None or status is None) and \
200 self.status != status
201 def _status(self):
202 return self.status
203
204 class TestCommon(TestCmd):
205
206 # Additional methods from the Perl Test::Cmd::Common module
207 # that we may wish to add in the future:
208 #
209 # $test->subdir('subdir', ...);
210 #
211 # $test->copy('src_file', 'dst_file');
212
213 def __init__(self, **kw):
214 """Initialize a new TestCommon instance. This involves just
215 calling the base class initialization, and then changing directory
216 to the workdir.
217 """
218 apply(TestCmd.__init__, [self], kw)
219 os.chdir(self.workdir)
220
221 def must_be_writable(self, *files):
222 """Ensures that the specified file(s) exist and are writable.
223 An individual file can be specified as a list of directory names,
224 in which case the pathname will be constructed by concatenating
225 them. Exits FAILED if any of the files does not exist or is
226 not writable.
227 """
228 files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
229 existing, missing = separate_files(files)
230 unwritable = filter(lambda x, iw=is_writable: not iw(x), existing)
231 if missing:
232 print "Missing files: `%s'" % string.join(missing, "', `")
233 if unwritable:
234 print "Unwritable files: `%s'" % string.join(unwritable, "', `")
235 self.fail_test(missing + unwritable)
236
237 def must_contain(self, file, required, mode = 'rb'):
238 """Ensures that the specified file contains the required text.
239 """
240 file_contents = self.read(file, mode)
241 contains = (string.find(file_contents, required) != -1)
242 if not contains:
243 print "File `%s' does not contain required string." % file
244 print self.banner('Required string ')
245 print required
246 print self.banner('%s contents ' % file)
247 print file_contents
248 self.fail_test(not contains)
249
250 def must_contain_all_lines(self, output, lines, title=None, find=None):
251 """Ensures that the specified output string (first argument)
252 contains all of the specified lines (second argument).
253
254 An optional third argument can be used to describe the type
255 of output being searched, and only shows up in failure output.
256
257 An optional fourth argument can be used to supply a different
258 function, of the form "find(line, output), to use when searching
259 for lines in the output.
260 """
261 if find is None:
262 find = lambda o, l: string.find(o, l) != -1
263 missing = []
264 for line in lines:
265 if not find(output, line):
266 missing.append(line)
267
268 if missing:
269 if title is None:
270 title = 'output'
271 sys.stdout.write("Missing expected lines from %s:\n" % title)
272 for line in missing:
273 sys.stdout.write(' ' + repr(line) + '\n')
274 sys.stdout.write(self.banner(title + ' '))
275 sys.stdout.write(output)
276 self.fail_test()
277
278 def must_contain_any_line(self, output, lines, title=None, find=None):
279 """Ensures that the specified output string (first argument)
280 contains at least one of the specified lines (second argument).
281
282 An optional third argument can be used to describe the type
283 of output being searched, and only shows up in failure output.
284
285 An optional fourth argument can be used to supply a different
286 function, of the form "find(line, output), to use when searching
287 for lines in the output.
288 """
289 if find is None:
290 find = lambda o, l: string.find(o, l) != -1
291 for line in lines:
292 if find(output, line):
293 return
294
295 if title is None:
296 title = 'output'
297 sys.stdout.write("Missing any expected line from %s:\n" % title)
298 for line in lines:
299 sys.stdout.write(' ' + repr(line) + '\n')
300 sys.stdout.write(self.banner(title + ' '))
301 sys.stdout.write(output)
302 self.fail_test()
303
304 def must_contain_lines(self, lines, output, title=None):
305 # Deprecated; retain for backwards compatibility.
306 return self.must_contain_all_lines(output, lines, title)
307
308 def must_exist(self, *files):
309 """Ensures that the specified file(s) must exist. An individual
310 file be specified as a list of directory names, in which case the
311 pathname will be constructed by concatenating them. Exits FAILED
312 if any of the files does not exist.
313 """
314 files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
315 missing = filter(lambda x: not os.path.exists(x), files)
316 if missing:
317 print "Missing files: `%s'" % string.join(missing, "', `")
318 self.fail_test(missing)
319
320 def must_match(self, file, expect, mode = 'rb'):
321 """Matches the contents of the specified file (first argument)
322 against the expected contents (second argument). The expected
323 contents are a list of lines or a string which will be split
324 on newlines.
325 """
326 file_contents = self.read(file, mode)
327 try:
328 self.fail_test(not self.match(file_contents, expect))
329 except KeyboardInterrupt:
330 raise
331 except:
332 print "Unexpected contents of `%s'" % file
333 self.diff(expect, file_contents, 'contents ')
334 raise
335
336 def must_not_contain_any_line(self, output, lines, title=None, find=None):
337 """Ensures that the specified output string (first argument)
338 does not contain any of the specified lines (second argument).
339
340 An optional third argument can be used to describe the type
341 of output being searched, and only shows up in failure output.
342
343 An optional fourth argument can be used to supply a different
344 function, of the form "find(line, output), to use when searching
345 for lines in the output.
346 """
347 if find is None:
348 find = lambda o, l: string.find(o, l) != -1
349 unexpected = []
350 for line in lines:
351 if find(output, line):
352 unexpected.append(line)
353
354 if unexpected:
355 if title is None:
356 title = 'output'
357 sys.stdout.write("Unexpected lines in %s:\n" % title)
358 for line in unexpected:
359 sys.stdout.write(' ' + repr(line) + '\n')
360 sys.stdout.write(self.banner(title + ' '))
361 sys.stdout.write(output)
362 self.fail_test()
363
364 def must_not_contain_lines(self, lines, output, title=None):
365 return self.must_not_contain_any_line(output, lines, title)
366
367 def must_not_exist(self, *files):
368 """Ensures that the specified file(s) must not exist.
369 An individual file be specified as a list of directory names, in
370 which case the pathname will be constructed by concatenating them.
371 Exits FAILED if any of the files exists.
372 """
373 files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
374 existing = filter(os.path.exists, files)
375 if existing:
376 print "Unexpected files exist: `%s'" % string.join(existing, "', `")
377 self.fail_test(existing)
378
379
380 def must_not_be_writable(self, *files):
381 """Ensures that the specified file(s) exist and are not writable.
382 An individual file can be specified as a list of directory names,
383 in which case the pathname will be constructed by concatenating
384 them. Exits FAILED if any of the files does not exist or is
385 writable.
386 """
387 files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
388 existing, missing = separate_files(files)
389 writable = filter(is_writable, existing)
390 if missing:
391 print "Missing files: `%s'" % string.join(missing, "', `")
392 if writable:
393 print "Writable files: `%s'" % string.join(writable, "', `")
394 self.fail_test(missing + writable)
395
396 def _complete(self, actual_stdout, expected_stdout,
397 actual_stderr, expected_stderr, status, match):
398 """
399 Post-processes running a subcommand, checking for failure
400 status and displaying output appropriately.
401 """
402 if _failed(self, status):
403 expect = ''
404 if status != 0:
405 expect = " (expected %s)" % str(status)
406 print "%s returned %s%s" % (self.program, str(_status(self)), expect )
407 print self.banner('STDOUT ')
408 print actual_stdout
409 print self.banner('STDERR ')
410 print actual_stderr
411 self.fail_test()
412 if not expected_stdout is None and not match(actual_stdout, expected_std out):
413 self.diff(expected_stdout, actual_stdout, 'STDOUT ')
414 if actual_stderr:
415 print self.banner('STDERR ')
416 print actual_stderr
417 self.fail_test()
418 if not expected_stderr is None and not match(actual_stderr, expected_std err):
419 print self.banner('STDOUT ')
420 print actual_stdout
421 self.diff(expected_stderr, actual_stderr, 'STDERR ')
422 self.fail_test()
423
424 def start(self, program = None,
425 interpreter = None,
426 arguments = None,
427 universal_newlines = None,
428 **kw):
429 """
430 Starts a program or script for the test environment.
431
432 This handles the "options" keyword argument and exceptions.
433 """
434 try:
435 options = kw['options']
436 del kw['options']
437 except KeyError:
438 pass
439 else:
440 if options:
441 if arguments is None:
442 arguments = options
443 else:
444 arguments = options + " " + arguments
445 try:
446 return apply(TestCmd.start,
447 (self, program, interpreter, arguments, universal_newli nes),
448 kw)
449 except KeyboardInterrupt:
450 raise
451 except Exception, e:
452 print self.banner('STDOUT ')
453 try:
454 print self.stdout()
455 except IndexError:
456 pass
457 print self.banner('STDERR ')
458 try:
459 print self.stderr()
460 except IndexError:
461 pass
462 cmd_args = self.command_args(program, interpreter, arguments)
463 sys.stderr.write('Exception trying to execute: %s\n' % cmd_args)
464 raise e
465
466 def finish(self, popen, stdout = None, stderr = '', status = 0, **kw):
467 """
468 Finishes and waits for the process being run under control of
469 the specified popen argument. Additional arguments are similar
470 to those of the run() method:
471
472 stdout The expected standard output from
473 the command. A value of None means
474 don't test standard output.
475
476 stderr The expected error output from
477 the command. A value of None means
478 don't test error output.
479
480 status The expected exit status from the
481 command. A value of None means don't
482 test exit status.
483 """
484 apply(TestCmd.finish, (self, popen,), kw)
485 match = kw.get('match', self.match)
486 self._complete(self.stdout(), stdout,
487 self.stderr(), stderr, status, match)
488
489 def run(self, options = None, arguments = None,
490 stdout = None, stderr = '', status = 0, **kw):
491 """Runs the program under test, checking that the test succeeded.
492
493 The arguments are the same as the base TestCmd.run() method,
494 with the addition of:
495
496 options Extra options that get appended to the beginning
497 of the arguments.
498
499 stdout The expected standard output from
500 the command. A value of None means
501 don't test standard output.
502
503 stderr The expected error output from
504 the command. A value of None means
505 don't test error output.
506
507 status The expected exit status from the
508 command. A value of None means don't
509 test exit status.
510
511 By default, this expects a successful exit (status = 0), does
512 not test standard output (stdout = None), and expects that error
513 output is empty (stderr = "").
514 """
515 if options:
516 if arguments is None:
517 arguments = options
518 else:
519 arguments = options + " " + arguments
520 kw['arguments'] = arguments
521 try:
522 match = kw['match']
523 del kw['match']
524 except KeyError:
525 match = self.match
526 apply(TestCmd.run, [self], kw)
527 self._complete(self.stdout(), stdout,
528 self.stderr(), stderr, status, match)
529
530 def skip_test(self, message="Skipping test.\n"):
531 """Skips a test.
532
533 Proper test-skipping behavior is dependent on the external
534 TESTCOMMON_PASS_SKIPS environment variable. If set, we treat
535 the skip as a PASS (exit 0), and otherwise treat it as NO RESULT.
536 In either case, we print the specified message as an indication
537 that the substance of the test was skipped.
538
539 (This was originally added to support development under Aegis.
540 Technically, skipping a test is a NO RESULT, but Aegis would
541 treat that as a test failure and prevent the change from going to
542 the next step. Since we ddn't want to force anyone using Aegis
543 to have to install absolutely every tool used by the tests, we
544 would actually report to Aegis that a skipped test has PASSED
545 so that the workflow isn't held up.)
546 """
547 if message:
548 sys.stdout.write(message)
549 sys.stdout.flush()
550 pass_skips = os.environ.get('TESTCOMMON_PASS_SKIPS')
551 if pass_skips in [None, 0, '0']:
552 # skip=1 means skip this function when showing where this
553 # result came from. They only care about the line where the
554 # script called test.skip_test(), not the line number where
555 # we call test.no_result().
556 self.no_result(skip=1)
557 else:
558 # We're under the development directory for this change,
559 # so this is an Aegis invocation; pass the test (exit 0).
560 self.pass_test()
561
562 # Local Variables:
563 # tab-width:4
564 # indent-tabs-mode:nil
565 # End:
566 # vim: set expandtab tabstop=4 shiftwidth=4:
OLDNEW
« no previous file with comments | « trunk/test/lib/TestCmd.py ('k') | trunk/test/lib/TestGyp.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698