2 TestCmd.py: a testing framework for commands and scripts.
4 The TestCmd module provides a framework for portable automated testing
5 of executable commands and scripts (in any language, not just Python),
6 especially commands and scripts that require file system interaction.
8 In addition to running tests and evaluating conditions, the TestCmd
9 module manages and cleans up one or more temporary workspace
10 directories, and provides methods for creating files and directories in
11 those workspace directories from in-line data, here-documents), allowing
12 tests to be completely self-contained.
14 A TestCmd environment object is created via the usual invocation:
17 test = TestCmd.TestCmd()
19 There are a bunch of keyword arguments that you can use at instantiation
22 test = TestCmd.TestCmd(description = 'string',
23 program = 'program_or_script_to_test',
24 interpreter = 'script_interpreter',
28 match = default_match_function,
31 There are a bunch of methods that let you do a bunch of different
32 things. Here is an overview of them:
36 test.description_set('string')
38 test.program_set('program_or_script_to_test')
40 test.interpreter_set('script_interpreter')
41 test.interpreter_set(['script_interpreter', 'arg'])
43 test.workdir_set('prefix')
47 test.workpath('subdir', 'file')
49 test.subdir('subdir', ...)
51 test.rmdir('subdir', ...)
53 test.write('file', "contents\n")
54 test.write(['subdir', 'file'], "contents\n")
57 test.read(['subdir', 'file'])
58 test.read('file', mode)
59 test.read(['subdir', 'file'], mode)
61 test.writable('dir', 1)
62 test.writable('dir', None)
64 test.preserve(condition, ...)
66 test.cleanup(condition)
68 test.run(program = 'program_or_script_to_run',
69 interpreter = 'script_interpreter',
70 arguments = 'arguments to pass to program',
71 chdir = 'directory_to_chdir_to',
72 stdin = 'input to feed to the program\n')
75 test.pass_test(condition)
76 test.pass_test(condition, function)
79 test.fail_test(condition)
80 test.fail_test(condition, function)
81 test.fail_test(condition, function, skip)
84 test.no_result(condition)
85 test.no_result(condition, function)
86 test.no_result(condition, function, skip)
94 test.symlink(target, link)
96 test.match(actual, expected)
98 test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n")
99 test.match_exact(["actual 1\n", "actual 2\n"],
100 ["expected 1\n", "expected 2\n"])
102 test.match_re("actual 1\nactual 2\n", regex_string)
103 test.match_re(["actual 1\n", "actual 2\n"], list_of_regexes)
105 test.match_re_dotall("actual 1\nactual 2\n", regex_string)
106 test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes)
109 test.tempdir('temporary-directory')
115 test.where_is('foo', 'PATH1:PATH2')
116 test.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4')
119 test.unlink('subdir', 'file')
121 The TestCmd module provides pass_test(), fail_test(), and no_result()
122 unbound functions that report test results for use with the Aegis change
123 management system. These methods terminate the test immediately,
124 reporting PASSED, FAILED, or NO RESULT respectively, and exiting with
125 status 0 (success), 1 or 2 respectively. This allows for a distinction
126 between an actual failed test and a test that could not be properly
127 evaluated because of an external condition (such as a full file system
128 or incorrect permissions).
133 TestCmd.pass_test(condition)
134 TestCmd.pass_test(condition, function)
137 TestCmd.fail_test(condition)
138 TestCmd.fail_test(condition, function)
139 TestCmd.fail_test(condition, function, skip)
142 TestCmd.no_result(condition)
143 TestCmd.no_result(condition, function)
144 TestCmd.no_result(condition, function, skip)
146 The TestCmd module also provides unbound functions that handle matching
147 in the same way as the match_*() methods described above.
151 test = TestCmd.TestCmd(match = TestCmd.match_exact)
153 test = TestCmd.TestCmd(match = TestCmd.match_re)
155 test = TestCmd.TestCmd(match = TestCmd.match_re_dotall)
157 Lastly, the where_is() method also exists in an unbound function
162 TestCmd.where_is('foo')
163 TestCmd.where_is('foo', 'PATH1:PATH2')
164 TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4')
167 # Copyright 2000, 2001, 2002, 2003, 2004 Steven Knight
168 # This module is free software, and you may redistribute it and/or modify
169 # it under the same terms as Python itself, so long as this copyright message
170 # and disclaimer are retained in their original form.
172 # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
173 # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
174 # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
177 # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
178 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
179 # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
180 # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
181 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
183 __author__ = "Steven Knight <knight at baldmt dot com>"
184 __revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight"
214 return type(e) is types.ListType \
215 or isinstance(e, UserList.UserList)
218 from UserString import UserString
223 if hasattr(types, 'UnicodeType'):
225 return type(e) is types.StringType \
226 or type(e) is types.UnicodeType \
227 or isinstance(e, UserString)
230 return type(e) is types.StringType or isinstance(e, UserString)
232 tempfile.template = 'testcmd.'
233 if os.name in ('posix', 'nt'):
234 tempfile.template = 'testcmd.' + str(os.getpid()) + '.'
236 tempfile.template = 'testcmd.'
238 re_space = re.compile('\s')
244 cleanlist = filter(None, _Cleanup)
247 for test in cleanlist:
250 sys.exitfunc = _clean
253 def __init__(self, top):
255 def __call__(self, arg, dirname, names):
256 pathjoin = lambda n, d=dirname: os.path.join(d, n)
257 self.entries.extend(map(pathjoin, names))
259 def _caller(tblist, skip):
262 for file, line, name, text in tblist:
263 if file[-10:] == "TestCmd.py":
265 arr = [(file, line, name, text)] + arr
267 for file, line, name, text in arr[skip:]:
271 name = " (" + name + ")"
272 string = string + ("%s line %d of %s%s\n" % (atfrom, line, file, name))
276 def fail_test(self = None, condition = 1, function = None, skip = 0):
277 """Cause the test to fail.
279 By default, the fail_test() method reports that the test FAILED
280 and exits with a status of 1. If a condition argument is supplied,
281 the test fails only if the condition is true.
285 if not function is None:
292 of = " of " + self.program
295 desc = " [" + self.description + "]"
298 at = _caller(traceback.extract_stack(), skip)
299 sys.stderr.write("FAILED test" + of + desc + sep + at)
303 def no_result(self = None, condition = 1, function = None, skip = 0):
304 """Causes a test to exit with no valid result.
306 By default, the no_result() method reports NO RESULT for the test
307 and exits with a status of 2. If a condition argument is supplied,
308 the test fails only if the condition is true.
312 if not function is None:
319 of = " of " + self.program
322 desc = " [" + self.description + "]"
325 at = _caller(traceback.extract_stack(), skip)
326 sys.stderr.write("NO RESULT for test" + of + desc + sep + at)
330 def pass_test(self = None, condition = 1, function = None):
331 """Causes a test to pass.
333 By default, the pass_test() method reports PASSED for the test
334 and exits with a status of 0. If a condition argument is supplied,
335 the test passes only if the condition is true.
339 if not function is None:
341 sys.stderr.write("PASSED\n")
344 def match_exact(lines = None, matches = None):
347 if not is_List(lines):
348 lines = string.split(lines, "\n")
349 if not is_List(matches):
350 matches = string.split(matches, "\n")
351 if len(lines) != len(matches):
353 for i in range(len(lines)):
354 if lines[i] != matches[i]:
358 def match_re(lines = None, res = None):
361 if not is_List(lines):
362 lines = string.split(lines, "\n")
364 res = string.split(res, "\n")
365 if len(lines) != len(res):
367 for i in range(len(lines)):
368 if not re.compile("^" + res[i] + "$").search(lines[i]):
372 def match_re_dotall(lines = None, res = None):
375 if not type(lines) is type(""):
376 lines = string.join(lines, "\n")
377 if not type(res) is type(""):
378 res = string.join(res, "\n")
379 if re.compile("^" + res + "$", re.DOTALL).match(lines):
382 def diff_re(a, b, fromfile='', tofile='',
383 fromfiledate='', tofiledate='', n=3, lineterm='\n'):
385 A simple "diff" of two sets of lines when the expected lines
386 are regular expressions. This is a really dumb thing that
387 just compares each line in turn, so it doesn't look for
388 chunks of matching lines and the like--but at least it lets
389 you know exactly which line first didn't compare correctl...
392 diff = len(a) - len(b)
398 for aline, bline in zip(a, b):
399 if not re.compile("^" + aline + "$").search(bline):
400 result.append("%sc%s" % (i+1, i+1))
401 result.append('< ' + repr(a[i]))
403 result.append('> ' + repr(b[i]))
407 if os.name == 'java':
409 python_executable = os.path.join(sys.prefix, 'jython')
413 python_executable = sys.executable
415 if sys.platform == 'win32':
417 default_sleep_seconds = 2
419 def where_is(file, path=None, pathext=None):
421 path = os.environ['PATH']
423 path = string.split(path, os.pathsep)
425 pathext = os.environ['PATHEXT']
426 if is_String(pathext):
427 pathext = string.split(pathext, os.pathsep)
429 if string.lower(ext) == string.lower(file[-len(ext):]):
433 f = os.path.join(dir, file)
436 if os.path.isfile(fext):
442 def where_is(file, path=None, pathext=None):
444 path = os.environ['PATH']
446 path = string.split(path, os.pathsep)
448 f = os.path.join(dir, file)
449 if os.path.isfile(f):
454 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
458 default_sleep_seconds = 1
464 def __init__(self, description = None,
472 universal_newlines = 1):
473 self._cwd = os.getcwd()
474 self.description_set(description)
475 self.program_set(program)
476 self.interpreter_set(interpreter)
479 verbose = max( 0, int(os.environ.get('TESTCMD_VERBOSE', 0)) )
482 self.verbose_set(verbose)
483 self.combine = combine
484 self.universal_newlines = universal_newlines
485 if not match is None:
486 self.match_func = match
488 self.match_func = match_re
490 self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0}
491 if os.environ.has_key('PRESERVE') and not os.environ['PRESERVE'] is '':
492 self._preserve['pass_test'] = os.environ['PRESERVE']
493 self._preserve['fail_test'] = os.environ['PRESERVE']
494 self._preserve['no_result'] = os.environ['PRESERVE']
497 self._preserve['pass_test'] = os.environ['PRESERVE_PASS']
501 self._preserve['fail_test'] = os.environ['PRESERVE_FAIL']
505 self._preserve['no_result'] = os.environ['PRESERVE_NO_RESULT']
511 self.condition = 'no_result'
512 self.workdir_set(workdir)
519 return "%x" % id(self)
521 if os.name == 'posix':
523 def escape(self, arg):
524 "escape shell special characters"
528 arg = string.replace(arg, slash, slash+slash)
530 arg = string.replace(arg, c, slash+c)
532 if re_space.search(arg):
533 arg = '"' + arg + '"'
538 # Windows does not allow special characters in file names
539 # anyway, so no need for an escape function, we will just quote
541 def escape(self, arg):
542 if re_space.search(arg):
543 arg = '"' + arg + '"'
546 def canonicalize(self, path):
548 path = apply(os.path.join, tuple(path))
549 if not os.path.isabs(path):
550 path = os.path.join(self.workdir, path)
553 def cleanup(self, condition = None):
554 """Removes any temporary working directories for the specified
555 TestCmd environment. If the environment variable PRESERVE was
556 set when the TestCmd environment was created, temporary working
557 directories are not removed. If any of the environment variables
558 PRESERVE_PASS, PRESERVE_FAIL, or PRESERVE_NO_RESULT were set
559 when the TestCmd environment was created, then temporary working
560 directories are not removed if the test passed, failed, or had
561 no result, respectively. Temporary working directories are also
562 preserved for conditions specified via the preserve method.
564 Typically, this method is not called directly, but is used when
565 the script exits to clean up temporary working directories as
566 appropriate for the exit status.
568 if not self._dirlist:
572 if condition is None:
573 condition = self.condition
574 if self._preserve[condition]:
575 for dir in self._dirlist:
576 print "Preserved directory", dir
578 list = self._dirlist[:]
581 self.writable(dir, 1)
582 shutil.rmtree(dir, ignore_errors = 1)
587 _Cleanup.remove(self)
588 except (AttributeError, ValueError):
591 def chmod(self, path, mode):
592 """Changes permissions on the specified file or directory
594 path = self.canonicalize(path)
597 def description_set(self, description):
598 """Set the description of the functionality being tested.
600 self.description = description
603 # """Diff two arrays.
606 def fail_test(self, condition = 1, function = None, skip = 0):
607 """Cause the test to fail.
611 self.condition = 'fail_test'
612 fail_test(self = self,
613 condition = condition,
617 def interpreter_set(self, interpreter):
618 """Set the program to be used to interpret the program
619 under test as a script.
621 self.interpreter = interpreter
623 def match(self, lines, matches):
624 """Compare actual and expected file contents.
626 return self.match_func(lines, matches)
628 def match_exact(self, lines, matches):
629 """Compare actual and expected file contents.
631 return match_exact(lines, matches)
633 def match_re(self, lines, res):
634 """Compare actual and expected file contents.
636 return match_re(lines, res)
638 def match_re_dotall(self, lines, res):
639 """Compare actual and expected file contents.
641 return match_re_dotall(lines, res)
643 def no_result(self, condition = 1, function = None, skip = 0):
644 """Report that the test could not be run.
648 self.condition = 'no_result'
649 no_result(self = self,
650 condition = condition,
654 def pass_test(self, condition = 1, function = None):
655 """Cause the test to pass.
659 self.condition = 'pass_test'
660 pass_test(self = self, condition = condition, function = function)
662 def preserve(self, *conditions):
663 """Arrange for the temporary working directories for the
664 specified TestCmd environment to be preserved for one or more
665 conditions. If no conditions are specified, arranges for
666 the temporary working directories to be preserved for all
670 conditions = ('pass_test', 'fail_test', 'no_result')
671 for cond in conditions:
672 self._preserve[cond] = 1
674 def program_set(self, program):
675 """Set the executable program or script to be tested.
677 if program and not os.path.isabs(program):
678 program = os.path.join(self._cwd, program)
679 self.program = program
681 def read(self, file, mode = 'rb'):
682 """Reads and returns the contents of the specified file name.
683 The file name may be a list, in which case the elements are
684 concatenated with the os.path.join() method. The file is
685 assumed to be under the temporary working directory unless it
686 is an absolute path name. The I/O mode for the file may
687 be specified; it must begin with an 'r'. The default is
690 file = self.canonicalize(file)
692 raise ValueError, "mode must begin with 'r'"
693 return open(file, mode).read()
695 def rmdir(self, dir):
696 """Removes the specified dir name.
697 The dir name may be a list, in which case the elements are
698 concatenated with the os.path.join() method. The dir is
699 assumed to be under the temporary working directory unless it
700 is an absolute path name.
701 The dir must be empty.
703 dir = self.canonicalize(dir)
706 def run(self, program = None,
711 universal_newlines = None):
712 """Runs a test of the program or script for the test
713 environment. Standard output and error output are saved for
714 future retrieval via the stdout() and stderr() methods.
716 The specified program will have the original directory
717 prepending unless it is enclosed in a [list].
721 if not os.path.isabs(chdir):
722 chdir = os.path.join(self.workpath(chdir))
724 sys.stderr.write("chdir(" + chdir + ")\n")
727 if type(program) == type('') and not os.path.isabs(program):
728 program = os.path.join(self._cwd, program)
730 program = self.program
732 interpreter = self.interpreter
733 if not type(program) in [type([]), type(())]:
737 if not type(interpreter) in [type([]), type(())]:
738 interpreter = [interpreter]
739 cmd = list(interpreter) + cmd
741 if type(arguments) == type(''):
742 arguments = string.split(arguments)
743 cmd.extend(arguments)
744 cmd_string = string.join(map(self.escape, cmd), ' ')
746 sys.stderr.write(cmd_string + "\n")
747 if universal_newlines is None:
748 universal_newlines = self.universal_newlines
754 Popen3 = popen2.Popen3
755 except AttributeError:
757 def __init__(self, command):
758 (stdin, stdout, stderr) = os.popen3(' ' + command)
762 def close_output(self):
764 self.resultcode = self.stderr.close()
766 return self.resultcode
767 if sys.platform == 'win32' and cmd_string[0] == '"':
768 cmd_string = '"' + cmd_string + '"'
769 p = Popen3(cmd_string)
773 p.stdout = p.fromchild
774 p.stderr = p.childerr
776 p = subprocess.Popen(cmd,
777 stdin=subprocess.PIPE,
778 stdout=subprocess.PIPE,
779 stderr=subprocess.PIPE,
780 universal_newlines=universal_newlines)
790 out = p.stdout.read()
791 err = p.stderr.read()
794 except AttributeError:
798 self.status = p.wait()
803 self._stdout.append(out + err)
805 self._stdout.append(out)
806 self._stderr.append(err)
810 if self.verbose >= 2:
811 write = sys.stdout.write
812 write('============ STATUS: %d\n' % self.status)
814 if out or self.verbose >= 3:
815 write('============ BEGIN STDOUT (len=%d):\n' % len(out))
817 write('============ END STDOUT\n')
819 if err or self.verbose >= 3:
820 write('============ BEGIN STDERR (len=%d)\n' % len(err))
822 write('============ END STDERR\n')
824 def sleep(self, seconds = default_sleep_seconds):
825 """Sleeps at least the specified number of seconds. If no
826 number is specified, sleeps at least the minimum number of
827 seconds necessary to advance file time stamps on the current
828 system. Sleeping more seconds is all right.
832 def stderr(self, run = None):
833 """Returns the error output from the specified run number.
834 If there is no specified run number, then returns the error
835 output of the last run. If the run number is less than zero,
836 then returns the error output from that many runs back from the
840 run = len(self._stderr)
842 run = len(self._stderr) + run
844 return self._stderr[run]
846 def stdout(self, run = None):
847 """Returns the standard output from the specified run number.
848 If there is no specified run number, then returns the standard
849 output of the last run. If the run number is less than zero,
850 then returns the standard output from that many runs back from
854 run = len(self._stdout)
856 run = len(self._stdout) + run
858 return self._stdout[run]
860 def subdir(self, *subdirs):
861 """Create new subdirectories under the temporary working
862 directory, one for each argument. An argument may be a list,
863 in which case the list elements are concatenated using the
864 os.path.join() method. Subdirectories multiple levels deep
865 must be created using a separate argument for each level:
867 test.subdir('sub', ['sub', 'dir'], ['sub', 'dir', 'ectory'])
869 Returns the number of subdirectories actually created.
876 sub = apply(os.path.join, tuple(sub))
877 new = os.path.join(self.workdir, sub)
886 def symlink(self, target, link):
887 """Creates a symlink to the specified target.
888 The link name may be a list, in which case the elements are
889 concatenated with the os.path.join() method. The link is
890 assumed to be under the temporary working directory unless it
891 is an absolute path name. The target is *not* assumed to be
892 under the temporary working directory.
894 link = self.canonicalize(link)
895 os.symlink(target, link)
897 def tempdir(self, path=None):
898 """Creates a temporary directory.
899 A unique directory name is generated if no path name is specified.
900 The directory is created, and will be removed when the TestCmd
905 path = tempfile.mktemp(prefix=tempfile.template)
907 path = tempfile.mktemp()
910 # Symlinks in the path will report things
911 # differently from os.getcwd(), so chdir there
912 # and back to fetch the canonical path.
920 # Uppercase the drive letter since the case of drive
921 # letters is pretty much random on win32:
922 drive,rest = os.path.splitdrive(path)
924 path = string.upper(drive) + rest
927 self._dirlist.append(path)
932 _Cleanup.append(self)
936 def touch(self, path, mtime=None):
937 """Updates the modification time on the specified file or
938 directory path name. The default is to update to the
939 current time if no explicit modification time is specified.
941 path = self.canonicalize(path)
942 atime = os.path.getatime(path)
945 os.utime(path, (atime, mtime))
947 def unlink(self, file):
948 """Unlinks the specified file name.
949 The file name may be a list, in which case the elements are
950 concatenated with the os.path.join() method. The file is
951 assumed to be under the temporary working directory unless it
952 is an absolute path name.
954 file = self.canonicalize(file)
957 def verbose_set(self, verbose):
958 """Set the verbose level.
960 self.verbose = verbose
962 def where_is(self, file, path=None, pathext=None):
963 """Find an executable file.
966 file = apply(os.path.join, tuple(file))
967 if not os.path.isabs(file):
968 file = where_is(file, path, pathext)
971 def workdir_set(self, path):
972 """Creates a temporary working directory with the specified
973 path name. If the path is a null string (''), a unique
974 directory name is created.
979 path = self.tempdir(path)
982 def workpath(self, *args):
983 """Returns the absolute path name to a subdirectory or file
984 within the current temporary working directory. Concatenates
985 the temporary working directory name with the specified
986 arguments using the os.path.join() method.
988 return apply(os.path.join, (self.workdir,) + tuple(args))
990 def readable(self, top, read=1):
991 """Make the specified directory tree readable (read == 1)
992 or not (read == None).
997 try: st = os.stat(fname)
999 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0400))
1001 def do_chmod(fname):
1002 try: st = os.stat(fname)
1003 except OSError: pass
1004 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0400))
1006 if os.path.isfile(top):
1007 # If it's a file, that's easy, just chmod it.
1010 # It's a directory and we're trying to turn on read
1011 # permission, so it's also pretty easy, just chmod the
1012 # directory and then chmod every entry on our walk down the
1013 # tree. Because os.path.walk() is top-down, we'll enable
1014 # read permission on any directories that have it disabled
1015 # before os.path.walk() tries to list their contents.
1018 def chmod_entries(arg, dirname, names, do_chmod=do_chmod):
1019 pathnames = map(lambda n, d=dirname: os.path.join(d, n),
1021 map(lambda p, do=do_chmod: do(p), pathnames)
1023 os.path.walk(top, chmod_entries, None)
1025 # It's a directory and we're trying to turn off read
1026 # permission, which means we have to chmod the directoreis
1027 # in the tree bottom-up, lest disabling read permission from
1028 # the top down get in the way of being able to get at lower
1029 # parts of the tree. But os.path.walk() visits things top
1030 # down, so we just use an object to collect a list of all
1031 # of the entries in the tree, reverse the list, and then
1032 # chmod the reversed (bottom-up) list.
1033 col = Collector(top)
1034 os.path.walk(top, col, None)
1035 col.entries.reverse()
1036 map(lambda d, do=do_chmod: do(d), col.entries)
1038 def writable(self, top, write=1):
1039 """Make the specified directory tree writable (write == 1)
1040 or not (write == None).
1044 def do_chmod(fname):
1045 try: st = os.stat(fname)
1046 except OSError: pass
1047 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
1049 def do_chmod(fname):
1050 try: st = os.stat(fname)
1051 except OSError: pass
1052 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
1054 if os.path.isfile(top):
1057 col = Collector(top)
1058 os.path.walk(top, col, None)
1059 map(lambda d, do=do_chmod: do(d), col.entries)
1061 def executable(self, top, execute=1):
1062 """Make the specified directory tree executable (execute == 1)
1063 or not (execute == None).
1067 def do_chmod(fname):
1068 try: st = os.stat(fname)
1069 except OSError: pass
1070 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0100))
1072 def do_chmod(fname):
1073 try: st = os.stat(fname)
1074 except OSError: pass
1075 else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0100))
1077 if os.path.isfile(top):
1078 # If it's a file, that's easy, just chmod it.
1081 # It's a directory and we're trying to turn on execute
1082 # permission, so it's also pretty easy, just chmod the
1083 # directory and then chmod every entry on our walk down the
1084 # tree. Because os.path.walk() is top-down, we'll enable
1085 # execute permission on any directories that have it disabled
1086 # before os.path.walk() tries to list their contents.
1089 def chmod_entries(arg, dirname, names, do_chmod=do_chmod):
1090 pathnames = map(lambda n, d=dirname: os.path.join(d, n),
1092 map(lambda p, do=do_chmod: do(p), pathnames)
1094 os.path.walk(top, chmod_entries, None)
1096 # It's a directory and we're trying to turn off execute
1097 # permission, which means we have to chmod the directories
1098 # in the tree bottom-up, lest disabling execute permission from
1099 # the top down get in the way of being able to get at lower
1100 # parts of the tree. But os.path.walk() visits things top
1101 # down, so we just use an object to collect a list of all
1102 # of the entries in the tree, reverse the list, and then
1103 # chmod the reversed (bottom-up) list.
1104 col = Collector(top)
1105 os.path.walk(top, col, None)
1106 col.entries.reverse()
1107 map(lambda d, do=do_chmod: do(d), col.entries)
1109 def write(self, file, content, mode = 'wb'):
1110 """Writes the specified content text (second argument) to the
1111 specified file name (first argument). The file name may be
1112 a list, in which case the elements are concatenated with the
1113 os.path.join() method. The file is created under the temporary
1114 working directory. Any subdirectories in the path must already
1115 exist. The I/O mode for the file may be specified; it must
1116 begin with a 'w'. The default is 'wb' (binary write).
1118 file = self.canonicalize(file)
1120 raise ValueError, "mode must begin with 'w'"
1121 open(file, mode).write(content)