diff options
Diffstat (limited to 'QMTest')
-rw-r--r-- | QMTest/SConscript | 2 | ||||
-rw-r--r-- | QMTest/TestCmd.py | 43 | ||||
-rw-r--r-- | QMTest/TestCommon.py | 4 | ||||
-rw-r--r-- | QMTest/TestSCons.py | 24 | ||||
-rw-r--r-- | QMTest/TestSCons_time.py | 365 | ||||
-rw-r--r-- | QMTest/TestSConsign.py | 74 |
6 files changed, 504 insertions, 8 deletions
diff --git a/QMTest/SConscript b/QMTest/SConscript index e016dc4..309f0dc 100644 --- a/QMTest/SConscript +++ b/QMTest/SConscript @@ -37,6 +37,8 @@ files = [ 'TestCommon.py', 'TestRuntest.py', 'TestSCons.py', + 'TestSConsign.py', + 'TestSCons_time.py', 'unittest.py', ] diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 9b3e7a2..7a668e8 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -176,8 +176,8 @@ version. # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight <knight at baldmt dot com>" -__revision__ = "TestCmd.py 0.22.D001 2006/02/26 15:45:18 knight" -__version__ = "0.22" +__revision__ = "TestCmd.py 0.23.D001 2006/11/30 13:57:29 knight" +__version__ = "0.23" import os import os.path @@ -193,9 +193,17 @@ import traceback import types import UserList -__all__ = [ 'fail_test', 'no_result', 'pass_test', - 'match_exact', 'match_re', 'match_re_dotall', - 'python_executable', 'TestCmd' ] +__all__ = [ + 'diff_re', + 'fail_test', + 'no_result', + 'pass_test', + 'match_exact', + 'match_re', + 'match_re_dotall', + 'python_executable', + 'TestCmd' +] def is_List(e): return type(e) is types.ListType \ @@ -362,6 +370,31 @@ def match_re_dotall(lines = None, res = None): if re.compile("^" + res + "$", re.DOTALL).match(lines): return 1 +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): + if not re.compile("^" + aline + "$").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') diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py index b30b75c..b215a26 100644 --- a/QMTest/TestCommon.py +++ b/QMTest/TestCommon.py @@ -80,8 +80,8 @@ The TestCommon module also provides the following variables # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. __author__ = "Steven Knight <knight at baldmt dot com>" -__revision__ = "TestCommon.py 0.22.D001 2006/02/26 15:45:18 knight" -__version__ = "0.22" +__revision__ = "TestCommon.py 0.23.D001 2006/11/30 13:57:29 knight" +__version__ = "0.23" import os import os.path diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index de15526..091c29f 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -30,7 +30,7 @@ from TestCommon import __all__ # to what we expect. (If we derived the version number from the same # data driving the build we might miss errors if the logic breaks.) -SConsVersion = '0.96.92' +SConsVersion = '0.96.93' __all__.extend([ 'TestSCons', 'python', @@ -303,6 +303,28 @@ class TestSCons(TestCommon): i = i + 1 return "Actual matched the expected output???" + def python_file_line(self, file, line): + """ + Returns a Python error line for output comparisons. + + The exec of the traceback line gives us the correct format for + this version of Python. Before 2.5, this yielded: + + File "<string>", line 1, ? + + Python 2.5 changed this to: + + File "<string>", line 1, <module> + + We stick the requested file name and line number in the right + places, abstracting out the version difference. + """ + exec 'import traceback; x = traceback.format_stack()[-1]' + x = string.lstrip(x) + x = string.replace(x, '<string>', file) + x = string.replace(x, 'line 1,', 'line %s,' % line) + return x + def java_ENV(self): """ Return a default external environment that uses a local Java SDK diff --git a/QMTest/TestSCons_time.py b/QMTest/TestSCons_time.py new file mode 100644 index 0000000..90eb5a8 --- /dev/null +++ b/QMTest/TestSCons_time.py @@ -0,0 +1,365 @@ +""" +TestSCons_time.py: a testing framework for the scons-test.py script + +A TestSCons_time environment object is created via the usual invocation: + + test = TestSCons_time() + +TestSCons_time is a subclass of TestCommon, which is in turn is a subclass +of TestCmd), and hence has available all of the methods and attributes +from those classes, as well as any overridden or additional methods or +attributes defined in this subclass. +""" + +# Copyright (c) 2001, 2002, 2003, 2004 The SCons Foundation + +__revision__ = "QMTest/TestSCons_time.py 0.96.C629 2006/11/19 06:39:17 knight" + +import os +import os.path +import string +import sys + +from TestCommon import * +from TestCommon import __all__ + +__all__.extend([ 'TestSCons', + 'python', + '_exe', + '_obj', + '_shobj', + 'lib_', + '_lib', + 'dll_', + '_dll' + ]) + +python = python_executable +_python_ = '"' + python_executable + '"' + +SConstruct = """\ +import os +print "SConstruct file directory:", os.getcwd() +""" + +scons_py = """\ +#!/usr/bin/env python +import os +import sys +def write_args(fp, args): + fp.write(args[0] + '\\n') + for arg in args[1:]: + fp.write(' ' + arg + '\\n') +write_args(sys.stdout, sys.argv) +for arg in sys.argv[1:]: + if arg[:10] == '--profile=': + profile = open(arg[10:], 'wb') + profile.write('--profile\\n') + write_args(profile, sys.argv) + break +sys.stdout.write('SCONS_LIB_DIR = ' + os.environ['SCONS_LIB_DIR'] + '\\n') +execfile('SConstruct') +""" + +aegis_py = """\ +#!/usr/bin/env python +import os +import sys +script_dir = 'src/script' +if not os.path.exists(script_dir): + os.makedirs(script_dir) +open(script_dir + '/scons.py', 'w').write( +r'''%s''') +""" % scons_py + + +svn_py = """\ +#!/usr/bin/env python +import os +import sys +dir = sys.argv[-1] +script_dir = dir + '/src/script' +os.makedirs(script_dir) +open(script_dir + '/scons.py', 'w').write( +r'''%s''') +""" % scons_py + + +logfile_contents = """\ +Memory before reading SConscript files: 100%(index)s +Memory after reading SConscript files: 200%(index)s +Memory before building targets: 300%(index)s +Memory after building targets: 400%(index)s +Object counts: + pre- post- pre- post- + read read build build Class + 101%(index)s 102%(index)s 103%(index)s 104%(index)s Action.CommandAction + 201%(index)s 202%(index)s 203%(index)s 204%(index)s Action.CommandGeneratorAction + 301%(index)s 302%(index)s 303%(index)s 304%(index)s Action.FunctionAction + 401%(index)s 402%(index)s 403%(index)s 404%(index)s Action.LazyAction + 501%(index)s 502%(index)s 503%(index)s 504%(index)s Action.ListAction + 601%(index)s 602%(index)s 603%(index)s 604%(index)s Builder.BuilderBase + 701%(index)s 702%(index)s 703%(index)s 704%(index)s Builder.CompositeBuilder + 801%(index)s 802%(index)s 803%(index)s 804%(index)s Builder.ListBuilder + 901%(index)s 902%(index)s 903%(index)s 904%(index)s Builder.MultiStepBuilder + 1001%(index)s 1002%(index)s 1003%(index)s 1004%(index)s Builder.OverrideWarner + 1101%(index)s 1102%(index)s 1103%(index)s 1104%(index)s Environment.Base + 1201%(index)s 1202%(index)s 1203%(index)s 1204%(index)s Environment.EnvironmentClone + 1301%(index)s 1302%(index)s 1303%(index)s 1304%(index)s Environment.OverrideEnvironment + 1401%(index)s 1402%(index)s 1403%(index)s 1404%(index)s Executor.Executor + 1501%(index)s 1502%(index)s 1503%(index)s 1504%(index)s Node.FS + 1601%(index)s 1602%(index)s 1603%(index)s 1604%(index)s Node.FS.Base + 1701%(index)s 1702%(index)s 1703%(index)s 1704%(index)s Node.FS.Dir + 1801%(index)s 1802%(index)s 1803%(index)s 1804%(index)s Node.FS.File + 1901%(index)s 1902%(index)s 1904%(index)s 1904%(index)s Node.FS.RootDir + 2001%(index)s 2002%(index)s 2003%(index)s 2004%(index)s Node.Node +Total build time: 11.123456 seconds +Total SConscript file execution time: 22.234567 seconds +Total SCons execution time: 33.345678 seconds +Total command execution time: 44.456789 seconds +""" + + +profile_py = """\ +%(body)s + +import profile + +try: dispatch = profile.Profile.dispatch +except AttributeError: pass +else: dispatch['c_exception'] = profile.Profile.trace_dispatch_return + +prof = profile.Profile() +prof.runcall(%(call)s) +prof.dump_stats(r'%(profile_name)s') +""" + + +class TestSCons_time(TestCommon): + """Class for testing the scons-time script. + + This provides a common place for initializing scons-time tests, + eliminating the need to begin every test with the same repeated + initializations. + """ + + def __init__(self, **kw): + """Initialize an SCons_time testing object. + + If they're not overridden by keyword arguments, this + initializes the object with the following default values: + + program = 'scons-time' + interpreter = ['python', '-tt'] + match = match_exact + workdir = '' + + The workdir value means that, by default, a temporary workspace + directory is created for a TestSCons_time environment. + In addition, this method changes directory (chdir) to the + workspace directory, so an explicit "chdir = '.'" on all of the + run() method calls is not necessary. + """ + + self.orig_cwd = os.getcwd() + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) + if not kw.has_key('program'): + p = os.environ.get('SCONS_TIME') + if not p: + p = 'scons-time' + if not os.path.exists(p): + p = 'scons-time.py' + kw['program'] = p + + if not kw.has_key('interpreter'): + kw['interpreter'] = [python, '-tt'] + + if not kw.has_key('match'): + kw['match'] = match_exact + + if not kw.has_key('workdir'): + kw['workdir'] = '' + + apply(TestCommon.__init__, [self], kw) + + try: + eval('[x for x in [1, 2]]') + except SyntaxError: + version = string.split(sys.version)[0] + msg = 'scons-time does not work on Python version %s\n' % version + self.skip_test(msg) + + def archive_split(self, path): + if path[-7:] == '.tar.gz': + return path[:-7], path[-7:] + else: + return os.path.splitext(path) + + def must_contain_all_lines(self, name, content, expected, exists=None): + missing_lines = [] + + if exists is None: + exists = lambda e, c: string.find(c, e) != -1 + + for e in expected: + if not exists(e, content): + missing_lines.append(e) + + if missing_lines: + sys.stdout.write('%s is missing expected string(s):\n' % name) + for m in missing_lines: + sys.stdout.write(' ' + repr(m) + '\n') + sys.stdout.write('%s content:\n' % name) + sys.stdout.write(content) + self.fail_test() + + def fake_logfile(self, logfile_name, index=0): + self.write(self.workpath(logfile_name), logfile_contents % locals()) + + def profile_data(self, profile_name, python_name, call, body): + profile_name = self.workpath(profile_name) + python_name = self.workpath(python_name) + d = { + 'profile_name' : profile_name, + 'python_name' : python_name, + 'call' : call, + 'body' : body, + } + self.write(python_name, profile_py % d) + self.run(program = python_name, interpreter = sys.executable) + + def skip_test(self, message="Skipping test.\n"): + """Skips a test. + + Proper test-skipping behavior is dependent on whether we're being + executed as part of development of a change under Aegis. + + Technically, skipping a test is a NO RESULT, but Aegis will + treat that as a test failure and prevent the change from going + to the next step. We don't want to force anyone using Aegis + to have to install absolutely every tool used by the tests, + so we actually report to Aegis that a skipped test has PASSED + so that the workflow isn't held up. + """ + if message: + sys.stdout.write(message) + sys.stdout.flush() + devdir = os.popen("aesub '$dd' 2>/dev/null", "r").read()[:-1] + intdir = os.popen("aesub '$intd' 2>/dev/null", "r").read()[:-1] + if devdir and self._cwd[:len(devdir)] == devdir or \ + intdir and self._cwd[:len(intdir)] == intdir: + # We're under the development directory for this change, + # so this is an Aegis invocation; pass the test (exit 0). + self.pass_test() + else: + # skip=1 means skip this function when showing where this + # result came from. They only care about the line where the + # script called test.skip_test(), not the line number where + # we call test.no_result(). + self.no_result(skip=1) + + def write_fake_aegis_py(self, name): + name = self.workpath(name) + self.write(name, aegis_py) + os.chmod(name, 0755) + return name + + def write_fake_scons_py(self): + self.subdir('src', ['src', 'script']) + self.write('src/script/scons.py', scons_py) + + def write_fake_svn_py(self, name): + name = self.workpath(name) + self.write(name, svn_py) + os.chmod(name, 0755) + return name + + def write_sample_directory(self, archive, dir, files): + dir = self.workpath(dir) + for name, content in files: + path = os.path.join(dir, name) + d, f = os.path.split(path) + if not os.path.isdir(d): + os.makedirs(d) + open(path, 'wb').write(content) + return dir + + def write_sample_tarfile(self, archive, dir, files): + import shutil + try: + import tarfile + + except ImportError: + + self.skip_test('no tarfile module\n') + + else: + + base, suffix = self.archive_split(archive) + + mode = { + '.tar' : 'w', + '.tar.gz' : 'w:gz', + '.tgz' : 'w:gz', + } + + tar = tarfile.open(archive, mode[suffix]) + for name, content in files: + path = os.path.join(dir, name) + open(path, 'wb').write(content) + tarinfo = tar.gettarinfo(path, path) + tarinfo.uid = 111 + tarinfo.gid = 111 + tarinfo.uname = 'fake_user' + tarinfo.gname = 'fake_group' + tar.addfile(tarinfo, open(path, 'rb')) + tar.close() + shutil.rmtree(dir) + return self.workpath(archive) + + def write_sample_zipfile(self, archive, dir, files): + import shutil + try: + import zipfile + except ImportError: + + sys.stderr.write('no zipfile module\n') + self.no_result() + + else: + + zip = zipfile.ZipFile(archive, 'w') + for name, content in files: + path = os.path.join(dir, name) + open(path, 'wb').write(content) + zip.write(path) + zip.close() + shutil.rmtree(dir) + return self.workpath(archive) + + sample_project_files = [ + ('SConstruct', SConstruct), + ] + + def write_sample_project(self, archive, dir=None): + base, suffix = self.archive_split(archive) + + write_sample = { + '.tar' : self.write_sample_tarfile, + '.tar.gz' : self.write_sample_tarfile, + '.tgz' : self.write_sample_tarfile, + '.zip' : self.write_sample_zipfile, + }.get(suffix, self.write_sample_directory) + + if not dir: + dir = base + + os.mkdir(dir) + path = write_sample(archive, dir, self.sample_project_files) + + return path diff --git a/QMTest/TestSConsign.py b/QMTest/TestSConsign.py new file mode 100644 index 0000000..d144040 --- /dev/null +++ b/QMTest/TestSConsign.py @@ -0,0 +1,74 @@ +# __COPYRIGHT__ + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +__doc__ = """ +TestSConsign.py: a testing framework for the "sconsign" script +tool. + +A TestSConsign environment object is created via the usual invocation: + + test = TestSConsign() + +TestSconsign is a subclass of TestSCons, which is a subclass of +TestCommon, which is in turn is a subclass of TestCmd), and hence +has available all of the methods and attributes from those classes, +as well as any overridden or additional methods or attributes defined +in this subclass. +""" + +import os +import os.path +import string +import sys + +from TestSCons import * +from TestSCons import __all__ + +__all__.extend([ 'TestSConsign', ]) + +class TestSConsign(TestSCons): + """Class for testing the sconsign.py script. + + This provides a common place for initializing sconsign tests, + eliminating the need to begin every test with the same repeated + initializations. + + This adds additional methods for running the sconsign script + without changing the basic ability of the run() method to run + "scons" itself, since we need to run scons to generate the + .sconsign files that we want the sconsign script to read. + """ + def __init__(self, *args, **kw): + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) + self.script_dir = os.getcwd() + + apply(TestSCons.__init__, (self,)+args, kw) + + self.my_kw = { + 'interpreter' : python, # imported from TestSCons + } + + if os.path.exists(self.script_path('sconsign.py')): + sconsign = 'sconsign.py' + elif os.path.exists(self.script_path('sconsign')): + sconsign = 'sconsign' + else: + print "Can find neither 'sconsign.py' nor 'sconsign' scripts." + self.no_result() + self.set_sconsign(sconsign) + + def script_path(self, script): + return os.path.join(self.script_dir, script) + + def set_sconsign(self, sconsign): + self.my_kw['program'] = sconsign + + def run_sconsign(self, *args, **kw): + kw.update(self.my_kw) + return apply(self.run, args, kw) |