Merged revisions 1502-1677,1679-1682,1684-1918,1920-1968,1970-2116,2118-2125,2127...
[scons.git] / QMTest / TestSCons.py
1 """
2 TestSCons.py:  a testing framework for the SCons software construction
3 tool.
4
5 A TestSCons environment object is created via the usual invocation:
6
7     test = TestSCons()
8
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.
13 """
14
15 # __COPYRIGHT__
16
17 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
18
19 import os
20 import os.path
21 import re
22 import string
23 import sys
24
25 import __builtin__
26 try:
27     __builtin__.zip
28 except AttributeError:
29     def zip(*lists):
30         result = []
31         for i in xrange(len(lists[0])):
32             result.append(tuple(map(lambda l, i=i: l[i], lists)))
33         return result
34     __builtin__.zip = zip
35
36 from TestCommon import *
37 from TestCommon import __all__
38
39 # Some tests which verify that SCons has been packaged properly need to
40 # look for specific version file names.  Replicating the version number
41 # here provides independent verification that what we packaged conforms
42 # to what we expect.  (If we derived the version number from the same
43 # data driving the build we might miss errors if the logic breaks.)
44
45 SConsVersion = '0.97'
46
47 __all__.extend([ 'TestSCons',
48                  'machine',
49                  'python',
50                  '_exe',
51                  '_obj',
52                  '_shobj',
53                  'lib_',
54                  '_lib',
55                  'dll_',
56                  '_dll'
57                ])
58
59 machine_map = {
60     'i686'  : 'i386',
61     'i586'  : 'i386',
62     'i486'  : 'i386',
63 }
64
65 try:
66     uname = os.uname
67 except AttributeError:
68     # Windows doesn't have a uname() function.  We could use something like
69     # sys.platform as a fallback, but that's not really a "machine," so
70     # just leave it as None.
71     machine = None
72 else:
73     machine = uname()[4]
74     machine = machine_map.get(machine, machine)
75
76 python = python_executable
77 _python_ = '"' + python_executable + '"'
78 _exe = exe_suffix
79 _obj = obj_suffix
80 _shobj = shobj_suffix
81 _lib = lib_suffix
82 lib_ = lib_prefix
83 _dll = dll_suffix
84 dll_ = dll_prefix
85
86 def gccFortranLibs():
87     """Test whether -lfrtbegin is required.  This can probably be done in
88     a more reliable way, but using popen3 is relatively efficient."""
89
90     libs = ['g2c']
91
92     try:
93         import popen2
94         stderr = popen2.popen3('gcc -v')[2]
95     except OSError:
96         return libs
97
98     for l in stderr.readlines():
99         list = string.split(l)
100         if len(list) > 3 and list[:2] == ['gcc', 'version']:
101             if list[2][:2] in ('3.', '4.'):
102                 libs = ['frtbegin'] + libs
103                 break
104     return libs
105
106
107 if sys.platform == 'cygwin':
108     # On Cygwin, os.path.normcase() lies, so just report back the
109     # fact that the underlying Win32 OS is case-insensitive.
110     def case_sensitive_suffixes(s1, s2):
111         return 0
112 else:
113     def case_sensitive_suffixes(s1, s2):
114         return (os.path.normcase(s1) != os.path.normcase(s2))
115
116
117 if sys.platform == 'win32':
118     fortran_lib = gccFortranLibs()
119 elif sys.platform == 'cygwin':
120     fortran_lib = gccFortranLibs()
121 elif string.find(sys.platform, 'irix') != -1:
122     fortran_lib = ['ftn']
123 else:
124     fortran_lib = gccFortranLibs()
125
126
127
128 file_expr = r"""File "[^"]*", line \d+, in .+
129 """
130
131 # re.escape escapes too much.
132 def re_escape(str):
133     for c in ['.', '[', ']', '(', ')', '*', '+', '?']:  # Not an exhaustive list.
134         str = string.replace(str, c, '\\' + c)
135     return str
136
137
138
139 class TestSCons(TestCommon):
140     """Class for testing SCons.
141
142     This provides a common place for initializing SCons tests,
143     eliminating the need to begin every test with the same repeated
144     initializations.
145     """
146
147     scons_version = SConsVersion
148
149     def __init__(self, **kw):
150         """Initialize an SCons testing object.
151
152         If they're not overridden by keyword arguments, this
153         initializes the object with the following default values:
154
155                 program = 'scons' if it exists,
156                           else 'scons.py'
157                 interpreter = 'python'
158                 match = match_exact
159                 workdir = ''
160
161         The workdir value means that, by default, a temporary workspace
162         directory is created for a TestSCons environment.  In addition,
163         this method changes directory (chdir) to the workspace directory,
164         so an explicit "chdir = '.'" on all of the run() method calls
165         is not necessary.
166         """
167         self.orig_cwd = os.getcwd()
168         try:
169             script_dir = os.environ['SCONS_SCRIPT_DIR']
170         except KeyError:
171             pass
172         else:
173             os.chdir(script_dir)
174         if not kw.has_key('program'):
175             kw['program'] = os.environ.get('SCONS')
176             if not kw['program']:
177                 if os.path.exists('scons'):
178                     kw['program'] = 'scons'
179                 else:
180                     kw['program'] = 'scons.py'
181         if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
182             kw['interpreter'] = [python, '-tt']
183         if not kw.has_key('match'):
184             kw['match'] = match_exact
185         if not kw.has_key('workdir'):
186             kw['workdir'] = ''
187         apply(TestCommon.__init__, [self], kw)
188
189     def Environment(self, ENV=None, *args, **kw):
190         """
191         Return a construction Environment that optionally overrides
192         the default external environment with the specified ENV.
193         """
194         import SCons.Environment
195         import SCons.Errors
196         if not ENV is None:
197             kw['ENV'] = ENV
198         try:
199             return apply(SCons.Environment.Environment, args, kw)
200         except (SCons.Errors.UserError, SCons.Errors.InternalError):
201             return None
202
203     def detect(self, var, prog=None, ENV=None):
204         """
205         Detect a program named 'prog' by first checking the construction
206         variable named 'var' and finally searching the path used by
207         SCons. If either method fails to detect the program, then false
208         is returned, otherwise the full path to prog is returned. If
209         prog is None, then the value of the environment variable will be
210         used as prog.
211         """
212         env = self.Environment(ENV)
213         v = env.subst('$'+var)
214         if not v:
215             return None
216         if prog is None:
217             prog = v
218         if v != prog:
219             return None
220         return env.WhereIs(prog)
221
222     def detect_tool(self, tool, prog=None, ENV=None):
223         """
224         Given a tool (i.e., tool specification that would be passed
225         to the "tools=" parameter of Environment()) and a program that
226         corresponds to that tool, return true if and only if we can find
227         that tool using Environment.Detect().
228
229         By default, prog is set to the value passed into the tools parameter.
230         """
231
232         if not prog:
233             prog = tool
234         env = self.Environment(ENV, tools=[tool])
235         if env is None:
236             return None
237         return env.Detect([prog])
238
239     def where_is(self, prog, path=None):
240         """
241         Given a program, search for it in the specified external PATH,
242         or in the actual external PATH is none is specified.
243         """
244         import SCons.Environment
245         env = SCons.Environment.Environment()
246         if path is None:
247             path = os.environ['PATH']
248         return env.WhereIs(prog, path)
249
250     def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0):
251         """Wraps standard output string(s) in the normal
252         "Reading ... done" and "Building ... done" strings
253         """
254         cap,lc = [ ('Build','build'),
255                    ('Clean','clean') ][cleaning]
256         if error:
257             term = "scons: %sing terminated because of errors.\n" % lc
258         else:
259             term = "scons: done %sing targets.\n" % lc
260         return "scons: Reading SConscript files ...\n" + \
261                read_str + \
262                "scons: done reading SConscript files.\n" + \
263                "scons: %sing targets ...\n" % cap + \
264                build_str + \
265                term
266
267     def up_to_date(self, options = None, arguments = None, read_str = "", **kw):
268         s = ""
269         for arg in string.split(arguments):
270             s = s + "scons: `%s' is up to date.\n" % arg
271             if options:
272                 arguments = options + " " + arguments
273         kw['arguments'] = arguments
274         kw['stdout'] = self.wrap_stdout(read_str = read_str, build_str = s)
275         kw['match'] = self.match_exact
276         apply(self.run, [], kw)
277
278     def not_up_to_date(self, options = None, arguments = None, **kw):
279         """Asserts that none of the targets listed in arguments is
280         up to date, but does not make any assumptions on other targets.
281         This function is most useful in conjunction with the -n option.
282         """
283         s = ""
284         for  arg in string.split(arguments):
285             s = s + "(?!scons: `%s' is up to date.)" % arg
286             if options:
287                 arguments = options + " " + arguments
288         kw['arguments'] = arguments
289         kw['stdout'] = self.wrap_stdout(build_str="("+s+"[^\n]*\n)*")
290         kw['stdout'] = string.replace(kw['stdout'],'\n','\\n')
291         kw['stdout'] = string.replace(kw['stdout'],'.','\\.')
292         kw['match'] = self.match_re_dotall
293         apply(self.run, [], kw)
294
295     def skip_test(self, message="Skipping test.\n"):
296         """Skips a test.
297
298         Proper test-skipping behavior is dependent on whether we're being
299         executed as part of development of a change under Aegis.
300
301         Technically, skipping a test is a NO RESULT, but Aegis will
302         treat that as a test failure and prevent the change from going
303         to the next step.  We don't want to force anyone using Aegis
304         to have to install absolutely every tool used by the tests,
305         so we actually report to Aegis that a skipped test has PASSED
306         so that the workflow isn't held up.
307         """
308         if message:
309             sys.stdout.write(message)
310             sys.stdout.flush()
311         devdir = os.popen("aesub '$dd' 2>/dev/null", "r").read()[:-1]
312         intdir = os.popen("aesub '$intd' 2>/dev/null", "r").read()[:-1]
313         if devdir and self._cwd[:len(devdir)] == devdir or \
314            intdir and self._cwd[:len(intdir)] == intdir:
315             # We're under the development directory for this change,
316             # so this is an Aegis invocation; pass the test (exit 0).
317             self.pass_test()
318         else:
319             # skip=1 means skip this function when showing where this
320             # result came from.  They only care about the line where the
321             # script called test.skip_test(), not the line number where
322             # we call test.no_result().
323             self.no_result(skip=1)
324
325     def diff_substr(self, expect, actual, prelen=20, postlen=40):
326         i = 0
327         for x, y in zip(expect, actual):
328             if x != y:
329                 return "Actual did not match expect at char %d:\n" \
330                        "    Expect:  %s\n" \
331                        "    Actual:  %s\n" \
332                        % (i, repr(expect[i-prelen:i+postlen]),
333                              repr(actual[i-prelen:i+postlen]))
334             i = i + 1
335         return "Actual matched the expected output???"
336
337     def python_file_line(self, file, line):
338         """
339         Returns a Python error line for output comparisons.
340
341         The exec of the traceback line gives us the correct format for
342         this version of Python.  Before 2.5, this yielded:
343
344             File "<string>", line 1, ?
345
346         Python 2.5 changed this to:
347
348             File "<string>", line 1, <module>
349
350         We stick the requested file name and line number in the right
351         places, abstracting out the version difference.
352         """
353         exec 'import traceback; x = traceback.format_stack()[-1]'
354         x = string.lstrip(x)
355         x = string.replace(x, '<string>', file)
356         x = string.replace(x, 'line 1,', 'line %s,' % line)
357         return x
358
359     def normalize_pdf(self, s):
360         s = re.sub(r'/CreationDate \(D:[^)]*\)',
361                    r'/CreationDate (D:XXXX)', s)
362         s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
363                    r'/ID [<XXXX> <XXXX>]', s)
364         return s
365
366     def java_ENV(self):
367         """
368         Return a default external environment that uses a local Java SDK
369         in preference to whatever's found in the default PATH.
370         """
371         import SCons.Environment
372         env = SCons.Environment.Environment()
373         java_path = [
374             '/usr/local/j2sdk1.4.2/bin',
375             '/usr/local/j2sdk1.4.1/bin',
376             '/usr/local/j2sdk1.3.1/bin',
377             '/usr/local/j2sdk1.3.0/bin',
378             '/usr/local/j2sdk1.2.2/bin',
379             '/usr/local/j2sdk1.2/bin',
380             '/usr/local/j2sdk1.1.8/bin',
381             '/usr/local/j2sdk1.1.7/bin',
382             '/usr/local/j2sdk1.1.6/bin',
383             '/usr/local/j2sdk1.1.5/bin',
384             '/usr/local/j2sdk1.1.4/bin',
385             '/usr/local/j2sdk1.1.3/bin',
386             '/usr/local/j2sdk1.1.2/bin',
387             '/usr/local/j2sdk1.1.1/bin',
388             env['ENV']['PATH'],
389         ]
390         env['ENV']['PATH'] = string.join(java_path, os.pathsep)
391         return env['ENV']
392
393     def Qt_dummy_installation(self, dir='qt'):
394         # create a dummy qt installation
395
396         self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] )
397
398         self.write([dir, 'bin', 'mymoc.py'], """\
399 import getopt
400 import sys
401 import string
402 import re
403 cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
404 output = None
405 impl = 0
406 opt_string = ''
407 for opt, arg in cmd_opts:
408     if opt == '-o': output = open(arg, 'wb')
409     elif opt == '-i': impl = 1
410     else: opt_string = opt_string + ' ' + opt
411 for a in args:
412     contents = open(a, 'rb').read()
413     a = string.replace(a, '\\\\', '\\\\\\\\')
414     subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
415     if impl:
416         contents = re.sub( r'#include.*', '', contents )
417     output.write(string.replace(contents, 'Q_OBJECT', subst))
418 output.close()
419 sys.exit(0)
420 """)
421
422         self.write([dir, 'bin', 'myuic.py'], """\
423 import os.path
424 import re
425 import sys
426 import string
427 output_arg = 0
428 impl_arg = 0
429 impl = None
430 source = None
431 for arg in sys.argv[1:]:
432     if output_arg:
433         output = open(arg, 'wb')
434         output_arg = 0
435     elif impl_arg:
436         impl = arg
437         impl_arg = 0
438     elif arg == "-o":
439         output_arg = 1
440     elif arg == "-impl":
441         impl_arg = 1
442     else:
443         if source:
444             sys.exit(1)
445         source = open(arg, 'rb')
446         sourceFile = arg
447 if impl:
448     output.write( '#include "' + impl + '"\\n' )
449     includes = re.findall('<include.*?>(.*?)</include>', source.read())
450     for incFile in includes:
451         # this is valid for ui.h files, at least
452         if os.path.exists(incFile):
453             output.write('#include "' + incFile + '"\\n')
454 else:
455     output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
456 output.close()
457 sys.exit(0)
458 """ )
459
460         self.write([dir, 'include', 'my_qobject.h'], r"""
461 #define Q_OBJECT ;
462 void my_qt_symbol(const char *arg);
463 """)
464
465         self.write([dir, 'lib', 'my_qobject.cpp'], r"""
466 #include "../include/my_qobject.h"
467 #include <stdio.h>
468 void my_qt_symbol(const char *arg) {
469   printf( arg );
470 }
471 """)
472
473         self.write([dir, 'lib', 'SConstruct'], r"""
474 env = Environment()
475 import sys
476 if sys.platform == 'win32':
477     env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
478 else:
479     env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
480 """)
481
482         self.run(chdir = self.workpath(dir, 'lib'),
483                  arguments = '.',
484                  stderr = noisy_ar,
485                  match = self.match_re_dotall)
486
487         self.QT = self.workpath(dir)
488         self.QT_LIB = 'myqt'
489         self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py'))
490         self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py'))
491         self.QT_LIB_DIR = self.workpath(dir, 'lib')
492
493     def Qt_create_SConstruct(self, place):
494         if type(place) is type([]):
495             place = apply(test.workpath, place)
496         self.write(place, """\
497 if ARGUMENTS.get('noqtdir', 0): QTDIR=None
498 else: QTDIR=r'%s'
499 env = Environment(QTDIR = QTDIR,
500                   QT_LIB = r'%s',
501                   QT_MOC = r'%s',
502                   QT_UIC = r'%s',
503                   tools=['default','qt'])
504 dup = 1
505 if ARGUMENTS.get('build_dir', 0):
506     if ARGUMENTS.get('chdir', 0):
507         SConscriptChdir(1)
508     else:
509         SConscriptChdir(0)
510     dup=int(ARGUMENTS.get('dup', 1))
511     if dup == 0:
512         builddir = 'build_dup0'
513         env['QT_DEBUG'] = 1
514     else:
515         builddir = 'build'
516     BuildDir(builddir, '.', duplicate=dup)
517     print builddir, dup
518     sconscript = Dir(builddir).File('SConscript')
519 else:
520     sconscript = File('SConscript')
521 Export("env dup")
522 SConscript( sconscript )
523 """ % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC))
524
525     def msvs_versions(self):
526         if not hasattr(self, '_msvs_versions'):
527
528             # Determine the SCons version and the versions of the MSVS
529             # environments installed on the test machine.
530             #
531             # We do this by executing SCons with an SConstruct file
532             # (piped on stdin) that spits out Python assignments that
533             # we can just exec().  We construct the SCons.__"version"__
534             # string in the input here so that the SCons build itself
535             # doesn't fill it in when packaging SCons.
536             input = """\
537 import SCons
538 print "self._scons_version =", repr(SCons.__%s__)
539 env = Environment();
540 print "self._msvs_versions =", str(env['MSVS']['VERSIONS'])
541 """ % 'version'
542         
543             self.run(arguments = '-n -q -Q -f -', stdin = input)
544             exec(self.stdout())
545
546         return self._msvs_versions
547
548     def vcproj_sys_path(self, fname):
549         """
550         """
551         orig = 'sys.path = [ join(sys'
552
553         enginepath = repr(os.path.join(self._cwd, '..', 'engine'))
554         replace = 'sys.path = [ %s, join(sys' % enginepath
555
556         contents = self.read(fname)
557         contents = string.replace(contents, orig, replace)
558         self.write(fname, contents)
559
560     def msvs_substitute(self, input, msvs_ver,
561                         subdir=None, sconscript=None,
562                         python=sys.executable,
563                         project_guid=None):
564         if not hasattr(self, '_msvs_versions'):
565             self.msvs_versions()
566
567         if subdir:
568             workpath = self.workpath(subdir)
569         else:
570             workpath = self.workpath()
571
572         if sconscript is None:
573             sconscript = self.workpath('SConstruct')
574
575         if project_guid is None:
576             project_guid = "{E5466E26-0003-F18B-8F8A-BCD76C86388D}"
577
578         if os.environ.has_key('SCONS_LIB_DIR'):
579             exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % os.environ['SCONS_LIB_DIR']
580         else:
581             exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%s'), join(sys.prefix, 'scons-%s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % (self._scons_version, self._scons_version)
582         exec_script_main_xml = string.replace(exec_script_main, "'", "&apos;")
583
584         result = string.replace(input, r'<WORKPATH>', workpath)
585         result = string.replace(result, r'<PYTHON>', python)
586         result = string.replace(result, r'<SCONSCRIPT>', sconscript)
587         result = string.replace(result, r'<SCONS_SCRIPT_MAIN>', exec_script_main)
588         result = string.replace(result, r'<SCONS_SCRIPT_MAIN_XML>', exec_script_main_xml)
589         result = string.replace(result, r'<PROJECT_GUID>', project_guid)
590         return result
591
592     def get_msvs_executable(self, version):
593         """Returns a full path to the executable (MSDEV or devenv)
594         for the specified version of Visual Studio.
595         """
596         common_msdev98_bin_msdev_com = ['Common', 'MSDev98', 'Bin', 'MSDEV.COM']
597         common7_ide_devenv_com       = ['Common7', 'IDE', 'devenv.com']
598         common7_ide_vcexpress_exe    = ['Common7', 'IDE', 'VCExpress.exe']
599         sub_paths = {
600             '6.0' : [
601                 common_msdev98_bin_msdev_com,
602             ],
603             '7.0' : [
604                 common7_ide_devenv_com,
605             ],
606             '7.1' : [
607                 common7_ide_devenv_com,
608             ],
609             '8.0' : [
610                 common7_ide_devenv_com,
611                 common7_ide_vcexpress_exe,
612             ],
613         }
614         from SCons.Tool.msvs import get_msvs_install_dirs
615         vs_path = get_msvs_install_dirs(version)['VSINSTALLDIR']
616         for sp in sub_paths[version]:
617             p = apply(os.path.join, [vs_path] + sp)
618             if os.path.exists(p):
619                 return p
620         return apply(os.path.join, [vs_path] + sub_paths[version][0])
621
622
623     NCR = 0 # non-cached rebuild
624     CR  = 1 # cached rebuild (up to date)
625     NCF = 2 # non-cached build failure
626     CF  = 3 # cached build failure
627
628     if sys.platform == 'win32':
629         Configure_lib = 'msvcrt'
630     else:
631         Configure_lib = 'm'
632
633     # to use cygwin compilers on cmd.exe -> uncomment following line
634     #Configure_lib = 'm'
635
636     def checkLogAndStdout(self, checks, results, cached,
637                           logfile, sconf_dir, sconstruct,
638                           doCheckLog=1, doCheckStdout=1):
639
640         class NoMatch:
641             def __init__(self, p):
642                 self.pos = p
643
644         def matchPart(log, logfile, lastEnd):
645             m = re.match(log, logfile[lastEnd:])
646             if not m:
647                 raise NoMatch, lastEnd
648             return m.end() + lastEnd
649         try:
650             #print len(os.linesep)
651             ls = os.linesep
652             nols = "("
653             for i in range(len(ls)):
654                 nols = nols + "("
655                 for j in range(i):
656                     nols = nols + ls[j]
657                 nols = nols + "[^" + ls[i] + "])"
658                 if i < len(ls)-1:
659                     nols = nols + "|"
660             nols = nols + ")"
661             lastEnd = 0
662             logfile = self.read(self.workpath(logfile))
663             if (doCheckLog and
664                 string.find( logfile, "scons: warning: The stored build "
665                              "information has an unexpected class." ) >= 0):
666                 self.fail_test()
667             sconf_dir = sconf_dir
668             sconstruct = sconstruct
669
670             log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
671             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
672             log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
673             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
674             rdstr = ""
675             cnt = 0
676             for check,result,cache_desc in zip(checks, results, cached):
677                 log   = re.escape("scons: Configure: " + check) + ls
678                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
679                 log = ""
680                 result_cached = 1
681                 for bld_desc in cache_desc: # each TryXXX
682                     for ext, flag in bld_desc: # each file in TryBuild
683                         file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
684                         if flag == self.NCR:
685                             # rebuild will pass
686                             if ext in ['.c', '.cpp']:
687                                 log=log + re.escape(file + " <-") + ls
688                                 log=log + r"(  \|" + nols + "*" + ls + ")+?"
689                             else:
690                                 log=log + "(" + nols + "*" + ls +")*?"
691                             result_cached = 0
692                         if flag == self.CR:
693                             # up to date
694                             log=log + \
695                                  re.escape("scons: Configure: \"%s\" is up to date." 
696                                            % file) + ls
697                             log=log+re.escape("scons: Configure: The original builder "
698                                               "output was:") + ls
699                             log=log+r"(  \|.*"+ls+")+"
700                         if flag == self.NCF:
701                             # non-cached rebuild failure
702                             log=log + "(" + nols + "*" + ls + ")*?"
703                             result_cached = 0
704                         if flag == self.CF:
705                             # cached rebuild failure
706                             log=log + \
707                                  re.escape("scons: Configure: Building \"%s\" failed "
708                                            "in a previous run and all its sources are"
709                                            " up to date." % file) + ls
710                             log=log+re.escape("scons: Configure: The original builder "
711                                               "output was:") + ls
712                             log=log+r"(  \|.*"+ls+")+"
713                     cnt = cnt + 1
714                 if result_cached:
715                     result = "(cached) " + result
716                 rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
717                 log=log + re.escape("scons: Configure: " + result) + ls + ls
718                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
719                 log = ""
720             if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
721             if doCheckLog and lastEnd != len(logfile):
722                 raise NoMatch, lastEnd
723             
724         except NoMatch, m:
725             print "Cannot match log file against log regexp."
726             print "log file: "
727             print "------------------------------------------------------"
728             print logfile[m.pos:]
729             print "------------------------------------------------------"
730             print "log regexp: "
731             print "------------------------------------------------------"
732             print log
733             print "------------------------------------------------------"
734             self.fail_test()
735
736         if doCheckStdout:
737             exp_stdout = self.wrap_stdout(".*", rdstr)
738             if not self.match_re_dotall(self.stdout(), exp_stdout):
739                 print "Unexpected stdout: "
740                 print "-----------------------------------------------------"
741                 print repr(self.stdout())
742                 print "-----------------------------------------------------"
743                 print repr(exp_stdout)
744                 print "-----------------------------------------------------"
745                 self.fail_test()
746
747 # In some environments, $AR will generate a warning message to stderr
748 # if the library doesn't previously exist and is being created.  One
749 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
750 # but this is difficult to do in a platform-/implementation-specific
751 # method.  Instead, we will use the following as a stderr match for
752 # tests that use AR so that we will view zero or more "ar: creating
753 # <file>" messages to be successful executions of the test (see
754 # test/AR.py for sample usage).
755
756 noisy_ar=r'(ar: creating( archive)? \S+\n?)*'