mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1594 lines
51 KiB
1594 lines
51 KiB
"""
|
|
TestCmd.py: a testing framework for commands and scripts.
|
|
|
|
The TestCmd module provides a framework for portable automated testing
|
|
of executable commands and scripts (in any language, not just Python),
|
|
especially commands and scripts that require file system interaction.
|
|
|
|
In addition to running tests and evaluating conditions, the TestCmd
|
|
module manages and cleans up one or more temporary workspace
|
|
directories, and provides methods for creating files and directories in
|
|
those workspace directories from in-line data, here-documents), allowing
|
|
tests to be completely self-contained.
|
|
|
|
A TestCmd environment object is created via the usual invocation:
|
|
|
|
import TestCmd
|
|
test = TestCmd.TestCmd()
|
|
|
|
There are a bunch of keyword arguments available at instantiation:
|
|
|
|
test = TestCmd.TestCmd(description = 'string',
|
|
program = 'program_or_script_to_test',
|
|
interpreter = 'script_interpreter',
|
|
workdir = 'prefix',
|
|
subdir = 'subdir',
|
|
verbose = Boolean,
|
|
match = default_match_function,
|
|
diff = default_diff_function,
|
|
combine = Boolean)
|
|
|
|
There are a bunch of methods that let you do different things:
|
|
|
|
test.verbose_set(1)
|
|
|
|
test.description_set('string')
|
|
|
|
test.program_set('program_or_script_to_test')
|
|
|
|
test.interpreter_set('script_interpreter')
|
|
test.interpreter_set(['script_interpreter', 'arg'])
|
|
|
|
test.workdir_set('prefix')
|
|
test.workdir_set('')
|
|
|
|
test.workpath('file')
|
|
test.workpath('subdir', 'file')
|
|
|
|
test.subdir('subdir', ...)
|
|
|
|
test.rmdir('subdir', ...)
|
|
|
|
test.write('file', "contents\n")
|
|
test.write(['subdir', 'file'], "contents\n")
|
|
|
|
test.read('file')
|
|
test.read(['subdir', 'file'])
|
|
test.read('file', mode)
|
|
test.read(['subdir', 'file'], mode)
|
|
|
|
test.writable('dir', 1)
|
|
test.writable('dir', None)
|
|
|
|
test.preserve(condition, ...)
|
|
|
|
test.cleanup(condition)
|
|
|
|
test.command_args(program = 'program_or_script_to_run',
|
|
interpreter = 'script_interpreter',
|
|
arguments = 'arguments to pass to program')
|
|
|
|
test.run(program = 'program_or_script_to_run',
|
|
interpreter = 'script_interpreter',
|
|
arguments = 'arguments to pass to program',
|
|
chdir = 'directory_to_chdir_to',
|
|
stdin = 'input to feed to the program\n')
|
|
universal_newlines = True)
|
|
|
|
p = test.start(program = 'program_or_script_to_run',
|
|
interpreter = 'script_interpreter',
|
|
arguments = 'arguments to pass to program',
|
|
universal_newlines = None)
|
|
|
|
test.finish(self, p)
|
|
|
|
test.pass_test()
|
|
test.pass_test(condition)
|
|
test.pass_test(condition, function)
|
|
|
|
test.fail_test()
|
|
test.fail_test(condition)
|
|
test.fail_test(condition, function)
|
|
test.fail_test(condition, function, skip)
|
|
|
|
test.no_result()
|
|
test.no_result(condition)
|
|
test.no_result(condition, function)
|
|
test.no_result(condition, function, skip)
|
|
|
|
test.stdout()
|
|
test.stdout(run)
|
|
|
|
test.stderr()
|
|
test.stderr(run)
|
|
|
|
test.symlink(target, link)
|
|
|
|
test.banner(string)
|
|
test.banner(string, width)
|
|
|
|
test.diff(actual, expected)
|
|
|
|
test.match(actual, expected)
|
|
|
|
test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n")
|
|
test.match_exact(["actual 1\n", "actual 2\n"],
|
|
["expected 1\n", "expected 2\n"])
|
|
|
|
test.match_re("actual 1\nactual 2\n", regex_string)
|
|
test.match_re(["actual 1\n", "actual 2\n"], list_of_regexes)
|
|
|
|
test.match_re_dotall("actual 1\nactual 2\n", regex_string)
|
|
test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes)
|
|
|
|
test.tempdir()
|
|
test.tempdir('temporary-directory')
|
|
|
|
test.sleep()
|
|
test.sleep(seconds)
|
|
|
|
test.where_is('foo')
|
|
test.where_is('foo', 'PATH1:PATH2')
|
|
test.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4')
|
|
|
|
test.unlink('file')
|
|
test.unlink('subdir', 'file')
|
|
|
|
The TestCmd module provides pass_test(), fail_test(), and no_result()
|
|
unbound functions that report test results for use with the Aegis change
|
|
management system. These methods terminate the test immediately,
|
|
reporting PASSED, FAILED, or NO RESULT respectively, and exiting with
|
|
status 0 (success), 1 or 2 respectively. This allows for a distinction
|
|
between an actual failed test and a test that could not be properly
|
|
evaluated because of an external condition (such as a full file system
|
|
or incorrect permissions).
|
|
|
|
import TestCmd
|
|
|
|
TestCmd.pass_test()
|
|
TestCmd.pass_test(condition)
|
|
TestCmd.pass_test(condition, function)
|
|
|
|
TestCmd.fail_test()
|
|
TestCmd.fail_test(condition)
|
|
TestCmd.fail_test(condition, function)
|
|
TestCmd.fail_test(condition, function, skip)
|
|
|
|
TestCmd.no_result()
|
|
TestCmd.no_result(condition)
|
|
TestCmd.no_result(condition, function)
|
|
TestCmd.no_result(condition, function, skip)
|
|
|
|
The TestCmd module also provides unbound functions that handle matching
|
|
in the same way as the match_*() methods described above.
|
|
|
|
import TestCmd
|
|
|
|
test = TestCmd.TestCmd(match = TestCmd.match_exact)
|
|
|
|
test = TestCmd.TestCmd(match = TestCmd.match_re)
|
|
|
|
test = TestCmd.TestCmd(match = TestCmd.match_re_dotall)
|
|
|
|
The TestCmd module provides unbound functions that can be used for the
|
|
"diff" argument to TestCmd.TestCmd instantiation:
|
|
|
|
import TestCmd
|
|
|
|
test = TestCmd.TestCmd(match = TestCmd.match_re,
|
|
diff = TestCmd.diff_re)
|
|
|
|
test = TestCmd.TestCmd(diff = TestCmd.simple_diff)
|
|
|
|
The "diff" argument can also be used with standard difflib functions:
|
|
|
|
import difflib
|
|
|
|
test = TestCmd.TestCmd(diff = difflib.context_diff)
|
|
|
|
test = TestCmd.TestCmd(diff = difflib.unified_diff)
|
|
|
|
Lastly, the where_is() method also exists in an unbound function
|
|
version.
|
|
|
|
import TestCmd
|
|
|
|
TestCmd.where_is('foo')
|
|
TestCmd.where_is('foo', 'PATH1:PATH2')
|
|
TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4')
|
|
"""
|
|
|
|
# Copyright 2000-2010 Steven Knight
|
|
# This module is free software, and you may redistribute it and/or modify
|
|
# it under the same terms as Python itself, so long as this copyright message
|
|
# and disclaimer are retained in their original form.
|
|
#
|
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
|
|
# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
|
|
# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
# DAMAGE.
|
|
#
|
|
# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
|
|
# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
|
|
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
|
|
|
__author__ = "Steven Knight <knight at baldmt dot com>"
|
|
__revision__ = "TestCmd.py 0.37.D001 2010/01/11 16:55:50 knight"
|
|
__version__ = "0.37"
|
|
|
|
import errno
|
|
import os
|
|
import os.path
|
|
import re
|
|
import shutil
|
|
import stat
|
|
import string
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import traceback
|
|
import types
|
|
import UserList
|
|
|
|
__all__ = [
|
|
'diff_re',
|
|
'fail_test',
|
|
'no_result',
|
|
'pass_test',
|
|
'match_exact',
|
|
'match_re',
|
|
'match_re_dotall',
|
|
'python_executable',
|
|
'TestCmd'
|
|
]
|
|
|
|
try:
|
|
import difflib
|
|
except ImportError:
|
|
__all__.append('simple_diff')
|
|
|
|
def is_List(e):
|
|
return type(e) is types.ListType \
|
|
or isinstance(e, UserList.UserList)
|
|
|
|
try:
|
|
from UserString import UserString
|
|
except ImportError:
|
|
class UserString:
|
|
pass
|
|
|
|
if hasattr(types, 'UnicodeType'):
|
|
def is_String(e):
|
|
return type(e) is types.StringType \
|
|
or type(e) is types.UnicodeType \
|
|
or isinstance(e, UserString)
|
|
else:
|
|
def is_String(e):
|
|
return type(e) is types.StringType or isinstance(e, UserString)
|
|
|
|
tempfile.template = 'testcmd.'
|
|
if os.name in ('posix', 'nt'):
|
|
tempfile.template = 'testcmd.' + str(os.getpid()) + '.'
|
|
else:
|
|
tempfile.template = 'testcmd.'
|
|
|
|
re_space = re.compile('\s')
|
|
|
|
_Cleanup = []
|
|
|
|
_chain_to_exitfunc = None
|
|
|
|
def _clean():
|
|
global _Cleanup
|
|
cleanlist = filter(None, _Cleanup)
|
|
del _Cleanup[:]
|
|
cleanlist.reverse()
|
|
for test in cleanlist:
|
|
test.cleanup()
|
|
if _chain_to_exitfunc:
|
|
_chain_to_exitfunc()
|
|
|
|
try:
|
|
import atexit
|
|
except ImportError:
|
|
# TODO(1.5): atexit requires python 2.0, so chain sys.exitfunc
|
|
try:
|
|
_chain_to_exitfunc = sys.exitfunc
|
|
except AttributeError:
|
|
pass
|
|
sys.exitfunc = _clean
|
|
else:
|
|
atexit.register(_clean)
|
|
|
|
try:
|
|
zip
|
|
except NameError:
|
|
def zip(*lists):
|
|
result = []
|
|
for i in xrange(min(map(len, lists))):
|
|
result.append(tuple(map(lambda l, i=i: l[i], lists)))
|
|
return result
|
|
|
|
class Collector:
|
|
def __init__(self, top):
|
|
self.entries = [top]
|
|
def __call__(self, arg, dirname, names):
|
|
pathjoin = lambda n, d=dirname: os.path.join(d, n)
|
|
self.entries.extend(map(pathjoin, names))
|
|
|
|
def _caller(tblist, skip):
|
|
string = ""
|
|
arr = []
|
|
for file, line, name, text in tblist:
|
|
if file[-10:] == "TestCmd.py":
|
|
break
|
|
arr = [(file, line, name, text)] + arr
|
|
atfrom = "at"
|
|
for file, line, name, text in arr[skip:]:
|
|
if name in ("?", "<module>"):
|
|
name = ""
|
|
else:
|
|
name = " (" + name + ")"
|
|
string = string + ("%s line %d of %s%s\n" % (atfrom, line, file, name))
|
|
atfrom = "\tfrom"
|
|
return string
|
|
|
|
def fail_test(self = None, condition = 1, function = None, skip = 0):
|
|
"""Cause the test to fail.
|
|
|
|
By default, the fail_test() method reports that the test FAILED
|
|
and exits with a status of 1. If a condition argument is supplied,
|
|
the test fails only if the condition is true.
|
|
"""
|
|
if not condition:
|
|
return
|
|
if not function is None:
|
|
function()
|
|
of = ""
|
|
desc = ""
|
|
sep = " "
|
|
if not self is None:
|
|
if self.program:
|
|
of = " of " + self.program
|
|
sep = "\n\t"
|
|
if self.description:
|
|
desc = " [" + self.description + "]"
|
|
sep = "\n\t"
|
|
|
|
at = _caller(traceback.extract_stack(), skip)
|
|
sys.stderr.write("FAILED test" + of + desc + sep + at)
|
|
|
|
sys.exit(1)
|
|
|
|
def no_result(self = None, condition = 1, function = None, skip = 0):
|
|
"""Causes a test to exit with no valid result.
|
|
|
|
By default, the no_result() method reports NO RESULT for the test
|
|
and exits with a status of 2. If a condition argument is supplied,
|
|
the test fails only if the condition is true.
|
|
"""
|
|
if not condition:
|
|
return
|
|
if not function is None:
|
|
function()
|
|
of = ""
|
|
desc = ""
|
|
sep = " "
|
|
if not self is None:
|
|
if self.program:
|
|
of = " of " + self.program
|
|
sep = "\n\t"
|
|
if self.description:
|
|
desc = " [" + self.description + "]"
|
|
sep = "\n\t"
|
|
|
|
at = _caller(traceback.extract_stack(), skip)
|
|
sys.stderr.write("NO RESULT for test" + of + desc + sep + at)
|
|
|
|
sys.exit(2)
|
|
|
|
def pass_test(self = None, condition = 1, function = None):
|
|
"""Causes a test to pass.
|
|
|
|
By default, the pass_test() method reports PASSED for the test
|
|
and exits with a status of 0. If a condition argument is supplied,
|
|
the test passes only if the condition is true.
|
|
"""
|
|
if not condition:
|
|
return
|
|
if not function is None:
|
|
function()
|
|
sys.stderr.write("PASSED\n")
|
|
sys.exit(0)
|
|
|
|
def match_exact(lines = None, matches = None):
|
|
"""
|
|
"""
|
|
if not is_List(lines):
|
|
lines = string.split(lines, "\n")
|
|
if not is_List(matches):
|
|
matches = string.split(matches, "\n")
|
|
if len(lines) != len(matches):
|
|
return
|
|
for i in range(len(lines)):
|
|
if lines[i] != matches[i]:
|
|
return
|
|
return 1
|
|
|
|
def match_re(lines = None, res = None):
|
|
"""
|
|
"""
|
|
if not is_List(lines):
|
|
lines = string.split(lines, "\n")
|
|
if not is_List(res):
|
|
res = string.split(res, "\n")
|
|
if len(lines) != len(res):
|
|
return
|
|
for i in range(len(lines)):
|
|
s = "^" + res[i] + "$"
|
|
try:
|
|
expr = re.compile(s)
|
|
except re.error, e:
|
|
msg = "Regular expression error in %s: %s"
|
|
raise re.error, msg % (repr(s), e[0])
|
|
if not expr.search(lines[i]):
|
|
return
|
|
return 1
|
|
|
|
def match_re_dotall(lines = None, res = None):
|
|
"""
|
|
"""
|
|
if not type(lines) is type(""):
|
|
lines = string.join(lines, "\n")
|
|
if not type(res) is type(""):
|
|
res = string.join(res, "\n")
|
|
s = "^" + res + "$"
|
|
try:
|
|
expr = re.compile(s, re.DOTALL)
|
|
except re.error, e:
|
|
msg = "Regular expression error in %s: %s"
|
|
raise re.error, msg % (repr(s), e[0])
|
|
if expr.match(lines):
|
|
return 1
|
|
|
|
try:
|
|
import difflib
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
def simple_diff(a, b, fromfile='', tofile='',
|
|
fromfiledate='', tofiledate='', n=3, lineterm='\n'):
|
|
"""
|
|
A function with the same calling signature as difflib.context_diff
|
|
(diff -c) and difflib.unified_diff (diff -u) but which prints
|
|
output like the simple, unadorned 'diff" command.
|
|
"""
|
|
sm = difflib.SequenceMatcher(None, a, b)
|
|
def comma(x1, x2):
|
|
return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2)
|
|
result = []
|
|
for op, a1, a2, b1, b2 in sm.get_opcodes():
|
|
if op == 'delete':
|
|
result.append("%sd%d" % (comma(a1, a2), b1))
|
|
result.extend(map(lambda l: '< ' + l, a[a1:a2]))
|
|
elif op == 'insert':
|
|
result.append("%da%s" % (a1, comma(b1, b2)))
|
|
result.extend(map(lambda l: '> ' + l, b[b1:b2]))
|
|
elif op == 'replace':
|
|
result.append("%sc%s" % (comma(a1, a2), comma(b1, b2)))
|
|
result.extend(map(lambda l: '< ' + l, a[a1:a2]))
|
|
result.append('---')
|
|
result.extend(map(lambda l: '> ' + l, b[b1:b2]))
|
|
return result
|
|
|
|
def diff_re(a, b, fromfile='', tofile='',
|
|
fromfiledate='', tofiledate='', n=3, lineterm='\n'):
|
|
"""
|
|
A simple "diff" of two sets of lines when the expected lines
|
|
are regular expressions. This is a really dumb thing that
|
|
just compares each line in turn, so it doesn't look for
|
|
chunks of matching lines and the like--but at least it lets
|
|
you know exactly which line first didn't compare correctl...
|
|
"""
|
|
result = []
|
|
diff = len(a) - len(b)
|
|
if diff < 0:
|
|
a = a + ['']*(-diff)
|
|
elif diff > 0:
|
|
b = b + ['']*diff
|
|
i = 0
|
|
for aline, bline in zip(a, b):
|
|
s = "^" + aline + "$"
|
|
try:
|
|
expr = re.compile(s)
|
|
except re.error, e:
|
|
msg = "Regular expression error in %s: %s"
|
|
raise re.error, msg % (repr(s), e[0])
|
|
if not expr.search(bline):
|
|
result.append("%sc%s" % (i+1, i+1))
|
|
result.append('< ' + repr(a[i]))
|
|
result.append('---')
|
|
result.append('> ' + repr(b[i]))
|
|
i = i+1
|
|
return result
|
|
|
|
if os.name == 'java':
|
|
|
|
python_executable = os.path.join(sys.prefix, 'jython')
|
|
|
|
else:
|
|
|
|
python_executable = sys.executable
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
default_sleep_seconds = 2
|
|
|
|
def where_is(file, path=None, pathext=None):
|
|
if path is None:
|
|
path = os.environ['PATH']
|
|
if is_String(path):
|
|
path = string.split(path, os.pathsep)
|
|
if pathext is None:
|
|
pathext = os.environ['PATHEXT']
|
|
if is_String(pathext):
|
|
pathext = string.split(pathext, os.pathsep)
|
|
for ext in pathext:
|
|
if string.lower(ext) == string.lower(file[-len(ext):]):
|
|
pathext = ['']
|
|
break
|
|
for dir in path:
|
|
f = os.path.join(dir, file)
|
|
for ext in pathext:
|
|
fext = f + ext
|
|
if os.path.isfile(fext):
|
|
return fext
|
|
return None
|
|
|
|
else:
|
|
|
|
def where_is(file, path=None, pathext=None):
|
|
if path is None:
|
|
path = os.environ['PATH']
|
|
if is_String(path):
|
|
path = string.split(path, os.pathsep)
|
|
for dir in path:
|
|
f = os.path.join(dir, file)
|
|
if os.path.isfile(f):
|
|
try:
|
|
st = os.stat(f)
|
|
except OSError:
|
|
continue
|
|
if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
|
|
return f
|
|
return None
|
|
|
|
default_sleep_seconds = 1
|
|
|
|
|
|
|
|
try:
|
|
import subprocess
|
|
except ImportError:
|
|
# The subprocess module doesn't exist in this version of Python,
|
|
# so we're going to cobble up something that looks just enough
|
|
# like its API for our purposes below.
|
|
import new
|
|
|
|
subprocess = new.module('subprocess')
|
|
|
|
subprocess.PIPE = 'PIPE'
|
|
subprocess.STDOUT = 'STDOUT'
|
|
subprocess.mswindows = (sys.platform == 'win32')
|
|
|
|
try:
|
|
import popen2
|
|
popen2.Popen3
|
|
except AttributeError:
|
|
class Popen3:
|
|
universal_newlines = 1
|
|
def __init__(self, command, **kw):
|
|
if sys.platform == 'win32' and command[0] == '"':
|
|
command = '"' + command + '"'
|
|
(stdin, stdout, stderr) = os.popen3(' ' + command)
|
|
self.stdin = stdin
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
def close_output(self):
|
|
self.stdout.close()
|
|
self.resultcode = self.stderr.close()
|
|
def wait(self):
|
|
resultcode = self.resultcode
|
|
if os.WIFEXITED(resultcode):
|
|
return os.WEXITSTATUS(resultcode)
|
|
elif os.WIFSIGNALED(resultcode):
|
|
return os.WTERMSIG(resultcode)
|
|
else:
|
|
return None
|
|
|
|
else:
|
|
try:
|
|
popen2.Popen4
|
|
except AttributeError:
|
|
# A cribbed Popen4 class, with some retrofitted code from
|
|
# the Python 1.5 Popen3 class methods to do certain things
|
|
# by hand.
|
|
class Popen4(popen2.Popen3):
|
|
childerr = None
|
|
|
|
def __init__(self, cmd, bufsize=-1):
|
|
p2cread, p2cwrite = os.pipe()
|
|
c2pread, c2pwrite = os.pipe()
|
|
self.pid = os.fork()
|
|
if self.pid == 0:
|
|
# Child
|
|
os.dup2(p2cread, 0)
|
|
os.dup2(c2pwrite, 1)
|
|
os.dup2(c2pwrite, 2)
|
|
for i in range(3, popen2.MAXFD):
|
|
try:
|
|
os.close(i)
|
|
except: pass
|
|
try:
|
|
os.execvp(cmd[0], cmd)
|
|
finally:
|
|
os._exit(1)
|
|
# Shouldn't come here, I guess
|
|
os._exit(1)
|
|
os.close(p2cread)
|
|
self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
|
|
os.close(c2pwrite)
|
|
self.fromchild = os.fdopen(c2pread, 'r', bufsize)
|
|
popen2._active.append(self)
|
|
|
|
popen2.Popen4 = Popen4
|
|
|
|
class Popen3(popen2.Popen3, popen2.Popen4):
|
|
universal_newlines = 1
|
|
def __init__(self, command, **kw):
|
|
if kw.get('stderr') == 'STDOUT':
|
|
apply(popen2.Popen4.__init__, (self, command, 1))
|
|
else:
|
|
apply(popen2.Popen3.__init__, (self, command, 1))
|
|
self.stdin = self.tochild
|
|
self.stdout = self.fromchild
|
|
self.stderr = self.childerr
|
|
def wait(self, *args, **kw):
|
|
resultcode = apply(popen2.Popen3.wait, (self,)+args, kw)
|
|
if os.WIFEXITED(resultcode):
|
|
return os.WEXITSTATUS(resultcode)
|
|
elif os.WIFSIGNALED(resultcode):
|
|
return os.WTERMSIG(resultcode)
|
|
else:
|
|
return None
|
|
|
|
subprocess.Popen = Popen3
|
|
|
|
|
|
|
|
# From Josiah Carlson,
|
|
# ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms
|
|
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554
|
|
|
|
PIPE = subprocess.PIPE
|
|
|
|
if subprocess.mswindows:
|
|
from win32file import ReadFile, WriteFile
|
|
from win32pipe import PeekNamedPipe
|
|
import msvcrt
|
|
else:
|
|
import select
|
|
import fcntl
|
|
|
|
try: fcntl.F_GETFL
|
|
except AttributeError: fcntl.F_GETFL = 3
|
|
|
|
try: fcntl.F_SETFL
|
|
except AttributeError: fcntl.F_SETFL = 4
|
|
|
|
class Popen(subprocess.Popen):
|
|
def recv(self, maxsize=None):
|
|
return self._recv('stdout', maxsize)
|
|
|
|
def recv_err(self, maxsize=None):
|
|
return self._recv('stderr', maxsize)
|
|
|
|
def send_recv(self, input='', maxsize=None):
|
|
return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
|
|
|
|
def get_conn_maxsize(self, which, maxsize):
|
|
if maxsize is None:
|
|
maxsize = 1024
|
|
elif maxsize < 1:
|
|
maxsize = 1
|
|
return getattr(self, which), maxsize
|
|
|
|
def _close(self, which):
|
|
getattr(self, which).close()
|
|
setattr(self, which, None)
|
|
|
|
if subprocess.mswindows:
|
|
def send(self, input):
|
|
if not self.stdin:
|
|
return None
|
|
|
|
try:
|
|
x = msvcrt.get_osfhandle(self.stdin.fileno())
|
|
(errCode, written) = WriteFile(x, input)
|
|
except ValueError:
|
|
return self._close('stdin')
|
|
except (subprocess.pywintypes.error, Exception), why:
|
|
if why[0] in (109, errno.ESHUTDOWN):
|
|
return self._close('stdin')
|
|
raise
|
|
|
|
return written
|
|
|
|
def _recv(self, which, maxsize):
|
|
conn, maxsize = self.get_conn_maxsize(which, maxsize)
|
|
if conn is None:
|
|
return None
|
|
|
|
try:
|
|
x = msvcrt.get_osfhandle(conn.fileno())
|
|
(read, nAvail, nMessage) = PeekNamedPipe(x, 0)
|
|
if maxsize < nAvail:
|
|
nAvail = maxsize
|
|
if nAvail > 0:
|
|
(errCode, read) = ReadFile(x, nAvail, None)
|
|
except ValueError:
|
|
return self._close(which)
|
|
except (subprocess.pywintypes.error, Exception), why:
|
|
if why[0] in (109, errno.ESHUTDOWN):
|
|
return self._close(which)
|
|
raise
|
|
|
|
#if self.universal_newlines:
|
|
# read = self._translate_newlines(read)
|
|
return read
|
|
|
|
else:
|
|
def send(self, input):
|
|
if not self.stdin:
|
|
return None
|
|
|
|
if not select.select([], [self.stdin], [], 0)[1]:
|
|
return 0
|
|
|
|
try:
|
|
written = os.write(self.stdin.fileno(), input)
|
|
except OSError, why:
|
|
if why[0] == errno.EPIPE: #broken pipe
|
|
return self._close('stdin')
|
|
raise
|
|
|
|
return written
|
|
|
|
def _recv(self, which, maxsize):
|
|
conn, maxsize = self.get_conn_maxsize(which, maxsize)
|
|
if conn is None:
|
|
return None
|
|
|
|
try:
|
|
flags = fcntl.fcntl(conn, fcntl.F_GETFL)
|
|
except TypeError:
|
|
flags = None
|
|
else:
|
|
if not conn.closed:
|
|
fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
|
|
|
|
try:
|
|
if not select.select([conn], [], [], 0)[0]:
|
|
return ''
|
|
|
|
r = conn.read(maxsize)
|
|
if not r:
|
|
return self._close(which)
|
|
|
|
#if self.universal_newlines:
|
|
# r = self._translate_newlines(r)
|
|
return r
|
|
finally:
|
|
if not conn.closed and not flags is None:
|
|
fcntl.fcntl(conn, fcntl.F_SETFL, flags)
|
|
|
|
disconnect_message = "Other end disconnected!"
|
|
|
|
def recv_some(p, t=.1, e=1, tr=5, stderr=0):
|
|
if tr < 1:
|
|
tr = 1
|
|
x = time.time()+t
|
|
y = []
|
|
r = ''
|
|
pr = p.recv
|
|
if stderr:
|
|
pr = p.recv_err
|
|
while time.time() < x or r:
|
|
r = pr()
|
|
if r is None:
|
|
if e:
|
|
raise Exception(disconnect_message)
|
|
else:
|
|
break
|
|
elif r:
|
|
y.append(r)
|
|
else:
|
|
time.sleep(max((x-time.time())/tr, 0))
|
|
return ''.join(y)
|
|
|
|
# TODO(3.0: rewrite to use memoryview()
|
|
def send_all(p, data):
|
|
while len(data):
|
|
sent = p.send(data)
|
|
if sent is None:
|
|
raise Exception(disconnect_message)
|
|
data = buffer(data, sent)
|
|
|
|
|
|
|
|
try:
|
|
object
|
|
except NameError:
|
|
class object:
|
|
pass
|
|
|
|
|
|
|
|
class TestCmd(object):
|
|
"""Class TestCmd
|
|
"""
|
|
|
|
def __init__(self, description = None,
|
|
program = None,
|
|
interpreter = None,
|
|
workdir = None,
|
|
subdir = None,
|
|
verbose = None,
|
|
match = None,
|
|
diff = None,
|
|
combine = 0,
|
|
universal_newlines = 1):
|
|
self._cwd = os.getcwd()
|
|
self.description_set(description)
|
|
self.program_set(program)
|
|
self.interpreter_set(interpreter)
|
|
if verbose is None:
|
|
try:
|
|
verbose = max( 0, int(os.environ.get('TESTCMD_VERBOSE', 0)) )
|
|
except ValueError:
|
|
verbose = 0
|
|
self.verbose_set(verbose)
|
|
self.combine = combine
|
|
self.universal_newlines = universal_newlines
|
|
if match is not None:
|
|
self.match_function = match
|
|
else:
|
|
self.match_function = match_re
|
|
if diff is not None:
|
|
self.diff_function = diff
|
|
else:
|
|
try:
|
|
difflib
|
|
except NameError:
|
|
pass
|
|
else:
|
|
self.diff_function = simple_diff
|
|
#self.diff_function = difflib.context_diff
|
|
#self.diff_function = difflib.unified_diff
|
|
self._dirlist = []
|
|
self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0}
|
|
if os.environ.has_key('PRESERVE') and not os.environ['PRESERVE'] is '':
|
|
self._preserve['pass_test'] = os.environ['PRESERVE']
|
|
self._preserve['fail_test'] = os.environ['PRESERVE']
|
|
self._preserve['no_result'] = os.environ['PRESERVE']
|
|
else:
|
|
try:
|
|
self._preserve['pass_test'] = os.environ['PRESERVE_PASS']
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
self._preserve['fail_test'] = os.environ['PRESERVE_FAIL']
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
self._preserve['no_result'] = os.environ['PRESERVE_NO_RESULT']
|
|
except KeyError:
|
|
pass
|
|
self._stdout = []
|
|
self._stderr = []
|
|
self.status = None
|
|
self.condition = 'no_result'
|
|
self.workdir_set(workdir)
|
|
self.subdir(subdir)
|
|
|
|
def __del__(self):
|
|
self.cleanup()
|
|
|
|
def __repr__(self):
|
|
return "%x" % id(self)
|
|
|
|
banner_char = '='
|
|
banner_width = 80
|
|
|
|
def banner(self, s, width=None):
|
|
if width is None:
|
|
width = self.banner_width
|
|
return s + self.banner_char * (width - len(s))
|
|
|
|
if os.name == 'posix':
|
|
|
|
def escape(self, arg):
|
|
"escape shell special characters"
|
|
slash = '\\'
|
|
special = '"$'
|
|
|
|
arg = string.replace(arg, slash, slash+slash)
|
|
for c in special:
|
|
arg = string.replace(arg, c, slash+c)
|
|
|
|
if re_space.search(arg):
|
|
arg = '"' + arg + '"'
|
|
return arg
|
|
|
|
else:
|
|
|
|
# Windows does not allow special characters in file names
|
|
# anyway, so no need for an escape function, we will just quote
|
|
# the arg.
|
|
def escape(self, arg):
|
|
if re_space.search(arg):
|
|
arg = '"' + arg + '"'
|
|
return arg
|
|
|
|
def canonicalize(self, path):
|
|
if is_List(path):
|
|
path = apply(os.path.join, tuple(path))
|
|
if not os.path.isabs(path):
|
|
path = os.path.join(self.workdir, path)
|
|
return path
|
|
|
|
def chmod(self, path, mode):
|
|
"""Changes permissions on the specified file or directory
|
|
path name."""
|
|
path = self.canonicalize(path)
|
|
os.chmod(path, mode)
|
|
|
|
def cleanup(self, condition = None):
|
|
"""Removes any temporary working directories for the specified
|
|
TestCmd environment. If the environment variable PRESERVE was
|
|
set when the TestCmd environment was created, temporary working
|
|
directories are not removed. If any of the environment variables
|
|
PRESERVE_PASS, PRESERVE_FAIL, or PRESERVE_NO_RESULT were set
|
|
when the TestCmd environment was created, then temporary working
|
|
directories are not removed if the test passed, failed, or had
|
|
no result, respectively. Temporary working directories are also
|
|
preserved for conditions specified via the preserve method.
|
|
|
|
Typically, this method is not called directly, but is used when
|
|
the script exits to clean up temporary working directories as
|
|
appropriate for the exit status.
|
|
"""
|
|
if not self._dirlist:
|
|
return
|
|
os.chdir(self._cwd)
|
|
self.workdir = None
|
|
if condition is None:
|
|
condition = self.condition
|
|
if self._preserve[condition]:
|
|
for dir in self._dirlist:
|
|
print "Preserved directory", dir
|
|
else:
|
|
list = self._dirlist[:]
|
|
list.reverse()
|
|
for dir in list:
|
|
self.writable(dir, 1)
|
|
shutil.rmtree(dir, ignore_errors = 1)
|
|
self._dirlist = []
|
|
|
|
try:
|
|
global _Cleanup
|
|
_Cleanup.remove(self)
|
|
except (AttributeError, ValueError):
|
|
pass
|
|
|
|
def command_args(self, program = None,
|
|
interpreter = None,
|
|
arguments = None):
|
|
if program:
|
|
if type(program) == type('') and not os.path.isabs(program):
|
|
program = os.path.join(self._cwd, program)
|
|
else:
|
|
program = self.program
|
|
if not interpreter:
|
|
interpreter = self.interpreter
|
|
if not type(program) in [type([]), type(())]:
|
|
program = [program]
|
|
cmd = list(program)
|
|
if interpreter:
|
|
if not type(interpreter) in [type([]), type(())]:
|
|
interpreter = [interpreter]
|
|
cmd = list(interpreter) + cmd
|
|
if arguments:
|
|
if type(arguments) == type(''):
|
|
arguments = string.split(arguments)
|
|
cmd.extend(arguments)
|
|
return cmd
|
|
|
|
def description_set(self, description):
|
|
"""Set the description of the functionality being tested.
|
|
"""
|
|
self.description = description
|
|
|
|
try:
|
|
difflib
|
|
except NameError:
|
|
def diff(self, a, b, name, *args, **kw):
|
|
print self.banner('Expected %s' % name)
|
|
print a
|
|
print self.banner('Actual %s' % name)
|
|
print b
|
|
else:
|
|
def diff(self, a, b, name, *args, **kw):
|
|
print self.banner(name)
|
|
args = (a.splitlines(), b.splitlines()) + args
|
|
lines = apply(self.diff_function, args, kw)
|
|
for l in lines:
|
|
print l
|
|
|
|
def fail_test(self, condition = 1, function = None, skip = 0):
|
|
"""Cause the test to fail.
|
|
"""
|
|
if not condition:
|
|
return
|
|
self.condition = 'fail_test'
|
|
fail_test(self = self,
|
|
condition = condition,
|
|
function = function,
|
|
skip = skip)
|
|
|
|
def interpreter_set(self, interpreter):
|
|
"""Set the program to be used to interpret the program
|
|
under test as a script.
|
|
"""
|
|
self.interpreter = interpreter
|
|
|
|
def match(self, lines, matches):
|
|
"""Compare actual and expected file contents.
|
|
"""
|
|
return self.match_function(lines, matches)
|
|
|
|
def match_exact(self, lines, matches):
|
|
"""Compare actual and expected file contents.
|
|
"""
|
|
return match_exact(lines, matches)
|
|
|
|
def match_re(self, lines, res):
|
|
"""Compare actual and expected file contents.
|
|
"""
|
|
return match_re(lines, res)
|
|
|
|
def match_re_dotall(self, lines, res):
|
|
"""Compare actual and expected file contents.
|
|
"""
|
|
return match_re_dotall(lines, res)
|
|
|
|
def no_result(self, condition = 1, function = None, skip = 0):
|
|
"""Report that the test could not be run.
|
|
"""
|
|
if not condition:
|
|
return
|
|
self.condition = 'no_result'
|
|
no_result(self = self,
|
|
condition = condition,
|
|
function = function,
|
|
skip = skip)
|
|
|
|
def pass_test(self, condition = 1, function = None):
|
|
"""Cause the test to pass.
|
|
"""
|
|
if not condition:
|
|
return
|
|
self.condition = 'pass_test'
|
|
pass_test(self = self, condition = condition, function = function)
|
|
|
|
def preserve(self, *conditions):
|
|
"""Arrange for the temporary working directories for the
|
|
specified TestCmd environment to be preserved for one or more
|
|
conditions. If no conditions are specified, arranges for
|
|
the temporary working directories to be preserved for all
|
|
conditions.
|
|
"""
|
|
if conditions is ():
|
|
conditions = ('pass_test', 'fail_test', 'no_result')
|
|
for cond in conditions:
|
|
self._preserve[cond] = 1
|
|
|
|
def program_set(self, program):
|
|
"""Set the executable program or script to be tested.
|
|
"""
|
|
if program and not os.path.isabs(program):
|
|
program = os.path.join(self._cwd, program)
|
|
self.program = program
|
|
|
|
def read(self, file, mode = 'rb'):
|
|
"""Reads and returns the contents of the specified file name.
|
|
The file name may be a list, in which case the elements are
|
|
concatenated with the os.path.join() method. The file is
|
|
assumed to be under the temporary working directory unless it
|
|
is an absolute path name. The I/O mode for the file may
|
|
be specified; it must begin with an 'r'. The default is
|
|
'rb' (binary read).
|
|
"""
|
|
file = self.canonicalize(file)
|
|
if mode[0] != 'r':
|
|
raise ValueError, "mode must begin with 'r'"
|
|
with open(file, mode) as f:
|
|
result = f.read()
|
|
return result
|
|
|
|
def rmdir(self, dir):
|
|
"""Removes the specified dir name.
|
|
The dir name may be a list, in which case the elements are
|
|
concatenated with the os.path.join() method. The dir is
|
|
assumed to be under the temporary working directory unless it
|
|
is an absolute path name.
|
|
The dir must be empty.
|
|
"""
|
|
dir = self.canonicalize(dir)
|
|
os.rmdir(dir)
|
|
|
|
def start(self, program = None,
|
|
interpreter = None,
|
|
arguments = None,
|
|
universal_newlines = None,
|
|
**kw):
|
|
"""
|
|
Starts a program or script for the test environment.
|
|
|
|
The specified program will have the original directory
|
|
prepended unless it is enclosed in a [list].
|
|
"""
|
|
cmd = self.command_args(program, interpreter, arguments)
|
|
cmd_string = string.join(map(self.escape, cmd), ' ')
|
|
if self.verbose:
|
|
sys.stderr.write(cmd_string + "\n")
|
|
if universal_newlines is None:
|
|
universal_newlines = self.universal_newlines
|
|
|
|
# On Windows, if we make stdin a pipe when we plan to send
|
|
# no input, and the test program exits before
|
|
# Popen calls msvcrt.open_osfhandle, that call will fail.
|
|
# So don't use a pipe for stdin if we don't need one.
|
|
stdin = kw.get('stdin', None)
|
|
if stdin is not None:
|
|
stdin = subprocess.PIPE
|
|
|
|
combine = kw.get('combine', self.combine)
|
|
if combine:
|
|
stderr_value = subprocess.STDOUT
|
|
else:
|
|
stderr_value = subprocess.PIPE
|
|
|
|
return Popen(cmd,
|
|
stdin=stdin,
|
|
stdout=subprocess.PIPE,
|
|
stderr=stderr_value,
|
|
universal_newlines=universal_newlines)
|
|
|
|
def finish(self, popen, **kw):
|
|
"""
|
|
Finishes and waits for the process being run under control of
|
|
the specified popen argument, recording the exit status,
|
|
standard output and error output.
|
|
"""
|
|
popen.stdin.close()
|
|
self.status = popen.wait()
|
|
if not self.status:
|
|
self.status = 0
|
|
self._stdout.append(popen.stdout.read())
|
|
if popen.stderr:
|
|
stderr = popen.stderr.read()
|
|
else:
|
|
stderr = ''
|
|
self._stderr.append(stderr)
|
|
|
|
def run(self, program = None,
|
|
interpreter = None,
|
|
arguments = None,
|
|
chdir = None,
|
|
stdin = None,
|
|
universal_newlines = None):
|
|
"""Runs a test of the program or script for the test
|
|
environment. Standard output and error output are saved for
|
|
future retrieval via the stdout() and stderr() methods.
|
|
|
|
The specified program will have the original directory
|
|
prepended unless it is enclosed in a [list].
|
|
"""
|
|
if chdir:
|
|
oldcwd = os.getcwd()
|
|
if not os.path.isabs(chdir):
|
|
chdir = os.path.join(self.workpath(chdir))
|
|
if self.verbose:
|
|
sys.stderr.write("chdir(" + chdir + ")\n")
|
|
os.chdir(chdir)
|
|
p = self.start(program,
|
|
interpreter,
|
|
arguments,
|
|
universal_newlines,
|
|
stdin=stdin)
|
|
if stdin:
|
|
if is_List(stdin):
|
|
for line in stdin:
|
|
p.stdin.write(line)
|
|
else:
|
|
p.stdin.write(stdin)
|
|
p.stdin.close()
|
|
|
|
out = p.stdout.read()
|
|
if p.stderr is None:
|
|
err = ''
|
|
else:
|
|
err = p.stderr.read()
|
|
try:
|
|
close_output = p.close_output
|
|
except AttributeError:
|
|
p.stdout.close()
|
|
if not p.stderr is None:
|
|
p.stderr.close()
|
|
else:
|
|
close_output()
|
|
|
|
self._stdout.append(out)
|
|
self._stderr.append(err)
|
|
|
|
self.status = p.wait()
|
|
if not self.status:
|
|
self.status = 0
|
|
|
|
if chdir:
|
|
os.chdir(oldcwd)
|
|
if self.verbose >= 2:
|
|
write = sys.stdout.write
|
|
write('============ STATUS: %d\n' % self.status)
|
|
out = self.stdout()
|
|
if out or self.verbose >= 3:
|
|
write('============ BEGIN STDOUT (len=%d):\n' % len(out))
|
|
write(out)
|
|
write('============ END STDOUT\n')
|
|
err = self.stderr()
|
|
if err or self.verbose >= 3:
|
|
write('============ BEGIN STDERR (len=%d)\n' % len(err))
|
|
write(err)
|
|
write('============ END STDERR\n')
|
|
|
|
def sleep(self, seconds = default_sleep_seconds):
|
|
"""Sleeps at least the specified number of seconds. If no
|
|
number is specified, sleeps at least the minimum number of
|
|
seconds necessary to advance file time stamps on the current
|
|
system. Sleeping more seconds is all right.
|
|
"""
|
|
time.sleep(seconds)
|
|
|
|
def stderr(self, run = None):
|
|
"""Returns the error output from the specified run number.
|
|
If there is no specified run number, then returns the error
|
|
output of the last run. If the run number is less than zero,
|
|
then returns the error output from that many runs back from the
|
|
current run.
|
|
"""
|
|
if not run:
|
|
run = len(self._stderr)
|
|
elif run < 0:
|
|
run = len(self._stderr) + run
|
|
run = run - 1
|
|
return self._stderr[run]
|
|
|
|
def stdout(self, run = None):
|
|
"""Returns the standard output from the specified run number.
|
|
If there is no specified run number, then returns the standard
|
|
output of the last run. If the run number is less than zero,
|
|
then returns the standard output from that many runs back from
|
|
the current run.
|
|
"""
|
|
if not run:
|
|
run = len(self._stdout)
|
|
elif run < 0:
|
|
run = len(self._stdout) + run
|
|
run = run - 1
|
|
return self._stdout[run]
|
|
|
|
def subdir(self, *subdirs):
|
|
"""Create new subdirectories under the temporary working
|
|
directory, one for each argument. An argument may be a list,
|
|
in which case the list elements are concatenated using the
|
|
os.path.join() method. Subdirectories multiple levels deep
|
|
must be created using a separate argument for each level:
|
|
|
|
test.subdir('sub', ['sub', 'dir'], ['sub', 'dir', 'ectory'])
|
|
|
|
Returns the number of subdirectories actually created.
|
|
"""
|
|
count = 0
|
|
for sub in subdirs:
|
|
if sub is None:
|
|
continue
|
|
if is_List(sub):
|
|
sub = apply(os.path.join, tuple(sub))
|
|
new = os.path.join(self.workdir, sub)
|
|
try:
|
|
os.mkdir(new)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
count = count + 1
|
|
return count
|
|
|
|
def symlink(self, target, link):
|
|
"""Creates a symlink to the specified target.
|
|
The link name may be a list, in which case the elements are
|
|
concatenated with the os.path.join() method. The link is
|
|
assumed to be under the temporary working directory unless it
|
|
is an absolute path name. The target is *not* assumed to be
|
|
under the temporary working directory.
|
|
"""
|
|
link = self.canonicalize(link)
|
|
os.symlink(target, link)
|
|
|
|
def tempdir(self, path=None):
|
|
"""Creates a temporary directory.
|
|
A unique directory name is generated if no path name is specified.
|
|
The directory is created, and will be removed when the TestCmd
|
|
object is destroyed.
|
|
"""
|
|
if path is None:
|
|
try:
|
|
path = tempfile.mktemp(prefix=tempfile.template)
|
|
except TypeError:
|
|
path = tempfile.mktemp()
|
|
os.mkdir(path)
|
|
|
|
# Symlinks in the path will report things
|
|
# differently from os.getcwd(), so chdir there
|
|
# and back to fetch the canonical path.
|
|
cwd = os.getcwd()
|
|
try:
|
|
os.chdir(path)
|
|
path = os.getcwd()
|
|
finally:
|
|
os.chdir(cwd)
|
|
|
|
# Uppercase the drive letter since the case of drive
|
|
# letters is pretty much random on win32:
|
|
drive,rest = os.path.splitdrive(path)
|
|
if drive:
|
|
path = string.upper(drive) + rest
|
|
|
|
#
|
|
self._dirlist.append(path)
|
|
global _Cleanup
|
|
try:
|
|
_Cleanup.index(self)
|
|
except ValueError:
|
|
_Cleanup.append(self)
|
|
|
|
return path
|
|
|
|
def touch(self, path, mtime=None):
|
|
"""Updates the modification time on the specified file or
|
|
directory path name. The default is to update to the
|
|
current time if no explicit modification time is specified.
|
|
"""
|
|
path = self.canonicalize(path)
|
|
atime = os.path.getatime(path)
|
|
if mtime is None:
|
|
mtime = time.time()
|
|
os.utime(path, (atime, mtime))
|
|
|
|
def unlink(self, file):
|
|
"""Unlinks the specified file name.
|
|
The file name may be a list, in which case the elements are
|
|
concatenated with the os.path.join() method. The file is
|
|
assumed to be under the temporary working directory unless it
|
|
is an absolute path name.
|
|
"""
|
|
file = self.canonicalize(file)
|
|
os.unlink(file)
|
|
|
|
def verbose_set(self, verbose):
|
|
"""Set the verbose level.
|
|
"""
|
|
self.verbose = verbose
|
|
|
|
def where_is(self, file, path=None, pathext=None):
|
|
"""Find an executable file.
|
|
"""
|
|
if is_List(file):
|
|
file = apply(os.path.join, tuple(file))
|
|
if not os.path.isabs(file):
|
|
file = where_is(file, path, pathext)
|
|
return file
|
|
|
|
def workdir_set(self, path):
|
|
"""Creates a temporary working directory with the specified
|
|
path name. If the path is a null string (''), a unique
|
|
directory name is created.
|
|
"""
|
|
if (path != None):
|
|
if path == '':
|
|
path = None
|
|
path = self.tempdir(path)
|
|
self.workdir = path
|
|
|
|
def workpath(self, *args):
|
|
"""Returns the absolute path name to a subdirectory or file
|
|
within the current temporary working directory. Concatenates
|
|
the temporary working directory name with the specified
|
|
arguments using the os.path.join() method.
|
|
"""
|
|
return apply(os.path.join, (self.workdir,) + tuple(args))
|
|
|
|
def readable(self, top, read=1):
|
|
"""Make the specified directory tree readable (read == 1)
|
|
or not (read == None).
|
|
|
|
This method has no effect on Windows systems, which use a
|
|
completely different mechanism to control file readability.
|
|
"""
|
|
|
|
if sys.platform == 'win32':
|
|
return
|
|
|
|
if read:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IREAD))
|
|
else:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IREAD))
|
|
|
|
if os.path.isfile(top):
|
|
# If it's a file, that's easy, just chmod it.
|
|
do_chmod(top)
|
|
elif read:
|
|
# It's a directory and we're trying to turn on read
|
|
# permission, so it's also pretty easy, just chmod the
|
|
# directory and then chmod every entry on our walk down the
|
|
# tree. Because os.path.walk() is top-down, we'll enable
|
|
# read permission on any directories that have it disabled
|
|
# before os.path.walk() tries to list their contents.
|
|
do_chmod(top)
|
|
|
|
def chmod_entries(arg, dirname, names, do_chmod=do_chmod):
|
|
for n in names:
|
|
do_chmod(os.path.join(dirname, n))
|
|
|
|
os.path.walk(top, chmod_entries, None)
|
|
else:
|
|
# It's a directory and we're trying to turn off read
|
|
# permission, which means we have to chmod the directoreis
|
|
# in the tree bottom-up, lest disabling read permission from
|
|
# the top down get in the way of being able to get at lower
|
|
# parts of the tree. But os.path.walk() visits things top
|
|
# down, so we just use an object to collect a list of all
|
|
# of the entries in the tree, reverse the list, and then
|
|
# chmod the reversed (bottom-up) list.
|
|
col = Collector(top)
|
|
os.path.walk(top, col, None)
|
|
col.entries.reverse()
|
|
for d in col.entries: do_chmod(d)
|
|
|
|
def writable(self, top, write=1):
|
|
"""Make the specified directory tree writable (write == 1)
|
|
or not (write == None).
|
|
"""
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
if write:
|
|
def do_chmod(fname):
|
|
try: os.chmod(fname, stat.S_IWRITE)
|
|
except OSError: pass
|
|
else:
|
|
def do_chmod(fname):
|
|
try: os.chmod(fname, stat.S_IREAD)
|
|
except OSError: pass
|
|
|
|
else:
|
|
|
|
if write:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
|
|
else:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
|
|
|
|
if os.path.isfile(top):
|
|
do_chmod(top)
|
|
else:
|
|
col = Collector(top)
|
|
os.path.walk(top, col, None)
|
|
for d in col.entries: do_chmod(d)
|
|
|
|
def executable(self, top, execute=1):
|
|
"""Make the specified directory tree executable (execute == 1)
|
|
or not (execute == None).
|
|
|
|
This method has no effect on Windows systems, which use a
|
|
completely different mechanism to control file executability.
|
|
"""
|
|
|
|
if sys.platform == 'win32':
|
|
return
|
|
|
|
if execute:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IEXEC))
|
|
else:
|
|
def do_chmod(fname):
|
|
try: st = os.stat(fname)
|
|
except OSError: pass
|
|
else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IEXEC))
|
|
|
|
if os.path.isfile(top):
|
|
# If it's a file, that's easy, just chmod it.
|
|
do_chmod(top)
|
|
elif execute:
|
|
# It's a directory and we're trying to turn on execute
|
|
# permission, so it's also pretty easy, just chmod the
|
|
# directory and then chmod every entry on our walk down the
|
|
# tree. Because os.path.walk() is top-down, we'll enable
|
|
# execute permission on any directories that have it disabled
|
|
# before os.path.walk() tries to list their contents.
|
|
do_chmod(top)
|
|
|
|
def chmod_entries(arg, dirname, names, do_chmod=do_chmod):
|
|
for n in names:
|
|
do_chmod(os.path.join(dirname, n))
|
|
|
|
os.path.walk(top, chmod_entries, None)
|
|
else:
|
|
# It's a directory and we're trying to turn off execute
|
|
# permission, which means we have to chmod the directories
|
|
# in the tree bottom-up, lest disabling execute permission from
|
|
# the top down get in the way of being able to get at lower
|
|
# parts of the tree. But os.path.walk() visits things top
|
|
# down, so we just use an object to collect a list of all
|
|
# of the entries in the tree, reverse the list, and then
|
|
# chmod the reversed (bottom-up) list.
|
|
col = Collector(top)
|
|
os.path.walk(top, col, None)
|
|
col.entries.reverse()
|
|
for d in col.entries: do_chmod(d)
|
|
|
|
def write(self, file, content, mode = 'wb'):
|
|
"""Writes the specified content text (second argument) to the
|
|
specified file name (first argument). The file name may be
|
|
a list, in which case the elements are concatenated with the
|
|
os.path.join() method. The file is created under the temporary
|
|
working directory. Any subdirectories in the path must already
|
|
exist. The I/O mode for the file may be specified; it must
|
|
begin with a 'w'. The default is 'wb' (binary write).
|
|
"""
|
|
file = self.canonicalize(file)
|
|
if mode[0] != 'w':
|
|
raise ValueError, "mode must begin with 'w'"
|
|
with open(file, mode) as f:
|
|
f.write(content)
|
|
|
|
# Local Variables:
|
|
# tab-width:4
|
|
# indent-tabs-mode:nil
|
|
# End:
|
|
# vim: set expandtab tabstop=4 shiftwidth=4:
|
|
|