Merge back from checkpoint.
[scons.git] / QMTest / TestSCons.py
index d66bd931b7301fe19d9906262799fe5e225a1528..d8dda8a9bc4d4bf2ab65ba395c28eb98b861efd9 100644 (file)
@@ -34,6 +34,14 @@ except AttributeError:
         return result
     __builtin__.zip = zip
 
+try:
+    x = True
+except NameError:
+    True = not 0
+    False = not 1
+else:
+    del x
+
 from TestCommon import *
 from TestCommon import __all__
 
@@ -44,7 +52,7 @@ from TestCommon import __all__
 
 default_version = '1.2.0'
 
-copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009'
+copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010'
 
 # In the checked-in source, the value of SConsVersion in the following
 # line must remain "__ VERSION __" (without the spaces) so the built
@@ -233,6 +241,8 @@ class TestSCons(TestCommon):
                     kw['program'] = 'scons'
                 else:
                     kw['program'] = 'scons.py'
+            elif not os.path.isabs(kw['program']):
+                kw['program'] = os.path.join(self.orig_cwd, kw['program'])
         if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
             kw['interpreter'] = [python, '-tt']
         if not kw.has_key('match'):
@@ -350,15 +360,23 @@ class TestSCons(TestCommon):
         Add the --warn=no-python-version option to SCONSFLAGS every
         command so test scripts don't have to filter out Python version
         deprecation warnings.
+        Same for --warn=no-visual-c-missing.
         """
         save_sconsflags = os.environ.get('SCONSFLAGS')
+        if save_sconsflags:
+            sconsflags = [save_sconsflags]
+        else:
+            sconsflags = []
         if self.ignore_python_version and deprecated_python_version():
-            if save_sconsflags:
-                sconsflags = [save_sconsflags]
-            else:
-                sconsflags = []
             sconsflags = sconsflags + ['--warn=no-python-version']
-            os.environ['SCONSFLAGS'] = string.join(sconsflags)
+        # Provide a way to suppress or provide alternate flags for
+        # TestSCons purposes by setting TESTSCONS_SCONSFLAGS.
+        # (The intended use case is to set it to null when running
+        # timing tests of earlier versions of SCons which don't
+        # support the --warn=no-visual-c-missing warning.)
+        sconsflags = sconsflags + [os.environ.get('TESTSCONS_SCONSFLAGS',
+                                                  '--warn=no-visual-c-missing')]
+        os.environ['SCONSFLAGS'] = string.join(sconsflags)
         try:
             result = apply(TestCommon.run, (self,)+args, kw)
         finally:
@@ -396,6 +414,24 @@ class TestSCons(TestCommon):
         kw['match'] = self.match_re_dotall
         apply(self.run, [], kw)
 
+    def option_not_yet_implemented(self, option, arguments=None, **kw):
+        """
+        Verifies expected behavior for options that are not yet implemented:
+        a warning message, and exit status 1.
+        """
+        msg = "Warning:  the %s option is not yet implemented\n" % option
+        kw['stderr'] = msg
+        if arguments:
+            # If it's a long option and the argument string begins with '=',
+            # it's of the form --foo=bar and needs no separating space.
+            if option[:2] == '--' and arguments[0] == '=':
+                kw['arguments'] = option + arguments
+            else:
+                kw['arguments'] = option + ' ' + arguments
+        # TODO(1.5)
+        #return self.run(**kw)
+        return apply(self.run, (), kw)
+
     def diff_substr(self, expect, actual, prelen=20, postlen=40):
         i = 0
         for x, y in zip(expect, actual):
@@ -639,7 +675,8 @@ import getopt
 import sys
 import string
 import re
-cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
+# -w and -z are fake options used in test/QT/QTFLAGS.py
+cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:wz', [])
 output = None
 impl = 0
 opt_string = ''
@@ -647,6 +684,7 @@ for opt, arg in cmd_opts:
     if opt == '-o': output = open(arg, 'wb')
     elif opt == '-i': impl = 1
     else: opt_string = opt_string + ' ' + opt
+output.write("/* mymoc.py%s */\\n" % opt_string)
 for a in args:
     contents = open(a, 'rb').read()
     a = string.replace(a, '\\\\', '\\\\\\\\')
@@ -667,6 +705,7 @@ output_arg = 0
 impl_arg = 0
 impl = None
 source = None
+opt_string = ''
 for arg in sys.argv[1:]:
     if output_arg:
         output = open(arg, 'wb')
@@ -678,11 +717,14 @@ for arg in sys.argv[1:]:
         output_arg = 1
     elif arg == "-impl":
         impl_arg = 1
+    elif arg[0:1] == "-":
+        opt_string = opt_string + ' ' + arg
     else:
         if source:
             sys.exit(1)
         source = open(arg, 'rb')
         sourceFile = arg
+output.write("/* myuic.py%s */\\n" % opt_string)
 if impl:
     output.write( '#include "' + impl + '"\\n' )
     includes = re.findall('<include.*?>(.*?)</include>', source.read())
@@ -705,7 +747,7 @@ void my_qt_symbol(const char *arg);
 #include "../include/my_qobject.h"
 #include <stdio.h>
 void my_qt_symbol(const char *arg) {
-  printf( arg );
+  fputs( arg, stdout );
 }
 """)
 
