Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_33_1/tools/build/v2/test/TestCmd.py @ 12

Last change on this file since 12 was 12, checked in by landauf, 18 years ago

added boost

File size: 20.3 KB
Line 
1"""
2TestCmd.py:  a testing framework for commands and scripts.
3
4The TestCmd module provides a framework for portable automated testing
5of executable commands and scripts (in any language, not just Python),
6especially commands and scripts that require file system interaction.
7
8In addition to running tests and evaluating conditions, the TestCmd module
9manages and cleans up one or more temporary workspace directories, and
10provides methods for creating files and directories in those workspace
11directories from in-line data, here-documents), allowing tests to be
12completely self-contained.
13
14A TestCmd environment object is created via the usual invocation:
15
16    test = TestCmd()
17
18The TestCmd module provides pass_test(), fail_test(), and no_result()
19unbound methods that report test results for use with the Aegis change
20management system.  These methods terminate the test immediately,
21reporting PASSED, FAILED, or NO RESULT respectively, and exiting with
22status 0 (success), 1 or 2 respectively.  This allows for a distinction
23between an actual failed test and a test that could not be properly
24evaluated because of an external condition (such as a full file system
25or incorrect permissions).
26"""
27
28# Copyright 2000 Steven Knight
29# This module is free software, and you may redistribute it and/or modify
30# it under the same terms as Python itself, so long as this copyright message
31# and disclaimer are retained in their original form.
32#
33# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
34# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
35# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
36# DAMAGE.
37#
38# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
39# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
40# PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
41# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
42# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
43
44from string import join, split
45
46__author__ = "Steven Knight <knight@baldmt.com>"
47__revision__ = "TestCmd.py 0.D002 2001/08/31 14:56:12 software"
48__version__ = "0.02"
49
50from types import *
51
52import os
53import os.path
54import popen2
55import re
56import shutil
57import stat
58import sys
59import tempfile
60import traceback
61
62tempfile.template = 'testcmd.'
63
64_Cleanup = []
65
66def _clean():
67    global _Cleanup
68    list = _Cleanup[:]
69    _Cleanup = []
70    list.reverse()
71    for test in list:
72        test.cleanup()
73
74sys.exitfunc = _clean
75
76def _caller(tblist, skip):
77    string = ""
78    arr = []
79    for file, line, name, text in tblist:
80        if file[-10:] == "TestCmd.py":
81                break
82        arr = [(file, line, name, text)] + arr
83    atfrom = "at"
84    for file, line, name, text in arr[skip:]:
85        if name == "?":
86            name = ""
87        else:
88            name = " (" + name + ")"
89        string = string + ("%s line %d of %s%s\n" % (atfrom, line, file, name))
90        atfrom = "\tfrom"
91    return string
92
93def fail_test(self = None, condition = 1, function = None, skip = 0):
94    """Cause the test to fail.
95
96    By default, the fail_test() method reports that the test FAILED
97    and exits with a status of 1.  If a condition argument is supplied,
98    the test fails only if the condition is true.
99    """
100    if not condition:
101        return
102    if not function is None:
103        function()
104    of = ""
105    desc = ""
106    sep = " "
107    if not self is None:
108        if self.program:
109            of = " of " + join(self.program, " ")
110            sep = "\n\t"
111        if self.description:
112            desc = " [" + self.description + "]"
113            sep = "\n\t"
114
115    at = _caller(traceback.extract_stack(), skip)
116
117    sys.stderr.write("FAILED test" + of + desc + sep + at + """
118in directory: """ + os.getcwd() )
119
120    sys.exit(1)
121
122def no_result(self = None, condition = 1, function = None, skip = 0):
123    """Causes a test to exit with no valid result.
124
125    By default, the no_result() method reports NO RESULT for the test
126    and exits with a status of 2.  If a condition argument is supplied,
127    the test fails only if the condition is true.
128    """
129    if not condition:
130        return
131    if not function is None:
132        function()
133    of = ""
134    desc = ""
135    sep = " "
136    if not self is None:
137        if self.program:
138            of = " of " + self.program
139            sep = "\n\t"
140        if self.description:
141            desc = " [" + self.description + "]"
142            sep = "\n\t"
143
144    at = _caller(traceback.extract_stack(), skip)
145    sys.stderr.write("NO RESULT for test" + of + desc + sep + at)
146
147    sys.exit(2)
148
149def pass_test(self = None, condition = 1, function = None):
150    """Causes a test to pass.
151
152    By default, the pass_test() method reports PASSED for the test
153    and exits with a status of 0.  If a condition argument is supplied,
154    the test passes only if the condition is true.
155    """
156    if not condition:
157        return
158    if not function is None:
159        function()
160    sys.stderr.write("PASSED\n")
161    sys.exit(0)
162
163def match_exact(lines = None, matches = None):
164    """
165    """
166    if not type(lines) is ListType:
167        lines = split(lines, "\n")
168    if not type(matches) is ListType:
169        matches = split(matches, "\n")
170    if len(lines) != len(matches):
171        return
172    for i in range(len(lines)):
173        if lines[i] != matches[i]:
174            return
175    return 1
176
177def match_re(lines = None, res = None):
178    """
179    """
180    if not type(lines) is ListType:
181        lines = split(lines, "\n")
182    if not type(res) is ListType:
183        res = split(res, "\n")
184    if len(lines) != len(res):
185        return
186    for i in range(len(lines)):
187        if not re.compile("^" + res[i] + "$").search(lines[i]):
188            return
189    return 1
190
191class TestCmd:
192    """Class TestCmd
193    """
194
195    def __init__(self, description = None,
196                        program = None,
197                        interpreter = None,
198                        workdir = None,
199                        subdir = None,
200                        verbose = 0,
201                        match = None):
202        self._cwd = os.getcwd()
203        self.description_set(description)
204        self.program_set(program)
205        self.interpreter_set(interpreter)
206        self.verbose_set(verbose)
207        if not match is None:
208            self.match_func = match
209        else:
210            self.match_func = match_re
211        self._dirlist = []
212        self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0}
213        if os.environ.has_key('PRESERVE') and not os.environ['PRESERVE'] is '':
214            self._preserve['pass_test'] = os.environ['PRESERVE']
215            self._preserve['fail_test'] = os.environ['PRESERVE']
216            self._preserve['no_result'] = os.environ['PRESERVE']
217        else:
218            try:
219                self._preserve['pass_test'] = os.environ['PRESERVE_PASS']
220            except KeyError:
221                pass
222            try:
223                self._preserve['fail_test'] = os.environ['PRESERVE_FAIL']
224            except KeyError:
225                pass
226            try:
227                self._preserve['no_result'] = os.environ['PRESERVE_NO_RESULT']
228            except KeyError:
229                pass
230        self._stdout = []
231        self._stderr = []
232        self.status = None
233        self.condition = 'no_result'
234        self.workdir_set(workdir)
235        self.subdir(subdir)
236
237    def __del__(self):
238        self.cleanup()
239
240    def __repr__(self):
241        return "%x" % id(self)
242
243    def cleanup(self, condition = None):
244        """Removes any temporary working directories for the specified
245        TestCmd environment.  If the environment variable PRESERVE was
246        set when the TestCmd environment was created, temporary working
247        directories are not removed.  If any of the environment variables
248        PRESERVE_PASS, PRESERVE_FAIL, or PRESERVE_NO_RESULT were set
249        when the TestCmd environment was created, then temporary working
250        directories are not removed if the test passed, failed, or had
251        no result, respectively.  Temporary working directories are also
252        preserved for conditions specified via the preserve method.
253
254        Typically, this method is not called directly, but is used when
255        the script exits to clean up temporary working directories as
256        appropriate for the exit status.
257        """
258        if not self._dirlist:
259            return
260        if condition is None:
261            condition = self.condition
262        if self._preserve[condition]:
263            for dir in self._dirlist:
264                print "Preserved directory", dir
265        else:
266            list = self._dirlist[:]
267            list.reverse()
268            for dir in list:
269                self.writable(dir, 1)
270                shutil.rmtree(dir, ignore_errors = 1)
271               
272        self._dirlist = []
273        self.workdir = None
274        os.chdir(self._cwd)           
275        try:
276            global _Cleanup
277            _Cleanup.remove(self)
278        except (AttributeError, ValueError):
279            pass
280
281    def description_set(self, description):
282        """Set the description of the functionality being tested.
283        """
284        self.description = description
285
286#    def diff(self):
287#        """Diff two arrays.
288#        """
289
290    def fail_test(self, condition = 1, function = None, skip = 0):
291        """Cause the test to fail.
292        """
293        if not condition:
294            return
295        self.condition = 'fail_test'
296        fail_test(self = self,
297                  condition = condition,
298                  function = function,
299                  skip = skip)
300
301    def interpreter_set(self, interpreter):
302        """Set the program to be used to interpret the program
303        under test as a script.
304        """
305        self.interpreter = interpreter
306
307    def match(self, lines, matches):
308        """Compare actual and expected file contents.
309        """
310        return self.match_func(lines, matches)
311
312    def match_exact(self, lines, matches):
313        """Compare actual and expected file contents.
314        """
315        return match_exact(lines, matches)
316
317    def match_re(self, lines, res):
318        """Compare actual and expected file contents.
319        """
320        return match_re(lines, res)
321
322    def no_result(self, condition = 1, function = None, skip = 0):
323        """Report that the test could not be run.
324        """
325        if not condition:
326            return
327        self.condition = 'no_result'
328        no_result(self = self,
329                  condition = condition,
330                  function = function,
331                  skip = skip)
332
333    def pass_test(self, condition = 1, function = None):
334        """Cause the test to pass.
335        """
336        if not condition:
337            return
338        self.condition = 'pass_test'
339        pass_test(self = self, condition = condition, function = function)
340
341    def preserve(self, *conditions):
342        """Arrange for the temporary working directories for the
343        specified TestCmd environment to be preserved for one or more
344        conditions.  If no conditions are specified, arranges for
345        the temporary working directories to be preserved for all
346        conditions.
347        """
348        if conditions is ():
349            conditions = ('pass_test', 'fail_test', 'no_result')
350        for cond in conditions:
351            self._preserve[cond] = 1
352
353    def program_set(self, program):
354        """Set the executable program or script to be tested.
355        """
356        if program and program[0] and not os.path.isabs(program[0]):
357            program[0] = os.path.join(self._cwd, program[0])
358        self.program = program
359
360    def read(self, file, mode = 'rb'):
361        """Reads and returns the contents of the specified file name.
362        The file name may be a list, in which case the elements are
363        concatenated with the os.path.join() method.  The file is
364        assumed to be under the temporary working directory unless it
365        is an absolute path name.  The I/O mode for the file may
366        be specified; it must begin with an 'r'.  The default is
367        'rb' (binary read).
368        """
369        if type(file) is ListType:
370            file = apply(os.path.join, tuple(file))
371        if not os.path.isabs(file):
372            file = os.path.join(self.workdir, file)
373        if mode[0] != 'r':
374            raise ValueError, "mode must begin with 'r'"
375        return open(file, mode).read()
376
377    def run(self, program = None,
378                  interpreter = None,
379                  arguments = None,
380                  chdir = None,
381                  stdin = None):
382        """Runs a test of the program or script for the test
383        environment.  Standard output and error output are saved for
384        future retrieval via the stdout() and stderr() methods.
385        """
386        if chdir:
387            oldcwd = os.getcwd()
388            if not os.path.isabs(chdir):
389                chdir = os.path.join(self.workpath(chdir))
390            if self.verbose:
391                sys.stderr.write("chdir(" + chdir + ")\n")
392            os.chdir(chdir)
393        cmd = []
394        if program and program[0]:
395            if not os.path.isabs(program[0]):
396                program[0] = os.path.join(self._cwd, program[0])
397            cmd += program
398        #    if interpreter:
399        #        cmd = interpreter + " " + cmd
400        else:
401            cmd += self.program
402        #    if self.interpreter:
403        #        cmd =  self.interpreter + " " + cmd
404        if arguments:
405            cmd += arguments.split(" ")
406        if self.verbose:
407            sys.stderr.write(join(cmd, " ") + "\n")
408        try:
409            p = popen2.Popen3(cmd, 1)
410        except AttributeError:
411            (tochild, fromchild, childerr) = os.popen3(join(cmd, " "))
412            if stdin:
413                if type(stdin) is ListType:
414                    for line in stdin:
415                        tochild.write(line)
416                else:
417                    tochild.write(stdin)
418            tochild.close()
419            self._stdout.append(fromchild.read())
420            self._stderr.append(childerr.read())               
421            fromchild.close()
422            self.status = childerr.close()
423            if not self.status:
424                self.status = 0
425        except:
426            raise
427        else:
428            if stdin:
429                if type(stdin) is ListType:
430                    for line in stdin:
431                        p.tochild.write(line)
432                else:
433                    p.tochild.write(stdin)
434            p.tochild.close()
435            self._stdout.append(p.fromchild.read())
436            self._stderr.append(p.childerr.read())
437            self.status = p.wait()
438           
439        if self.verbose:
440            sys.stdout.write(self._stdout[-1])
441            sys.stderr.write(self._stderr[-1])
442           
443        if chdir:
444            os.chdir(oldcwd)
445
446    def stderr(self, run = None):
447        """Returns the error output from the specified run number.
448        If there is no specified run number, then returns the error
449        output of the last run.  If the run number is less than zero,
450        then returns the error output from that many runs back from the
451        current run.
452        """
453        if not run:
454            run = len(self._stderr)
455        elif run < 0:
456            run = len(self._stderr) + run
457        run = run - 1
458        if (run < 0):
459            return ''
460        return self._stderr[run]
461
462    def stdout(self, run = None):
463        """Returns the standard output from the specified run number.
464        If there is no specified run number, then returns the standard
465        output of the last run.  If the run number is less than zero,
466        then returns the standard output from that many runs back from
467        the current run.
468        """
469        if not run:
470            run = len(self._stdout)
471        elif run < 0:
472            run = len(self._stdout) + run
473        run = run - 1
474        if (run < 0):
475            return ''
476        return self._stdout[run]
477
478    def subdir(self, *subdirs):
479        """Create new subdirectories under the temporary working
480        directory, one for each argument.  An argument may be a list,
481        in which case the list elements are concatenated using the
482        os.path.join() method.  Subdirectories multiple levels deep
483        must be created using a separate argument for each level:
484
485                test.subdir('sub', ['sub', 'dir'], ['sub', 'dir', 'ectory'])
486
487        Returns the number of subdirectories actually created.
488        """
489        count = 0
490        for sub in subdirs:
491            if sub is None:
492                continue
493            if type(sub) is ListType:
494                sub = apply(os.path.join, tuple(sub))
495            new = os.path.join(self.workdir, sub)
496            try:
497                os.mkdir(new)
498            except:
499                pass
500            else:
501                count = count + 1
502        return count
503
504    def unlink (self, file):
505        """Unlinks the specified file name.
506        The file name may be a list, in which case the elements are
507        concatenated with the os.path.join() method.  The file is
508        assumed to be under the temporary working directory unless it
509        is an absolute path name.
510        """
511        if type(file) is ListType:
512            file = apply(os.path.join, tuple(file))
513        if not os.path.isabs(file):
514            file = os.path.join(self.workdir, file)
515        os.unlink(file)
516
517
518    def verbose_set(self, verbose):
519        """Set the verbose level.
520        """
521        self.verbose = verbose
522
523    def workdir_set(self, path):
524        """Creates a temporary working directory with the specified
525        path name.  If the path is a null string (''), a unique
526        directory name is created.
527        """
528
529        if os.path.isabs(path):
530            self.workdir = path
531        else:
532            if (path != None):
533                if path == '':
534                    path = tempfile.mktemp()
535                if path != None:
536                    os.mkdir(path)
537                self._dirlist.append(path)
538                global _Cleanup
539                try:
540                    _Cleanup.index(self)
541                except ValueError:
542                    _Cleanup.append(self)
543                # We'd like to set self.workdir like this:
544                #        self.workdir = path
545                # But symlinks in the path will report things
546                # differently from os.getcwd(), so chdir there
547                # and back to fetch the canonical path.
548                cwd = os.getcwd()
549                os.chdir(path)
550                self.workdir = os.getcwd()
551                os.chdir(cwd)
552            else:
553                self.workdir = None
554
555    def workpath(self, *args):
556        """Returns the absolute path name to a subdirectory or file
557        within the current temporary working directory.  Concatenates
558        the temporary working directory name with the specified
559        arguments using the os.path.join() method.
560        """
561        return apply(os.path.join, (self.workdir,) + tuple(args))
562
563    def writable(self, top, write):
564        """Make the specified directory tree writable (write == 1)
565        or not (write == None).
566        """
567
568        def _walk_chmod(arg, dirname, names):
569            st = os.stat(dirname)
570            os.chmod(dirname, arg(st[stat.ST_MODE]))
571            for name in names:
572                n = os.path.join(dirname, name)
573                st = os.stat(n)
574                os.chmod(n, arg(st[stat.ST_MODE]))
575
576        def _mode_writable(mode):
577            return stat.S_IMODE(mode|0200)
578
579        def _mode_non_writable(mode):
580            return stat.S_IMODE(mode&~0200)
581
582        if write:
583            f = _mode_writable
584        else:
585            f = _mode_non_writable
586        try:
587            os.path.walk(top, _walk_chmod, f)
588        except:
589            pass # ignore any problems changing modes
590
591    def write(self, file, content, mode = 'wb'):
592        """Writes the specified content text (second argument) to the
593        specified file name (first argument).  The file name may be
594        a list, in which case the elements are concatenated with the
595        os.path.join() method.        The file is created under the temporary
596        working directory.  Any subdirectories in the path must already
597        exist.  The I/O mode for the file may be specified; it must
598        begin with a 'w'.  The default is 'wb' (binary write).
599        """
600        if type(file) is ListType:
601            file = apply(os.path.join, tuple(file))
602        if not os.path.isabs(file):
603            file = os.path.join(self.workdir, file)
604        if mode[0] != 'w':
605            raise ValueError, "mode must begin with 'w'"
606        open(file, mode).write(content)
Note: See TracBrowser for help on using the repository browser.