summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SCons/SConsign.py11
-rw-r--r--SCons/SConsignTests.py6
-rw-r--r--SCons/Util.py78
-rw-r--r--SCons/UtilTests.py176
-rw-r--r--test/Configure/ConfigureDryRunError.py13
-rw-r--r--test/Configure/VariantDir-SConscript.py2
-rw-r--r--test/Configure/implicit-cache.py28
-rw-r--r--test/Configure/option--config.py6
-rw-r--r--test/Repository/variants.py66
-rw-r--r--test/option/hash-format.py42
-rw-r--r--test/option/option-n.py15
-rw-r--r--test/question/Configure.py14
-rw-r--r--test/sconsign/corrupt.py13
-rw-r--r--test/sconsign/script/Configure.py4
-rw-r--r--testing/framework/TestSCons.py22
15 files changed, 395 insertions, 101 deletions
diff --git a/SCons/SConsign.py b/SCons/SConsign.py
index ade8a10..95ceac1 100644
--- a/SCons/SConsign.py
+++ b/SCons/SConsign.py
@@ -64,10 +64,17 @@ def Get_DataBase(dir):
if DB_Name is None:
hash_format = SCons.Util.get_hash_format()
- if hash_format is None:
+ current_hash_algorithm = SCons.Util.get_current_hash_algorithm_used()
+ # if the user left the options defaulted AND the default algorithm set by
+ # SCons is md5, then set the database name to be the special default name
+ #
+ # otherwise, if it defaults to something like 'sha1' or the user explicitly
+ # set 'md5' as the hash format, set the database name to .sconsign_<algorithm>
+ # eg .sconsign_sha1, etc.
+ if hash_format is None and current_hash_algorithm == 'md5':
DB_Name = ".sconsign"
else:
- DB_Name = ".sconsign_%s" % hash_format
+ DB_Name = ".sconsign_%s" % current_hash_algorithm
top = dir.fs.Top
if not os.path.isabs(DB_Name) and top.repositories:
diff --git a/SCons/SConsignTests.py b/SCons/SConsignTests.py
index d4f4418..e9f2071 100644
--- a/SCons/SConsignTests.py
+++ b/SCons/SConsignTests.py
@@ -28,6 +28,7 @@ import TestCmd
import SCons.dblite
import SCons.SConsign
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
class BuildInfo:
def merge(self, object):
@@ -295,7 +296,10 @@ class SConsignFileTestCase(SConsignTestCase):
file = test.workpath('sconsign_file')
assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase
- assert SCons.SConsign.DB_Name == ".sconsign", SCons.SConsign.DB_Name
+ if get_hash_format() is None and get_current_hash_algorithm_used() == 'md5':
+ assert SCons.SConsign.DB_Name == ".sconsign", SCons.SConsign.DB_Name
+ else:
+ assert SCons.SConsign.DB_Name == ".sconsign_{}".format(get_current_hash_algorithm_used()), SCons.SConsign.DB_Name
assert SCons.SConsign.DB_Module is SCons.dblite, SCons.SConsign.DB_Module
SCons.SConsign.File(file)
diff --git a/SCons/Util.py b/SCons/Util.py
index 8b79a3e..cbd99b0 100644
--- a/SCons/Util.py
+++ b/SCons/Util.py
@@ -29,7 +29,6 @@ import os
import pprint
import re
import sys
-import inspect
from collections import UserDict, UserList, UserString, OrderedDict
from collections.abc import MappingView
from contextlib import suppress
@@ -1670,12 +1669,12 @@ def AddMethod(obj, function, name=None):
# Default hash function and format. SCons-internal.
-_DEFAULT_HASH_FORMATS = ['md5', 'sha1', 'sha256']
+DEFAULT_HASH_FORMATS = ['md5', 'sha1', 'sha256']
ALLOWED_HASH_FORMATS = []
_HASH_FUNCTION = None
_HASH_FORMAT = None
-def _attempt_init_of_python_3_9_hash_object(hash_function_object):
+def _attempt_init_of_python_3_9_hash_object(hash_function_object, sys_used=sys):
"""Python 3.9 and onwards lets us initialize the hash function object with the
key "usedforsecurity"=false. This lets us continue to use algorithms that have
been deprecated either by FIPS or by Python itself, as the MD5 algorithm SCons
@@ -1696,7 +1695,7 @@ def _attempt_init_of_python_3_9_hash_object(hash_function_object):
# however, for our purposes checking the version is greater than or equal to 3.9 is good enough, as
# the API is guaranteed to have support for the 'usedforsecurity' flag in 3.9. See
# https://docs.python.org/3/library/hashlib.html#:~:text=usedforsecurity for the version support notes.
- if (sys.version_info.major > 3) or (sys.version_info.major == 3 and sys.version_info.minor >= 9):
+ if (sys_used.version_info.major > 3) or (sys_used.version_info.major == 3 and sys_used.version_info.minor >= 9):
return hash_function_object(usedforsecurity=False)
# note that this can throw a ValueError in FIPS-enabled versions of Linux prior to 3.9
@@ -1704,7 +1703,7 @@ def _attempt_init_of_python_3_9_hash_object(hash_function_object):
# the caller to diagnose the ValueError & potentially display the error to screen.
return hash_function_object()
-def _set_allowed_viable_default_hashes(hashlib_used):
+def _set_allowed_viable_default_hashes(hashlib_used, sys_used=sys):
"""Checks if SCons has ability to call the default algorithms normally supported.
This util class is sometimes called prior to setting the user-selected hash algorithm,
@@ -1712,7 +1711,7 @@ def _set_allowed_viable_default_hashes(hashlib_used):
and throw an exception in set_hash_format. A common case is using the SConf options,
which can run prior to main, and thus ignore the options.hash_format variable.
- This function checks the _DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORMATS
+ This function checks the DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORMATS
to only the ones that can be called. In Python >= 3.9 this will always default to
MD5 as in Python 3.9 there is an optional attribute "usedforsecurity" set for the method.
@@ -1724,7 +1723,7 @@ def _set_allowed_viable_default_hashes(hashlib_used):
# otherwise it keeps appending valid formats to the string
ALLOWED_HASH_FORMATS = []
- for test_algorithm in _DEFAULT_HASH_FORMATS:
+ for test_algorithm in DEFAULT_HASH_FORMATS:
_test_hash = getattr(hashlib_used, test_algorithm, None)
# we know hashlib claims to support it... check to see if we can call it.
if _test_hash is not None:
@@ -1733,7 +1732,7 @@ def _set_allowed_viable_default_hashes(hashlib_used):
# throw if it's a bad algorithm, otherwise it will append it to the known
# good formats.
try:
- _attempt_init_of_python_3_9_hash_object(_test_hash)
+ _attempt_init_of_python_3_9_hash_object(_test_hash, sys_used)
ALLOWED_HASH_FORMATS.append(test_algorithm)
except ValueError as e:
_last_error = e
@@ -1761,8 +1760,27 @@ def get_hash_format():
"""
return _HASH_FORMAT
+def _attempt_get_hash_function(hash_name, hashlib_used=hashlib, sys_used=sys):
+ """Wrapper used to try to initialize a hash function given.
-def set_hash_format(hash_format, hashlib_used=hashlib):
+ If successful, returns the name of the hash function back to the user.
+
+ Otherwise returns None.
+ """
+ try:
+ _fetch_hash = getattr(hashlib_used, hash_name, None)
+ if _fetch_hash is None:
+ return None
+ _attempt_init_of_python_3_9_hash_object(_fetch_hash, sys_used)
+ return hash_name
+ except ValueError:
+ # if attempt_init_of_python_3_9 throws, this is typically due to FIPS being enabled
+ # however, if we get to this point, the viable hash function check has either been
+ # bypassed or otherwise failed to properly restrict the user to only the supported
+ # functions. As such throw the UserError as an internal assertion-like error.
+ return None
+
+def set_hash_format(hash_format, hashlib_used=hashlib, sys_used=sys):
"""Sets the default hash format used by SCons.
If `hash_format` is ``None`` or
@@ -1782,7 +1800,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib):
# user can select something not supported by their OS but normally supported by
# SCons, example, selecting MD5 in an OS with FIPS-mode turned on. Therefore we first
# check if SCons supports it, and then if their local OS supports it.
- if hash_format_lower in _DEFAULT_HASH_FORMATS:
+ if hash_format_lower in DEFAULT_HASH_FORMATS:
raise UserError('While hash format "%s" is supported by SCons, the '
'local system indicates only the following hash '
'formats are supported by the hashlib library: %s' %
@@ -1793,7 +1811,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib):
# the hash format isn't supported by SCons in any case. Warn the user, and
# if we detect that SCons supports more algorithms than their local system
# supports, warn the user about that too.
- if ALLOWED_HASH_FORMATS == _DEFAULT_HASH_FORMATS:
+ if ALLOWED_HASH_FORMATS == DEFAULT_HASH_FORMATS:
raise UserError('Hash format "%s" is not supported by SCons. Only '
'the following hash formats are supported: %s' %
(hash_format_lower,
@@ -1805,23 +1823,14 @@ def set_hash_format(hash_format, hashlib_used=hashlib):
'is reporting; SCons supports: %s. Your local system only '
'supports: %s' %
(hash_format_lower,
- ', '.join(_DEFAULT_HASH_FORMATS),
+ ', '.join(DEFAULT_HASH_FORMATS),
', '.join(ALLOWED_HASH_FORMATS))
)
# this is not expected to fail. If this fails it means the set_allowed_viable_default_hashes
# function did not throw, or when it threw, the exception was caught and ignored, or
# the global ALLOWED_HASH_FORMATS was changed by an external user.
- try:
- _HASH_FUNCTION = None
- _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, hash_format_lower, None))
- _HASH_FUNCTION = hash_format_lower
- except ValueError:
- # if attempt_init_of_python_3_9 throws, this is typically due to FIPS being enabled
- # however, if we get to this point, the viable hash function check has either been
- # bypassed or otherwise failed to properly restrict the user to only the supported
- # functions. As such throw the UserError as an internal assertion-like error.
- pass
+ _HASH_FUNCTION = _attempt_get_hash_function(hash_format_lower, hashlib_used, sys_used)
if _HASH_FUNCTION is None:
from SCons.Errors import UserError # pylint: disable=import-outside-toplevel
@@ -1838,12 +1847,7 @@ def set_hash_format(hash_format, hashlib_used=hashlib):
# in FIPS-compliant systems this usually defaults to SHA1, unless that too has been
# disabled.
for choice in ALLOWED_HASH_FORMATS:
- try:
- _HASH_FUNCTION = None
- _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, choice, None))
- _HASH_FUNCTION = choice
- except ValueError:
- continue
+ _HASH_FUNCTION = _attempt_get_hash_function(choice, hashlib_used, sys_used)
if _HASH_FUNCTION is not None:
break
@@ -1864,7 +1868,19 @@ def set_hash_format(hash_format, hashlib_used=hashlib):
set_hash_format(None)
-def _get_hash_object(hash_format, hashlib_used=hashlib):
+def get_current_hash_algorithm_used():
+ """Returns the current hash algorithm name used.
+
+ Where the python version >= 3.9, this is expected to return md5.
+ If python's version is <= 3.8, this returns md5 on non-FIPS-mode platforms, and
+ sha1 or sha256 on FIPS-mode Linux platforms.
+
+ This function is primarily useful for testing, where one expects a value to be
+ one of N distinct hashes, and therefore the test needs to know which hash to select.
+ """
+ return _HASH_FUNCTION
+
+def _get_hash_object(hash_format, hashlib_used=hashlib, sys_used=sys):
"""Allocates a hash object using the requested hash format.
Args:
@@ -1879,7 +1895,7 @@ def _get_hash_object(hash_format, hashlib_used=hashlib):
raise UserError('There is no default hash function. Did you call '
'a hashing function before SCons was initialized?')
- return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HASH_FUNCTION, None))
+ return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HASH_FUNCTION, None), sys_used)
if not hasattr(hashlib, hash_format):
from SCons.Errors import UserError # pylint: disable=import-outside-toplevel
@@ -1888,7 +1904,7 @@ def _get_hash_object(hash_format, hashlib_used=hashlib):
'Hash format "%s" is not available in your Python interpreter.' %
hash_format)
- return _attempt_init_of_python_3_9_hash_object(getattr(hashlib, hash_format))
+ return _attempt_init_of_python_3_9_hash_object(getattr(hashlib, hash_format), sys_used)
def hash_signature(s, hash_format=None):
diff --git a/SCons/UtilTests.py b/SCons/UtilTests.py
index 616ea37..4417ba5 100644
--- a/SCons/UtilTests.py
+++ b/SCons/UtilTests.py
@@ -26,13 +26,17 @@ import io
import os
import sys
import unittest
-from collections import UserDict, UserList, UserString
+import unittest.mock
+import hashlib
+import warnings
+from collections import UserDict, UserList, UserString, namedtuple
import TestCmd
import SCons.Errors
import SCons.compat
from SCons.Util import (
+ ALLOWED_HASH_FORMATS,
AddPathIfNotExists,
AppendPath,
CLVar,
@@ -42,6 +46,9 @@ from SCons.Util import (
Proxy,
Selector,
WhereIs,
+ _attempt_init_of_python_3_9_hash_object,
+ _get_hash_object,
+ _set_allowed_viable_default_hashes,
adjustixes,
containsAll,
containsAny,
@@ -61,6 +68,7 @@ from SCons.Util import (
is_Tuple,
print_tree,
render_tree,
+ set_hash_format,
silent_intern,
splitext,
to_String,
@@ -838,12 +846,17 @@ class HashTestCase(unittest.TestCase):
'25235f0fcab8767b7b5ac6568786fbc4f7d5d83468f0626bf07c3dbeed391a7a',
'f8d3d0729bf2427e2e81007588356332e7e8c4133fae4bceb173b93f33411d17'),
}.items():
- hs = functools.partial(hash_signature, hash_format=algorithm)
- s = list(map(hs, ('111', '222', '333')))
-
- assert expected[0] == hash_collect(s[0:1], hash_format=algorithm)
- assert expected[1] == hash_collect(s[0:2], hash_format=algorithm)
- assert expected[2] == hash_collect(s, hash_format=algorithm)
+ # if the current platform does not support the algorithm we're looking at,
+ # skip the test steps for that algorithm, but display a warning to the user
+ if algorithm not in ALLOWED_HASH_FORMATS:
+ warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
+ else:
+ hs = functools.partial(hash_signature, hash_format=algorithm)
+ s = list(map(hs, ('111', '222', '333')))
+
+ assert expected[0] == hash_collect(s[0:1], hash_format=algorithm)
+ assert expected[1] == hash_collect(s[0:2], hash_format=algorithm)
+ assert expected[2] == hash_collect(s, hash_format=algorithm)
def test_MD5signature(self):
"""Test generating a signature"""
@@ -855,11 +868,150 @@ class HashTestCase(unittest.TestCase):
'sha256': ('f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae',
'9b871512327c09ce91dd649b3f96a63b7408ef267c8cc5710114e629730cb61f'),
}.items():
- s = hash_signature('111', hash_format=algorithm)
- assert expected[0] == s, s
-
- s = hash_signature('222', hash_format=algorithm)
- assert expected[1] == s, s
+ # if the current platform does not support the algorithm we're looking at,
+ # skip the test steps for that algorithm, but display a warning to the user
+ if algorithm not in ALLOWED_HASH_FORMATS:
+ warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
+ else:
+ s = hash_signature('111', hash_format=algorithm)
+ assert expected[0] == s, s
+
+ s = hash_signature('222', hash_format=algorithm)
+ assert expected[1] == s, s
+
+# this uses mocking out, which is platform specific, however, the FIPS
+# behavior this is testing is also platform-specific, and only would be
+# visible in hosts running Linux with the fips_mode kernel flag along
+# with using OpenSSL.
+
+class FIPSHashTestCase(unittest.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(FIPSHashTestCase, self).__init__(*args, **kwargs)
+
+ ###############################
+ # algorithm mocks, can check if we called with usedforsecurity=False for python >= 3.9
+ self.fake_md5=lambda usedforsecurity=True: (usedforsecurity, 'md5')
+ self.fake_sha1=lambda usedforsecurity=True: (usedforsecurity, 'sha1')
+ self.fake_sha256=lambda usedforsecurity=True: (usedforsecurity, 'sha256')
+ ###############################
+
+ ###############################
+ # hashlib mocks
+ md5Available = unittest.mock.Mock(md5=self.fake_md5)
+ del md5Available.sha1
+ del md5Available.sha256
+ self.md5Available=md5Available
+
+ md5Default = unittest.mock.Mock(md5=self.fake_md5, sha1=self.fake_sha1)
+ del md5Default.sha256
+ self.md5Default=md5Default
+
+ sha1Default = unittest.mock.Mock(sha1=self.fake_sha1, sha256=self.fake_sha256)
+ del sha1Default.md5
+ self.sha1Default=sha1Default
+
+ sha256Default = unittest.mock.Mock(sha256=self.fake_sha256, **{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError})
+ self.sha256Default=sha256Default
+
+ all_throw = unittest.mock.Mock(**{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError, 'sha256.side_effect': ValueError})
+ self.all_throw=all_throw
+
+ no_algorithms = unittest.mock.Mock()
+ del no_algorithms.md5
+ del no_algorithms.sha1
+ del no_algorithms.sha256
+ self.no_algorithms=no_algorithms
+
+ unsupported_algorithm = unittest.mock.Mock(unsupported=self.fake_sha256)
+ del unsupported_algorithm.md5
+ del unsupported_algorithm.sha1
+ del unsupported_algorithm.sha256
+ self.unsupported_algorithm=unsupported_algorithm
+ ###############################
+
+ ###############################
+ # system version mocks
+ VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
+ v3_8 = VersionInfo(3, 8, 199, 'super-beta', 1337)
+ v3_9 = VersionInfo(3, 9, 0, 'alpha', 0)
+ v4_8 = VersionInfo(4, 8, 0, 'final', 0)
+
+ self.sys_v3_8 = unittest.mock.Mock(version_info=v3_8)
+ self.sys_v3_9 = unittest.mock.Mock(version_info=v3_9)
+ self.sys_v4_8 = unittest.mock.Mock(version_info=v4_8)
+ ###############################
+
+ def test_usedforsecurity_flag_behavior(self):
+ """Test usedforsecurity flag -> should be set to 'True' on older versions of python, and 'False' on Python >= 3.9"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'md5'),
+ self.sys_v3_9: (False, 'md5'),
+ self.sys_v4_8: (False, 'md5'),
+ }.items():
+ assert _attempt_init_of_python_3_9_hash_object(self.fake_md5, version) == expected
+
+ def test_automatic_default_to_md5(self):
+ """Test automatic default to md5 even if sha1 available"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'md5'),
+ self.sys_v3_9: (False, 'md5'),
+ self.sys_v4_8: (False, 'md5'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.md5Default, version)
+ set_hash_format(None, self.md5Default, version)
+ assert _get_hash_object(None, self.md5Default, version) == expected
+
+ def test_automatic_default_to_sha256(self):
+ """Test automatic default to sha256 if other algorithms available but throw"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'sha256'),
+ self.sys_v3_9: (False, 'sha256'),
+ self.sys_v4_8: (False, 'sha256'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.sha256Default, version)
+ set_hash_format(None, self.sha256Default, version)
+ assert _get_hash_object(None, self.sha256Default, version) == expected
+
+ def test_automatic_default_to_sha1(self):
+ """Test automatic default to sha1 if md5 is missing from hashlib entirely"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'sha1'),
+ self.sys_v3_9: (False, 'sha1'),
+ self.sys_v4_8: (False, 'sha1'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.sha1Default, version)
+ set_hash_format(None, self.sha1Default, version)
+ assert _get_hash_object(None, self.sha1Default, version) == expected
+
+ def test_no_available_algorithms(self):
+ """expect exceptions on no available algorithms or when all algorithms throw"""
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.no_algorithms)
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.all_throw)
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.unsupported_algorithm)
+
+ def test_bad_algorithm_set_attempt(self):
+ """expect exceptions on user setting an unsupported algorithm selections, either by host or by SCons"""
+
+ # nonexistant hash algorithm, not supported by SCons
+ _set_allowed_viable_default_hashes(self.md5Available)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'blah blah blah', hashlib_used=self.no_algorithms)
+
+ # md5 is default-allowed, but in this case throws when we attempt to use it
+ _set_allowed_viable_default_hashes(self.md5Available)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)
+
+ # user attempts to use an algorithm that isn't supported by their current system but is supported by SCons
+ _set_allowed_viable_default_hashes(self.sha1Default)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)
+
+ # user attempts to use an algorithm that is supported by their current system but isn't supported by SCons
+ _set_allowed_viable_default_hashes(self.sha1Default)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'unsupported', hashlib_used=self.unsupported_algorithm)
+
+ def tearDown(self):
+ """Return SCons back to the normal global state for the hashing functions."""
+ _set_allowed_viable_default_hashes(hashlib, sys)
+ set_hash_format(None)
class NodeListTestCase(unittest.TestCase):
diff --git a/test/Configure/ConfigureDryRunError.py b/test/Configure/ConfigureDryRunError.py
index 3648518..224154b 100644
--- a/test/Configure/ConfigureDryRunError.py
+++ b/test/Configure/ConfigureDryRunError.py
@@ -31,6 +31,8 @@ import os
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
+
_obj = TestSCons._obj
test = TestSCons.TestSCons()
@@ -65,7 +67,16 @@ 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_df286a1d2f67e69d030b4eff75ca7e12_0.c")
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_df286a1d2f67e69d030b4eff75ca7e12_0.c",
+ 'sha1': "conftest_6e784ac3248d146c68396335df2f9428d78c1d24_0.c",
+ 'sha256': "conftest_be4b3c1d600e20dfc3e8d98748cd8193c850331643466d800ef8a4229a5be410_0.c"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
+conftest_0_c = os.path.join(".sconf_temp", test_filename)
SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
expect = """
diff --git a/test/Configure/VariantDir-SConscript.py b/test/Configure/VariantDir-SConscript.py
index deb7b8f..5818fc7 100644
--- a/test/Configure/VariantDir-SConscript.py
+++ b/test/Configure/VariantDir-SConscript.py
@@ -128,7 +128,7 @@ test.checkLogAndStdout( ["Checking for C header file math.h... ",
import shutil
shutil.rmtree(test.workpath(".sconf_temp"))
-test.unlink(".sconsign.dblite")
+test.unlink(test.get_sconsignname()+".dblite")
# now with SConscriptChdir(1)
test.run(arguments='chdir=yes')
diff --git a/test/Configure/implicit-cache.py b/test/Configure/implicit-cache.py
index f4f3e94..4078a98 100644
--- a/test/Configure/implicit-cache.py
+++ b/test/Configure/implicit-cache.py
@@ -55,6 +55,7 @@ get longer and longer until it blew out the users's memory.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import TestSConsign
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
test = TestSConsign.TestSConsign()
@@ -75,7 +76,28 @@ test.write('foo.h', "#define FOO 1\n")
test.run(arguments = '.')
-test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+# this test is different than the other tests, as the database name is used here.
+# when the database defaults to md5, that's a different name than when the user selects md5 directly.
+possible_filenames = {
+ 'default': "conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c",
+ 'md5': "conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c",
+ 'sha1': "conftest_80e5b88f2c7427a92f0e6c7184f144f874f10e60_0.c",
+ 'sha256': "conftest_ba8270c26647ad00993cd7777f4c5d3751018372b97d16eb993563bea051c3df_0.c"
+}
+# user left algorithm default, it defaulted to md5, with the special database name
+if get_hash_format() is None and get_current_hash_algorithm_used() == 'md5':
+ test_filename = possible_filenames['default']
+# either user selected something (like explicitly setting md5) or algorithm defaulted to something else.
+# SCons can default to something else if it detects the hashlib doesn't support it, example md5 in FIPS
+# mode prior to Python 3.9
+else:
+ test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
+database_name=test.get_sconsignname() + ".dblite"
+
+test.run_sconsign(f'-d .sconf_temp -e {test_filename} --raw {database_name}')
old_sconsign_dblite = test.stdout()
# Second run: Have the configure subsystem also look for foo.h, so
@@ -88,11 +110,11 @@ old_sconsign_dblite = test.stdout()
test.run(arguments = '--implicit-cache USE_FOO=1 .')
-test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
+test.run_sconsign(f'-d .sconf_temp -e {test_filename} --raw {database_name}')
new_sconsign_dblite = test.stdout()
if old_sconsign_dblite != new_sconsign_dblite:
- print(".sconsign.dblite did not match:")
+ print(f"{database_name} did not match:")
print("FIRST RUN ==========")
print(old_sconsign_dblite)
print("SECOND RUN ==========")
diff --git a/test/Configure/option--config.py b/test/Configure/option--config.py
index f31336f..3b38892 100644
--- a/test/Configure/option--config.py
+++ b/test/Configure/option--config.py
@@ -32,6 +32,7 @@ import os.path
from TestSCons import TestSCons, ConfigCheckInfo, _obj
from TestCmd import IS_WINDOWS
+from SCons.Util import get_current_hash_algorithm_used
test = TestSCons()
@@ -42,6 +43,11 @@ CR = test.CR # cached rebuild (up to date)
NCF = test.NCF # non-cached build failure
CF = test.CF # cached build failure
+# as this test is somewhat complicated, skip it if the library doesn't support md5
+# as the default hashing algorithm.
+if get_current_hash_algorithm_used() != 'md5':
+ test.skip_test('Skipping test as could not continue without the hash algorithm set to md5!')
+
SConstruct_path = test.workpath('SConstruct')
test.write(SConstruct_path, """
diff --git a/test/Repository/variants.py b/test/Repository/variants.py
index f89605b..c95e853 100644
--- a/test/Repository/variants.py
+++ b/test/Repository/variants.py
@@ -216,12 +216,14 @@ repository/src1/bbb.c: REPOSITORY_FOO
repository/src1/main.c: REPOSITORY_FOO
""")
+database_name=test.get_sconsignname()
+
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.run(program=repository_build2_foo_src2_xxx_xxx, stdout="""\
repository/src2/include/my_string.h: FOO
@@ -236,11 +238,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
# Make the entire repository non-writable, so we'll detect
# if we try to write into it accidentally.
@@ -270,11 +272,11 @@ repository/src1/main.c: REPOSITORY_BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=bar", arguments='build1')
@@ -301,11 +303,11 @@ repository/src1/main.c: WORK_BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=bar", arguments='build1')
@@ -319,11 +321,11 @@ repository/src1/main.c: WORK_FOO
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=foo", arguments='build1')
@@ -376,11 +378,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
# Ensure file time stamps will be newer.
time.sleep(2)
@@ -411,11 +413,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
#
test.unlink(['work2', 'src2', 'include', 'my_string.h'])
@@ -435,11 +437,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
#
test.pass_test()
diff --git a/test/option/hash-format.py b/test/option/hash-format.py
index 9fa10ee..f0156f3 100644
--- a/test/option/hash-format.py
+++ b/test/option/hash-format.py
@@ -27,28 +27,52 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import hashlib
import os
import TestSCons
+import warnings
+from SCons.Util import ALLOWED_HASH_FORMATS, DEFAULT_HASH_FORMATS
# Test passing the hash format by command-line.
INVALID_ALGORITHM = 'testfailure'
-for algorithm in ['md5', 'sha1', 'sha256', INVALID_ALGORITHM, None]:
+
+for algorithm in [*DEFAULT_HASH_FORMATS, INVALID_ALGORITHM, None]:
test = TestSCons.TestSCons()
test.dir_fixture('hash-format')
+ # Expect failure due to an unsupported/invalid algorithm.
+ # The error message however changes if SCons detects that the host system doesn't support one or more algorithms
+ # Primary reason the message changes is so user doesn't have to start with unsupported algorithm A and then attempt
+ # to switch to unsupported algorithm B.
+ # On normal systems (allowed=default) this will output a fixed message, but on FIPS-enabled or other weird systems
+ # that don't have default allowed algorithms, it informs the user of the mismatch _and_ the currently supported
+ # algorithms on the system they're using.
+ # In Python 3.9 this becomes somewhat obselete as the hashlib is informed we don't use hashing for security but
+ # for loose integrity.
if algorithm == INVALID_ALGORITHM:
- # Expect failure due to an unsupported/invalid algorithm.
- test.run('--hash-format=%s .' % algorithm, stderr=r"""
-scons: \*\*\* Hash format "{}" is not supported by SCons. Only the following hash formats are supported: md5, sha1, sha256
+ if ALLOWED_HASH_FORMATS == DEFAULT_HASH_FORMATS:
+ test.run('--hash-format=%s .' % algorithm, stderr=r"""
+scons: \*\*\* Hash format "{}" is not supported by SCons. Only the following hash formats are supported: {}
+File "[^"]+", line \d+, in \S+
+""".format(algorithm, ', '.join(DEFAULT_HASH_FORMATS)), status=2, match=TestSCons.match_re)
+ else:
+ test.run('--hash-format=%s .' % algorithm, stderr=r"""
+scons: \*\*\* Hash format "{}" is not supported by SCons. SCons supports more hash formats than your local system is reporting; SCons supports: {}. Your local system only supports: {}
File "[^"]+", line \d+, in \S+
-""".format(algorithm), status=2, match=TestSCons.match_re)
+""".format(algorithm, ', '.join(DEFAULT_HASH_FORMATS), ', '.join(ALLOWED_HASH_FORMATS)), status=2, match=TestSCons.match_re)
continue
elif algorithm is not None:
- # Skip any algorithm that the Python interpreter doesn't have.
- if hasattr(hashlib, algorithm):
+ if algorithm in ALLOWED_HASH_FORMATS:
expected_dblite = test.workpath('.sconsign_%s.dblite' % algorithm)
test.run('--hash-format=%s .' % algorithm)
else:
- print('Skipping test with --hash-format=%s because that '
- 'algorithm is not available.' % algorithm)
+ test.run('--hash-format=%s' % algorithm, stderr=r"""
+scons: \*\*\* While hash format "{}" is supported by SCons, the local system indicates only the following hash formats are supported by the hashlib library: {}
+File "[^"]+", line \d+, in \S+
+Error in atexit._run_exitfuncs:
+Traceback \(most recent call last\):
+ File "[^"]+", line \d+, in \S+
+ assert csig == '[a-z0-9]+', csig
+AssertionError: [a-z0-9]+
+""".format(algorithm, ', '.join(ALLOWED_HASH_FORMATS)), status=2, match=TestSCons.match_re)
+ continue
else:
# The SConsign file in the hash-format folder has logic to call
# SCons.Util.set_hash_format('sha256') if the default algorithm is
diff --git a/test/option/option-n.py b/test/option/option-n.py
index e647b8e..eb8eb62 100644
--- a/test/option/option-n.py
+++ b/test/option/option-n.py
@@ -43,6 +43,7 @@ import os
import re
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
_python_ = TestSCons._python_
@@ -118,7 +119,7 @@ test.fail_test(not os.path.exists(test.workpath('f1.out')))
expect = test.wrap_stdout("""\
%(_python_)s build.py f1.out
""" % locals())
-test.unlink('.sconsign.dblite')
+test.unlink(test.get_sconsignname()+'.dblite')
test.write('f1.out', "X1.out\n")
test.run(arguments='-n f1.out', stdout=expect)
test.run(arguments='-n f1.out', stdout=expect)
@@ -204,13 +205,23 @@ test.run(arguments="-n", stderr=stderr, status=2,
test.fail_test(os.path.exists(test.workpath("configure", "config.test")))
test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in",
+ 'sha1': "conftest_0a4d55a8d778e5022fab701977c5d840bbc486d0_0.in",
+ 'sha256': "conftest_a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e_0.in"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
# test that targets are not built, if conf_dir exists.
# verify that .cache and config.log are not created.
# an error should be raised
stderr = r"""
scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
File \S+, line \S+, in \S+
-""" % re.escape(os.path.join("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+""" % re.escape(os.path.join("config.test", test_filename))
test.subdir(['configure', 'config.test'])
test.run(arguments="-n", stderr=stderr, status=2,
chdir=test.workpath("configure"))
diff --git a/test/question/Configure.py b/test/question/Configure.py
index 7df29f5..d01d0fa 100644
--- a/test/question/Configure.py
+++ b/test/question/Configure.py
@@ -36,6 +36,7 @@ import re
import TestCmd
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
test = TestSCons.TestSCons(match = TestCmd.match_re_dotall)
@@ -80,13 +81,22 @@ test.run(arguments="-q aaa.out",stderr=stderr,status=2)
test.must_not_exist(test.workpath("config.test"))
test.must_not_exist(test.workpath("config.log"))
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in",
+ 'sha1': "conftest_0a4d55a8d778e5022fab701977c5d840bbc486d0_0.in",
+ 'sha256': "conftest_a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e_0.in"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
# test that targets are not built, if conf_dir exists.
# verify that .cache and config.log are not created.
# an error should be raised
stderr=r"""
scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
File \S+, line \S+, in \S+
-""" % re.escape(os.path.join("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+""" % re.escape(os.path.join("config.test", test_filename))
test.subdir('config.test')
@@ -94,7 +104,7 @@ test.run(arguments="-q aaa.out",stderr=stderr,status=2)
test.must_not_exist(test.workpath("config.test", ".cache"))
test.must_not_exist(test.workpath("config.test", "conftest_0"))
-test.must_not_exist(test.workpath("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+test.must_not_exist(test.workpath("config.test", test_filename))
test.must_not_exist(test.workpath("config.log"))
# test that no error is raised, if all targets are up-to-date. In this
diff --git a/test/sconsign/corrupt.py b/test/sconsign/corrupt.py
index 25b48e2..61da3a2 100644
--- a/test/sconsign/corrupt.py
+++ b/test/sconsign/corrupt.py
@@ -30,14 +30,19 @@ Test that we get proper warnings when .sconsign* files are corrupt.
import TestSCons
import TestCmd
+import re
test = TestSCons.TestSCons(match = TestCmd.match_re)
test.subdir('work1', ['work1', 'sub'],
'work2', ['work2', 'sub'])
-work1__sconsign_dblite = test.workpath('work1', '.sconsign.dblite')
-work2_sub__sconsign = test.workpath('work2', 'sub', '.sconsign')
+database_filename = test.get_sconsignname() + ".dblite"
+
+# for test1 we're using the default database filename
+work1__sconsign_dblite = test.workpath('work1', database_filename)
+# for test 2 we have an explicit hardcode to .sconsign
+work2_sub__sconsign = test.workpath('work2', 'sub', ".sconsign")
SConstruct_contents = """\
def build1(target, source, env):
@@ -57,9 +62,9 @@ test.write(['work1', 'SConstruct'], SConstruct_contents)
test.write(['work1', 'foo.in'], "work1/foo.in\n")
stderr = r'''
-scons: warning: Ignoring corrupt .sconsign file: \.sconsign\.dblite
+scons: warning: Ignoring corrupt .sconsign file: {}
.*
-'''
+'''.format(re.escape(database_filename))
stdout = test.wrap_stdout(r'build1\(\["sub.foo\.out"\], \["foo\.in"\]\)' + '\n')
diff --git a/test/sconsign/script/Configure.py b/test/sconsign/script/Configure.py
index 7bf1f05..02a2c20 100644
--- a/test/sconsign/script/Configure.py
+++ b/test/sconsign/script/Configure.py
@@ -90,7 +90,9 @@ conftest_%(sig_re)s_0_%(sig_re)s%(_obj)s:
%(CC_file)s: %(sig_re)s \d+ \d+
""" % locals()
-test.run_sconsign(arguments = ".sconsign",
+# grab .sconsign or .sconsign_<hashname>
+database_name=test.get_sconsignname()
+test.run_sconsign(arguments = database_name,
stdout = expect)
test.pass_test()
diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py
index 2a19818..29bd123 100644
--- a/testing/framework/TestSCons.py
+++ b/testing/framework/TestSCons.py
@@ -45,6 +45,7 @@ from collections import namedtuple
from TestCommon import *
from TestCommon import __all__
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
from TestCmd import Popen
from TestCmd import PIPE
@@ -719,6 +720,27 @@ class TestSCons(TestCommon):
for p in patterns:
result.extend(sorted(glob.glob(p)))
return result
+
+ def get_sconsignname(self):
+ """Get the scons database name used, and return both the prefix and full filename.
+ if the user left the options defaulted AND the default algorithm set by
+ SCons is md5, then set the database name to be the special default name
+
+ otherwise, if it defaults to something like 'sha1' or the user explicitly
+ set 'md5' as the hash format, set the database name to .sconsign_<algorithm>
+ eg .sconsign_sha1, etc.
+
+ Returns:
+ a pair containing: the current dbname, the dbname.dblite filename
+ """
+ hash_format = get_hash_format()
+ current_hash_algorithm = get_current_hash_algorithm_used()
+ if hash_format is None and current_hash_algorithm == 'md5':
+ return ".sconsign"
+ else:
+ database_prefix=".sconsign_%s" % current_hash_algorithm
+ return database_prefix
+
def unlink_sconsignfile(self, name='.sconsign.dblite'):
"""Delete the sconsign file.