From 4d8c8379e1d4c7860a1c1d29b7302b394c920844 Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Fri, 3 Aug 2012 23:01:53 +0200 Subject: - basic merge with source from the external scons-test-framework --- QMTest/TestCmd.py | 124 +++++++++++++++++++++++++++++++++++--- QMTest/TestSCons.py | 170 +++++++++++++++++++++++++++++++++------------------- runtest.py | 142 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 353 insertions(+), 83 deletions(-) diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index a27df27..8d382ce 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -897,6 +897,11 @@ class TestCmd(object): combine = 0, universal_newlines = 1, timeout = None): + self.external = 0 + try: + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) + except KeyError: + pass self._cwd = os.getcwd() self.description_set(description) self.program_set(program) @@ -1009,13 +1014,19 @@ class TestCmd(object): def command_args(self, program = None, interpreter = None, arguments = None): - if program: - if isinstance(program, str) and not os.path.isabs(program): - program = os.path.join(self._cwd, program) + if not self.external: + if program: + if isinstance(program, str) and not os.path.isabs(program): + program = os.path.join(self._cwd, program) + else: + program = self.program + if not interpreter: + interpreter = self.interpreter else: - program = self.program - if not interpreter: - interpreter = self.interpreter + if not program: + program = self.program + if not interpreter: + interpreter = self.interpreter if not isinstance(program, (list, tuple)): program = [program] cmd = list(program) @@ -1189,8 +1200,9 @@ class TestCmd(object): 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) + if not self.external: + if program and not os.path.isabs(program): + program = os.path.join(self._cwd, program) self.program = program def read(self, file, mode = 'rb'): @@ -1227,6 +1239,96 @@ class TestCmd(object): self.timeout = timeout self.timer = None + def parse_path(self, path, suppress_current=False): + """Return a list with the single path components of path. + """ + head, tail = os.path.split(path) + result = [] + if not tail: + if head == path: + return [head] + else: + result.append(tail) + head, tail = os.path.split(head) + while head and tail: + result.append(tail) + head, tail = os.path.split(head) + result.append(head or tail) + result.reverse() + + return result + + def dir_fixture(self, srcdir, dstdir=None): + """Copies the contents of the specified folder srcdir from + the directory of the called script, to the current + working directory. + The srcdir name may be a list, in which case the elements are + concatenated with the os.path.join() method. The dstdir is + assumed to be under the temporary working directory, it gets + created automatically, if it does not already exist. + """ + if srcdir and self.script_srcdir and not os.path.isabs(srcdir): + spath = os.path.join(self.script_srcdir, srcdir) + else: + spath = srcdir + if dstdir: + dstdir = self.canonicalize(dstdir) + else: + dstdir = '.' + + if dstdir != '.' and not os.path.exists(dstdir): + dstlist = self.parse_path(dstdir) + if len(dstlist) > 0 and dstlist[0] == ".": + dstlist = dstlist[1:] + for idx in range(len(dstlist)): + self.subdir(dstlist[:idx+1]) + + if dstdir and self.workdir: + dstdir = os.path.join(self.workdir, dstdir) + + for entry in os.listdir(spath): + epath = os.path.join(spath, entry) + dpath = os.path.join(dstdir, entry) + if os.path.isdir(epath): + # Copy the subfolder + shutil.copytree(epath, dpath) + else: + shutil.copy(epath, dpath) + + def file_fixture(self, srcfile, dstfile=None): + """Copies the file srcfile from the directory of + the called script, to the current working directory. + The dstfile is assumed to be under the temporary working + directory unless it is an absolute path name. + If dstfile is specified its target directory gets created + automatically, if it does not already exist. + """ + srcpath, srctail = os.path.split(srcfile) + if srcpath: + if self.script_srcdir and not os.path.isabs(srcpath): + spath = os.path.join(self.script_srcdir, srcfile) + else: + spath = srcfile + else: + spath = os.path.join(self.script_srcdir, srcfile) + if not dstfile: + if srctail: + dpath = os.path.join(self.workdir, srctail) + else: + return + else: + dstpath, dsttail = os.path.split(dstfile) + if dstpath: + if not os.path.exists(os.path.join(self.workdir, dstpath)): + dstlist = self.parse_path(dstpath) + if len(dstlist) > 0 and dstlist[0] == ".": + dstlist = dstlist[1:] + for idx in range(len(dstlist)): + self.subdir(dstlist[:idx+1]) + + dpath = os.path.join(self.workdir, dstfile) + shutil.copy(spath, dpath) + def start(self, program = None, interpreter = None, arguments = None, @@ -1304,6 +1406,12 @@ class TestCmd(object): The specified program will have the original directory prepended unless it is enclosed in a [list]. """ + if self.external: + if not program: + program = self.program + if not interpreter: + interpreter = self.interpreter + if chdir: oldcwd = os.getcwd() if not os.path.isabs(chdir): diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 4f04a48..cd3836d 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -231,20 +231,36 @@ class TestSCons(TestCommon): is not necessary. """ self.orig_cwd = os.getcwd() + self.external = 0 try: - script_dir = os.environ['SCONS_SCRIPT_DIR'] + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) except KeyError: pass + + if not self.external: + try: + script_dir = os.environ['SCONS_SCRIPT_DIR'] + except KeyError: + pass + else: + os.chdir(script_dir) else: - os.chdir(script_dir) + try: + self.script_srcdir = os.environ['PYTHON_SCRIPT_DIR'] + except KeyError: + pass if 'program' not in kw: kw['program'] = os.environ.get('SCONS') if not kw['program']: - if os.path.exists('scons'): - kw['program'] = 'scons' + if not self.external: + if os.path.exists('scons'): + kw['program'] = 'scons' + else: + kw['program'] = 'scons.py' else: - kw['program'] = 'scons.py' - elif not os.path.isabs(kw['program']): + kw['program'] = 'scons' + kw['interpreter'] = '' + elif not self.external and not os.path.isabs(kw['program']): kw['program'] = os.path.join(self.orig_cwd, kw['program']) if 'interpreter' not in kw and not os.environ.get('SCONS_EXEC'): kw['interpreter'] = [python, '-tt'] @@ -264,23 +280,27 @@ class TestSCons(TestCommon): TestCommon.__init__(self, **kw) - import SCons.Node.FS - if SCons.Node.FS.default_fs is None: - SCons.Node.FS.default_fs = SCons.Node.FS.FS() + if not self.external: + import SCons.Node.FS + if SCons.Node.FS.default_fs is None: + SCons.Node.FS.default_fs = SCons.Node.FS.FS() def Environment(self, ENV=None, *args, **kw): """ Return a construction Environment that optionally overrides the default external environment with the specified ENV. """ - import SCons.Environment - import SCons.Errors - if not ENV is None: - kw['ENV'] = ENV - try: - return SCons.Environment.Environment(*args, **kw) - except (SCons.Errors.UserError, SCons.Errors.InternalError): - return None + if not self.external: + import SCons.Environment + import SCons.Errors + if not ENV is None: + kw['ENV'] = ENV + try: + return SCons.Environment.Environment(*args, **kw) + except (SCons.Errors.UserError, SCons.Errors.InternalError): + return None + + return None def detect(self, var, prog=None, ENV=None, norm=None): """ @@ -292,17 +312,20 @@ class TestSCons(TestCommon): used as prog. """ env = self.Environment(ENV) - v = env.subst('$'+var) - if not v: - return None - if prog is None: - prog = v - if v != prog: - return None - result = env.WhereIs(prog) - if norm and os.sep != '/': - result = result.replace(os.sep, '/') - return result + if env: + v = env.subst('$'+var) + if not v: + return None + if prog is None: + prog = v + if v != prog: + return None + result = env.WhereIs(prog) + if norm and os.sep != '/': + result = result.replace(os.sep, '/') + return result + + return self.where_is(prog) def detect_tool(self, tool, prog=None, ENV=None): """ @@ -324,13 +347,35 @@ class TestSCons(TestCommon): def where_is(self, prog, path=None): """ Given a program, search for it in the specified external PATH, - or in the actual external PATH is none is specified. + or in the actual external PATH if none is specified. """ - import SCons.Environment - env = SCons.Environment.Environment() if path is None: path = os.environ['PATH'] - return env.WhereIs(prog, path) + if isinstance(prog, str): + prog = [prog] + if self.external: + import stat + paths = path.split(os.pathsep) + for p in prog: + for d in paths: + f = os.path.join(d, p) + if os.path.isfile(f): + try: + st = os.stat(f) + except OSError: + # os.stat() raises OSError, not IOError if the file + # doesn't exist, so in this case we let IOError get + # raised so as to not mask possibly serious disk or + # network issues. + continue + if stat.S_IMODE(st[stat.ST_MODE]) & 0111: + return os.path.normpath(f) + else: + import SCons.Environment + env = SCons.Environment.Environment() + return env.WhereIs(prog, path) + + return None def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0): """Wraps standard output string(s) in the normal @@ -616,36 +661,39 @@ class TestSCons(TestCommon): Initialize with a default external environment that uses a local Java SDK in preference to whatever's found in the default PATH. """ - try: - return self._java_env[version]['ENV'] - except AttributeError: - self._java_env = {} - except KeyError: - pass - - import SCons.Environment - env = SCons.Environment.Environment() - self._java_env[version] = env - - - if version: - patterns = [ - '/usr/java/jdk%s*/bin' % version, - '/usr/lib/jvm/*-%s*/bin' % version, - '/usr/local/j2sdk%s*/bin' % version, - ] - java_path = self.paths(patterns) + [env['ENV']['PATH']] - else: - patterns = [ - '/usr/java/latest/bin', - '/usr/lib/jvm/*/bin', - '/usr/local/j2sdk*/bin', - ] - java_path = self.paths(patterns) + [env['ENV']['PATH']] - - env['ENV']['PATH'] = os.pathsep.join(java_path) - return env['ENV'] + if not self.external: + try: + return self._java_env[version]['ENV'] + except AttributeError: + self._java_env = {} + except KeyError: + pass + + import SCons.Environment + env = SCons.Environment.Environment() + self._java_env[version] = env + + + if version: + patterns = [ + '/usr/java/jdk%s*/bin' % version, + '/usr/lib/jvm/*-%s*/bin' % version, + '/usr/local/j2sdk%s*/bin' % version, + ] + java_path = self.paths(patterns) + [env['ENV']['PATH']] + else: + patterns = [ + '/usr/java/latest/bin', + '/usr/lib/jvm/*/bin', + '/usr/local/j2sdk*/bin', + ] + java_path = self.paths(patterns) + [env['ENV']['PATH']] + + env['ENV']['PATH'] = os.pathsep.join(java_path) + return env['ENV'] + return None + def java_where_includes(self,version=None): """ Return java include paths compiling java jni code diff --git a/runtest.py b/runtest.py index 58950dd..281bfcc 100644 --- a/runtest.py +++ b/runtest.py @@ -28,6 +28,9 @@ # # -a Run all tests; does a virtual 'find' for # all SCons tests under the current directory. +# You can also specify a list of subdirectories +# (not available with the "--qmtest" option!). Then, +# only the given folders are searched for test files. # # --aegis Print test results to an output file (specified # by the -o option) in the format expected by @@ -39,11 +42,17 @@ # debugger (pdb.py) so you don't have to # muck with PYTHONPATH yourself. # +# -e Starts the script in external mode, for +# testing separate Tools and packages. +# # -f file Only execute the tests listed in the specified # file. # # -h Print the help and exit. # +# -j Suppress printing of count and percent progress for +# the single tests. +# # -l List available tests and exit. # # -n No execute, just print command lines. @@ -64,6 +73,12 @@ # command line it will execute before # executing it. This suppresses that print. # +# -s Short progress. Prints only the command line +# and a percentage value, based on the total and +# current number of tests. +# All stdout and stderr messages get suppressed (this +# does only work with subprocess though)! +# # --sp The Aegis search path. # # --spe The Aegis executable search path. @@ -124,6 +139,7 @@ cwd = os.getcwd() all = 0 baseline = 0 builddir = os.path.join(cwd, 'build') +external = 0 debug = '' execute_tests = 1 format = None @@ -141,6 +157,9 @@ print_times = None python = None sp = None spe = None +print_progress = 1 +suppress_stdout = False +suppress_stderr = False helpstr = """\ Usage: runtest.py [OPTIONS] [TEST ...] @@ -151,8 +170,10 @@ Options: -b BASE, --baseline BASE Run test scripts against baseline BASE. --builddir DIR Directory in which packages were built. -d, --debug Run test scripts under the Python debugger. + -e, --external Run the script in external mode (for testing separate Tools) -f FILE, --file FILE Run tests in specified FILE. -h, --help Print this message and exit. + -j, --no-progress Suppress count and percent progress messages. -l, --list List available tests and exit. -n, --no-exec No execute, just print command lines. --noqmtest Execute tests directly, not using QMTest. @@ -188,12 +209,12 @@ Environment Variables: TESTCMD_VERBOSE: turn on verbosity in TestCommand """ -opts, args = getopt.getopt(sys.argv[1:], "3ab:df:hlno:P:p:qv:Xx:t", +opts, args = getopt.getopt(sys.argv[1:], "3ab:def:hjlno:P:p:qsv:Xx:t", ['all', 'aegis', 'baseline=', 'builddir=', - 'debug', 'file=', 'help', + 'debug', 'external', 'file=', 'help', 'no-progress', 'list', 'no-exec', 'noqmtest', 'output=', 'package=', 'passed', 'python=', 'qmtest', - 'quiet', 'sp=', 'spe=', 'time', + 'quiet', 'short-progress', 'sp=', 'spe=', 'time', 'version=', 'exec=', 'verbose=', 'xml']) @@ -214,6 +235,8 @@ for o, a in opts: if os.path.exists(pdb): debug = pdb break + elif o in ['-e', '--external']: + external = 1 elif o in ['-f', '--file']: if not os.path.isabs(a): a = os.path.join(cwd, a) @@ -221,6 +244,8 @@ for o, a in opts: elif o in ['-h', '--help']: print helpstr sys.exit(0) + elif o in ['-j', '--no-progress']: + print_progress = 0 elif o in ['-l', '--list']: list_only = 1 elif o in ['-n', '--no-exec']: @@ -245,6 +270,12 @@ for o, a in opts: qmtest = 'qmtest' elif o in ['-q', '--quiet']: printcommand = 0 + suppress_stdout = True + suppress_stderr = True + elif o in ['-s', '--short-progress']: + print_progress = 1 + suppress_stdout = True + suppress_stderr = True elif o in ['--sp']: sp = a.split(os.pathsep) elif o in ['--spe']: @@ -349,6 +380,40 @@ def escape(s): s = s.replace('\\', '\\\\') return s +# Try to use subprocess instead of the more low-level +# spawn command... +has_subprocess = True +try: + import subprocess + + def spawn_it(command_args): + p = subprocess.Popen(' '.join(command_args), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True) + spawned_stdout = p.stdout.read() + spawned_stderr = p.stderr.read() + return (spawned_stderr, spawned_stdout, p.wait()) +except: + has_subprocess = False + # Set up lowest-common-denominator spawning of a process on both Windows + # and non-Windows systems that works all the way back to Python 1.5.2. + try: + os.spawnv + except AttributeError: + def spawn_it(command_args): + pid = os.fork() + if pid == 0: + os.execv(command_args[0], command_args) + else: + pid, status = os.waitpid(pid, 0) + return (None, None, status >> 8) + else: + def spawn_it(command_args): + command = command_args[0] + command_args = list(map(escape, command_args)) + return (None, None, os.spawnv(os.P_WAIT, command, command_args)) + class Base(object): def __init__(self, path, spe=None): self.path = path @@ -363,15 +428,12 @@ class Base(object): class SystemExecutor(Base): def execute(self): - command = self.command_args[0] - command_args = [escape(arg) for arg in self.command_args] - s = self.status = os.spawnv(os.P_WAIT, command, command_args) + self.stderr, self.stdout, s = spawn_it(self.command_args) + self.status = s if s < 0 or s > 2: sys.stdout.write("Unexpected exit status %d\n" % s) -try: - import subprocess -except ImportError: +if not has_subprocess: import popen2 try: popen2.Popen3 @@ -541,9 +603,12 @@ else: scons_runtest_dir = base - scons_script_dir = sd or os.path.join(base, 'src', 'script') - - scons_lib_dir = ld or os.path.join(base, 'src', 'engine') + if not external: + scons_script_dir = sd or os.path.join(base, 'src', 'script') + scons_lib_dir = ld or os.path.join(base, 'src', 'engine') + else: + scons_script_dir = sd or '' + scons_lib_dir = ld or '' pythonpath_dir = scons_lib_dir @@ -560,6 +625,9 @@ elif scons_lib_dir: if scons_exec: os.environ['SCONS_EXEC'] = '1' +if external: + os.environ['SCONS_EXTERNAL_TEST'] = '1' + os.environ['SCONS_RUNTEST_DIR'] = scons_runtest_dir os.environ['SCONS_SCRIPT_DIR'] = scons_script_dir os.environ['SCONS_CWD'] = cwd @@ -582,6 +650,10 @@ for dir in sp: q = os.path.join(dir, 'QMTest') pythonpaths.append(q) +# Add path of the QMTest folder to PYTHONPATH +scriptpath = os.path.dirname(os.path.realpath(__file__)) +pythonpaths.append(os.path.join(scriptpath, 'QMTest')) + os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = os.pathsep.join(spe) os.environ['PYTHONPATH'] = os.pathsep.join(pythonpaths) @@ -599,6 +671,8 @@ tests = [] def find_Tests_py(directory): result = [] for dirpath, dirnames, filenames in os.walk(directory): + if 'sconstest.skip' in filenames: + continue for fname in filenames: if fname.endswith("Tests.py"): result.append(os.path.join(dirpath, fname)) @@ -607,6 +681,8 @@ def find_Tests_py(directory): def find_py(directory): result = [] for dirpath, dirnames, filenames in os.walk(directory): + if 'sconstest.skip' in filenames: + continue try: exclude_fp = open(os.path.join(dirpath, ".exclude_tests")) except EnvironmentError: @@ -619,6 +695,24 @@ def find_py(directory): result.append(os.path.join(dirpath, fname)) return sorted(result) +def find_sconstest_py(directory): + result = [] + for dirpath, dirnames, filenames in os.walk(directory): + # Skip folders containing a sconstest.skip file + if 'sconstest.skip' in filenames: + continue + try: + exclude_fp = open(os.path.join(dirpath, ".exclude_tests")) + except EnvironmentError: + excludes = [] + else: + excludes = [ e.split('#', 1)[0].strip() + for e in exclude_fp.readlines() ] + for fname in filenames: + if fname.endswith(".py") and fname.startswith("sconstest-") and fname not in excludes: + result.append(os.path.join(dirpath, fname)) + return sorted(result) + if args: if spe: for a in args: @@ -640,6 +734,7 @@ if args: elif path[:4] == 'test': tests.extend(find_py(path)) + tests.extend(find_sconstest_py(path)) else: tests.append(path) elif testlistfile: @@ -661,6 +756,7 @@ elif all and not qmtest: # things correctly.) tests.extend(find_Tests_py('src')) tests.extend(find_py('test')) + tests.extend(find_sconstest_py('test')) if format == '--aegis' and aegis: cmd = "aegis -list -unf pf 2>/dev/null" for line in os.popen(cmd, "r").readlines(): @@ -783,7 +879,8 @@ else: print_time_func = lambda fmt, time: None total_start_time = time_func() -for t in tests: +total_num_tests = len(tests) +for idx,t in enumerate(tests): command_args = ['-tt'] if python3incompatibilities: command_args.append('-3') @@ -793,10 +890,27 @@ for t in tests: t.command_args = [python] + command_args t.command_str = " ".join([escape(python)] + command_args) if printcommand: - sys.stdout.write(t.command_str + "\n") + if print_progress: + sys.stdout.write("%d/%d (%.2f%s) %s\n" % (idx+1, total_num_tests, + float(idx+1)*100.0/float(total_num_tests), + '%', + t.command_str)) + else: + sys.stdout.write(t.command_str + "\n") + if external: + head, tail = os.path.split(t.abspath) + if head: + os.environ['PYTHON_SCRIPT_DIR'] = head + else: + os.environ['PYTHON_SCRIPT_DIR'] = '' test_start_time = time_func() if execute_tests: t.execute() + if not suppress_stdout and t.stdout: + print t.stdout + if not suppress_stderr and t.stderr: + print t.stderr + t.test_time = time_func() - test_start_time print_time_func("Test execution time: %.1f seconds\n", t.test_time) if len(tests) > 0: -- cgit v0.12 From 7356cfca325dbd31d7817d374b70158dc76f9a35 Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Fri, 3 Aug 2012 23:45:05 +0200 Subject: - fixed usage of subprocess module in runtest.py - fixed tests in test/runtest (needed -j option for suppressing the count/percent messages) --- runtest.py | 23 +++++++++++++++++++---- test/runtest/aegis/batch-output.py | 2 +- test/runtest/baseline/combined.py | 2 +- test/runtest/baseline/fail.py | 2 +- test/runtest/baseline/no_result.py | 2 +- test/runtest/baseline/pass.py | 2 +- test/runtest/fallback.py | 2 +- test/runtest/noqmtest.py | 2 +- test/runtest/print_time.py | 2 +- test/runtest/python.py | 2 +- test/runtest/simple/combined.py | 2 +- test/runtest/simple/fail.py | 2 +- test/runtest/simple/no_result.py | 2 +- test/runtest/simple/pass.py | 2 +- test/runtest/src.py | 2 +- test/runtest/testlistfile.py | 2 +- 16 files changed, 34 insertions(+), 19 deletions(-) diff --git a/runtest.py b/runtest.py index 281bfcc..c1dbaab 100644 --- a/runtest.py +++ b/runtest.py @@ -382,10 +382,25 @@ def escape(s): # Try to use subprocess instead of the more low-level # spawn command... -has_subprocess = True +use_subprocess = True try: import subprocess - +except: + use_subprocess = False + +if (use_subprocess and + not suppress_stdout and + not suppress_stderr): + # If no suppress mode is selected, we still use the + # old spawn routines instead of the modern subprocess module. + # This is important for the test/runtest scripts, where we + # call runtest.py within the single tests. With subprocess the + # stderr of the subprocess lands in stdout of the top test script, + # which lets the test fail. :( + # TODO: find a way to use subprocess with proper stream redirection... + use_subprocess = False + +if use_subprocess: def spawn_it(command_args): p = subprocess.Popen(' '.join(command_args), stdout=subprocess.PIPE, @@ -394,7 +409,7 @@ try: spawned_stdout = p.stdout.read() spawned_stderr = p.stderr.read() return (spawned_stderr, spawned_stdout, p.wait()) -except: +else: has_subprocess = False # Set up lowest-common-denominator spawning of a process on both Windows # and non-Windows systems that works all the way back to Python 1.5.2. @@ -433,7 +448,7 @@ class SystemExecutor(Base): if s < 0 or s > 2: sys.stdout.write("Unexpected exit status %d\n" % s) -if not has_subprocess: +if not use_subprocess: import popen2 try: popen2.Popen3 diff --git a/test/runtest/aegis/batch-output.py b/test/runtest/aegis/batch-output.py index 68bd166..e29183d 100644 --- a/test/runtest/aegis/batch-output.py +++ b/test/runtest/aegis/batch-output.py @@ -52,7 +52,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments = '-o aegis.out --aegis test', stderr=expect_stderr) +test.run(arguments = '-j -o aegis.out --aegis test', stderr=expect_stderr) expect = """\ test_result = [ diff --git a/test/runtest/baseline/combined.py b/test/runtest/baseline/combined.py index 119fc63..1983ae0 100644 --- a/test/runtest/baseline/combined.py +++ b/test/runtest/baseline/combined.py @@ -69,7 +69,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='-b . test', +test.run(arguments='-j -b . test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/fail.py b/test/runtest/baseline/fail.py index baa974a..a96ba80 100644 --- a/test/runtest/baseline/fail.py +++ b/test/runtest/baseline/fail.py @@ -47,7 +47,7 @@ expect_stderr = """\ FAILING TEST STDERR """ -test.run(arguments='-b . test/fail.py', +test.run(arguments='-j -b . test/fail.py', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/no_result.py b/test/runtest/baseline/no_result.py index 8e0d0dd..7a07c01 100644 --- a/test/runtest/baseline/no_result.py +++ b/test/runtest/baseline/no_result.py @@ -47,7 +47,7 @@ expect_stderr = """\ NO RESULT TEST STDERR """ -test.run(arguments='-b . test/no_result.py', +test.run(arguments='-j -b . test/no_result.py', status=2, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/pass.py b/test/runtest/baseline/pass.py index 77cd84d..bcd87d7 100644 --- a/test/runtest/baseline/pass.py +++ b/test/runtest/baseline/pass.py @@ -50,7 +50,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='-b . test', +test.run(arguments='-j -b . test', stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/fallback.py b/test/runtest/fallback.py index 3bfb9f4..ae7b814 100644 --- a/test/runtest/fallback.py +++ b/test/runtest/fallback.py @@ -82,7 +82,7 @@ testlist = [ test_pass_py, ] -test.run(arguments = ' '.join(testlist), +test.run(arguments = '-j '+' '.join(testlist), status = 1, stdout = expect_stdout, stderr = expect_stderr) diff --git a/test/runtest/noqmtest.py b/test/runtest/noqmtest.py index 9d0c8e3..e2cb5f8 100644 --- a/test/runtest/noqmtest.py +++ b/test/runtest/noqmtest.py @@ -74,7 +74,7 @@ testlist = [ test_pass_py, ] -test.run(arguments = '--noqmtest %s' % ' '.join(testlist), +test.run(arguments = '-j --noqmtest %s' % ' '.join(testlist), status = 1, stdout = expect_stdout, stderr = expect_stderr) diff --git a/test/runtest/print_time.py b/test/runtest/print_time.py index e068447..dfce273 100644 --- a/test/runtest/print_time.py +++ b/test/runtest/print_time.py @@ -75,7 +75,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='-t test', +test.run(arguments='-j -t test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/python.py b/test/runtest/python.py index 5c2f737..b45e8d4 100644 --- a/test/runtest/python.py +++ b/test/runtest/python.py @@ -66,7 +66,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments=['-P', mypython, 'test'], +test.run(arguments=['-j','-P', mypython, 'test'], stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/combined.py b/test/runtest/simple/combined.py index 6817820..6e0539a 100644 --- a/test/runtest/simple/combined.py +++ b/test/runtest/simple/combined.py @@ -70,7 +70,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='test', +test.run(arguments='-j test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/fail.py b/test/runtest/simple/fail.py index 36ec0d0..d891597 100644 --- a/test/runtest/simple/fail.py +++ b/test/runtest/simple/fail.py @@ -47,7 +47,7 @@ expect_stderr = """\ FAILING TEST STDERR """ -test.run(arguments='test/fail.py', +test.run(arguments='-j test/fail.py', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/no_result.py b/test/runtest/simple/no_result.py index 97dcede..91ba50c 100644 --- a/test/runtest/simple/no_result.py +++ b/test/runtest/simple/no_result.py @@ -47,7 +47,7 @@ expect_stderr = """\ NO RESULT TEST STDERR """ -test.run(arguments='test/no_result.py', +test.run(arguments='-j test/no_result.py', status=2, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/pass.py b/test/runtest/simple/pass.py index 6332e5f..c954336 100644 --- a/test/runtest/simple/pass.py +++ b/test/runtest/simple/pass.py @@ -47,7 +47,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='test/pass.py', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-j test/pass.py', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() diff --git a/test/runtest/src.py b/test/runtest/src.py index 9136a4d..84b08a0 100644 --- a/test/runtest/src.py +++ b/test/runtest/src.py @@ -62,7 +62,7 @@ PASSING TEST STDERR PASSING TEST STDERR """ % locals() -test.run(arguments='src', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-j src', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() diff --git a/test/runtest/testlistfile.py b/test/runtest/testlistfile.py index 8836c8e..5a56bb3 100644 --- a/test/runtest/testlistfile.py +++ b/test/runtest/testlistfile.py @@ -62,7 +62,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='-f t.txt', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-j -f t.txt', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() -- cgit v0.12 From 1fea3bfcaf2ee4cbb7498426bac6b83e6c4bc1d9 Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Sat, 4 Aug 2012 01:07:27 +0200 Subject: - fixed where_is for standard mode --- QMTest/TestSCons.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index cd3836d..e6e0c8c 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -351,9 +351,9 @@ class TestSCons(TestCommon): """ if path is None: path = os.environ['PATH'] - if isinstance(prog, str): - prog = [prog] if self.external: + if isinstance(prog, str): + prog = [prog] import stat paths = path.split(os.pathsep) for p in prog: -- cgit v0.12 From 780d9ca6577e5a15b4d4cfad111b74132c255be1 Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Thu, 9 Aug 2012 23:56:44 +0200 Subject: - renamed the '-j' option to '-k', such that we can use the former for parallel processing later on - changed list(map()) to list comprehensions - removed try/except block around os.environ.get() for SCONS_EXTERNAL_TEST - fixed a potential deadlock for subprocess.Popen by using temporary files - added the '--nopipefiles' option to switch off this temp file fix (not recommended though) --- QMTest/TestCmd.py | 6 +- QMTest/TestSCons.py | 6 +- runtest.py | 157 ++++++++++++++++++++++++++++--------- test/runtest/aegis/batch-output.py | 2 +- test/runtest/baseline/combined.py | 2 +- test/runtest/baseline/fail.py | 2 +- test/runtest/baseline/no_result.py | 2 +- test/runtest/baseline/pass.py | 2 +- test/runtest/fallback.py | 2 +- test/runtest/noqmtest.py | 2 +- test/runtest/print_time.py | 2 +- test/runtest/python.py | 2 +- test/runtest/simple/combined.py | 2 +- test/runtest/simple/fail.py | 2 +- test/runtest/simple/no_result.py | 2 +- test/runtest/simple/pass.py | 2 +- test/runtest/src.py | 2 +- test/runtest/testlistfile.py | 2 +- 18 files changed, 139 insertions(+), 60 deletions(-) diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 8d382ce..8a465a0 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -897,11 +897,7 @@ class TestCmd(object): combine = 0, universal_newlines = 1, timeout = None): - self.external = 0 - try: - self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) - except KeyError: - pass + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) self._cwd = os.getcwd() self.description_set(description) self.program_set(program) diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index e6e0c8c..86c0dec 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -231,11 +231,7 @@ class TestSCons(TestCommon): is not necessary. """ self.orig_cwd = os.getcwd() - self.external = 0 - try: - self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) - except KeyError: - pass + self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0) if not self.external: try: diff --git a/runtest.py b/runtest.py index c1dbaab..89ec9ee 100644 --- a/runtest.py +++ b/runtest.py @@ -50,7 +50,7 @@ # # -h Print the help and exit. # -# -j Suppress printing of count and percent progress for +# -k Suppress printing of count and percent progress for # the single tests. # # -l List available tests and exit. @@ -160,6 +160,7 @@ spe = None print_progress = 1 suppress_stdout = False suppress_stderr = False +allow_pipe_files = True helpstr = """\ Usage: runtest.py [OPTIONS] [TEST ...] @@ -173,10 +174,15 @@ Options: -e, --external Run the script in external mode (for testing separate Tools) -f FILE, --file FILE Run tests in specified FILE. -h, --help Print this message and exit. - -j, --no-progress Suppress count and percent progress messages. + -k, --no-progress Suppress count and percent progress messages. -l, --list List available tests and exit. -n, --no-exec No execute, just print command lines. --noqmtest Execute tests directly, not using QMTest. + --nopipefiles Doesn't use the "file pipe" workaround for subprocess.Popen() + for starting tests. WARNING: Only use this when too much file + traffic is giving you trouble AND you can be sure that none of + your tests create output that exceed 65K chars! You might + run into some deadlocks else. -o FILE, --output FILE Print test results to FILE. -P Python Use the specified Python interpreter. -p PACKAGE, --package PACKAGE @@ -209,10 +215,10 @@ Environment Variables: TESTCMD_VERBOSE: turn on verbosity in TestCommand """ -opts, args = getopt.getopt(sys.argv[1:], "3ab:def:hjlno:P:p:qsv:Xx:t", +opts, args = getopt.getopt(sys.argv[1:], "3ab:def:hklno:P:p:qsv:Xx:t", ['all', 'aegis', 'baseline=', 'builddir=', 'debug', 'external', 'file=', 'help', 'no-progress', - 'list', 'no-exec', 'noqmtest', 'output=', + 'list', 'no-exec', 'noqmtest', 'nopipefiles', 'output=', 'package=', 'passed', 'python=', 'qmtest', 'quiet', 'short-progress', 'sp=', 'spe=', 'time', 'version=', 'exec=', @@ -244,7 +250,7 @@ for o, a in opts: elif o in ['-h', '--help']: print helpstr sys.exit(0) - elif o in ['-j', '--no-progress']: + elif o in ['-k', '--no-progress']: print_progress = 0 elif o in ['-l', '--list']: list_only = 1 @@ -252,6 +258,8 @@ for o, a in opts: execute_tests = None elif o in ['--noqmtest']: qmtest = None + elif o in ['--nopipefiles']: + allow_pipe_files = False elif o in ['-o', '--output']: if a != '-' and not os.path.isabs(a): a = os.path.join(cwd, a) @@ -388,27 +396,77 @@ try: except: use_subprocess = False -if (use_subprocess and - not suppress_stdout and - not suppress_stderr): - # If no suppress mode is selected, we still use the - # old spawn routines instead of the modern subprocess module. - # This is important for the test/runtest scripts, where we - # call runtest.py within the single tests. With subprocess the - # stderr of the subprocess lands in stdout of the top test script, - # which lets the test fail. :( - # TODO: find a way to use subprocess with proper stream redirection... - use_subprocess = False - if use_subprocess: - def spawn_it(command_args): - p = subprocess.Popen(' '.join(command_args), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True) - spawned_stdout = p.stdout.read() - spawned_stderr = p.stderr.read() - return (spawned_stderr, spawned_stdout, p.wait()) + if not suppress_stdout and not suppress_stderr: + # Without any output suppressed, we let the subprocess + # write its stuff freely to stdout/stderr. + def spawn_it(command_args): + p = subprocess.Popen(' '.join(command_args), + shell=True) + return (None, None, p.wait()) + else: + # Else, we catch the output of both pipes... + if allow_pipe_files: + # The subprocess.Popen() suffers from a well-known + # problem. Data for stdout/stderr is read into a + # memory buffer of fixed size, 65K which is not very much. + # When it fills up, it simply stops letting the child process + # write to it. The child will then sit and patiently wait to + # be able to write the rest of its output. Hang! + # In order to work around this, we follow a suggestion + # by Anders Pearson in + # http://http://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ + # and pass temp file objects to Popen() instead of the ubiquitous + # subprocess.PIPE. + def spawn_it(command_args): + # Create temporary files + import tempfile + tmp_stdout = tempfile.TemporaryFile(mode='w+t') + tmp_stderr = tempfile.TemporaryFile(mode='w+t') + # Start subprocess... + p = subprocess.Popen(' '.join(command_args), + stdout=tmp_stdout, + stderr=tmp_stderr, + shell=True) + # ... and wait for it to finish. + ret = p.wait() + + try: + # Rewind to start of files + tmp_stdout.seek(0) + tmp_stderr.seek(0) + # Read output + spawned_stdout = tmp_stdout.read() + spawned_stderr = tmp_stderr.read() + finally: + # Remove temp files by closing them + tmp_stdout.close() + tmp_stderr.close() + + # Return values + return (spawned_stderr, spawned_stdout, ret) + + else: + # We get here only if the user gave the '--nopipefiles' + # option, meaning the "temp file" approach for + # subprocess.communicate() above shouldn't be used. + # He hopefully knows what he's doing, but again we have a + # potential deadlock situation in the following code: + # If the subprocess writes a lot of data to its stderr, + # the pipe will fill up (nobody's reading it yet) and the + # subprocess will wait for someone to read it. + # But the parent process is trying to read from stdin + # (but the subprocess isn't writing anything there). + # Hence a deadlock. + # Be dragons here! Better don't use this! + def spawn_it(command_args): + p = subprocess.Popen(' '.join(command_args), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True) + spawned_stdout = p.stdout.read() + spawned_stderr = p.stderr.read() + return (spawned_stderr, spawned_stdout, p.wait()) else: has_subprocess = False # Set up lowest-common-denominator spawning of a process on both Windows @@ -426,7 +484,7 @@ else: else: def spawn_it(command_args): command = command_args[0] - command_args = list(map(escape, command_args)) + command_args = [escape(c) for c in command_args] return (None, None, os.spawnv(os.P_WAIT, command, command_args)) class Base(object): @@ -476,14 +534,43 @@ if not use_subprocess: self.status = self.status >> 8 else: class PopenExecutor(Base): - def execute(self): - p = subprocess.Popen(self.command_str, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True) - self.stdout = p.stdout.read() - self.stderr = p.stderr.read() - self.status = p.wait() + # For an explanation of the following 'if ... else' + # and the 'allow_pipe_files' option, please check out the + # use_subprocess path in the definition of spawn_it() above. + if allow_pipe_files: + def execute(self): + # Create temporary files + import tempfile + tmp_stdout = tempfile.TemporaryFile(mode='w+t') + tmp_stderr = tempfile.TemporaryFile(mode='w+t') + # Start subprocess... + p = subprocess.Popen(self.command_str, + stdout=tmp_stdout, + stderr=tmp_stderr, + shell=True) + # ... and wait for it to finish. + self.status = p.wait() + + try: + # Rewind to start of files + tmp_stdout.seek(0) + tmp_stderr.seek(0) + # Read output + self.stdout = tmp_stdout.read() + self.stderr = tmp_stderr.read() + finally: + # Remove temp files by closing them + tmp_stdout.close() + tmp_stderr.close() + else: + def execute(self): + p = subprocess.Popen(self.command_str, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True) + self.stdout = p.stdout.read() + self.stderr = p.stderr.read() + self.status = p.wait() class Aegis(SystemExecutor): def header(self, f): @@ -851,7 +938,7 @@ if qmtest: #except OSError: # pass -tests = list(map(Test, tests)) +tests = [Test(t) for t in tests] class Unbuffered(object): def __init__(self, file): diff --git a/test/runtest/aegis/batch-output.py b/test/runtest/aegis/batch-output.py index e29183d..e371def 100644 --- a/test/runtest/aegis/batch-output.py +++ b/test/runtest/aegis/batch-output.py @@ -52,7 +52,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments = '-j -o aegis.out --aegis test', stderr=expect_stderr) +test.run(arguments = '-k -o aegis.out --aegis test', stderr=expect_stderr) expect = """\ test_result = [ diff --git a/test/runtest/baseline/combined.py b/test/runtest/baseline/combined.py index 1983ae0..35c1796 100644 --- a/test/runtest/baseline/combined.py +++ b/test/runtest/baseline/combined.py @@ -69,7 +69,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='-j -b . test', +test.run(arguments='-k -b . test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/fail.py b/test/runtest/baseline/fail.py index a96ba80..5687160 100644 --- a/test/runtest/baseline/fail.py +++ b/test/runtest/baseline/fail.py @@ -47,7 +47,7 @@ expect_stderr = """\ FAILING TEST STDERR """ -test.run(arguments='-j -b . test/fail.py', +test.run(arguments='-k -b . test/fail.py', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/no_result.py b/test/runtest/baseline/no_result.py index 7a07c01..2149594 100644 --- a/test/runtest/baseline/no_result.py +++ b/test/runtest/baseline/no_result.py @@ -47,7 +47,7 @@ expect_stderr = """\ NO RESULT TEST STDERR """ -test.run(arguments='-j -b . test/no_result.py', +test.run(arguments='-k -b . test/no_result.py', status=2, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/baseline/pass.py b/test/runtest/baseline/pass.py index bcd87d7..affa486 100644 --- a/test/runtest/baseline/pass.py +++ b/test/runtest/baseline/pass.py @@ -50,7 +50,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='-j -b . test', +test.run(arguments='-k -b . test', stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/fallback.py b/test/runtest/fallback.py index ae7b814..1229b28 100644 --- a/test/runtest/fallback.py +++ b/test/runtest/fallback.py @@ -82,7 +82,7 @@ testlist = [ test_pass_py, ] -test.run(arguments = '-j '+' '.join(testlist), +test.run(arguments = '-k '+' '.join(testlist), status = 1, stdout = expect_stdout, stderr = expect_stderr) diff --git a/test/runtest/noqmtest.py b/test/runtest/noqmtest.py index e2cb5f8..eb33223 100644 --- a/test/runtest/noqmtest.py +++ b/test/runtest/noqmtest.py @@ -74,7 +74,7 @@ testlist = [ test_pass_py, ] -test.run(arguments = '-j --noqmtest %s' % ' '.join(testlist), +test.run(arguments = '-k --noqmtest %s' % ' '.join(testlist), status = 1, stdout = expect_stdout, stderr = expect_stderr) diff --git a/test/runtest/print_time.py b/test/runtest/print_time.py index dfce273..244c6f8 100644 --- a/test/runtest/print_time.py +++ b/test/runtest/print_time.py @@ -75,7 +75,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='-j -t test', +test.run(arguments='-k -t test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/python.py b/test/runtest/python.py index b45e8d4..d406be4 100644 --- a/test/runtest/python.py +++ b/test/runtest/python.py @@ -66,7 +66,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments=['-j','-P', mypython, 'test'], +test.run(arguments=['-k','-P', mypython, 'test'], stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/combined.py b/test/runtest/simple/combined.py index 6e0539a..616f4d5 100644 --- a/test/runtest/simple/combined.py +++ b/test/runtest/simple/combined.py @@ -70,7 +70,7 @@ NO RESULT TEST STDERR PASSING TEST STDERR """ -test.run(arguments='-j test', +test.run(arguments='-k test', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/fail.py b/test/runtest/simple/fail.py index d891597..8b800fb 100644 --- a/test/runtest/simple/fail.py +++ b/test/runtest/simple/fail.py @@ -47,7 +47,7 @@ expect_stderr = """\ FAILING TEST STDERR """ -test.run(arguments='-j test/fail.py', +test.run(arguments='-k test/fail.py', status=1, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/no_result.py b/test/runtest/simple/no_result.py index 91ba50c..91af7e4 100644 --- a/test/runtest/simple/no_result.py +++ b/test/runtest/simple/no_result.py @@ -47,7 +47,7 @@ expect_stderr = """\ NO RESULT TEST STDERR """ -test.run(arguments='-j test/no_result.py', +test.run(arguments='-k test/no_result.py', status=2, stdout=expect_stdout, stderr=expect_stderr) diff --git a/test/runtest/simple/pass.py b/test/runtest/simple/pass.py index c954336..6e5b6b0 100644 --- a/test/runtest/simple/pass.py +++ b/test/runtest/simple/pass.py @@ -47,7 +47,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='-j test/pass.py', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-k test/pass.py', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() diff --git a/test/runtest/src.py b/test/runtest/src.py index 84b08a0..23894f9 100644 --- a/test/runtest/src.py +++ b/test/runtest/src.py @@ -62,7 +62,7 @@ PASSING TEST STDERR PASSING TEST STDERR """ % locals() -test.run(arguments='-j src', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-k src', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() diff --git a/test/runtest/testlistfile.py b/test/runtest/testlistfile.py index 5a56bb3..b86b0f2 100644 --- a/test/runtest/testlistfile.py +++ b/test/runtest/testlistfile.py @@ -62,7 +62,7 @@ expect_stderr = """\ PASSING TEST STDERR """ -test.run(arguments='-j -f t.txt', stdout=expect_stdout, stderr=expect_stderr) +test.run(arguments='-k -f t.txt', stdout=expect_stdout, stderr=expect_stderr) test.pass_test() -- cgit v0.12 From 4ae583161be1735d0450be17fe2383ec5c4f475e Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Sun, 12 Aug 2012 15:06:36 +0200 Subject: - fixed directory/file fixtures for "general" mode - added documentation for the testing framework - converted two old tests from the "packaging" section to using fixtures as reference examples --- QMTest/TestCmd.py | 1 + QMTest/TestSCons.py | 10 +- QMTest/test-framework.rst | 430 +++++++++++++++++++++ runtest.py | 36 +- test/README | 4 + test/packaging/convenience-functions.py | 79 ---- .../convenience-functions/convenience-functions.py | 64 +++ .../convenience-functions/image/SConstruct | 10 + test/packaging/convenience-functions/image/f1 | 0 test/packaging/convenience-functions/image/f2 | 0 test/packaging/convenience-functions/image/f3 | 0 test/packaging/sandbox-test.py | 79 ---- test/packaging/sandbox-test/SConstruct | 19 + test/packaging/sandbox-test/sandbox-test.py | 56 +++ test/packaging/sandbox-test/src/foobar.c | 0 test/packaging/sandbox-test/src/foobar.h | 0 16 files changed, 599 insertions(+), 189 deletions(-) create mode 100644 QMTest/test-framework.rst delete mode 100644 test/packaging/convenience-functions.py create mode 100644 test/packaging/convenience-functions/convenience-functions.py create mode 100644 test/packaging/convenience-functions/image/SConstruct create mode 100644 test/packaging/convenience-functions/image/f1 create mode 100644 test/packaging/convenience-functions/image/f2 create mode 100644 test/packaging/convenience-functions/image/f3 delete mode 100644 test/packaging/sandbox-test.py create mode 100644 test/packaging/sandbox-test/SConstruct create mode 100644 test/packaging/sandbox-test/sandbox-test.py create mode 100644 test/packaging/sandbox-test/src/foobar.c create mode 100644 test/packaging/sandbox-test/src/foobar.h diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 8a465a0..708fdc4 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -939,6 +939,7 @@ class TestCmd(object): self.condition = 'no_result' self.workdir_set(workdir) self.subdir(subdir) + self.script_srcdir = None def __del__(self): self.cleanup() diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 86c0dec..360a799 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -240,11 +240,6 @@ class TestSCons(TestCommon): pass else: os.chdir(script_dir) - else: - try: - self.script_srcdir = os.environ['PYTHON_SCRIPT_DIR'] - except KeyError: - pass if 'program' not in kw: kw['program'] = os.environ.get('SCONS') if not kw['program']: @@ -281,6 +276,11 @@ class TestSCons(TestCommon): if SCons.Node.FS.default_fs is None: SCons.Node.FS.default_fs = SCons.Node.FS.FS() + try: + self.script_srcdir = os.environ['PYTHON_SCRIPT_DIR'] + except KeyError: + pass + def Environment(self, ENV=None, *args, **kw): """ Return a construction Environment that optionally overrides diff --git a/QMTest/test-framework.rst b/QMTest/test-framework.rst new file mode 100644 index 0000000..844d99b --- /dev/null +++ b/QMTest/test-framework.rst @@ -0,0 +1,430 @@ +======================= +SCons Testing Framework +======================= + +SCons uses extensive automated tests to try to ensure quality. The primary goal +is that users should be able to upgrade from version to version without any surprise +changes in behavior. + +In general, no change goes into SCons unless it has one or more new or modified +tests that demonstrably exercise the bug being fixed or the feature being added. +There are exceptions to this guideline, but they should be just that, ''exceptions''. +When in doubt, make sure it's tested. + +Test Organization +================= + +There are three types of SCons tests: + +*End-to-End Tests* + End-to-end tests of SCons are all Python scripts (``*.py``) underneath + the ``test/`` subdirectory. They use the test infrastructure modules in the + ``QMTest`` subdirectory. + +*Unit Tests* + Unit tests for individual SCons modules live underneath the + ``src/engine/`` subdirectory and are the same base name as the module + with ``Tests.py`` appended--for example, the unit tests for the + ``Builder.py`` module are in the ``BuilderTests.py`` script. + +*External Tests* + For the support of external Tools (in the form of packages, preferably), the + testing framework got extended, such that it can run in standalone mode. + You can start it from the top-level folder of your Tool's source tree, + where it then finds all Python scripts (``*.py``) underneath the + local ``test/`` directory. + This implies that Tool tests have to be kept in a folder named ``test``, + like for the SCons core. + + +Contrasting End-to-End and Unit Tests +##################################### + +In general, anything that we've put into an end-to-end test script should +be considered a hardened part of the interface (that is, it's something +that a user might do) and should not be broken. Unit tests are now +considered more malleable, more for testing internal interfaces that +can change so long as we don't break users' ``SConscript`` files. (This +wasn't always the case, and there's a lot of meaty code in many of the +unit test scripts that does, in fact, capture external interface +behavior. In general, we should try to move those things to end-to-end +scripts as we find them.) + +It's more difficult to debug end-to-end tests. You can actually go +straight into the Python debugger on the unit test scripts by using the +``runtest.py --pdb`` option, but the end-to-end tests treat an SCons +invocation as a "black box" and just look for external effects. +Simple ``print`` statements within the SCons code itself often don't help +debug end-to-end because they end up in SCons output that gets compared +against expected output and cause a test failure. Probably the most +effective technique is to use the internal ``SCons.Debug.Trace()`` function, +which prints output to ``/dev/tty`` on Linux/UNIX systems and ``con`` on +Windows systems, so you can see what's going on. + +Naming conventions +################## + +The end-to-end tests, more or less, stick to the following naming conventions: + +1. All tests end with a .py suffix. + +2. In the *General* form we use + + ``Feature.py`` + for the test of a specified feature; try to + keep this description reasonably short + + ``Feature-x.py`` + for the test of a specified feature using + option ``x`` + +3. The *command line option* tests take the form + + ``option-x.py`` + for a lower-case single-letter option + + ``option--X.py`` + upper-case single-letter option + (with an extra hyphen, so the file names will + be unique on case-insensitive systems) + + ``option--lo.py`` + long option; abbreviate the long + option name to a few characters + + +Running Tests +============= + +The standard set of SCons tests are run from the top-level source directory +by the ``runtest.py`` script. +There is a ``--qmtest`` option that checks whether the ``QMTest`` package +is installed on your system. If it can be found, then the ``runtest.py`` script +will use it to carry out the tests. + +Help is available through the ``-h`` option: + +:: + + $ python runtest.py -h + +To simply run all the tests, use the ``-a`` option: + +:: + + $ python runtest.py -a + +By default, ``runtest.py`` prints a count and percentage message for each test +case, along with the name of the test file. +If you need the output to be more silent, have a look at the ``-q``, ``-s`` and +``-k`` options. + +You may specifically list one or more tests to be run: + +:: + + $ python runtest.py src/engine/SCons/BuilderTests.py + $ python runtest.py test/option-j.py test/Program.py + +Folder names are allowed arguments as well, so you can do a + +:: + + $ python runtest.py test/SWIG + +to run all SWIG tests only. + +You can also use the ``-f`` option to execute just the tests listed in a specified +text file: + +:: + + $ cat testlist.txt + test/option-j.py + test/Program.py + $ python runtest.py -f testlist.txt + + +One test must be listed per line, and any lines that begin with '#' +will be ignored (the intent being to allow you, for example, +to comment out tests that +are currently passing and then uncomment all of the tests in the file +for a final validation run). + +If more than one test is run, the ``runtest.py`` script prints a summary +of how many tests passed, failed, or yielded no result, and lists any +unsuccessful tests. + +The above invocations all test directly the files underneath the ``src/`` +subdirectory, and do not require that a packaging build be performed first. +The ``runtest.py`` script supports additional options to run tests against +unpacked packages in the ``build/test-*/`` subdirectories. + +If you are testing a separate Tool outside of the SCons source tree, you have +to call the ``runtest.py`` script in *external* (stand-alone) mode:: + + $ python ~/scons/runtest.py -e -a + +. This ensures that the testing framework doesn't try to access SCons classes +needed for some of the *internal* test cases. + +Note, that the actual tests are carried out in a temporary folder each, which gets +deleted afterwards. This ensures that your source directories don't get clobbered +with temporary files from the test runs. It also means that you can't simply change +into a folder to "debug things" after a test has gone wrong. For a way around this, +check out the ``PRESERVE`` environment variable. It can be seen in action in +`How to convert old tests`_ below. + +Not Running Tests +================= + +If you simply want to check which tests would get executed, you can call the +``runtest.py`` script with the ``-l`` option:: + + $ python runtest.py -l + +Then there is also the ``-n`` option, which prints the command line for each +single test, but doesn't actually execute them:: + + $ python runtest.py -n + +Finding Tests +============= + +When started in *standard* mode + +:: + + $ python runtest.py -a + + +, ``runtest.py`` assumes that it is run from the SCons top-level source directory. +It then dives into the ``src`` and ``test`` folders, where it tries to find filenames + + ``*Test.py`` + for the ``src`` directory, and + + ``*.py`` + for the ``test`` folder. + +When using fixtures, you may quickly end up in a position where you have supporting +Python script files in a subfolder, but they shouldn't get picked up as test scripts. +In this case you have two options: + +1. Add a file with the name ``sconstest.skip`` to your subfolder. This lets + ``runtest.py`` skip the contents of the directory completely. +2. Create a file ``.exclude_tests`` in each folder in question, and in it list + line-by-line the files to get excluded from testing. + +The same rules apply when testing external Tools by using the ``-e`` option. + + +"Hello, world!" SCons Test Script +================================= + +To illustrate how the end-to-end test scripts work, +let's walk through a simple "Hello, world!" example: + +:: + + #!python + import TestSCons + + test = TestSCons.TestSCons() + + test.write('SConstruct', """\ + Program('hello.c') + """) + + test.write('hello.c', """\ + int + main(int argc, char *argv[]) + { + printf("Hello, world!\\n"); + exit (0); + } + """) + + test.run() + + test.run(program='./hello', stdout="Hello, world!\n") + + test.pass_test() + + +``import TestSCons`` + Imports the main infrastructure for writing SCons tests. This is normally the only part of the infrastructure that needs importing. Sometimes other Python modules are necessary or helpful, and get imported before this line. + +``test = TestSCons.TestSCons()`` + This initializes an object for testing. A fair amount happens under the covers when the object is created, including: + + * A temporary directory is created for all the in-line files that will get created. + * The temporary directory's removal is arranged for when the test is finished. + * We ``os.chdir()`` to the temporary directory. + +``test.write('SConstruct', ...`` + This line creates an ``SConstruct`` file in the temporary directory, to be used as input to the ``scons`` run(s) that we're testing. Note the use of the Python triple-quote syntax for the contents of the ``SConstruct`` file. Because input files for tests are all created from in-line data like this, the tests can sometimes get a little confusing to read, because some of the Python code is found + +``test.write('hello.c', ...`` + This lines creates an ``hello.c`` file in the temporary directory. Note that we have to escape the ``\\n`` in the ``"Hello, world!\\n"`` string so that it ends up as a single backslash in the ``hello.c`` file on disk. + +``test.run()`` + This actually runs SCons. Like the object initialization, things happen under the covers: + + * The exit status is verified; the test exits with a failure if the exit status is not zero. + * The error output is examined, and the test exits with a failure if there is any + +``test.run(program='./hello', stdout="Hello, world!\n")`` + This shows use of the ``TestSCons.run()`` method to execute a program other than ``scons``, in this case the ``hello`` program we just presumably built. The ``stdout=`` keyword argument also tells the ``TestSCons.run()`` method to fail if the program output does not match the expected string ``"Hello, world!\n"``. Like the previous ``test.run()`` line, it will also fail the test if the exit status is non-zero, or there is any error output. + +``test.pass_test()`` + This is always the last line in a test script. It prints ``PASSED`` on the screen and makes sure we exit with a ``0`` status to indicate the test passed. As a side effect of destroying the ``test`` object, the created temporary directory will be removed. + +Working with fixtures +===================== + +In the simple example above, we have seen how to create files in the temporary test directory. +We give a filename to the ``TestSCons.write()`` method, together with its contents, and it gets +written to the test folder right before its start. + +This technique can still be seen throughout most of the end-to-end tests, but there is a better +way. It's much easier to edit, create and maintain real files, instead of copy/pasting +content to/from a Python script. If the test files get longer, the test script +gets longer and is harder to read. + +Against this, we now have the possibility to copy single files or the contents of a +local folder to the test directory. Since we can reuse these files/folders to setup +several tests, we call them *fixtures* in the following. + +Directory fixtures +################## + +The function ``dir_fixture(self, srcdir, dstdir=None)`` in the ``TestCmd`` class +copies the contents of the specified folder ``srcdir`` from +the directory of the called test script, to the current +temporary test directory. +The ``srcdir`` name may be a list, in which case the elements are +concatenated with the ``os.path.join()`` method. The ``dstdir`` is +assumed to be under the temporary working directory, it gets +created automatically, if it does not already exist. + +A short syntax example:: + + test = TestSCons.TestSCons() + test.dir_fixture('image') + test.run() + +would copy all files and subfolders from the local ``image`` folder, to +the temporary directory for the current test. + +If you'd like to see a real example for this in action, refer to the test +named ``test/packaging/convenience-functions/convenience-functions.py``. + +File fixtures +############# + +Like for directory fixtures, ``file_fixture(self, srcfile, dstfile=None)`` +copies the file ``srcfile`` from the directory of +the called script, to the temporary test directory. +The ``dstfile`` is assumed to be under the temporary working +directory, unless it is an absolute path name. +If ``dstfile`` is specified, its target directory gets created +automatically if it doesn't already exist. + +With a:: + + test = TestSCons.TestSCons() + test.file_fixture('SConstruct') + test.file_fixture(['src','main.cpp'],['src','main.cpp']) + test.run() + +you would copy the files ``SConstruct`` and ``src/main.cpp`` to the temporary +test folder, prior to running the test itself. + +Again, a reference example can be found in the current *default* revision of +SCons, it is ``test/packaging/sandbox-test/sandbox-test.py``. + +For even more examples you should check out one of the external Tools, e.g. the +*Qt4* Tool at https://bitbucket.org/dirkbaechle/scons_qt4. Also visit the SCons +Tools Index at http://www.scons.org/wiki/ToolsIndex for a complete +list of available Tools, though not all may have tests yet. + +How to convert old tests +######################## + +We now show how to convert a test, still using the ``TestSCons.write()`` method, to +the fixture based approach. For this, we need to get at the files as they +are written to each temporary test folder. + +Luckily, ``runtest.py`` checks for the existence of an environment variable named +``PRESERVE``. If it is set to a non-zero value, the testing framework doesn't delete +the test folder as ususal, but prints its name to the screen. + +So, you should be able to give the commands + +:: + + $ export PRESERVE=1 + $ python runtest.py test/packaging/sandbox-test.py + +, assuming Linux and a bash-like shell. + +The output should then look something like this:: + + 1/1 (100.00%) /usr/bin/python -tt test/packaging/sandbox-test.py + PASSED + Preserved directory /tmp/testcmd.4060.twlYNI + +and you see that the test files have been kept in the folder ``/tmp/testcmd.4060.twlYNI``, +where you can now copy them from to your new *fixture* folder. Then, in the test +script you simply remove all the tedious ``TestSCons.write()`` statements and +replace them by a single ``TestSCons.dir_fixture()``. + +Finally, you shouldn't forget to clean up and remove the temporary test directory. ``;)`` + +Test Infrastructure +=================== + +The test API is in ``QMTest/TestSCons.py``. ``TestSCons`` is a subclass of +``TestCommon``, which is a subclass of ``TestCmd``; all those python files are +in ``QMTest``. Start in ``QMTest/TestCmd.py`` for the base API definitions, +like how to create files (``test.write()``) and run commands (``test.run()``). + +You want to use ``TestSCons`` for the end-to-end tests in ``test``, but ``TestCmd`` +for the unit tests in the ``src`` folder. + +The match functions work like this: + +TestSCons.match_re:: match each line with a RE + * Splits the lines into a list (unless they already are) + * splits the REs at newlines (unless already a list) and puts ^..$ around each + * then each RE must match each line. This means there must be as many REs as lines. + +TestSCons.match_re_dotall:: match all the lines against a single RE + * Joins the lines with newline (unless already a string) + * joins the REs with newline (unless it's a string) and puts ^..$ around the whole thing + * then whole thing must match with python re.DOTALL. + +Use them in a test like this:: + + test.run(..., match=TestSCons.match_re, ...) + +or:: + + test.must_match(..., match=TestSCons.match_re, ...) + +Avoiding Tests based on Tool existence +====================================== + +Here's an easy sample:: + + #!python + intelc = test.detect_tool('intelc', prog='icpc') + if not intelc: + test.skip_test("Could not load 'intelc' Tool; skipping test(s).\n") + +See ``QMTest/TestSCons.py`` for the ``detect_tool`` method. It calls the tool's +``generate()`` method, and then looks for the given prog (tool name by default) in +``env['ENV']['PATH']``. + + diff --git a/runtest.py b/runtest.py index 89ec9ee..0dfa4ea 100644 --- a/runtest.py +++ b/runtest.py @@ -198,6 +198,9 @@ Options: --passed Summarize which tests passed. --qmtest Run using the QMTest harness. -q, --quiet Don't print the test being executed. + -s, --short-progress Short progress, prints only the command line + and a percentage value, based on the total and + current number of tests. --sp PATH The Aegis search path. --spe PATH The Aegis executable search path. -t, --time Print test execution time. @@ -773,6 +776,7 @@ tests = [] def find_Tests_py(directory): result = [] for dirpath, dirnames, filenames in os.walk(directory): + # Skip folders containing a sconstest.skip file if 'sconstest.skip' in filenames: continue for fname in filenames: @@ -783,23 +787,6 @@ def find_Tests_py(directory): def find_py(directory): result = [] for dirpath, dirnames, filenames in os.walk(directory): - if 'sconstest.skip' in filenames: - continue - try: - exclude_fp = open(os.path.join(dirpath, ".exclude_tests")) - except EnvironmentError: - excludes = [] - else: - excludes = [ e.split('#', 1)[0].strip() - for e in exclude_fp.readlines() ] - for fname in filenames: - if fname.endswith(".py") and fname not in excludes: - result.append(os.path.join(dirpath, fname)) - return sorted(result) - -def find_sconstest_py(directory): - result = [] - for dirpath, dirnames, filenames in os.walk(directory): # Skip folders containing a sconstest.skip file if 'sconstest.skip' in filenames: continue @@ -811,7 +798,7 @@ def find_sconstest_py(directory): excludes = [ e.split('#', 1)[0].strip() for e in exclude_fp.readlines() ] for fname in filenames: - if fname.endswith(".py") and fname.startswith("sconstest-") and fname not in excludes: + if fname.endswith(".py") and fname not in excludes: result.append(os.path.join(dirpath, fname)) return sorted(result) @@ -836,7 +823,6 @@ if args: elif path[:4] == 'test': tests.extend(find_py(path)) - tests.extend(find_sconstest_py(path)) else: tests.append(path) elif testlistfile: @@ -858,7 +844,6 @@ elif all and not qmtest: # things correctly.) tests.extend(find_Tests_py('src')) tests.extend(find_py('test')) - tests.extend(find_sconstest_py('test')) if format == '--aegis' and aegis: cmd = "aegis -list -unf pf 2>/dev/null" for line in os.popen(cmd, "r").readlines(): @@ -999,12 +984,11 @@ for idx,t in enumerate(tests): t.command_str)) else: sys.stdout.write(t.command_str + "\n") - if external: - head, tail = os.path.split(t.abspath) - if head: - os.environ['PYTHON_SCRIPT_DIR'] = head - else: - os.environ['PYTHON_SCRIPT_DIR'] = '' + head, tail = os.path.split(t.abspath) + if head: + os.environ['PYTHON_SCRIPT_DIR'] = head + else: + os.environ['PYTHON_SCRIPT_DIR'] = '' test_start_time = time_func() if execute_tests: t.execute() diff --git a/test/README b/test/README index 7c88b39..2c8423f 100644 --- a/test/README +++ b/test/README @@ -53,3 +53,7 @@ semblance of uniformity, here are the naming conventions for tests: option--lo.py long option; abbreviate the long option name to a few characters + +For some more information about running the tests and writing them, please +refer to the documentation for the testing framework. It can be found in +the 'QMTest' folder, as file 'test-framework.rst'. diff --git a/test/packaging/convenience-functions.py b/test/packaging/convenience-functions.py deleted file mode 100644 index 2fc6aee..0000000 --- a/test/packaging/convenience-functions.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -# __COPYRIGHT__ -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -""" -Test the FindInstalledFiles() and the FindSourceFiles() functions. -""" - -import os.path -import TestSCons - -python = TestSCons.python -test = TestSCons.TestSCons() - -test.write( "f1", "" ) -test.write( "f2", "" ) -test.write( "f3", "" ) - -test.write( 'SConstruct', r""" -env = Environment(tools=['default', 'packaging']) -prog = env.Install( 'bin/', ["f1", "f2"] ) -env.File( "f3" ) - -src_files = sorted(map(str, env.FindSourceFiles())) -oth_files = sorted(map(str, env.FindInstalledFiles())) - -print src_files -print oth_files -""") - -bin_f1 = os.path.join('bin', 'f1') -bin_f2 = os.path.join('bin', 'f2') - -bin__f1 = bin_f1.replace('\\', '\\\\') -bin__f2 = bin_f2.replace('\\', '\\\\') - -expect_read = """\ -['SConstruct', 'f1', 'f2', 'f3'] -['%(bin__f1)s', '%(bin__f2)s'] -""" % locals() - -expect_build = """\ -Install file: "f1" as "%(bin_f1)s" -Install file: "f2" as "%(bin_f2)s" -""" % locals() - -expected = test.wrap_stdout(read_str = expect_read, build_str = expect_build) - -test.run(stdout=expected) - -test.pass_test() - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/packaging/convenience-functions/convenience-functions.py b/test/packaging/convenience-functions/convenience-functions.py new file mode 100644 index 0000000..a1be041 --- /dev/null +++ b/test/packaging/convenience-functions/convenience-functions.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test the FindInstalledFiles() and the FindSourceFiles() functions. +""" + +import os.path +import TestSCons + +test = TestSCons.TestSCons() + +test.dir_fixture( "image" ) + +bin_f1 = os.path.join('bin', 'f1') +bin_f2 = os.path.join('bin', 'f2') + +bin__f1 = bin_f1.replace('\\', '\\\\') +bin__f2 = bin_f2.replace('\\', '\\\\') + +expect_read = """\ +['SConstruct', 'f1', 'f2', 'f3'] +['%(bin__f1)s', '%(bin__f2)s'] +""" % locals() + +expect_build = """\ +Install file: "f1" as "%(bin_f1)s" +Install file: "f2" as "%(bin_f2)s" +""" % locals() + +expected = test.wrap_stdout(read_str = expect_read, build_str = expect_build) + +test.run(stdout=expected) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/packaging/convenience-functions/image/SConstruct b/test/packaging/convenience-functions/image/SConstruct new file mode 100644 index 0000000..461961e --- /dev/null +++ b/test/packaging/convenience-functions/image/SConstruct @@ -0,0 +1,10 @@ + +env = Environment(tools=['default', 'packaging']) +prog = env.Install( 'bin/', ["f1", "f2"] ) +env.File( "f3" ) + +src_files = sorted(map(str, env.FindSourceFiles())) +oth_files = sorted(map(str, env.FindInstalledFiles())) + +print src_files +print oth_files diff --git a/test/packaging/convenience-functions/image/f1 b/test/packaging/convenience-functions/image/f1 new file mode 100644 index 0000000..e69de29 diff --git a/test/packaging/convenience-functions/image/f2 b/test/packaging/convenience-functions/image/f2 new file mode 100644 index 0000000..e69de29 diff --git a/test/packaging/convenience-functions/image/f3 b/test/packaging/convenience-functions/image/f3 new file mode 100644 index 0000000..e69de29 diff --git a/test/packaging/sandbox-test.py b/test/packaging/sandbox-test.py deleted file mode 100644 index 7110c87..0000000 --- a/test/packaging/sandbox-test.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# -# __COPYRIGHT__ -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - -""" -Test a simple project -""" - -import TestSCons - -python = TestSCons.python - -test = TestSCons.TestSCons() - -tar = test.detect('TAR', 'tar') - -if not tar: - test.skip_test('tar not found, skipping test\n') - -test.subdir('src') - -test.write([ 'src', 'foobar.h' ], '') -test.write([ 'src', 'foobar.c' ], '') - -test.write('SConstruct', """ -from glob import glob - -src_files = glob( 'src/*.c' ) -include_files = glob( 'src/*.h' ) - -SharedLibrary( 'foobar', src_files ) - -env = Environment(tools=['default', 'packaging']) - -env.Package( NAME = 'libfoobar', - VERSION = '1.2.3', - PACKAGETYPE = 'targz', - source = src_files + include_files ) - -env.Package( NAME = 'libfoobar', - VERSION = '1.2.3', - PACKAGETYPE = 'zip', - source = src_files + include_files ) -""") - -test.run(stderr=None) - -test.must_exist( 'libfoobar-1.2.3.tar.gz' ) -test.must_exist( 'libfoobar-1.2.3.zip' ) - -test.pass_test() - -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/packaging/sandbox-test/SConstruct b/test/packaging/sandbox-test/SConstruct new file mode 100644 index 0000000..f44a471 --- /dev/null +++ b/test/packaging/sandbox-test/SConstruct @@ -0,0 +1,19 @@ + +from glob import glob + +src_files = glob( 'src/*.c' ) +include_files = glob( 'src/*.h' ) + +SharedLibrary( 'foobar', src_files ) + +env = Environment(tools=['default', 'packaging']) + +env.Package( NAME = 'libfoobar', + VERSION = '1.2.3', + PACKAGETYPE = 'targz', + source = src_files + include_files ) + +env.Package( NAME = 'libfoobar', + VERSION = '1.2.3', + PACKAGETYPE = 'zip', + source = src_files + include_files ) diff --git a/test/packaging/sandbox-test/sandbox-test.py b/test/packaging/sandbox-test/sandbox-test.py new file mode 100644 index 0000000..c6d2140 --- /dev/null +++ b/test/packaging/sandbox-test/sandbox-test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test a simple project +""" + +import TestSCons + +python = TestSCons.python + +test = TestSCons.TestSCons() + +tar = test.detect('TAR', 'tar') + +if not tar: + test.skip_test('tar not found, skipping test\n') + +test.dir_fixture('src','src') +test.file_fixture('SConstruct') + +test.run(stderr=None) + +test.must_exist( 'libfoobar-1.2.3.tar.gz' ) +test.must_exist( 'libfoobar-1.2.3.zip' ) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/packaging/sandbox-test/src/foobar.c b/test/packaging/sandbox-test/src/foobar.c new file mode 100644 index 0000000..e69de29 diff --git a/test/packaging/sandbox-test/src/foobar.h b/test/packaging/sandbox-test/src/foobar.h new file mode 100644 index 0000000..e69de29 -- cgit v0.12 From c5f2f746e26c77190109eb4e23c1f18779e27528 Mon Sep 17 00:00:00 2001 From: dirkbaechle Date: Sun, 12 Aug 2012 15:34:33 +0200 Subject: - removed Aegis support --- QMTest/TestRuntest.py | 10 --- runtest.py | 184 +++++--------------------------------------------- 2 files changed, 17 insertions(+), 177 deletions(-) diff --git a/QMTest/TestRuntest.py b/QMTest/TestRuntest.py index ec1c5d7..68563da 100644 --- a/QMTest/TestRuntest.py +++ b/QMTest/TestRuntest.py @@ -125,11 +125,6 @@ class TestRuntest(TestCommon): dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)] - spe = os.environ.get('SCONS_SOURCE_PATH_EXECUTABLE', orig_cwd) - for d in spe.split(os.pathsep): - dirs.append(os.path.join(d, 'build')) - dirs.append(d) - for thing in things_to_copy: for dir in dirs: t = os.path.join(dir, thing) @@ -143,12 +138,7 @@ class TestRuntest(TestCommon): self.program_set(self.workpath(kw['program'])) - for key in os.environ.keys(): - if key[:5] == 'AEGIS': - os.environ[key] = '' - os.environ['PYTHONPATH'] = '' - os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = '' def write_fake_scons_source_tree(self): os.mkdir('src') diff --git a/runtest.py b/runtest.py index 0dfa4ea..9780f1c 100644 --- a/runtest.py +++ b/runtest.py @@ -10,13 +10,6 @@ # By default, it directly uses the modules in the local tree: # ./src/ (source files we ship) and ./QMTest/ (other modules we don't). # -# HOWEVER, now that SCons has Repository support, we don't have -# Aegis copy all of the files into the local tree. So if you're -# using Aegis and want to run tests by hand using this script, you -# must "aecp ." the entire source tree into your local directory -# structure. When you're done with your change, you can then -# "aecpu -unch ." to un-copy any files that you haven't changed. -# # When any -p option is specified, this script assumes it's in a # directory in which a build has been performed, and sets PYTHONPATH # so that it *only* references the modules that have unpacked from @@ -32,12 +25,6 @@ # (not available with the "--qmtest" option!). Then, # only the given folders are searched for test files. # -# --aegis Print test results to an output file (specified -# by the -o option) in the format expected by -# aetest(5). This is intended for use in the -# batch_test_command field in the Aegis project -# config file. -# # -d Debug. Runs the script under the Python # debugger (pdb.py) so you don't have to # muck with PYTHONPATH yourself. @@ -58,7 +45,7 @@ # -n No execute, just print command lines. # # -o file Print test results to the specified file. -# The --aegis and --xml options specify the +# The --xml option specifies the # output format. # # -P Python Use the specified Python interpreter. @@ -79,10 +66,6 @@ # All stdout and stderr messages get suppressed (this # does only work with subprocess though)! # -# --sp The Aegis search path. -# -# --spe The Aegis executable search path. -# # -t Print the execution time of each test. # # -X The scons "script" is an executable; don't @@ -109,31 +92,6 @@ import stat import sys import time -try: - sorted -except NameError: - # Pre-2.4 Python has no sorted() function. - # - # The pre-2.4 Python list.sort() method does not support - # list.sort(key=) nor list.sort(reverse=) keyword arguments, so - # we must implement the functionality of those keyword arguments - # by hand instead of passing them to list.sort(). - def sorted(iterable, cmp=None, key=None, reverse=0): - if key is not None: - result = [(key(x), x) for x in iterable] - else: - result = iterable[:] - if cmp is None: - # Pre-2.3 Python does not support list.sort(None). - result.sort() - else: - result.sort(cmp) - if key is not None: - result = [t1 for t0,t1 in result] - if reverse: - result.reverse() - return result - cwd = os.getcwd() all = 0 @@ -155,8 +113,7 @@ testlistfile = None version = '' print_times = None python = None -sp = None -spe = None +sp = [] print_progress = 1 suppress_stdout = False suppress_stderr = False @@ -167,7 +124,6 @@ Usage: runtest.py [OPTIONS] [TEST ...] Options: -3 Warn about Python 3.x incompatibilities. -a, --all Run all tests. - --aegis Print results in Aegis format. -b BASE, --baseline BASE Run test scripts against baseline BASE. --builddir DIR Directory in which packages were built. -d, --debug Run test scripts under the Python debugger. @@ -201,8 +157,6 @@ Options: -s, --short-progress Short progress, prints only the command line and a percentage value, based on the total and current number of tests. - --sp PATH The Aegis search path. - --spe PATH The Aegis executable search path. -t, --time Print test execution time. -v version Specify the SCons version. --verbose=LEVEL Set verbose level: 1 = print executed commands, @@ -219,11 +173,11 @@ Environment Variables: """ opts, args = getopt.getopt(sys.argv[1:], "3ab:def:hklno:P:p:qsv:Xx:t", - ['all', 'aegis', 'baseline=', 'builddir=', + ['all', 'baseline=', 'builddir=', 'debug', 'external', 'file=', 'help', 'no-progress', 'list', 'no-exec', 'noqmtest', 'nopipefiles', 'output=', 'package=', 'passed', 'python=', 'qmtest', - 'quiet', 'short-progress', 'sp=', 'spe=', 'time', + 'quiet', 'short-progress', 'time', 'version=', 'exec=', 'verbose=', 'xml']) @@ -287,10 +241,6 @@ for o, a in opts: print_progress = 1 suppress_stdout = True suppress_stderr = True - elif o in ['--sp']: - sp = a.split(os.pathsep) - elif o in ['--spe']: - spe = a.split(os.pathsep) elif o in ['-t', '--time']: print_times = 1 elif o in ['--verbose']: @@ -301,7 +251,7 @@ for o, a in opts: scons_exec = 1 elif o in ['-x', '--exec']: scons = a - elif o in ['--aegis', '--xml']: + elif o in ['--xml']: format = o if not args and not all and not testlistfile: @@ -345,39 +295,6 @@ try: qmtest except NameError: qmtest = None - # Old code for using QMTest by default if it's installed. - # We now default to not using QMTest unless explicitly asked for. - #for q in ['qmtest', 'qmtest.py']: - # path = whereis(q) - # if path: - # # The name was found on $PATH; just execute the found name so - # # we don't have to worry about paths containing white space. - # qmtest = q - # break - #if not qmtest: - # msg = ('Warning: found neither qmtest nor qmtest.py on $PATH;\n' + - # '\tassuming --noqmtest option.\n') - # sys.stderr.write(msg) - # sys.stderr.flush() - -aegis = whereis('aegis') - -if format == '--aegis' and aegis: - change = os.popen("aesub '$c' 2>/dev/null", "r").read() - if change: - if sp is None: - paths = os.popen("aesub '$sp' 2>/dev/null", "r").read()[:-1] - sp = paths.split(os.pathsep) - if spe is None: - spe = os.popen("aesub '$spe' 2>/dev/null", "r").read()[:-1] - spe = spe.split(os.pathsep) - else: - aegis = None - -if sp is None: - sp = [] -if spe is None: - spe = [] sp.append(builddir) sp.append(cwd) @@ -575,15 +492,6 @@ else: self.stderr = p.stderr.read() self.status = p.wait() -class Aegis(SystemExecutor): - def header(self, f): - f.write('test_result = [\n') - def write(self, f): - f.write(' { file_name = "%s";\n' % self.path) - f.write(' exit_status = %d; },\n' % self.status) - def footer(self, f): - f.write('];\n') - class XML(PopenExecutor): def header(self, f): f.write(' \n') @@ -602,7 +510,6 @@ class XML(PopenExecutor): format_class = { None : SystemExecutor, - '--aegis' : Aegis, '--xml' : XML, } @@ -657,21 +564,6 @@ else: sd = None ld = None - # XXX: Logic like the following will be necessary once - # we fix runtest.py to run tests within an Aegis change - # without symlinks back to the baseline(s). - # - #if spe: - # if not scons: - # for dir in spe: - # d = os.path.join(dir, 'src', 'script') - # f = os.path.join(d, 'scons.py') - # if os.path.isfile(f): - # sd = d - # scons = f - # spe = map(lambda x: os.path.join(x, 'src', 'engine'), spe) - # ld = os.pathsep.join(spe) - if not baseline or baseline == '.': base = cwd elif baseline == '-': @@ -749,18 +641,13 @@ old_pythonpath = os.environ.get('PYTHONPATH') pythonpaths = [ pythonpath_dir ] for dir in sp: - if format == '--aegis': - q = os.path.join(dir, 'build', 'QMTest') - else: - q = os.path.join(dir, 'QMTest') + q = os.path.join(dir, 'QMTest') pythonpaths.append(q) # Add path of the QMTest folder to PYTHONPATH scriptpath = os.path.dirname(os.path.realpath(__file__)) pythonpaths.append(os.path.join(scriptpath, 'QMTest')) -os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = os.pathsep.join(spe) - os.environ['PYTHONPATH'] = os.pathsep.join(pythonpaths) if old_pythonpath: @@ -803,28 +690,16 @@ def find_py(directory): return sorted(result) if args: - if spe: - for a in args: - if os.path.isabs(a): - tests.extend(glob.glob(a)) + for a in args: + for path in glob.glob(a): + if os.path.isdir(path): + if path[:3] == 'src': + tests.extend(find_Tests_py(path)) + + elif path[:4] == 'test': + tests.extend(find_py(path)) else: - for dir in spe: - x = os.path.join(dir, a) - globs = glob.glob(x) - if globs: - tests.extend(globs) - break - else: - for a in args: - for path in glob.glob(a): - if os.path.isdir(path): - if path[:3] == 'src': - tests.extend(find_Tests_py(path)) - - elif path[:4] == 'test': - tests.extend(find_py(path)) - else: - tests.append(path) + tests.append(path) elif testlistfile: tests = open(testlistfile, 'r').readlines() tests = [x for x in tests if x[0] != '#'] @@ -844,20 +719,6 @@ elif all and not qmtest: # things correctly.) tests.extend(find_Tests_py('src')) tests.extend(find_py('test')) - if format == '--aegis' and aegis: - cmd = "aegis -list -unf pf 2>/dev/null" - for line in os.popen(cmd, "r").readlines(): - a = line.split() - if a[0] == "test" and a[-1] not in tests: - tests.append(Test(a[-1], spe)) - cmd = "aegis -list -unf cf 2>/dev/null" - for line in os.popen(cmd, "r").readlines(): - a = line.split() - if a[0] == "test": - if a[1] == "remove": - tests.remove(a[-1]) - elif a[-1] not in tests: - tests.append(Test(a[-1], spe)) tests.sort() if qmtest: @@ -873,12 +734,6 @@ if qmtest: qmtest_args = [ qmtest, ] - if format == '--aegis': - dir = builddir - if not os.path.isdir(dir): - dir = cwd - qmtest_args.extend(['-D', dir]) - qmtest_args.extend([ 'run', '--output %s' % qmr_file, @@ -898,10 +753,7 @@ if qmtest: rs = '--result-stream="%s(filename=%s)"' % (rsclass, qof) qmtest_args.append(rs) - if format == '--aegis': - tests = [x.replace(cwd+os.sep, '') for x in tests] - else: - os.environ['SCONS'] = os.path.join(cwd, 'src', 'script', 'scons.py') + os.environ['SCONS'] = os.path.join(cwd, 'src', 'script', 'scons.py') cmd = ' '.join(qmtest_args + tests) if printcommand: @@ -1044,9 +896,7 @@ if outputfile: if outputfile != '-': f.close() -if format == '--aegis': - sys.exit(0) -elif len(fail): +if len(fail): sys.exit(1) elif len(no_result): sys.exit(2) -- cgit v0.12