@@ -917,6 +959,18 @@ print py_ver
 
         return [python] + string.split(string.strip(self.stdout()), '\n')
 
+    def start(self, *args, **kw):
+        """
+        Starts SCons in the test environment.
+
+        This method exists to tell Test{Cmd,Common} that we're going to
+        use standard input without forcing every .start() call in the
+        individual tests to do so explicitly.
+        """
+        if not kw.has_key('stdin'):
+            kw['stdin'] = True
+        return apply(TestCommon.start, (self,) + args, kw)
+
     def wait_for(self, fname, timeout=10.0, popen=None):
         """
         Waits for the specified file name to exist.
@@ -947,6 +1001,37 @@ print py_ver
         return alt_cpp_suffix
 
 
+class Stat:
+    def __init__(self, name, units, expression, convert=None):
+        if convert is None:
+            convert = lambda x: x
+        self.name = name
+        self.units = units
+        self.expression = re.compile(expression)
+        self.convert = convert
+
+StatList = [
+    Stat('memory-initial', 'kbytes',
+         r'Memory before reading SConscript files:\s+(\d+)',
+         convert=lambda s: int(s) / 1024),
+    Stat('memory-prebuild', 'kbytes',
+         r'Memory before building targets:\s+(\d+)',
+         convert=lambda s: int(s) / 1024),
+    Stat('memory-final', 'kbytes',
+         r'Memory after building targets:\s+(\d+)',
+         convert=lambda s: int(s) / 1024),
+
+    Stat('time-sconscript', 'seconds',
+         r'Total SConscript file execution time:\s+([\d.]+) seconds'),
+    Stat('time-scons', 'seconds',
+         r'Total SCons execution time:\s+([\d.]+) seconds'),
+    Stat('time-commands', 'seconds',
+         r'Total command execution time:\s+([\d.]+) seconds'),
+    Stat('time-total', 'seconds',
+         r'Total build time:\s+([\d.]+) seconds'),
+]
+
+
 class TimeSCons(TestSCons):
     """Class for timing SCons."""
     def __init__(self, *args, **kw):
@@ -957,8 +1042,25 @@ class TimeSCons(TestSCons):
         directory containing the executing script to the temporary
         working directory.
         """
-        if not kw.has_key('verbose'):
+        self.variables = kw.get('variables')
+        if self.variables is not None:
+            for variable, value in self.variables.items():
+                value = os.environ.get(variable, value)
+                try:
+                    value = int(value)
+                except ValueError:
+                    try:
+                        value = float(value)
+                    except ValueError:
+                        pass
+                self.variables[variable] = value
+            del kw['variables']
+
+        self.calibrate = os.environ.get('TIMESCONS_CALIBRATE', '0') != '0'
+
+        if not kw.has_key('verbose') and not self.calibrate:
             kw['verbose'] = True
+
         # TODO(1.5)
         #TestSCons.__init__(self, *args, **kw)
         apply(TestSCons.__init__, (self,)+args, kw)
@@ -988,13 +1090,68 @@ class TimeSCons(TestSCons):
         The elapsed time to execute each build is printed after
         it has finished.
         """
-        # TODO(1.5)
-        #self.help(*args, **kw)
-        #self.full(*args, **kw)
-        #self.null(*args, **kw)
-        apply(self.help, args, kw)
-        apply(self.full, args, kw)
-        apply(self.null, args, kw)
+        if not kw.has_key('options') and self.variables:
+            options = []
+            for variable, value in self.variables.items():
+                options.append('%s=%s' % (variable, value))
+            kw['options'] = ' '.join(options)
+        if self.calibrate:
+            # TODO(1.5)
+            #self.calibration(*args, **kw)
+            apply(self.calibration, args, kw)
+        else:
+            self.uptime()
+            # TODO(1.5)
+            #self.help(*args, **kw)
+            #self.full(*args, **kw)
+            #self.null(*args, **kw)
+            apply(self.help, args, kw)
+            apply(self.full, args, kw)
+            apply(self.null, args, kw)
+
+    def trace(self, graph, name, value, units, sort=None):
+        fmt = "TRACE: graph=%s name=%s value=%s units=%s"
+        line = fmt % (graph, name, value, units)
+        if sort is not None:
+          line = line + (' sort=%s' % sort)
+        line = line + '\n'
+        sys.stdout.write(line)
+        sys.stdout.flush()
+
+    def report_traces(self, trace, stats):
+        self.trace('TimeSCons-elapsed',
+                   trace,
+                   self.elapsed_time(),
+                   "seconds",
+                   sort=0)
+        for name, args in stats.items():
+            # TODO(1.5)
+            #self.trace(name, trace, *args)
+            apply(self.trace, (name, trace), args)
+
+    def uptime(self):
+        try:
+            fp = open('/proc/loadavg')
+        except EnvironmentError:
+            pass
+        else:
+            avg1, avg5, avg15 = fp.readline().split(" ")[:3]
+            fp.close()
+            self.trace('load-average',  'average1', avg1, 'processes')
+            self.trace('load-average',  'average5', avg5, 'processes')
+            self.trace('load-average',  'average15', avg15, 'processes')
+
+    def collect_stats(self, input):
+        result = {}
+        for stat in StatList:
+            m = stat.expression.search(input)
+            if m:
+                value = stat.convert(m.group(1))
+                # The dict keys match the keyword= arguments
+                # of the trace() method above so they can be
+                # applied directly to that call.
+                result[stat.name] = {'value':value, 'units':stat.units}
+        return result
 
     def help(self, *args, **kw):
         """
