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