mirror of
https://github.com/boostorg/build.git
synced 2026-01-26 06:22:26 +00:00
485 lines
15 KiB
Python
485 lines
15 KiB
Python
|
|
import TestCmd
|
|
from tree import build_tree, trees_difference
|
|
import copy
|
|
import fnmatch
|
|
import os
|
|
import shutil
|
|
import string
|
|
import types
|
|
import time
|
|
import tempfile
|
|
import sys
|
|
|
|
# Compute an executable suffix for tests to use
|
|
exe_suffix = ''
|
|
dll_suffix = '.so'
|
|
if os.environ.get('OS','').lower().startswith('windows'):
|
|
exe_suffix = '.exe'
|
|
dll_suffix = '.dll'
|
|
#
|
|
# FIXME: this is copy-pasted from TestSCons.py
|
|
# Should be moved to TestCmd.py?
|
|
#
|
|
if os.name == 'posix':
|
|
def _failed(self, status = 0):
|
|
if self.status is None:
|
|
return None
|
|
if os.WIFSIGNALED(status):
|
|
return None
|
|
return _status(self) != status
|
|
def _status(self):
|
|
if os.WIFEXITED(self.status):
|
|
return os.WEXITSTATUS(self.status)
|
|
else:
|
|
return None
|
|
elif os.name == 'nt':
|
|
def _failed(self, status = 0):
|
|
return not self.status is None and self.status != status
|
|
def _status(self):
|
|
return self.status
|
|
|
|
class Tester(TestCmd.TestCmd):
|
|
"""Class for testing Boost.Build.
|
|
|
|
Optional argument `executable` indicates the name of the
|
|
executable to invoke. Set this to "jam" to test Boost.Build v1
|
|
behavior.
|
|
"""
|
|
def __init__(self, arguments="", executable = 'bjam', match =
|
|
TestCmd.match_exact, boost_build_path = None,
|
|
**keywords):
|
|
|
|
self.original_workdir = os.getcwd()
|
|
self.last_build_time = 0
|
|
|
|
jam_build_dir = ""
|
|
if os.name == 'nt':
|
|
jam_build_dir = "bin.ntx86"
|
|
elif os.name == 'posix':
|
|
if os.uname()[0].lower().startswith('cygwin'):
|
|
jam_build_dir = "bin.cygwinx86"
|
|
else:
|
|
jam_build_dir = "bin.linuxx86"
|
|
else:
|
|
raise "Don't know directory where jam is build for this system"
|
|
|
|
if boost_build_path is None:
|
|
boost_build_path = os.path.join(self.original_workdir,
|
|
"..", "new")
|
|
|
|
verbosity = ' -d0 --quiet '
|
|
if '--verbose' in sys.argv:
|
|
keywords['verbose'] = 1
|
|
verbosity = ' -d+2 '
|
|
|
|
TestCmd.TestCmd.__init__(
|
|
self
|
|
, program=os.path.join(
|
|
'..', 'jam_src', jam_build_dir, executable)
|
|
+ ' -sBOOST_BUILD_PATH=' + boost_build_path
|
|
+ verbosity
|
|
+ arguments
|
|
, match=match
|
|
, workdir=''
|
|
, **keywords)
|
|
|
|
os.chdir(self.workdir)
|
|
|
|
def cleanup(self):
|
|
try:
|
|
TestCmd.TestCmd.cleanup(self)
|
|
os.chdir(self.original_workdir)
|
|
except AttributeError:
|
|
# Whe this is called during by TestCmd.TestCmd.__del__ we can have both
|
|
# 'TestCmd' and 'os' unavailable in our scope. Do nothing in this case.
|
|
pass
|
|
|
|
#
|
|
# Methods that change working directory's content
|
|
#
|
|
def set_tree(self, tree_location):
|
|
# Seem like it's not possible to remove a directory which is
|
|
# current.
|
|
d = os.getcwd()
|
|
os.chdir(os.path.dirname(self.workdir))
|
|
shutil.rmtree(self.workdir, ignore_errors=0)
|
|
|
|
if not os.path.isabs(tree_location):
|
|
tree_location = os.path.join(self.original_workdir, tree_location)
|
|
shutil.copytree(tree_location, self.workdir)
|
|
|
|
os.chdir(d)
|
|
|
|
def make_writable(unused, dir, entries):
|
|
for e in entries:
|
|
name = os.path.join(dir, e)
|
|
os.chmod(name, os.stat(name)[0] | 0222)
|
|
|
|
os.path.walk(".", make_writable, None)
|
|
|
|
|
|
def write(self, file, content):
|
|
self.wait_for_time_change()
|
|
nfile = self.native_file_name(file)
|
|
try:
|
|
os.makedirs(os.path.dirname(nfile))
|
|
except Exception, e:
|
|
pass
|
|
open(nfile, "wb").write(content)
|
|
|
|
def rename(self, old, new):
|
|
try:
|
|
os.makedirs(os.path.dirname(new))
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
os.remove(new)
|
|
except:
|
|
pass
|
|
|
|
os.rename(old, new)
|
|
self.touch(new);
|
|
|
|
def copy(self, src, dst):
|
|
self.wait_for_time_change()
|
|
self.write(dst, self.read(src))
|
|
|
|
def touch(self, names):
|
|
self.wait_for_time_change()
|
|
if not type(names) == types.ListType:
|
|
names = [names]
|
|
for name in names:
|
|
os.utime(self.native_file_name(name), None)
|
|
|
|
def rm(self, names):
|
|
self.wait_for_time_change()
|
|
if not type(names) == types.ListType:
|
|
names = [names]
|
|
|
|
# Avoid attempts to remove current dir
|
|
os.chdir(self.original_workdir)
|
|
for name in names:
|
|
n = self.native_file_name(name)
|
|
if os.path.isdir(n):
|
|
shutil.rmtree(n, ignore_errors=0)
|
|
else:
|
|
os.unlink(n)
|
|
|
|
# Create working dir root again, in case
|
|
# we've removed it
|
|
os.mkdir(self.workdir)
|
|
os.chdir(self.workdir)
|
|
|
|
#
|
|
# FIXME: Large portion copied from TestSCons.py, should be moved?
|
|
#
|
|
def run_build_system(
|
|
self, extra_args='', subdir='', stdout = None, stderr = '',
|
|
status = 0, match = None, **kw):
|
|
|
|
self.previous_tree = build_tree(self.workdir)
|
|
|
|
if match is None:
|
|
match = self.match
|
|
|
|
try:
|
|
kw['program'] = self.program + ' ' + extra_args
|
|
kw['chdir'] = subdir
|
|
apply(TestCmd.TestCmd.run, [self], kw)
|
|
except:
|
|
print "STDOUT ============"
|
|
print self.stdout()
|
|
print "STDERR ============"
|
|
print self.stderr()
|
|
raise
|
|
|
|
if status != None and _failed(self, status):
|
|
expect = ''
|
|
if status != 0:
|
|
expect = " (expected %d)" % status
|
|
|
|
print '"%s %s" returned %d%s' % (
|
|
self.program, extra_args, _status(self), expect)
|
|
|
|
print "STDOUT ============"
|
|
print self.stdout()
|
|
print "STDERR ============"
|
|
print self.stderr()
|
|
self.fail_test(1)
|
|
|
|
if not stdout is None and not match(self.stdout(), stdout):
|
|
print "Expected STDOUT =========="
|
|
print stdout
|
|
print "Actual STDOUT ============"
|
|
print self.stdout()
|
|
stderr = self.stderr()
|
|
if stderr:
|
|
print "STDERR ==================="
|
|
print stderr
|
|
self.maybe_do_diff(self.stdout(), stdout)
|
|
self.fail_test(1)
|
|
|
|
if not stderr is None and not match(self.stderr(), stderr):
|
|
print "STDOUT ==================="
|
|
print self.stdout()
|
|
print "Expected STDERR =========="
|
|
print stderr
|
|
print "Actual STDERR ============"
|
|
print self.stderr()
|
|
self.maybe_do_diff(self.stderr(), stderr)
|
|
self.fail_test(1)
|
|
|
|
self.tree = build_tree(self.workdir)
|
|
self.difference = trees_difference(self.previous_tree, self.tree)
|
|
self.difference.ignore_directories()
|
|
self.unexpected_difference = copy.deepcopy(self.difference)
|
|
|
|
self.last_build_time = time.time()
|
|
|
|
def read(self, name):
|
|
return open(self.native_file_name(name), "r").read()
|
|
|
|
def read_and_strip(self, name):
|
|
lines = open(self.native_file_name(name), "r").readlines()
|
|
result = string.join(map(string.rstrip, lines), "\n")
|
|
if lines and lines[-1][-1] == '\n':
|
|
return result + '\n'
|
|
else:
|
|
return result
|
|
|
|
def fail_test(self, *args, **kw):
|
|
if hasattr(self, 'difference'):
|
|
self.difference.pprint()
|
|
TestCmd.fail_test(self, *args, **kw)
|
|
|
|
# A number of methods below check expectations with actual difference
|
|
# between directory trees before and after build.
|
|
# All the 'expect*' methods require exact names to be passed.
|
|
# All the 'ignore*' methods allow wildcards.
|
|
|
|
# All names can be lists, which are taken to be directory components
|
|
def expect_addition(self, names):
|
|
if type(names) == types.StringType:
|
|
names = [names]
|
|
for name in names:
|
|
try:
|
|
self.unexpected_difference.added_files.remove(name)
|
|
except:
|
|
print "File %s not added as expected" % (name,)
|
|
self.fail_test(1)
|
|
|
|
def ignore_addition(self, wildcard):
|
|
ignore_elements(self.unexpected_difference.added_files, wildcard)
|
|
|
|
def expect_removal(self, names):
|
|
if type(names) == types.StringType:
|
|
names = [names]
|
|
for name in names:
|
|
try:
|
|
self.unexpected_difference.removed_files.remove(name)
|
|
except:
|
|
print "File %s not removed as expected" % (name,)
|
|
self.fail_test(1)
|
|
|
|
def ignore_removal(self, wildcard):
|
|
ignore_elements(self.unexpected_difference.removed_files, wildcard)
|
|
|
|
def expect_modification(self, names):
|
|
if type(names) == types.StringType:
|
|
names = [names]
|
|
for name in names:
|
|
try:
|
|
self.unexpected_difference.modified_files.remove(name)
|
|
except:
|
|
print "File %s not modified as expected" % (name,)
|
|
self.fail_test(1)
|
|
|
|
def ignore_modification(self, wildcard):
|
|
ignore_elements(self.unexpected_difference.modified_files, wildcard)
|
|
|
|
def expect_touch(self, names):
|
|
|
|
if type(names) == types.StringType:
|
|
names = [names]
|
|
|
|
d = self.unexpected_difference
|
|
for name in names:
|
|
|
|
# We need to check in both touched and modified files if
|
|
# it's a Windows exe because they sometimes have slight
|
|
# differences even with identical inputs
|
|
if name.endswith('.exe'):
|
|
filesets = [d.modified_files, d.touched_files]
|
|
else:
|
|
filesets = [d.touched_files]
|
|
|
|
while filesets:
|
|
try:
|
|
filesets[-1].remove(name)
|
|
break
|
|
except ValueError:
|
|
filesets.pop()
|
|
|
|
if not filesets:
|
|
print "File %s not touched as expected" % (name,)
|
|
self.fail_test(1)
|
|
|
|
|
|
def ignore_touch(self, wildcard):
|
|
ignore_elements(self.unexpected_difference.touched_files, wildcard)
|
|
|
|
def ignore(self, wildcard):
|
|
ignore_elements(self.unexpected_difference.added_files, wildcard)
|
|
ignore_elements(self.unexpected_difference.removed_files, wildcard)
|
|
ignore_elements(self.unexpected_difference.modified_files, wildcard)
|
|
ignore_elements(self.unexpected_difference.touched_files, wildcard)
|
|
|
|
def expect_nothing(self, names):
|
|
if type(names) == types.StringType:
|
|
names = [names]
|
|
for name in names:
|
|
if name in self.difference.added_files:
|
|
print "File %s is added, but no action was expected" % (name,)
|
|
self.fail_test(1)
|
|
if name in self.difference.removed_files:
|
|
print "File %s is removed, but no action was expected" % (name,)
|
|
self.fail_test(1)
|
|
pass
|
|
if name in self.difference.modified_files:
|
|
print "File %s is modified, but no action was expected" % (name,)
|
|
self.fail_test(1)
|
|
if name in self.difference.touched_files:
|
|
print "File %s is touched, but no action was expected" % (name,)
|
|
self.fail_test(1)
|
|
|
|
def expect_nothing_more(self):
|
|
if not self.unexpected_difference.empty():
|
|
print "Expected nothing more, but got the following:"
|
|
self.unexpected_difference.pprint()
|
|
self.fail_test(1)
|
|
|
|
def expect_content(self, name, content, exact=0):
|
|
if exact:
|
|
actual = self.read(name)
|
|
else:
|
|
actual = string.replace(self.read_and_strip(name), "\\", "/")
|
|
|
|
if actual != content:
|
|
print "Expected:\n"
|
|
print content
|
|
print "Got:\n"
|
|
print actual
|
|
self.fail_test(1)
|
|
|
|
def maybe_do_diff(self, actual, expected):
|
|
if os.environ.has_key("DO_DIFF") and os.environ["DO_DIFF"] != '':
|
|
|
|
e = tempfile.mktemp("expected")
|
|
a = tempfile.mktemp("actual")
|
|
open(e, "w").write(expected)
|
|
open(a, "w").write(actual)
|
|
print "DIFFERENCE"
|
|
if os.system("diff -u " + e + " " + a):
|
|
print "Unable to compute difference"
|
|
os.unlink(e)
|
|
os.unlink(a)
|
|
else:
|
|
print "Set environmental variable 'DO_DIFF' to examine difference."
|
|
|
|
# Helpers
|
|
def mul(self, *arguments):
|
|
if len(arguments) == 0:
|
|
return None
|
|
else:
|
|
here = arguments[0]
|
|
if type(here) == type(''):
|
|
here = [here]
|
|
|
|
if len(arguments) > 1:
|
|
there = apply(self.mul, arguments[1:])
|
|
result = []
|
|
for i in here:
|
|
for j in there:
|
|
result.append(i + j)
|
|
return result
|
|
else:
|
|
return here
|
|
|
|
|
|
|
|
# Internal methods
|
|
def ignore_elements(list, wildcard):
|
|
"""Removes in-place, element of 'list' that match the given wildcard."""
|
|
w = fnmatch.translate(wildcard)
|
|
list[:] = filter(lambda x, w=w: not w(x), list)
|
|
|
|
def native_file_name(self, name):
|
|
elements = string.split(name, "/")
|
|
return os.path.normpath(apply(os.path.join, [self.workdir]+elements))
|
|
|
|
# Wait while time is no longer equal to the time last "run_build_system"
|
|
# call finished.
|
|
def wait_for_time_change(self):
|
|
while int(time.time()) == int(self.last_build_time):
|
|
time.sleep(0.1)
|
|
|
|
|
|
class List:
|
|
|
|
def __init__(self, s=""):
|
|
# Have to handle espaced spaces correctly
|
|
s = string.replace(s, "\ ", '\001')
|
|
self.l = []
|
|
for e in string.split(s):
|
|
self.l.append(string.replace(e, '\001', ' '))
|
|
|
|
def __len__(self):
|
|
return len(l)
|
|
|
|
def __getitem__(self, key):
|
|
return self.l[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
self.l[key] = value
|
|
|
|
def __delitem__(self, key):
|
|
del self.l[key]
|
|
|
|
def __coerce__(self, other):
|
|
if isinstance(other, type("")):
|
|
return (self,List(other))
|
|
else:
|
|
return None
|
|
|
|
def __str__(self):
|
|
return str(self.l)
|
|
|
|
def __repr__(self):
|
|
return ( self.__module__ + '.List('
|
|
+ repr(string.join(self.l, ' '))
|
|
+ ')')
|
|
|
|
def __mul__(self, other):
|
|
result = List()
|
|
for f in self:
|
|
for s in other:
|
|
result.l.append(f + s)
|
|
return result
|
|
|
|
def __add__(self, other):
|
|
result = List()
|
|
result.l = self.l[:] + other.l[:]
|
|
return result
|
|
|
|
# quickie tests. Should use doctest instead.
|
|
if __name__ == '__main__':
|
|
assert str(List("foo bar") * "/baz") == "['foo/baz', 'bar/baz']"
|
|
assert repr("foo/" * List("bar baz")) == "__main__.List('foo/bar foo/baz')"
|
|
print 'tests passed'
|
|
|
|
|
|
|
|
|