@@ -1005,11 +1162,19 @@ class TimeSCons(TestSCons):
         "real work" is done.
         """
         kw['options'] = kw.get('options', '') + ' --help'
+        # Ignore the exit status.  If the --help run dies, we just
+        # won't report any statistics for it, but we can still execute
+        # the full and null builds.
+        kw['status'] = None
         # TODO(1.5)
         #self.run(*args, **kw)
         apply(self.run, args, kw)
         sys.stdout.write(self.stdout())
-        print "RESULT", self.elapsed_time()
+        stats = self.collect_stats(self.stdout())
+        # Delete the time-commands, since no commands are ever
+        # executed on the help run and it is (or should be) always 0.0.
+        del stats['time-commands']
+        self.report_traces('help', stats)
 
     def full(self, *args, **kw):
         """
@@ -1019,7 +1184,29 @@ class TimeSCons(TestSCons):
         #self.run(*args, **kw)
         apply(self.run, args, kw)
         sys.stdout.write(self.stdout())
-        print "RESULT", self.elapsed_time()
+        stats = self.collect_stats(self.stdout())
+        self.report_traces('full', stats)
+        # TODO(1.5)
+        #self.trace('full-memory', 'initial', **stats['memory-initial'])
+        #self.trace('full-memory', 'prebuild', **stats['memory-prebuild'])
+        #self.trace('full-memory', 'final', **stats['memory-final'])
+        apply(self.trace, ('full-memory', 'initial'), stats['memory-initial'])
+        apply(self.trace, ('full-memory', 'prebuild'), stats['memory-prebuild'])
+        apply(self.trace, ('full-memory', 'final'), stats['memory-final'])
+
+    def calibration(self, *args, **kw):
+        """
+        Runs a full build of SCons, but only reports calibration
+        information (the variable(s) that were set for this configuration,
+        and the elapsed time to run.
+        """
+        # TODO(1.5)
+        #self.run(*args, **kw)
+        apply(self.run, args, kw)
+        if self.variables:
+            for variable, value in self.variables.items():
+                sys.stdout.write('VARIABLE: %s=%s\n' % (variable, value))
+        sys.stdout.write('ELAPSED: %s\n' % self.elapsed_time())
 
     def null(self, *args, **kw):
         """
@@ -1033,7 +1220,22 @@ class TimeSCons(TestSCons):
         kw['arguments'] = '.'
         apply(self.up_to_date, (), kw)
         sys.stdout.write(self.stdout())
-        print "RESULT", self.elapsed_time()
+        stats = self.collect_stats(self.stdout())
+        # time-commands should always be 0.0 on a null build, because
+        # no commands should be executed.  Remove it from the stats
+        # so we don't trace it, but only if it *is* 0 so that we'll
+        # get some indication if a supposedly-null build actually does
+        # build something.
+        if float(stats['time-commands']['value']) == 0.0:
+            del stats['time-commands']
+        self.report_traces('null', stats)
+        # TODO(1.5)
+        #self.trace('null-memory', 'initial', **stats['memory-initial'])
+        #self.trace('null-memory', 'prebuild', **stats['memory-prebuild'])
+        #self.trace('null-memory', 'final', **stats['memory-final'])
+        apply(self.trace, ('null-memory', 'initial'), stats['memory-initial'])
+        apply(self.trace, ('null-memory', 'prebuild'), stats['memory-prebuild'])
+        apply(self.trace, ('null-memory', 'final'), stats['memory-final'])
 
     def elapsed_time(self):
         """