2 TestSCons.py: a testing framework for the SCons software construction
5 A TestSCons environment object is created via the usual invocation:
9 TestScons is a subclass of TestCommon, which is in turn is a subclass
10 of TestCmd), and hence has available all of the methods and attributes
11 from those classes, as well as any overridden or additional methods or
12 attributes defined in this subclass.
17 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
29 except AttributeError:
32 for i in xrange(len(lists[0])):
33 result.append(tuple(map(lambda l, i=i: l[i], lists)))
37 from TestCommon import *
38 from TestCommon import __all__
40 # Some tests which verify that SCons has been packaged properly need to
41 # look for specific version file names. Replicating the version number
42 # here provides some independent verification that what we packaged
43 # conforms to what we expect.
45 default_version = '1.2.0'
47 copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009'
49 # In the checked-in source, the value of SConsVersion in the following
50 # line must remain "__ VERSION __" (without the spaces) so the built
51 # version in build/QMTest/TestSCons.py contains the actual version
52 # string of the packages that have been built.
53 SConsVersion = '__VERSION__'
54 if SConsVersion == '__' + 'VERSION' + '__':
55 SConsVersion = default_version
57 __all__.extend([ 'TestSCons',
78 except AttributeError:
79 # Windows doesn't have a uname() function. We could use something like
80 # sys.platform as a fallback, but that's not really a "machine," so
81 # just leave it as None.
85 machine = machine_map.get(machine, machine)
87 python = python_executable
88 _python_ = '"' + python_executable + '"'
99 """Test whether -lfrtbegin is required. This can probably be done in
100 a more reliable way, but using popen3 is relatively efficient."""
110 stderr = popen2.popen3(cmd)[2]
114 p = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE)
117 for l in stderr.readlines():
118 list = string.split(l)
119 if len(list) > 3 and list[:2] == ['gcc', 'version']:
120 if list[2][:3] in ('4.1','4.2','4.3'):
121 libs = ['gfortranbegin']
123 if list[2][:2] in ('3.', '4.'):
124 libs = ['frtbegin'] + libs
129 if sys.platform == 'cygwin':
130 # On Cygwin, os.path.normcase() lies, so just report back the
131 # fact that the underlying Win32 OS is case-insensitive.
132 def case_sensitive_suffixes(s1, s2):
135 def case_sensitive_suffixes(s1, s2):
136 return (os.path.normcase(s1) != os.path.normcase(s2))
139 if sys.platform == 'win32':
140 fortran_lib = gccFortranLibs()
141 elif sys.platform == 'cygwin':
142 fortran_lib = gccFortranLibs()
143 elif string.find(sys.platform, 'irix') != -1:
144 fortran_lib = ['ftn']
146 fortran_lib = gccFortranLibs()
150 file_expr = r"""File "[^"]*", line \d+, in .+
153 # re.escape escapes too much.
155 for c in ['.', '[', ']', '(', ')', '*', '+', '?']: # Not an exhaustive list.
156 str = string.replace(str, c, '\\' + c)
163 except AttributeError:
164 # Pre-1.6 Python has no sys.version_info
165 version_string = string.split(sys.version)[0]
166 version_ints = map(int, string.split(version_string, '.'))
167 sys.version_info = tuple(version_ints + ['final', 0])
169 def python_version_string():
170 return string.split(sys.version)[0]
172 def python_minor_version_string():
173 return sys.version[:3]
175 def unsupported_python_version(version=sys.version_info):
176 return version < (1, 5, 2)
178 def deprecated_python_version(version=sys.version_info):
179 return version < (2, 4, 0)
181 if deprecated_python_version():
183 scons: warning: Support for pre-2.4 Python (%s) is deprecated.
184 If this will cause hardship, contact dev@scons.tigris.org.
187 deprecated_python_expr = re_escape(msg % python_version_string()) + file_expr
190 deprecated_python_expr = ""
194 class TestSCons(TestCommon):
195 """Class for testing SCons.
197 This provides a common place for initializing SCons tests,
198 eliminating the need to begin every test with the same repeated
202 scons_version = SConsVersion
204 def __init__(self, **kw):
205 """Initialize an SCons testing object.
207 If they're not overridden by keyword arguments, this
208 initializes the object with the following default values:
210 program = 'scons' if it exists,
212 interpreter = 'python'
216 The workdir value means that, by default, a temporary workspace
217 directory is created for a TestSCons environment. In addition,
218 this method changes directory (chdir) to the workspace directory,
219 so an explicit "chdir = '.'" on all of the run() method calls
222 self.orig_cwd = os.getcwd()
224 script_dir = os.environ['SCONS_SCRIPT_DIR']
229 if not kw.has_key('program'):
230 kw['program'] = os.environ.get('SCONS')
231 if not kw['program']:
232 if os.path.exists('scons'):
233 kw['program'] = 'scons'
235 kw['program'] = 'scons.py'
236 if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
237 kw['interpreter'] = [python, '-tt']
238 if not kw.has_key('match'):
239 kw['match'] = match_exact
240 if not kw.has_key('workdir'):
243 # Term causing test failures due to bogus readline init
244 # control character output on FC8
245 # TERM can cause test failures due to control chars in prompts etc.
246 os.environ['TERM'] = 'dumb'
248 self.ignore_python_version=kw.get('ignore_python_version',1)
249 if kw.get('ignore_python_version',-1) != -1:
250 del kw['ignore_python_version']
252 if self.ignore_python_version and deprecated_python_version():
253 sconsflags = os.environ.get('SCONSFLAGS')
255 sconsflags = [sconsflags]
258 sconsflags = sconsflags + ['--warn=no-python-version']
259 os.environ['SCONSFLAGS'] = string.join(sconsflags)
261 apply(TestCommon.__init__, [self], kw)
264 if SCons.Node.FS.default_fs is None:
265 SCons.Node.FS.default_fs = SCons.Node.FS.FS()
267 def Environment(self, ENV=None, *args, **kw):
269 Return a construction Environment that optionally overrides
270 the default external environment with the specified ENV.
272 import SCons.Environment
277 return apply(SCons.Environment.Environment, args, kw)
278 except (SCons.Errors.UserError, SCons.Errors.InternalError):
281 def detect(self, var, prog=None, ENV=None, norm=None):
283 Detect a program named 'prog' by first checking the construction
284 variable named 'var' and finally searching the path used by
285 SCons. If either method fails to detect the program, then false
286 is returned, otherwise the full path to prog is returned. If
287 prog is None, then the value of the environment variable will be
290 env = self.Environment(ENV)
291 v = env.subst('$'+var)
298 result = env.WhereIs(prog)
299 if norm and os.sep != '/':
300 result = string.replace(result, os.sep, '/')
303 def detect_tool(self, tool, prog=None, ENV=None):
305 Given a tool (i.e., tool specification that would be passed
306 to the "tools=" parameter of Environment()) and a program that
307 corresponds to that tool, return true if and only if we can find
308 that tool using Environment.Detect().
310 By default, prog is set to the value passed into the tools parameter.
315 env = self.Environment(ENV, tools=[tool])
318 return env.Detect([prog])
320 def where_is(self, prog, path=None):
322 Given a program, search for it in the specified external PATH,
323 or in the actual external PATH is none is specified.
325 import SCons.Environment
326 env = SCons.Environment.Environment()
328 path = os.environ['PATH']
329 return env.WhereIs(prog, path)
331 def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0):
332 """Wraps standard output string(s) in the normal
333 "Reading ... done" and "Building ... done" strings
335 cap,lc = [ ('Build','build'),
336 ('Clean','clean') ][cleaning]
338 term = "scons: %sing terminated because of errors.\n" % lc
340 term = "scons: done %sing targets.\n" % lc
341 return "scons: Reading SConscript files ...\n" + \
343 "scons: done reading SConscript files.\n" + \
344 "scons: %sing targets ...\n" % cap + \
348 def run(self, *args, **kw):
350 Add the --warn=no-python-version option to SCONSFLAGS every
351 command so test scripts don't have to filter out Python version
352 deprecation warnings.
354 save_sconsflags = os.environ.get('SCONSFLAGS')
355 if self.ignore_python_version and deprecated_python_version():
357 sconsflags = [save_sconsflags]
360 sconsflags = sconsflags + ['--warn=no-python-version']
361 os.environ['SCONSFLAGS'] = string.join(sconsflags)
363 result = apply(TestCommon.run, (self,)+args, kw)
365 sconsflags = save_sconsflags
368 def up_to_date(self, options = None, arguments = None, read_str = "", **kw):
370 for arg in string.split(arguments):
371 s = s + "scons: `%s' is up to date.\n" % arg
373 arguments = options + " " + arguments
374 kw['arguments'] = arguments
375 stdout = self.wrap_stdout(read_str = read_str, build_str = s)
376 # Append '.*' so that timing output that comes after the
377 # up-to-date output is okay.
378 kw['stdout'] = re.escape(stdout) + '.*'
379 kw['match'] = self.match_re_dotall
380 apply(self.run, [], kw)
382 def not_up_to_date(self, options = None, arguments = None, **kw):
383 """Asserts that none of the targets listed in arguments is
384 up to date, but does not make any assumptions on other targets.
385 This function is most useful in conjunction with the -n option.
388 for arg in string.split(arguments):
389 s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg)
391 arguments = options + " " + arguments
392 s = '('+s+'[^\n]*\n)*'
393 kw['arguments'] = arguments
394 stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE'))
395 kw['stdout'] = string.replace(stdout, 'ARGUMENTSGOHERE', s)
396 kw['match'] = self.match_re_dotall
397 apply(self.run, [], kw)
399 def diff_substr(self, expect, actual, prelen=20, postlen=40):
401 for x, y in zip(expect, actual):
403 return "Actual did not match expect at char %d:\n" \
406 % (i, repr(expect[i-prelen:i+postlen]),
407 repr(actual[i-prelen:i+postlen]))
409 return "Actual matched the expected output???"
411 def python_file_line(self, file, line):
413 Returns a Python error line for output comparisons.
415 The exec of the traceback line gives us the correct format for
416 this version of Python. Before 2.5, this yielded:
418 File "<string>", line 1, ?
420 Python 2.5 changed this to:
422 File "<string>", line 1, <module>
424 We stick the requested file name and line number in the right
425 places, abstracting out the version difference.
427 exec 'import traceback; x = traceback.format_stack()[-1]'
429 x = string.replace(x, '<string>', file)
430 x = string.replace(x, 'line 1,', 'line %s,' % line)
433 def normalize_pdf(self, s):
434 s = re.sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
435 r'/\1Date (D:XXXX)', s)
436 s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
437 r'/ID [<XXXX> <XXXX>]', s)
438 s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
440 s = re.sub(r'/Length \d+ *\n/Filter /FlateDecode\n',
441 r'/Length XXXX\n/Filter /FlateDecode\n', s)
449 begin_marker = '/FlateDecode\n>>\nstream\n'
450 end_marker = 'endstream\nendobj'
453 b = string.find(s, begin_marker, 0)
455 b = b + len(begin_marker)
456 e = string.find(s, end_marker, b)
457 encoded.append((b, e))
458 b = string.find(s, begin_marker, e + len(end_marker))
464 d = zlib.decompress(s[b:e])
465 d = re.sub(r'%%CreationDate: [^\n]*\n',
466 r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
467 d = re.sub(r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d',
468 r'%DVIPSSource: TeX output 1970.01.01:0000', d)
469 d = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
474 s = string.join(r, '')
478 def paths(self,patterns):
488 def java_ENV(self, version=None):
490 Initialize with a default external environment that uses a local
491 Java SDK in preference to whatever's found in the default PATH.
494 return self._java_env[version]['ENV']
495 except AttributeError:
500 import SCons.Environment
501 env = SCons.Environment.Environment()
502 self._java_env[version] = env
507 '/usr/java/jdk%s*/bin' % version,
508 '/usr/lib/jvm/*-%s*/bin' % version,
509 '/usr/local/j2sdk%s*/bin' % version,
511 java_path = self.paths(patterns) + [env['ENV']['PATH']]
514 '/usr/java/latest/bin',
515 '/usr/lib/jvm/*/bin',
516 '/usr/local/j2sdk*/bin',
518 java_path = self.paths(patterns) + [env['ENV']['PATH']]
520 env['ENV']['PATH'] = string.join(java_path, os.pathsep)
523 def java_where_includes(self,version=None):
525 Return java include paths compiling java jni code
531 frame = '/System/Library/Frameworks/JavaVM.framework/Headers/jni.h'
533 frame = '/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version
534 jni_dirs = ['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
535 '/usr/java/jdk%s*/include/jni.h'%version,
538 dirs = self.paths(jni_dirs)
541 d=os.path.dirname(self.paths(jni_dirs)[0])
544 if sys.platform == 'win32':
545 result.append(os.path.join(d,'win32'))
546 elif sys.platform == 'linux2':
547 result.append(os.path.join(d,'linux'))
551 def java_where_java_home(self,version=None):
552 if sys.platform[:6] == 'darwin':
554 home = '/System/Library/Frameworks/JavaVM.framework/Home'
556 home = '/System/Library/Frameworks/JavaVM.framework/Versions/%s/Home' % version
558 jar = self.java_where_jar(version)
559 home = os.path.normpath('%s/..'%jar)
560 if os.path.isdir(home):
562 print("Could not determine JAVA_HOME: %s is not a directory" % home)
565 def java_where_jar(self, version=None):
566 ENV = self.java_ENV(version)
567 if self.detect_tool('jar', ENV=ENV):
568 where_jar = self.detect('JAR', 'jar', ENV=ENV)
570 where_jar = self.where_is('jar', ENV['PATH'])
572 self.skip_test("Could not find Java jar, skipping test(s).\n")
575 def java_where_java(self, version=None):
577 Return a path to the java executable.
579 ENV = self.java_ENV(version)
580 where_java = self.where_is('java', ENV['PATH'])
582 self.skip_test("Could not find Java java, skipping test(s).\n")
585 def java_where_javac(self, version=None):
587 Return a path to the javac compiler.
589 ENV = self.java_ENV(version)
590 if self.detect_tool('javac'):
591 where_javac = self.detect('JAVAC', 'javac', ENV=ENV)
593 where_javac = self.where_is('javac', ENV['PATH'])
595 self.skip_test("Could not find Java javac, skipping test(s).\n")
596 self.run(program = where_javac,
597 arguments = '-version',
601 if string.find(self.stderr(), 'javac %s' % version) == -1:
602 fmt = "Could not find javac for Java version %s, skipping test(s).\n"
603 self.skip_test(fmt % version)
605 m = re.search(r'javac (\d\.\d)', self.stderr())
610 return where_javac, version
612 def java_where_javah(self, version=None):
613 ENV = self.java_ENV(version)
614 if self.detect_tool('javah'):
615 where_javah = self.detect('JAVAH', 'javah', ENV=ENV)
617 where_javah = self.where_is('javah', ENV['PATH'])
619 self.skip_test("Could not find Java javah, skipping test(s).\n")
622 def java_where_rmic(self, version=None):
623 ENV = self.java_ENV(version)
624 if self.detect_tool('rmic'):
625 where_rmic = self.detect('RMIC', 'rmic', ENV=ENV)
627 where_rmic = self.where_is('rmic', ENV['PATH'])
629 self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
632 def Qt_dummy_installation(self, dir='qt'):
633 # create a dummy qt installation
635 self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] )
637 self.write([dir, 'bin', 'mymoc.py'], """\
642 cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
646 for opt, arg in cmd_opts:
647 if opt == '-o': output = open(arg, 'wb')
648 elif opt == '-i': impl = 1
649 else: opt_string = opt_string + ' ' + opt
651 contents = open(a, 'rb').read()
652 a = string.replace(a, '\\\\', '\\\\\\\\')
653 subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
655 contents = re.sub( r'#include.*', '', contents )
656 output.write(string.replace(contents, 'Q_OBJECT', subst))
661 self.write([dir, 'bin', 'myuic.py'], """\
670 for arg in sys.argv[1:]:
672 output = open(arg, 'wb')
684 source = open(arg, 'rb')
687 output.write( '#include "' + impl + '"\\n' )
688 includes = re.findall('<include.*?>(.*?)</include>', source.read())
689 for incFile in includes:
690 # this is valid for ui.h files, at least
691 if os.path.exists(incFile):
692 output.write('#include "' + incFile + '"\\n')
694 output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
699 self.write([dir, 'include', 'my_qobject.h'], r"""
701 void my_qt_symbol(const char *arg);
704 self.write([dir, 'lib', 'my_qobject.cpp'], r"""
705 #include "../include/my_qobject.h"
707 void my_qt_symbol(const char *arg) {
712 self.write([dir, 'lib', 'SConstruct'], r"""
715 if sys.platform == 'win32':
716 env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
718 env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
721 self.run(chdir = self.workpath(dir, 'lib'),
724 match = self.match_re_dotall)
726 self.QT = self.workpath(dir)
728 self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py'))
729 self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py'))
730 self.QT_LIB_DIR = self.workpath(dir, 'lib')
732 def Qt_create_SConstruct(self, place):
733 if type(place) is type([]):
734 place = apply(test.workpath, place)
735 self.write(place, """\
736 if ARGUMENTS.get('noqtdir', 0): QTDIR=None
738 env = Environment(QTDIR = QTDIR,
742 tools=['default','qt'])
744 if ARGUMENTS.get('variant_dir', 0):
745 if ARGUMENTS.get('chdir', 0):
749 dup=int(ARGUMENTS.get('dup', 1))
751 builddir = 'build_dup0'
755 VariantDir(builddir, '.', duplicate=dup)
757 sconscript = Dir(builddir).File('SConscript')
759 sconscript = File('SConscript')
761 SConscript( sconscript )
762 """ % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC))
765 NCR = 0 # non-cached rebuild
766 CR = 1 # cached rebuild (up to date)
767 NCF = 2 # non-cached build failure
768 CF = 3 # cached build failure
770 if sys.platform == 'win32':
771 Configure_lib = 'msvcrt'
775 # to use cygwin compilers on cmd.exe -> uncomment following line
778 def checkLogAndStdout(self, checks, results, cached,
779 logfile, sconf_dir, sconstruct,
780 doCheckLog=1, doCheckStdout=1):
783 def __init__(self, p):
786 def matchPart(log, logfile, lastEnd, NoMatch=NoMatch):
787 m = re.match(log, logfile[lastEnd:])
789 raise NoMatch, lastEnd
790 return m.end() + lastEnd
792 #print len(os.linesep)
795 for i in range(len(ls)):
799 nols = nols + "[^" + ls[i] + "])"
804 logfile = self.read(self.workpath(logfile))
806 string.find( logfile, "scons: warning: The stored build "
807 "information has an unexpected class." ) >= 0):
809 sconf_dir = sconf_dir
810 sconstruct = sconstruct
812 log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
813 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
814 log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
815 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
818 for check,result,cache_desc in zip(checks, results, cached):
819 log = re.escape("scons: Configure: " + check) + ls
820 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
823 for bld_desc in cache_desc: # each TryXXX
824 for ext, flag in bld_desc: # each file in TryBuild
825 file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
828 if ext in ['.c', '.cpp']:
829 log=log + re.escape(file + " <-") + ls
830 log=log + r"( \|" + nols + "*" + ls + ")+?"
832 log=log + "(" + nols + "*" + ls +")*?"
837 re.escape("scons: Configure: \"%s\" is up to date."
839 log=log+re.escape("scons: Configure: The original builder "
841 log=log+r"( \|.*"+ls+")+"
843 # non-cached rebuild failure
844 log=log + "(" + nols + "*" + ls + ")*?"
847 # cached rebuild failure
849 re.escape("scons: Configure: Building \"%s\" failed "
850 "in a previous run and all its sources are"
851 " up to date." % file) + ls
852 log=log+re.escape("scons: Configure: The original builder "
854 log=log+r"( \|.*"+ls+")+"
857 result = "(cached) " + result
858 rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
859 log=log + re.escape("scons: Configure: " + result) + ls + ls
860 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
862 if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
863 if doCheckLog and lastEnd != len(logfile):
864 raise NoMatch, lastEnd
867 print "Cannot match log file against log regexp."
869 print "------------------------------------------------------"
870 print logfile[m.pos:]
871 print "------------------------------------------------------"
873 print "------------------------------------------------------"
875 print "------------------------------------------------------"
879 exp_stdout = self.wrap_stdout(".*", rdstr)
880 if not self.match_re_dotall(self.stdout(), exp_stdout):
881 print "Unexpected stdout: "
882 print "-----------------------------------------------------"
883 print repr(self.stdout())
884 print "-----------------------------------------------------"
885 print repr(exp_stdout)
886 print "-----------------------------------------------------"
889 def get_python_version(self):
891 Returns the Python version (just so everyone doesn't have to
892 hand-code slicing the right number of characters).
894 # see also sys.prefix documentation
895 return python_minor_version_string()
897 def get_platform_python_info(self):
899 Returns a path to a Python executable suitable for testing on
900 this platform and its associated include path, library path,
903 python = self.where_is('python')
905 self.skip_test('Can not find installed "python", skipping test.\n')
907 self.run(program = python, stdin = """\
910 py_ver = 'python%d.%d' % sys.version_info[:2]
911 except AttributeError:
912 py_ver = 'python' + sys.version[:3]
913 print os.path.join(sys.prefix, 'include', py_ver)
914 print os.path.join(sys.prefix, 'lib', py_ver, 'config')
918 return [python] + string.split(string.strip(self.stdout()), '\n')
920 def wait_for(self, fname, timeout=10.0, popen=None):
922 Waits for the specified file name to exist.
925 while not os.path.exists(fname):
926 if timeout and waited >= timeout:
927 sys.stderr.write('timed out waiting for %s to exist\n' % fname)
934 waited = waited + 1.0
936 def get_alt_cpp_suffix(self):
938 Many CXX tests have this same logic.
939 They all needed to determine if the current os supports
940 files with .C and .c as different files or not
941 in which case they are instructed to use .cpp instead of .C
943 if not case_sensitive_suffixes('.c','.C'):
944 alt_cpp_suffix = '.cpp'
946 alt_cpp_suffix = '.C'
947 return alt_cpp_suffix
950 class TimeSCons(TestSCons):
951 """Class for timing SCons."""
952 def __init__(self, *args, **kw):
954 In addition to normal TestSCons.TestSCons intialization,
955 this enables verbose mode (which causes the command lines to
956 be displayed in the output) and copies the contents of the
957 directory containing the executing script to the temporary
960 if not kw.has_key('verbose'):
963 #TestSCons.__init__(self, *args, **kw)
964 apply(TestSCons.__init__, (self,)+args, kw)
966 # TODO(sgk): better way to get the script dir than sys.argv[0]
967 test_dir = os.path.dirname(sys.argv[0])
968 test_name = os.path.basename(test_dir)
970 if not os.path.isabs(test_dir):
971 test_dir = os.path.join(self.orig_cwd, test_dir)
972 self.copy_timing_configuration(test_dir, self.workpath())
974 def main(self, *args, **kw):
976 The main entry point for standard execution of timings.
978 This method run SCons three times:
980 Once with the --help option, to have it exit after just reading
983 Once as a full build of all targets.
985 Once again as a (presumably) null or up-to-date build of
988 The elapsed time to execute each build is printed after
992 #self.help(*args, **kw)
993 #self.full(*args, **kw)
994 #self.null(*args, **kw)
995 apply(self.help, args, kw)
996 apply(self.full, args, kw)
997 apply(self.null, args, kw)
999 def help(self, *args, **kw):
1001 Runs scons with the --help option.
1003 This serves as a way to isolate just the amount of time spent
1004 reading up the configuration, since --help exits before any
1005 "real work" is done.
1007 kw['options'] = kw.get('options', '') + ' --help'
1009 #self.run(*args, **kw)
1010 apply(self.run, args, kw)
1011 sys.stdout.write(self.stdout())
1012 print "RESULT", self.elapsed_time()
1014 def full(self, *args, **kw):
1016 Runs a full build of SCons.
1019 #self.run(*args, **kw)
1020 apply(self.run, args, kw)
1021 sys.stdout.write(self.stdout())
1022 print "RESULT", self.elapsed_time()
1024 def null(self, *args, **kw):
1026 Runs an up-to-date null build of SCons.
1028 # TODO(sgk): allow the caller to specify the target (argument)
1029 # that must be up-to-date.
1031 #self.up_to_date(arguments='.', **kw)
1033 kw['arguments'] = '.'
1034 apply(self.up_to_date, (), kw)
1035 sys.stdout.write(self.stdout())
1036 print "RESULT", self.elapsed_time()
1038 def elapsed_time(self):
1040 Returns the elapsed time of the most recent command execution.
1042 return self.endTime - self.startTime
1044 def run(self, *args, **kw):
1046 Runs a single build command, capturing output in the specified file.
1048 Because this class is about timing SCons, we record the start
1049 and end times of the elapsed execution, and also add the
1050 --debug=memory and --debug=time options to have SCons report
1051 its own memory and timing statistics.
1053 kw['options'] = kw.get('options', '') + ' --debug=memory --debug=time'
1054 self.startTime = time.time()
1057 #result = TestSCons.run(self, *args, **kw)
1058 result = apply(TestSCons.run, (self,)+args, kw)
1060 self.endTime = time.time()
1063 def copy_timing_configuration(self, source_dir, dest_dir):
1065 Copies the timing configuration from the specified source_dir (the
1066 directory in which the controlling script lives) to the specified
1067 dest_dir (a temporary working directory).
1069 This ignores all files and directories that begin with the string
1070 'TimeSCons-', and all '.svn' subdirectories.
1072 for root, dirs, files in os.walk(source_dir):
1076 #dirs = [ d for d in dirs if not d.startswith('TimeSCons-') ]
1077 #files = [ f for f in files if not f.startswith('TimeSCons-') ]
1078 not_timescons_entries = lambda s: not s.startswith('TimeSCons-')
1079 dirs = filter(not_timescons_entries, dirs)
1080 files = filter(not_timescons_entries, files)
1081 for dirname in dirs:
1082 source = os.path.join(root, dirname)
1083 destination = source.replace(source_dir, dest_dir)
1084 os.mkdir(destination)
1085 if sys.platform != 'win32':
1086 shutil.copystat(source, destination)
1087 for filename in files:
1088 source = os.path.join(root, filename)
1089 destination = source.replace(source_dir, dest_dir)
1090 shutil.copy2(source, destination)
1093 # In some environments, $AR will generate a warning message to stderr
1094 # if the library doesn't previously exist and is being created. One
1095 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
1096 # but this is difficult to do in a platform-/implementation-specific
1097 # method. Instead, we will use the following as a stderr match for
1098 # tests that use AR so that we will view zero or more "ar: creating
1099 # <file>" messages to be successful executions of the test (see
1100 # test/AR.py for sample usage).
1102 noisy_ar=r'(ar: creating( archive)? \S+\n?)*'
1106 # indent-tabs-mode:nil
1108 # vim: set expandtab tabstop=4 shiftwidth=4: