summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xsrc/CHANGES.txt15
-rw-r--r--src/engine/SCons/Builder.py2
-rw-r--r--src/engine/SCons/Conftest.py4
-rw-r--r--src/engine/SCons/SConf.py46
-rw-r--r--test/Configure/ConfigureDryRunError.py2
-rw-r--r--test/Configure/basic.py6
-rw-r--r--test/Configure/implicit-cache.py4
-rw-r--r--test/Configure/issue-3469/fixture/SConstruct25
-rw-r--r--test/Configure/issue-3469/fixture/SConstruct.245
-rw-r--r--test/Configure/issue-3469/fixture/sconstest.skip0
-rw-r--r--test/Configure/issue-3469/issue-3469.py94
-rw-r--r--test/Configure/option--config.py142
-rw-r--r--testing/framework/TestSCons.py531
13 files changed, 665 insertions, 251 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 99184ed..3acd034 100755
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -16,7 +16,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
is a reasonable default and also aligns with changes in Appveyor's VS2019 image.
- Drop support for Python 2.7. SCons will be Python 3.5+ going forward.
- Change SCons.Node.ValueWithMemo to consider any name passed when memoizing Value() nodes
-
+
From Jeremy Elson:
- Updated design doc to use the correct syntax for Depends()
@@ -27,6 +27,19 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Added support for explicitly passing a name when creating Value() nodes. This may be useful
when the value can't be converted to a string or if having a name is otherwise desirable.
+ From Andrew Morrow:
+ - Fix Issue #3469 - Fixed improper reuse of temporary and compiled files by Configure when changing
+ the order and/or number of tests. This is done by using the hash of the generated temporary files
+ content and (For the target files) the hash of the action.
+ So where previously files would be named:
+ - config_1.c, config_1.o, config_1
+ The will now be named (For example)
+ - conftest_68b375d16e812c43e6d72d6e93401e7c_0.c,
+ conftest_68b375d16e812c43e6d72d6e93401e7c_0_5713f09fc605f46b2ab2f7950455f187.o
+ or
+ conftest_68b375d16e812c43e6d72d6e93401e7c_0.o
+ conftest_68b375d16e812c43e6d72d6e93401e7c_0_5713f09fc605f46b2ab2f7950455f187 (for executable)
+
From Mathew Robinson:
- Improve performance of Subst by preventing unnecessary frame
allocations by no longer defining the *Subber classes inside of their
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index caa0568..13949e5 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -647,6 +647,8 @@ class BuilderBase(object):
env_kw = kw
else:
env_kw = self.overrides
+
+ # TODO if env_kw: then the following line. there's no purpose in calling if no overrides.
env = env.Override(env_kw)
return self._execute(env, target, source, OverrideWarner(kw), ekw)
diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py
index c24adf8..4491884 100644
--- a/src/engine/SCons/Conftest.py
+++ b/src/engine/SCons/Conftest.py
@@ -315,8 +315,8 @@ int main(void) {
return ret
-def CheckHeader(context, header_name, header = None, language = None,
- include_quotes = None):
+def CheckHeader(context, header_name, header=None, language=None,
+ include_quotes=None):
"""
Configure check for a C or C++ header file "header_name".
Optional "header" can be defined to do something before including the
diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py
index 988362e..50a1329 100644
--- a/src/engine/SCons/SConf.py
+++ b/src/engine/SCons/SConf.py
@@ -56,6 +56,7 @@ import SCons.Warnings
import SCons.Conftest
from SCons.Debug import Trace
+from collections import defaultdict
# Turn off the Conftest error logging
SCons.Conftest.LogInputFiles = 0
@@ -98,7 +99,7 @@ def SetProgressDisplay(display):
SConfFS = None
-_ac_build_counter = 0 # incremented, whenever TryBuild is called
+_ac_build_counter = defaultdict(int)
_ac_config_logs = {} # all config.log files created in this build
_ac_config_hs = {} # all config.h files created in this build
sconf_global = None # current sconf object
@@ -161,11 +162,14 @@ class ConfigureCacheError(SConfError):
def __init__(self,target):
SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target))
+
# define actions for building text files
-def _createSource( target, source, env ):
+def _createSource(target, source, env):
fd = open(str(target[0]), "w")
fd.write(source[0].get_contents().decode())
fd.close()
+
+
def _stringSource( target, source, env ):
return (str(target[0]) + ' <-\n |' +
source[0].get_contents().decode().replace( '\n', "\n |" ) )
@@ -573,8 +577,7 @@ class SConfBase(object):
"""
return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
-
- def TryBuild(self, builder, text = None, extension = ""):
+ def TryBuild(self, builder, text=None, extension=""):
"""Low level TryBuild implementation. Normally you don't need to
call that - you can use TryCompile / TryLink / TryRun instead
"""
@@ -592,8 +595,30 @@ class SConfBase(object):
raise SCons.Errors.UserError('Missing SPAWN construction variable.')
nodesToBeBuilt = []
+ sourcetext = self.env.Value(text)
+ f = "conftest"
+
+ if text is not None:
+ textSig = SCons.Util.MD5signature(sourcetext)
+ textSigCounter = str(_ac_build_counter[textSig])
+ _ac_build_counter[textSig] += 1
+
+ f = "_".join([f, textSig, textSigCounter])
+ textFile = self.confdir.File(f + extension)
+ textFileNode = self.env.SConfSourceBuilder(target=textFile,
+ source=sourcetext)
+ nodesToBeBuilt.extend(textFileNode)
+
+ source = textFile
+ target = textFile.File(f + "SConfActionsContentDummyTarget")
+ else:
+ source = None
+ target = None
+
+ action = builder.builder.action.get_contents(target=target, source=[source], env=self.env)
+ actionsig = SCons.Util.MD5signature(action)
+ f = "_".join([f, actionsig])
- f = "conftest_" + str(_ac_build_counter)
pref = self.env.subst( builder.builder.prefix )
suff = self.env.subst( builder.builder.suffix )
target = self.confdir.File(pref + f + suff)
@@ -602,16 +627,6 @@ class SConfBase(object):
# Slide our wrapper into the construction environment as
# the SPAWN function.
self.env['SPAWN'] = self.pspawn_wrapper
- sourcetext = self.env.Value(text)
-
- if text is not None:
- textFile = self.confdir.File(f + extension)
- textFileNode = self.env.SConfSourceBuilder(target=textFile,
- source=sourcetext)
- nodesToBeBuilt.extend(textFileNode)
- source = textFileNode
- else:
- source = None
nodes = builder(target = target, source = source)
if not SCons.Util.is_List(nodes):
@@ -622,7 +637,6 @@ class SConfBase(object):
finally:
self.env['SPAWN'] = save_spawn
- _ac_build_counter = _ac_build_counter + 1
if result:
self.lastTarget = nodes[0]
else:
diff --git a/test/Configure/ConfigureDryRunError.py b/test/Configure/ConfigureDryRunError.py
index 1b89b03..ad40ea4 100644
--- a/test/Configure/ConfigureDryRunError.py
+++ b/test/Configure/ConfigureDryRunError.py
@@ -67,7 +67,7 @@ test.run(arguments='-n', status=2, stderr=expect)
test.must_not_exist('config.log')
test.subdir('.sconf_temp')
-conftest_0_c = os.path.join(".sconf_temp", "conftest_0.c")
+conftest_0_c = os.path.join(".sconf_temp", "conftest_df286a1d2f67e69d030b4eff75ca7e12_0.c")
SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
expect = """
diff --git a/test/Configure/basic.py b/test/Configure/basic.py
index 4038a45..253fa25 100644
--- a/test/Configure/basic.py
+++ b/test/Configure/basic.py
@@ -28,11 +28,11 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Verify that basic builds work with Configure contexts.
"""
-import TestSCons
+from TestSCons import TestSCons, ConfigCheckInfo, _obj
+from TestCmd import IS_WINDOWS
-_obj = TestSCons._obj
-test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
+test = TestSCons(match = TestSCons.match_re_dotall)
NCR = test.NCR # non-cached rebuild
CR = test.CR # cached rebuild (up to date)
diff --git a/test/Configure/implicit-cache.py b/test/Configure/implicit-cache.py
index 1a9ff34..20bdebb 100644
--- a/test/Configure/implicit-cache.py
+++ b/test/Configure/implicit-cache.py
@@ -77,7 +77,7 @@ test.write('foo.h', "#define FOO 1\n")
test.run(arguments = '.')
-test.run_sconsign('-d .sconf_temp -e conftest_0.c --raw .sconsign.dblite')
+test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
old_sconsign_dblite = test.stdout()
# Second run: Have the configure subsystem also look for foo.h, so
@@ -90,7 +90,7 @@ old_sconsign_dblite = test.stdout()
test.run(arguments = '--implicit-cache USE_FOO=1 .')
-test.run_sconsign('-d .sconf_temp -e conftest_0.c --raw .sconsign.dblite')
+test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
new_sconsign_dblite = test.stdout()
if old_sconsign_dblite != new_sconsign_dblite:
diff --git a/test/Configure/issue-3469/fixture/SConstruct b/test/Configure/issue-3469/fixture/SConstruct
new file mode 100644
index 0000000..4b5bedc
--- /dev/null
+++ b/test/Configure/issue-3469/fixture/SConstruct
@@ -0,0 +1,25 @@
+"""
+This tests if we add/remove a test in between other tests if a rerun will properly cache the results.
+Github issue #3469
+"""
+
+DefaultEnvironment(tools=[])
+
+vars = Variables()
+vars.Add(BoolVariable('SKIP', 'Skip Middle Conf test', 0))
+env = Environment(variables=vars)
+
+conf = Configure(env)
+if not conf.CheckCHeader('math.h'):
+ print('Math.h must be installed!')
+ Exit(1)
+
+if not env['SKIP'] and not conf.CheckCHeader('stdlib.h'):
+ print('stdlib.h must be installed!')
+ Exit(1)
+
+if not conf.CheckCHeader('stdio.h'):
+ print('stdio.h must be installed!')
+ Exit(1)
+
+env = conf.Finish()
diff --git a/test/Configure/issue-3469/fixture/SConstruct.2 b/test/Configure/issue-3469/fixture/SConstruct.2
new file mode 100644
index 0000000..171dc39
--- /dev/null
+++ b/test/Configure/issue-3469/fixture/SConstruct.2
@@ -0,0 +1,45 @@
+"""
+This tests if we add/remove a test in between other tests if a rerun will properly cache the results.
+Github issue #3469
+
+MongoDB's problem is on 3rd run, a check which expects it's objectfile to have a main doesn't.
+This means
+This one does the following.
+CheckLink
+CheckHeader
+
+"""
+import textwrap
+
+# DefaultEnvironment(tools=[])
+
+vars = Variables()
+vars.Add(BoolVariable('SKIP', 'Skip Middle Conf test', 0))
+env = Environment(variables=vars)
+
+print("SKIP:%s" % env['SKIP'])
+
+
+conf = Configure(env, conf_dir='conf2')
+
+if not env['SKIP']:
+ int_size = conf.CheckTypeSize('unsigned int')
+ print("Size:%d"%int_size)
+
+
+if env['SKIP'] and not conf.CheckCXXHeader('math.h'):
+ print('Math.h must be installed!')
+ Exit(1)
+
+
+
+#
+# if not conf.CheckCHeader('stdlib.h'):
+# print('stdlib.h must be installed!')
+# Exit(1)
+#
+# if not conf.CheckCHeader('stdio.h'):
+# print('stdio.h must be installed!')
+# Exit(1)
+
+env = conf.Finish()
diff --git a/test/Configure/issue-3469/fixture/sconstest.skip b/test/Configure/issue-3469/fixture/sconstest.skip
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/Configure/issue-3469/fixture/sconstest.skip
diff --git a/test/Configure/issue-3469/issue-3469.py b/test/Configure/issue-3469/issue-3469.py
new file mode 100644
index 0000000..a2fd7c2
--- /dev/null
+++ b/test/Configure/issue-3469/issue-3469.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that changing the order and/or number of config tests does not reuse
+incorrect temporary test files on successive runs.
+This addresses Issue 3469:
+https://github.com/SCons/scons/issues/3469
+"""
+
+import TestSCons
+
+_exe = TestSCons._exe
+_obj = TestSCons._obj
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+test.verbose_set(1)
+
+NCR = test.NCR # non-cached rebuild
+CR = test.CR # cached rebuild (up to date)
+NCF = test.NCF # non-cached build failure
+CF = test.CF # cached build failure
+
+test.file_fixture('./fixture/SConstruct')
+
+test.run()
+
+# First run all tests should build
+test.checkLogAndStdout(["Checking for C header file math.h... ",
+ "Checking for C header file stdlib.h... ",
+ "Checking for C header file stdio.h... "],
+ ["yes", "yes", "yes"],
+ [[(('.c', NCR), (_obj, NCR))],
+ [(('.c', NCR), (_obj, NCR))],
+ [(('.c', NCR), (_obj, NCR))]],
+ "config.log", ".sconf_temp", "SConstruct")
+
+
+# Second run, this will skip middle check. First and third (now second) checks should
+# reuse cached
+test.run('SKIP=1')
+test.checkLogAndStdout(["Checking for C header file math.h... ",
+ # "Checking for C header file stdlib.h... ",
+ "Checking for C header file stdio.h... "],
+ ["yes", "yes", "yes"],
+ [[(('.c', CR), (_obj, CR))],
+ # [(('.c', CR), (_obj, CR))],
+ [(('.c', CR), (_obj, CR))]],
+ "config.log", ".sconf_temp", "SConstruct")
+
+# Third run. We're re-adding the middle test, all tests should reuse cached.
+test.run('SKIP=0')
+test.checkLogAndStdout(["Checking for C header file math.h... ",
+ "Checking for C header file stdlib.h... ",
+ "Checking for C header file stdio.h... "],
+ ["yes", "yes", "yes"],
+ [[(('.c', CR), (_obj, CR))],
+ [(('.c', CR), (_obj, CR))],
+ [(('.c', CR), (_obj, CR))]],
+ "config.log", ".sconf_temp", "SConstruct")
+
+
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/Configure/option--config.py b/test/Configure/option--config.py
index 838409d..8b23496 100644
--- a/test/Configure/option--config.py
+++ b/test/Configure/option--config.py
@@ -30,11 +30,11 @@ Verify use of the --config=<auto|force|cache> option.
import os.path
-import TestSCons
+from TestSCons import TestSCons, ConfigCheckInfo, _obj
+from TestCmd import IS_WINDOWS
-_obj = TestSCons._obj
-
-test = TestSCons.TestSCons()
+test = TestSCons()
+# test.verbose_set(1)
test.subdir('include')
@@ -59,7 +59,20 @@ test.write(['include', 'non_system_header0.h'], """
/* A header */
""")
-conftest_0_c = os.path.join(".sconf_temp", "conftest_0.c")
+conftest_0_c_hash = 'cda36b76729ffb03bf36a48d13b2d98d'
+conftest_1_c_hash = 'acc476a565a3f6d5d67ddc21f187d062'
+
+if IS_WINDOWS:
+ conftest_0_obj_suffix = '_213c72f9eb682c6f27f2eb78ed8bd57a'+_obj
+ conftest_1_obj_suffix = '_7c505229a64dccfea6e7cfdffe76bd2a'+_obj
+else:
+ conftest_0_obj_suffix = '_9b191e4c46e9d6ba17c8cd4d730900cf'+_obj
+ conftest_1_obj_suffix = '_b9da1a844a8707269188b28a62c0d83e'+_obj
+
+conftest_0_base = os.path.join(".sconf_temp", "conftest_%s_0%%s"%conftest_0_c_hash)
+conftest_0_c = conftest_0_base%'.c'
+conftest_1_base = os.path.join(".sconf_temp", "conftest_%s_0%%s"%conftest_1_c_hash)
+
SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
expect = """
@@ -70,36 +83,67 @@ scons: *** "%(conftest_0_c)s" is not yet built and cache is forced.
test.run(arguments='--config=cache', status=2, stderr=expect)
test.run(arguments='--config=auto')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["yes", "no"],
- [[((".c", NCR), (_obj, NCR))],
- [((".c", NCR), (_obj, NCF))]],
- "config.log", ".sconf_temp", "SConstruct")
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'yes',
+ [((".c", NCR),
+ (_obj, NCR))],
+ conftest_0_base
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'no',
+ [((".c", NCR),
+ (_obj, NCF))],
+ conftest_1_base)]
+)
test.run(arguments='--config=auto')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["yes", "no"],
- [[((".c", CR), (_obj, CR))],
- [((".c", CR), (_obj, CF))]],
- "config.log", ".sconf_temp", "SConstruct")
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'yes',
+ [((".c", CR),
+ (conftest_0_obj_suffix, CR))],
+ conftest_0_base,
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'no',
+ [((".c", CR),
+ (conftest_1_obj_suffix, CF))],
+ conftest_1_base)]
+)
+
test.run(arguments='--config=force')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["yes", "no"],
- [[((".c", NCR), (_obj, NCR))],
- [((".c", NCR), (_obj, NCF))]],
- "config.log", ".sconf_temp", "SConstruct")
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'yes',
+ [((".c", NCR),
+ (conftest_0_obj_suffix, NCR))],
+ conftest_0_base,
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'no',
+ [((".c", NCR),
+ (conftest_1_obj_suffix, NCF))],
+ conftest_1_base)]
+)
+
test.run(arguments='--config=cache')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["yes", "no"],
- [[((".c", CR), (_obj, CR))],
- [((".c", CR), (_obj, CF))]],
- "config.log", ".sconf_temp", "SConstruct")
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'yes',
+ [((".c", CR),
+ (conftest_0_obj_suffix, CR))],
+ conftest_0_base,
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'no',
+ [((".c", CR),
+ (conftest_1_obj_suffix, CF))],
+ conftest_1_base)]
+)
+
test.write(['include', 'non_system_header1.h'], """
/* Another header */
@@ -107,21 +151,35 @@ test.write(['include', 'non_system_header1.h'], """
test.unlink(['include', 'non_system_header0.h'])
test.run(arguments='--config=cache')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["yes", "no"],
- [[((".c", CR), (_obj, CR))],
- [((".c", CR), (_obj, CF))]],
- "config.log", ".sconf_temp", "SConstruct")
-test.run(arguments='--config=auto')
-test.checkLogAndStdout(["Checking for C header file non_system_header0.h... ",
- "Checking for C header file non_system_header1.h... "],
- ["no", "yes"],
- [[((".c", CR), (_obj, NCF))],
- [((".c", CR), (_obj, NCR))]],
- "config.log", ".sconf_temp", "SConstruct")
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'yes',
+ [((".c", CR),
+ (conftest_0_obj_suffix, CR))],
+ conftest_0_base,
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'no',
+ [((".c", CR),
+ (conftest_1_obj_suffix, CF))],
+ conftest_1_base)]
+)
+test.run(arguments='--config=auto')
+test.checkConfigureLogAndStdout(checks=[
+ ConfigCheckInfo("Checking for C header file non_system_header0.h... ",
+ 'no',
+ [((".c", CR),
+ (conftest_0_obj_suffix, NCF))],
+ conftest_0_base,
+ ),
+ ConfigCheckInfo("Checking for C header file non_system_header1.h... ",
+ 'yes',
+ [((".c", CR),
+ (conftest_1_obj_suffix, NCR))],
+ conftest_1_base)]
+)
test.file_fixture('test_main.c')
diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py
index a633617..2f20950 100644
--- a/testing/framework/TestSCons.py
+++ b/testing/framework/TestSCons.py
@@ -23,6 +23,7 @@ import shutil
import sys
import time
import subprocess
+from collections import namedtuple
from TestCommon import *
from TestCommon import __all__
@@ -49,23 +50,23 @@ if SConsVersion == '__' + 'VERSION' + '__':
SConsVersion = default_version
__all__.extend([
- 'TestSCons',
- 'machine',
- 'python',
- '_exe',
- '_obj',
- '_shobj',
- 'shobj_',
- 'lib_',
- '_lib',
- 'dll_',
- '_dll'
- ])
+ 'TestSCons',
+ 'machine',
+ 'python',
+ '_exe',
+ '_obj',
+ '_shobj',
+ 'shobj_',
+ 'lib_',
+ '_lib',
+ 'dll_',
+ '_dll'
+])
machine_map = {
- 'i686' : 'i386',
- 'i586' : 'i386',
- 'i486' : 'i386',
+ 'i686': 'i386',
+ 'i586': 'i386',
+ 'i486': 'i386',
}
try:
@@ -88,7 +89,6 @@ lib_ = lib_prefix
_dll = dll_suffix
dll_ = dll_prefix
-
if sys.platform == 'cygwin':
# On Cygwin, os.path.normcase() lies, so just report back the
# fact that the underlying Win32 OS is case-insensitive.
@@ -98,16 +98,17 @@ else:
def case_sensitive_suffixes(s1, s2):
return (os.path.normcase(s1) != os.path.normcase(s2))
-
file_expr = r"""File "[^"]*", line \d+, in [^\n]+
"""
+
# re.escape escapes too much.
def re_escape(str):
- for c in '\\.[]()*+?': # Not an exhaustive list.
+ for c in '\\.[]()*+?': # Not an exhaustive list.
str = str.replace(c, '\\' + c)
return str
+
#
# Helper functions that we use as a replacement to the default re.match
# when searching for special strings in stdout/stderr.
@@ -122,6 +123,7 @@ def search_re(out, l):
return None
+
def search_re_in_list(out, l):
""" Search the regular expression 'l' in each line of
the given string list 'out' and return the line's index
@@ -134,21 +136,26 @@ def search_re_in_list(out, l):
return None
+
#
# Helpers for handling Python version numbers
#
def python_version_string():
return sys.version.split()[0]
+
def python_minor_version_string():
return sys.version[:3]
+
def unsupported_python_version(version=sys.version_info):
return version < python_version_unsupported
+
def deprecated_python_version(version=sys.version_info):
return version < python_version_deprecated
+
if deprecated_python_version():
msg = r"""
scons: warning: Support for pre-2.7.0 Python version (%s) is deprecated.
@@ -187,6 +194,7 @@ def initialize_sconsflags(ignore_python_version):
os.environ['SCONSFLAGS'] = ' '.join(sconsflags)
return save_sconsflags
+
def restore_sconsflags(sconsflags):
if sconsflags is None:
del os.environ['SCONSFLAGS']
@@ -194,6 +202,34 @@ def restore_sconsflags(sconsflags):
os.environ['SCONSFLAGS'] = sconsflags
+# Helpers for Configure()'s config.log processing
+ConfigCheckInfo = namedtuple('ConfigCheckInfo',
+ ['check_string', 'result', 'cached', 'temp_filename'])
+# check_string: the string output to for this checker
+# results : The expected results for each check
+# cached : If the corresponding check is expected to be cached
+# temp_filename : The name of the generated tempfile for this check
+
+
+class NoMatch(Exception):
+ """
+ Exception for matchPart to indicate there was no match found in the passed logfile
+ """
+ def __init__(self, p):
+ self.pos = p
+
+
+def match_part_of_configlog(log, logfile, lastEnd, NoMatch=NoMatch):
+ """
+ Match part of the logfile
+ """
+ # print("Match:\n%s\n==============\n%s" % (log , logfile[lastEnd:]))
+ m = re.match(log, logfile[lastEnd:])
+ if not m:
+ raise NoMatch(lastEnd)
+ return m.end() + lastEnd
+
+
class TestSCons(TestCommon):
"""Class for testing SCons.
@@ -247,7 +283,7 @@ class TestSCons(TestCommon):
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,]
+ kw['interpreter'] = [python, ]
if sys.version_info[0] < 3:
kw['interpreter'].append('-tt')
if 'match' not in kw:
@@ -365,12 +401,12 @@ class TestSCons(TestCommon):
return None
- def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0):
+ def wrap_stdout(self, build_str="", read_str="", error=0, cleaning=0):
"""Wraps standard output string(s) in the normal
"Reading ... done" and "Building ... done" strings
"""
- cap,lc = [ ('Build','build'),
- ('Clean','clean') ][cleaning]
+ cap, lc = [('Build', 'build'),
+ ('Clean', 'clean')][cleaning]
if error:
term = "scons: %sing terminated because of errors.\n" % lc
else:
@@ -393,30 +429,30 @@ class TestSCons(TestCommon):
finally:
restore_sconsflags(sconsflags)
-# Modifying the options should work and ought to be simpler, but this
-# class is used for more than just running 'scons' itself. If there's
-# an automated way of determining whether it's running 'scons' or
-# something else, this code should be resurected.
-# options = kw.get('options')
-# if options:
-# options = [options]
-# else:
-# options = []
-# if self.ignore_python_version and deprecated_python_version():
-# options.append('--warn=no-python-version')
-# # 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.)
-# visual_c = os.environ.get('TESTSCONS_SCONSFLAGS',
-# '--warn=no-visual-c-missing')
-# if visual_c:
-# options.append(visual_c)
-# kw['options'] = ' '.join(options)
-# TestCommon.run(self, *args, **kw)
-
- def up_to_date(self, arguments = '.', read_str = "", **kw):
+ # Modifying the options should work and ought to be simpler, but this
+ # class is used for more than just running 'scons' itself. If there's
+ # an automated way of determining whether it's running 'scons' or
+ # something else, this code should be resurected.
+ # options = kw.get('options')
+ # if options:
+ # options = [options]
+ # else:
+ # options = []
+ # if self.ignore_python_version and deprecated_python_version():
+ # options.append('--warn=no-python-version')
+ # # 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.)
+ # visual_c = os.environ.get('TESTSCONS_SCONSFLAGS',
+ # '--warn=no-visual-c-missing')
+ # if visual_c:
+ # options.append(visual_c)
+ # kw['options'] = ' '.join(options)
+ # TestCommon.run(self, *args, **kw)
+
+ def up_to_date(self, arguments='.', read_str="", **kw):
"""Asserts that all of the targets listed in arguments is
up to date, but does not make any assumptions on other targets.
This function is most useful in conjunction with the -n option.
@@ -425,14 +461,14 @@ class TestSCons(TestCommon):
for arg in arguments.split():
s = s + "scons: `%s' is up to date.\n" % arg
kw['arguments'] = arguments
- stdout = self.wrap_stdout(read_str = read_str, build_str = s)
+ stdout = self.wrap_stdout(read_str=read_str, build_str=s)
# Append '.*' so that timing output that comes after the
# up-to-date output is okay.
kw['stdout'] = re.escape(stdout) + '.*'
kw['match'] = self.match_re_dotall
self.run(**kw)
- def not_up_to_date(self, arguments = '.', **kw):
+ def not_up_to_date(self, arguments='.', **kw):
"""Asserts that none of the targets listed in arguments is
up to date, but does not make any assumptions on other targets.
This function is most useful in conjunction with the -n option.
@@ -440,7 +476,7 @@ class TestSCons(TestCommon):
s = ""
for arg in arguments.split():
s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg)
- s = '('+s+'[^\n]*\n)*'
+ s = '(' + s + '[^\n]*\n)*'
kw['arguments'] = arguments
stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE'))
kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s)
@@ -495,22 +531,22 @@ class TestSCons(TestCommon):
# no option, should get one of nothing, warning, or error
warning = self.deprecated_wrap(msg)
- self.run(arguments = '.', stderr = None)
+ self.run(arguments='.', stderr=None)
stderr = self.stderr()
if stderr:
# most common case done first
if match_re_dotall(stderr, warning):
- # expected output
- pass
+ # expected output
+ pass
elif match_re_dotall(stderr, err_out()):
- # now a fatal error; skip the rest of the tests
- self.pass_test()
+ # now a fatal error; skip the rest of the tests
+ self.pass_test()
else:
- # test failed; have to do this by hand...
- print(self.banner('STDOUT '))
- print(self.stdout())
- print(self.diff(warning, stderr, 'STDERR '))
- self.fail_test()
+ # test failed; have to do this by hand...
+ print(self.banner('STDOUT '))
+ print(self.stdout())
+ print(self.diff(warning, stderr, 'STDERR '))
+ self.fail_test()
return warning
@@ -529,14 +565,14 @@ class TestSCons(TestCommon):
def RunPair(option, expected):
# run the same test with the option on the command line and
# then with the option passed via SetOption().
- self.run(options = '--warn=' + option,
- arguments = '.',
- stderr = expected,
- match = match_re_dotall)
- self.run(options = 'WARN=' + option,
- arguments = '.',
- stderr = expected,
- match = match_re_dotall)
+ self.run(options='--warn=' + option,
+ arguments='.',
+ stderr=expected,
+ match=match_re_dotall)
+ self.run(options='WARN=' + option,
+ arguments='.',
+ stderr=expected,
+ match=match_re_dotall)
# all warnings off, should get no output
RunPair('no-deprecated', '')
@@ -557,8 +593,8 @@ class TestSCons(TestCommon):
return "Actual did not match expect at char %d:\n" \
" Expect: %s\n" \
" Actual: %s\n" \
- % (i, repr(expect[i-prelen:i+postlen]),
- repr(actual[i-prelen:i+postlen]))
+ % (i, repr(expect[i - prelen:i + postlen]),
+ repr(actual[i - prelen:i + postlen]))
i = i + 1
return "Actual matched the expected output???"
@@ -579,14 +615,14 @@ class TestSCons(TestCommon):
# traceback seems to be stable, so let's just format
# an appropriate string
#
- #exec('import traceback; x = traceback.format_stack()[-1]')
+ # exec('import traceback; x = traceback.format_stack()[-1]')
# import traceback
# x = traceback.format_stack()
# x = # XXX: .lstrip()
# x = x.replace('<string>', file)
# x = x.replace('line 1,', 'line %s,' % line)
# x="\n".join(x)
- x='File "%s", line %s, in <module>\n'%(file,line)
+ x = 'File "%s", line %s, in <module>\n' % (file, line)
return x
def normalize_ps(self, s):
@@ -613,13 +649,13 @@ class TestSCons(TestCommon):
def normalize_pdf(self, s):
s = self.to_bytes_re_sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
- r'/\1Date (D:XXXX)', s)
+ r'/\1Date (D:XXXX)', s)
s = self.to_bytes_re_sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
- r'/ID [<XXXX> <XXXX>]', s)
+ r'/ID [<XXXX> <XXXX>]', s)
s = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}',
- r'/\1 /XXXXXX', s)
+ r'/\1 /XXXXXX', s)
s = self.to_bytes_re_sub(r'/Length \d+ *\n/Filter /FlateDecode\n',
- r'/Length XXXX\n/Filter /FlateDecode\n', s)
+ r'/Length XXXX\n/Filter /FlateDecode\n', s)
try:
import zlib
@@ -643,11 +679,11 @@ class TestSCons(TestCommon):
r.append(s[x:b])
d = zlib.decompress(s[b:e])
d = self.to_bytes_re_sub(r'%%CreationDate: [^\n]*\n',
- r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
+ r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
d = self.to_bytes_re_sub(r'%DVIPSSource: TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d',
- r'%DVIPSSource: TeX output 1970.01.01:0000', d)
+ r'%DVIPSSource: TeX output 1970.01.01:0000', d)
d = self.to_bytes_re_sub(r'/(BaseFont|FontName) /[A-Z]{6}',
- r'/\1 /XXXXXX', d)
+ r'/\1 /XXXXXX', d)
r.append(d)
x = e
r.append(s[x:])
@@ -655,14 +691,14 @@ class TestSCons(TestCommon):
return s
- def paths(self,patterns):
+ def paths(self, patterns):
import glob
result = []
for p in patterns:
result.extend(sorted(glob.glob(p)))
return result
- def unlink_sconsignfile(self,name='.sconsign.dblite'):
+ def unlink_sconsignfile(self, name='.sconsign.dblite'):
"""
Delete sconsign file.
Note on python it seems to append .p3 to the file name so we take care of that
@@ -728,7 +764,7 @@ class TestSCons(TestCommon):
return None
- def java_where_includes(self,version=None):
+ def java_where_includes(self, version=None):
"""
Find include path needed for compiling java jni code.
@@ -740,30 +776,30 @@ class TestSCons(TestCommon):
result = []
if sys.platform[:6] == 'darwin':
java_home = self.java_where_java_home(version)
- jni_path = os.path.join(java_home,'include','jni.h')
+ jni_path = os.path.join(java_home, 'include', 'jni.h')
if os.path.exists(jni_path):
result.append(os.path.dirname(jni_path))
if not version:
- version=''
+ version = ''
jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Headers/jni.h',
'/usr/lib/jvm/default-java/include/jni.h',
'/usr/lib/jvm/java-*-oracle/include/jni.h']
else:
- jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version]
- jni_dirs.extend(['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
- '/usr/lib/jvm/java-%s*-openjdk*/include/jni.h'%version,
- '/usr/java/jdk%s*/include/jni.h'%version])
+ jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h' % version]
+ jni_dirs.extend(['/usr/lib/jvm/java-*-sun-%s*/include/jni.h' % version,
+ '/usr/lib/jvm/java-%s*-openjdk*/include/jni.h' % version,
+ '/usr/java/jdk%s*/include/jni.h' % version])
dirs = self.paths(jni_dirs)
if not dirs:
return None
- d=os.path.dirname(self.paths(jni_dirs)[0])
+ d = os.path.dirname(self.paths(jni_dirs)[0])
result.append(d)
if sys.platform == 'win32':
- result.append(os.path.join(d,'win32'))
+ result.append(os.path.join(d, 'win32'))
elif sys.platform.startswith('linux'):
- result.append(os.path.join(d,'linux'))
+ result.append(os.path.join(d, 'linux'))
return result
def java_where_java_home(self, version=None):
@@ -788,14 +824,14 @@ class TestSCons(TestCommon):
return java_home
else:
homes = ['/System/Library/Frameworks/JavaVM.framework/Home',
- # osx 10.10
+ # osx 10.10
'/System/Library/Frameworks/JavaVM.framework/Versions/Current/Home']
for home in homes:
if os.path.exists(home):
return home
else:
- if java_home.find('jdk%s'%version) != -1:
+ if java_home.find('jdk%s' % version) != -1:
return java_home
else:
home = '/System/Library/Frameworks/JavaVM.framework/Versions/%s/Home' % version
@@ -804,7 +840,7 @@ class TestSCons(TestCommon):
home = '/System/Library/Frameworks/JavaVM.framework/Versions/Current/'
else:
jar = self.java_where_jar(version)
- home = os.path.normpath('%s/..'%jar)
+ home = os.path.normpath('%s/..' % jar)
if os.path.isdir(home):
return home
print("Could not determine JAVA_HOME: %s is not a directory" % home)
@@ -873,8 +909,8 @@ class TestSCons(TestCommon):
elif sys.platform == "darwin":
self.java_mac_check(where_javac, 'javac')
- self.run(program = where_javac,
- arguments = '-version',
+ self.run(program=where_javac,
+ arguments='-version',
stderr=None,
status=None)
# Note recent versions output version info to stdout instead of stderr
@@ -893,7 +929,7 @@ class TestSCons(TestCommon):
version = m.group(1)
self.javac_is_gcj = False
elif self.stderr().find('gcj') != -1:
- version='1.2'
+ version = '1.2'
self.javac_is_gcj = True
else:
version = None
@@ -936,7 +972,6 @@ class TestSCons(TestCommon):
self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
return where_rmic
-
def java_get_class_files(self, dir):
result = []
for dirpath, dirnames, filenames in os.walk(dir):
@@ -945,7 +980,6 @@ class TestSCons(TestCommon):
result.append(os.path.join(dirpath, fname))
return sorted(result)
-
def Qt_dummy_installation(self, dir='qt'):
# create a dummy qt installation
@@ -1016,7 +1050,7 @@ with open(outfile, 'w') as ofp, open(source, 'r') as ifp:
else:
ofp.write('#include "my_qobject.h"\\n' + ifp.read() + " Q_OBJECT \\n")
sys.exit(0)
-""" )
+""")
self.write([dir, 'include', 'my_qobject.h'], r"""
#define Q_OBJECT ;
@@ -1040,10 +1074,10 @@ else:
env.SharedLibrary('myqt', 'my_qobject.cpp')
""")
- self.run(chdir = self.workpath(dir, 'lib'),
- arguments = '.',
- stderr = noisy_ar,
- match = self.match_re_dotall)
+ self.run(chdir=self.workpath(dir, 'lib'),
+ arguments='.',
+ stderr=noisy_ar,
+ match=self.match_re_dotall)
self.QT = self.workpath(dir)
self.QT_LIB = 'myqt'
@@ -1083,11 +1117,10 @@ Export("env dup")
SConscript(sconscript)
""" % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC))
-
- NCR = 0 # non-cached rebuild
- CR = 1 # cached rebuild (up to date)
- NCF = 2 # non-cached build failure
- CF = 3 # cached build failure
+ NCR = 0 # non-cached rebuild
+ CR = 1 # cached rebuild (up to date)
+ NCF = 2 # non-cached build failure
+ CF = 3 # cached build failure
if sys.platform == 'win32':
Configure_lib = 'msvcrt'
@@ -1095,7 +1128,7 @@ SConscript(sconscript)
Configure_lib = 'm'
# to use cygwin compilers on cmd.exe -> uncomment following line
- #Configure_lib = 'm'
+ # Configure_lib = 'm'
def coverage_run(self):
""" Check if the the tests are being run under coverage.
@@ -1125,6 +1158,145 @@ SConscript(sconscript)
except:
pass
+ def checkConfigureLogAndStdout(self, checks,
+ logfile='config.log',
+ sconf_dir='.sconf_temp',
+ sconstruct="SConstruct",
+ doCheckLog=True, doCheckStdout=True):
+ """
+ Used to verify the expected output from using Configure()
+ via the contents of one or both of stdout or config.log file.
+ The checks, results, cached parameters all are zipped together
+ for use in comparing results.
+
+ TODO: Perhaps a better API makes sense?
+
+ Parameters
+ ----------
+ checks : list of ConfigCheckInfo tuples which specify
+ logfile : Name of the config log
+ sconf_dir : Name of the sconf dir
+ sconstruct : SConstruct file name
+ doCheckLog : check specified log file, defaults to true
+ doCheckStdout : Check stdout, defaults to true
+
+ Returns
+ -------
+
+ """
+
+
+ try:
+ ls = '\n'
+ nols = '([^\n])'
+ lastEnd = 0
+
+ # Read the whole logfile
+ logfile = self.read(self.workpath(logfile), mode='r')
+
+ # Some debug code to keep around..
+ # sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile))
+
+ if (doCheckLog and
+ logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0):
+ self.fail_test()
+
+ log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
+
+ log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
+
+ rdstr = ""
+
+ for check_info in checks:
+ log = re.escape("scons: Configure: " + check_info.check_string) + ls
+
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
+
+ log = ""
+ result_cached = 1
+ for bld_desc in check_info.cached: # each TryXXX
+ for ext, flag in bld_desc: # each file in TryBuild
+ conf_filename = re.escape(check_info.temp_filename%ext)
+
+ if flag == self.NCR:
+ # NCR = Non Cached Rebuild
+ # rebuild will pass
+ if ext in ['.c', '.cpp']:
+ log = log + conf_filename + re.escape(" <-") + ls
+ log = log + r"( \|" + nols + "*" + ls + ")+?"
+ else:
+ log = log + "(" + nols + "*" + ls + ")*?"
+ result_cached = 0
+ if flag == self.CR:
+ # CR = cached rebuild (up to date)s
+ # up to date
+ log = log + \
+ re.escape("scons: Configure: \"") + \
+ conf_filename + \
+ re.escape("\" is up to date.") + ls
+ log = log + re.escape("scons: Configure: The original builder "
+ "output was:") + ls
+ log = log + r"( \|.*" + ls + ")+"
+ if flag == self.NCF:
+ # non-cached rebuild failure
+ log = log + "(" + nols + "*" + ls + ")*?"
+ result_cached = 0
+ if flag == self.CF:
+ # cached rebuild failure
+ log = log + \
+ re.escape("scons: Configure: Building \"") + \
+ conf_filename + \
+ re.escape("\" failed in a previous run and all its sources are up to date.") + ls
+ log = log + re.escape("scons: Configure: The original builder output was:") + ls
+ log = log + r"( \|.*" + ls + ")+"
+ if result_cached:
+ result = "(cached) " + check_info.result
+ else:
+ result = check_info.result
+ rdstr = rdstr + re.escape(check_info.check_string) + re.escape(result) + "\n"
+
+ log = log + re.escape("scons: Configure: " + result) + ls + ls
+
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
+
+ log = ""
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(ls, logfile, lastEnd)
+
+ if doCheckLog and lastEnd != len(logfile):
+ raise NoMatch(lastEnd)
+
+ except NoMatch as m:
+ print("Cannot match log file against log regexp.")
+ print("log file: ")
+ print("------------------------------------------------------")
+ print(logfile[m.pos:])
+ print("------------------------------------------------------")
+ print("log regexp: ")
+ print("------------------------------------------------------")
+ print(log)
+ print("------------------------------------------------------")
+ self.fail_test()
+
+ if doCheckStdout:
+ exp_stdout = self.wrap_stdout(".*", rdstr)
+ if not self.match_re_dotall(self.stdout(), exp_stdout):
+ print("Unexpected stdout: ")
+ print("-----------------------------------------------------")
+ print(repr(self.stdout()))
+ print("-----------------------------------------------------")
+ print(repr(exp_stdout))
+ print("-----------------------------------------------------")
+ self.fail_test()
+
+
+
def checkLogAndStdout(self, checks, results, cached,
logfile, sconf_dir, sconstruct,
doCheckLog=True, doCheckStdout=True):
@@ -1158,42 +1330,8 @@ SConscript(sconscript)
-------
"""
-
- class NoMatch(Exception):
- def __init__(self, p):
- self.pos = p
-
- def matchPart(log, logfile, lastEnd, NoMatch=NoMatch):
- """
- Match part of the logfile
- """
- m = re.match(log, logfile[lastEnd:])
- if not m:
- raise NoMatch(lastEnd)
- return m.end() + lastEnd
-
try:
- # Build regexp for a character which is not
- # a linesep, and in the case of CR/LF
- # build it with both CR and CR/LF
- # TODO: Not sure why this is a good idea. A static string
- # could do the same since we only have two variations
- # to do with?
- # ls = os.linesep
- # nols = "("
- # for i in range(len(ls)):
- # nols = nols + "("
- # for j in range(i):
- # nols = nols + ls[j]
- # nols = nols + "[^" + ls[i] + "])"
- # if i < len(ls)-1:
- # nols = nols + "|"
- # nols = nols + ")"
- #
- # Replaced above logic with \n as we're reading the file
- # using non-binary read. Python will translate \r\n -> \n
- # For us.
ls = '\n'
nols = '([^\n])'
lastEnd = 0
@@ -1205,7 +1343,7 @@ SConscript(sconscript)
# sys.stderr.write("LOGFILE[%s]:%s"%(type(logfile),logfile))
if (doCheckLog and
- logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0):
+ logfile.find("scons: warning: The stored build information has an unexpected class.") >= 0):
self.fail_test()
sconf_dir = sconf_dir
@@ -1213,67 +1351,90 @@ SConscript(sconscript)
log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
if doCheckLog:
- lastEnd = matchPart(log, logfile, lastEnd)
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
if doCheckLog:
- lastEnd = matchPart(log, logfile, lastEnd)
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
rdstr = ""
+
cnt = 0
- for check,result,cache_desc in zip(checks, results, cached):
- log = re.escape("scons: Configure: " + check) + ls
+ for check, result, cache_desc in zip(checks, results, cached):
+ log = re.escape("scons: Configure: " + check) + ls
if doCheckLog:
- lastEnd = matchPart(log, logfile, lastEnd)
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
log = ""
result_cached = 1
- for bld_desc in cache_desc: # each TryXXX
- for ext, flag in bld_desc: # each file in TryBuild
- file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
+ for bld_desc in cache_desc: # each TryXXX
+ for ext, flag in bld_desc: # each file in TryBuild
+ if ext in ['.c', '.cpp']:
+ conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
+ r'_[a-z0-9]{32}_\d+%s' % re.escape(ext)
+ elif ext == '':
+ conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
+ r'_[a-z0-9]{32}(_\d+_[a-z0-9]{32})?'
+
+ else:
+ # We allow the second hash group to be optional because
+ # TryLink() will create a c file, then compile to obj, then link that
+ # The intermediate object file will not get the action hash
+ # But TryCompile()'s where the product is the .o will get the
+ # action hash. Rather than add a ton of complications to this logic
+ # this shortcut should be sufficient.
+ # TODO: perhaps revisit and/or fix file naming for intermediate files in
+ # Configure context logic
+ conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
+ r'_[a-z0-9]{32}_\d+(_[a-z0-9]{32})?%s' % re.escape(ext)
+
if flag == self.NCR:
# NCR = Non Cached Rebuild
# rebuild will pass
if ext in ['.c', '.cpp']:
- log=log + re.escape(file + " <-") + ls
- log=log + r"( \|" + nols + "*" + ls + ")+?"
+ log = log + conf_filename + re.escape(" <-") + ls
+ log = log + r"( \|" + nols + "*" + ls + ")+?"
else:
- log=log + "(" + nols + "*" + ls +")*?"
+ log = log + "(" + nols + "*" + ls + ")*?"
result_cached = 0
if flag == self.CR:
# CR = cached rebuild (up to date)s
# up to date
- log=log + \
- re.escape("scons: Configure: \"%s\" is up to date."
- % file) + ls
- log=log+re.escape("scons: Configure: The original builder "
- "output was:") + ls
- log=log+r"( \|.*"+ls+")+"
+ log = log + \
+ re.escape("scons: Configure: \"") + \
+ conf_filename + \
+ re.escape("\" is up to date.") + ls
+ log = log + re.escape("scons: Configure: The original builder "
+ "output was:") + ls
+ log = log + r"( \|.*" + ls + ")+"
if flag == self.NCF:
# non-cached rebuild failure
- log=log + "(" + nols + "*" + ls + ")*?"
+ log = log + "(" + nols + "*" + ls + ")*?"
result_cached = 0
if flag == self.CF:
# cached rebuild failure
- log=log + \
- re.escape("scons: Configure: Building \"%s\" failed "
- "in a previous run and all its sources are"
- " up to date." % file) + ls
- log=log+re.escape("scons: Configure: The original builder "
- "output was:") + ls
- log=log+r"( \|.*"+ls+")+"
- cnt = cnt + 1
+ log = log + \
+ re.escape("scons: Configure: Building \"") + \
+ conf_filename + \
+ re.escape("\" failed in a previous run and all its sources are up to date.") + ls
+ log = log + re.escape("scons: Configure: The original builder output was:") + ls
+ log = log + r"( \|.*" + ls + ")+"
+ # cnt = cnt + 1
if result_cached:
result = "(cached) " + result
+
rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
- log=log + re.escape("scons: Configure: " + result) + ls + ls
+
+ log = log + re.escape("scons: Configure: " + result) + ls + ls
if doCheckLog:
- lastEnd = matchPart(log, logfile, lastEnd)
+ lastEnd = match_part_of_configlog(log, logfile, lastEnd)
log = ""
- if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
+ if doCheckLog:
+ lastEnd = match_part_of_configlog(ls, logfile, lastEnd)
+
if doCheckLog and lastEnd != len(logfile):
raise NoMatch(lastEnd)
@@ -1293,9 +1454,9 @@ SConscript(sconscript)
exp_stdout = self.wrap_stdout(".*", rdstr)
if not self.match_re_dotall(self.stdout(), exp_stdout):
print("Unexpected stdout: ")
- print("-----------------------------------------------------")
+ print("----Actual-------------------------------------------")
print(repr(self.stdout()))
- print("-----------------------------------------------------")
+ print("----Expected-----------------------------------------")
print(repr(exp_stdout))
print("-----------------------------------------------------")
self.fail_test()
@@ -1435,7 +1596,7 @@ else:
files with .C and .c as different files or not
in which case they are instructed to use .cpp instead of .C
"""
- if not case_sensitive_suffixes('.c','.C'):
+ if not case_sensitive_suffixes('.c', '.C'):
alt_cpp_suffix = '.cpp'
else:
alt_cpp_suffix = '.C'
@@ -1457,6 +1618,7 @@ class Stat:
self.expression = re.compile(expression)
self.convert = convert
+
StatList = [
Stat('memory-initial', 'kbytes',
r'Memory before reading SConscript files:\s+(\d+)',
@@ -1481,6 +1643,7 @@ StatList = [
class TimeSCons(TestSCons):
"""Class for timing SCons."""
+
def __init__(self, *args, **kw):
"""
In addition to normal TestSCons.TestSCons intialization,
@@ -1563,7 +1726,7 @@ class TimeSCons(TestSCons):
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 + (' sort=%s' % sort)
line = line + '\n'
sys.stdout.write(line)
sys.stdout.flush()
@@ -1585,9 +1748,9 @@ class TimeSCons(TestSCons):
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')
+ 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 = {}
@@ -1598,7 +1761,7 @@ class TimeSCons(TestSCons):
# 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}
+ result[stat.name] = {'value': value, 'units': stat.units}
return result
def add_timing_options(self, kw, additional=None):
@@ -1713,8 +1876,8 @@ class TimeSCons(TestSCons):
for root, dirs, files in os.walk(source_dir):
if '.svn' in dirs:
dirs.remove('.svn')
- dirs = [ d for d in dirs if not d.startswith('TimeSCons-') ]
- files = [ f for f in files if not f.startswith('TimeSCons-') ]
+ dirs = [d for d in dirs if not d.startswith('TimeSCons-')]
+ files = [f for f in files if not f.startswith('TimeSCons-')]
for dirname in dirs:
source = os.path.join(root, dirname)
destination = source.replace(source_dir, dest_dir)
@@ -1736,7 +1899,7 @@ class TimeSCons(TestSCons):
# <file>" messages to be successful executions of the test (see
# test/AR.py for sample usage).
-noisy_ar=r'(ar: creating( archive)? \S+\n?)*'
+noisy_ar = r'(ar: creating( archive)? \S+\n?)*'
# Local Variables:
# tab-width:4