Commits

Anonymous committed b8020b6

- basic merge with source from the external scons-test-framework

Comments (0)

Files changed (3)

QMTest/TestCmd.py

                        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)
     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)
     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'):
         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,
         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):

QMTest/TestSCons.py

         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'):
+                if not self.external:
+                    if os.path.exists('scons'):
+                        kw['program'] = 'scons'
+                    else:
+                        kw['program'] = 'scons.py'
+                else:
                     kw['program'] = 'scons'
-                else:
-                    kw['program'] = 'scons.py'
-            elif not os.path.isabs(kw['program']):
+                    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']
 
         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):
         """
         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):
         """
     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
         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
+        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']
 
-        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
 #
 #       -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
 #                       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.
 #                       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.
 all = 0
 baseline = 0
 builddir = os.path.join(cwd, 'build')
+external = 0
 debug = ''
 execute_tests = 1
 format = None
 python = None
 sp = None
 spe = None
+print_progress = 1
+suppress_stdout = False
+suppress_stderr = False
 
 helpstr = """\
 Usage: runtest.py [OPTIONS] [TEST ...]
   -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.
   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'])
 
             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)
     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']:
             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']:
     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
 
 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
 
     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
 
 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
         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)
 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))
 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:
                 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:
 
                     elif path[:4] == 'test':
                         tests.extend(find_py(path))
+                    tests.extend(find_sconstest_py(path))
                 else:
                     tests.append(path)
 elif testlistfile:
     # 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():
     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')
     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:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.