From 5f97909ef9d17de0f895650684046277bc1a5a88 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 13 Apr 2021 10:21:35 -0600 Subject: Test harness add a from_fw to skip calls Main functional change is a new kwarg to skip_test() to allow calls from inside the framework to skip an additional line of traceback in the skip output - i.e. don't just skip the entry for skip_test, but also the function in the fw that called it. Other functional change is for the try block in skip_if_not_msvc() (which is one of the internal callers of skip_test() mentioned for the other change) to catch Exception, thus avoiding system-existing exceptions that were caught by the existing bare except, which caused the skip to not actually skip. The remainder of the patch is docstring reformatting, some minor code reformats, top-of-file license blocks, etc. Signed-off-by: Mats Wichmann --- bin/scons-test.py | 60 +++++--- bin/scons-time.py | 19 ++- testing/framework/TestCmd.py | 81 ++++++---- testing/framework/TestCmdTests.py | 34 ++--- testing/framework/TestCommon.py | 89 ++++++----- testing/framework/TestCommonTests.py | 34 ++--- testing/framework/TestRuntest.py | 43 ++++-- testing/framework/TestSCons.py | 277 ++++++++++++++++++----------------- testing/framework/TestSConsMSVS.py | 31 +++- testing/framework/TestSCons_time.py | 35 +++-- testing/framework/TestSConsign.py | 30 +++- 11 files changed, 437 insertions(+), 296 deletions(-) diff --git a/bin/scons-test.py b/bin/scons-test.py index da92765..868688c 100644 --- a/bin/scons-test.py +++ b/bin/scons-test.py @@ -1,19 +1,46 @@ #!/usr/bin/env python + +# MIT License # -# A script that takes an scons-src-{version}.zip file, unwraps it in -# a temporary location, and calls runtest.py to execute one or more of -# its tests. +# Copyright The SCons Foundation # -# The default is to download the latest scons-src archive from the SCons -# web site, and to execute all of the tests. +# 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: # -# With a little more work, this will become the basis of an automated -# testing and reporting system that anyone will be able to use to -# participate in testing SCons on their system and regularly reporting -# back the results. A --xml option is a stab at gathering a lot of -# relevant information about the system, the Python version, etc., -# so that problems on different platforms can be identified sooner. +# 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. + + +""" +scons-test.py - test a particular SCons version. + +Takes an scons-src-{version}.zip file, unwraps it in a temporary location, +and calls runtest.py to execute one or more of its tests. + +The default is to download the latest scons-src archive from the SCons +web site, and to execute all of the tests. + +With a little more work, this will become the basis of an automated +testing and reporting system that anyone will be able to use to +participate in testing SCons on their system and regularly reporting +back the results. A --xml option is a stab at gathering a lot of +relevant information about the system, the Python version, etc., +so that problems on different platforms can be identified sooner. +""" + import atexit import getopt import os @@ -22,16 +49,7 @@ import sys import tempfile import time import zipfile - -try: - # try Python 3.x style - from urllib.request import urlretrieve -except ImportError: - # nope, must be 2.x; this hack is equivalent - import imp - # protect import from fixer - urlretrieve = imp.load_module('urllib', - *imp.find_module('urllib')).urlretrieve +from urllib.request import urlretrieve helpstr = """\ Usage: scons-test.py [-f zipfile] [-o outdir] [-v] [--xml] [runtest arguments] diff --git a/bin/scons-time.py b/bin/scons-time.py index e7087e7..44f0775 100644 --- a/bin/scons-time.py +++ b/bin/scons-time.py @@ -1,15 +1,8 @@ #!/usr/bin/env python -# -# scons-time - run SCons timings and collect statistics -# -# A script for running a configuration through SCons with a standard -# set of invocations to collect timing and memory statistics and to -# capture the results in a consistent set of output files for display -# and analysis. -# +# MIT License # -# __COPYRIGHT__ +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -30,7 +23,13 @@ # 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__" +""" +scons-time - run SCons timings and collect statistics + +A script for running a configuration through SCons with a standard set +of invocations to collect timing and memory statistics and to capture +the results in a consistent set of output files for display and analysis. +""" import getopt import glob diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index 76609a0..333901c 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -1,5 +1,5 @@ """ -TestCmd.py: a testing framework for commands and scripts. +A testing framework for commands and scripts. The TestCmd module provides a framework for portable automated testing of executable commands and scripts (in any language, not just Python), @@ -384,11 +384,19 @@ def _caller(tblist, skip): def fail_test(self=None, condition=True, function=None, skip=0, message=None): - """Cause the test to fail. - - By default, the fail_test() method reports that the test FAILED - and exits with a status of 1. If a condition argument is supplied, - the test fails only if the condition is true. + """Causes a test to exit with a fail. + + Reports that the test FAILED and exits with a status of 1, unless + a condition argument is supplied; if so the completion processing + takes place only if the condition is true. + + Args: + self: a test class instance. Must be passed in explicitly + by the caller since this is an unbound method. + condition (optional): if false, return to let test continue. + function (optional): function to call before completion processing. + skip (optional): how many lines at the top of the traceback to skip. + message (optional): additional text to include in the fail message. """ if not condition: return @@ -416,11 +424,24 @@ def fail_test(self=None, condition=True, function=None, skip=0, message=None): def no_result(self=None, condition=True, function=None, skip=0): - """Causes a test to exit with no valid result. - - By default, the no_result() method reports NO RESULT for the test - and exits with a status of 2. If a condition argument is supplied, - the test fails only if the condition is true. + """Causes a test to exit with a no result. + + In testing parlance NO RESULT means the test could not be completed + for reasons that imply neither success nor failure - for example a + component needed to run the test could be found. However, at this + point we still have an "outcome", so record the information and exit + with a status code of 2, unless a condition argument is supplied; + if so the completion processing takes place only if the condition is true. + + The different exit code and message allows other logic to distinguish + from a fail and decide how to treat NO RESULT tests. + + Args: + self: a test class instance. Must be passed in explicitly + by the caller since this is an unbound method. + condition (optional): if false, return to let test continue. + function (optional): function to call before completion processing. + skip (optional): how many lines at the top of the traceback to skip. """ if not condition: return @@ -444,11 +465,19 @@ def no_result(self=None, condition=True, function=None, skip=0): def pass_test(self=None, condition=True, function=None): - """Causes a test to pass. + """Causes a test to exit with a pass. + + Reports that the test PASSED and exits with a status of 0, unless + a condition argument is supplied; if so the completion processing + takes place only if the condition is true. - By default, the pass_test() method reports PASSED for the test - and exits with a status of 0. If a condition argument is supplied, the test passes only if the condition is true. + + Args: + self: a test class instance. Must be passed in explicitly + by the caller since this is an unbound method. + condition (optional): if false, return to let test continue. + function (optional): function to call before completion processing. """ if not condition: return @@ -466,7 +495,7 @@ def match_exact(lines=None, matches=None, newline=os.sep): :param matches: expected lines to match :type matches: str or list[str] :param newline: line separator - :returns: an object (1) on match, else None, like re.match + :returns: None on failure, 1 on success. """ if isinstance(lines, bytes): @@ -495,8 +524,7 @@ def match_caseinsensitive(lines=None, matches=None): :type lines: str or list[str] :param matches: expected lines to match :type matches: str or list[str] - :returns: True or False - :returns: an object (1) on match, else None, like re.match + :returns: None on failure, 1 on success. """ if not is_List(lines): @@ -518,7 +546,7 @@ def match_re(lines=None, res=None): :type lines: str or list[str] :param res: regular expression(s) for matching :type res: str or list[str] - :returns: an object (1) on match, else None, like re.match + :returns: None on failure, 1 on success. """ if not is_List(lines): @@ -553,7 +581,7 @@ def match_re_dotall(lines=None, res=None): :type lines: str or list[str] :param res: regular expression(s) for matching :type res: str or list[str] - :returns: a match object, or None as for re.match + :returns: a match object on match, else None, like re.match """ if not isinstance(lines, str): @@ -574,15 +602,13 @@ def simple_diff(a, b, fromfile='', tofile='', r"""Compare two sequences of lines; generate the delta as a simple diff. Similar to difflib.context_diff and difflib.unified_diff but - output is like from the 'diff" command without arguments. The function + output is like from the "diff" command without arguments. The function keeps the same signature as the difflib ones so they will be interchangeable, but except for lineterm, the arguments beyond the two sequences are ignored in this version. By default, the diff is not created with trailing newlines, set the lineterm argument to '\n' to do so. - :raises re.error: if a regex fails to compile - Example: >>> print(''.join(simple_diff('one\ntwo\nthree\nfour\n'.splitlines(True), @@ -623,13 +649,16 @@ def simple_diff(a, b, fromfile='', tofile='', def diff_re(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n'): - """Compare a and b (lists of strings) where a are regexes. + """Compare a and b (lists of strings) where a are regular expressions. A simple "diff" of two sets of lines when the expected lines are regular expressions. This is a really dumb thing that just compares each line in turn, so it doesn't look for chunks of matching lines and the like--but at least it lets you know exactly which line first didn't compare correctl... + + Raises: + re.error: if a regex fails to compile """ result = [] diff = len(a) - len(b) @@ -1207,8 +1236,7 @@ class TestCmd: return match_stderr_function(lines, matches) def match_stdout(self, lines, matches): - """Compare actual and expected file contents. - """ + """Compare actual and expected file contents.""" try: match_stdout_function = getattr(self, self._match_stdout_function) except TypeError: @@ -1237,8 +1265,7 @@ class TestCmd: skip=skip) def pass_test(self, condition=True, function=None): - """Cause the test to pass. - """ + """Cause the test to pass.""" if not condition: return self.condition = 'pass_test' diff --git a/testing/framework/TestCmdTests.py b/testing/framework/TestCmdTests.py index 01a1390..212c59a 100644 --- a/testing/framework/TestCmdTests.py +++ b/testing/framework/TestCmdTests.py @@ -1,24 +1,24 @@ #!/usr/bin/env python """ -TestCmdTests.py: Unit tests for the TestCmd.py module. - -Copyright 2000-2010 Steven Knight -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +Unit tests for the TestCmd.py module. """ +# Copyright 2000-2010 Steven Knight +# This module is free software, and you may redistribute it and/or modify +# it under the same terms as Python itself, so long as this copyright message +# and disclaimer are retained in their original form. +# +# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# +# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + __author__ = "Steven Knight " __revision__ = "TestCmdTests.py 1.3.D001 2010/06/03 12:58:27 knight" diff --git a/testing/framework/TestCommon.py b/testing/framework/TestCommon.py index 7c857bf..b1630e5 100644 --- a/testing/framework/TestCommon.py +++ b/testing/framework/TestCommon.py @@ -1,6 +1,5 @@ """ -TestCommon.py: a testing framework for commands and scripts - with commonly useful error handling +A testing framework for commands and scripts with commonly useful error handling The TestCommon module provides a simple, high-level interface for writing tests of executable commands and scripts, especially commands and scripts @@ -58,11 +57,13 @@ provided by the TestCommon class: test.must_not_be_empty('file') - test.run(options = "options to be prepended to arguments", - stdout = "expected standard output from the program", - stderr = "expected error output from the program", - status = expected_status, - match = match_function) + test.run( + options="options to be prepended to arguments", + stdout="expected standard output from the program", + stderr="expected error output from the program", + status=expected_status, + match=match_function, + ) The TestCommon module also provides the following variables @@ -103,30 +104,39 @@ import glob import os import stat import sys +import sysconfig from collections import UserList from TestCmd import * from TestCmd import __all__ -__all__.extend([ 'TestCommon', - 'exe_suffix', - 'obj_suffix', - 'shobj_prefix', - 'shobj_suffix', - 'lib_prefix', - 'lib_suffix', - 'dll_prefix', - 'dll_suffix', - ]) +__all__.extend( + [ + 'TestCommon', + 'exe_suffix', + 'obj_suffix', + 'shobj_prefix', + 'shobj_suffix', + 'lib_prefix', + 'lib_suffix', + 'dll_prefix', + 'dll_suffix', + ] +) # Variables that describe the prefixes and suffixes on this system. if sys.platform == 'win32': + if sysconfig.get_platform() == "mingw": + obj_suffix = '.o' + shobj_suffix = '.o' + else: + obj_suffix = '.obj' + shobj_suffix = '.obj' exe_suffix = '.exe' - obj_suffix = '.obj' - shobj_suffix = '.obj' shobj_prefix = '' lib_prefix = '' + # TODO: for mingw, is this .lib or .a? lib_suffix = '.lib' dll_prefix = '' dll_suffix = '.dll' @@ -303,9 +313,8 @@ class TestCommon(TestCmd): Calling test exits FAILED if search result is false """ if 'b' in mode: - # Python 3: reading a file in binary mode returns a - # bytes object. We cannot find the index of a different - # (str) type in that, so convert. + # Reading a file in binary mode returns a bytes object. + # We cannot search for a string in a bytes obj so convert. required = to_bytes(required) file_contents = self.read(file, mode) @@ -351,14 +360,10 @@ class TestCommon(TestCmd): function, of the form "find(output, line)", to use when searching for lines in the output. """ - missing = [] if is_List(output): output = '\n'.join(output) - for line in lines: - if not contains(output, line, find): - missing.append(line) - + missing = [line for line in lines if not contains(output, line, find)] if missing: if title is None: title = 'output' @@ -749,7 +754,7 @@ class TestCommon(TestCmd): self._complete(self.stdout(), stdout, self.stderr(), stderr, status, match) - def skip_test(self, message="Skipping test.\n"): + def skip_test(self, message="Skipping test.\n", from_fw=False): """Skips a test. Proper test-skipping behavior is dependent on the external @@ -758,13 +763,22 @@ class TestCommon(TestCmd): In either case, we print the specified message as an indication that the substance of the test was skipped. - (This was originally added to support development under Aegis. - Technically, skipping a test is a NO RESULT, but Aegis would - treat that as a test failure and prevent the change from going to - the next step. Since we ddn't want to force anyone using Aegis - to have to install absolutely every tool used by the tests, we - would actually report to Aegis that a skipped test has PASSED - so that the workflow isn't held up.) + The use case for treating the skip as a PASS was an old system + that the SCons project has not used for a long time, and that + code path could eventually be dropped. + + When reporting a NO RESULT, we normally skip the top line of the + traceback, as from no_result()'s point of view, that is this + function, and the user is likely to only be interested in the + test that called us. If from_fw is True, the skip was initiated + indirectly, coming from some function in the framework + (test_for_tool, skip_if_msvc, etc.), in this case we want to + skip an additional line to eliminate that function as well. + + Args: + message: text to include in the skip message. Callers + should normally provide a reason, to improve on the default. + from_fw: if true, skip an extra line of traceback. """ if message: sys.stdout.write(message) @@ -775,7 +789,10 @@ class TestCommon(TestCmd): # result came from. They only care about the line where the # script called test.skip_test(), not the line number where # we call test.no_result(). - self.no_result(skip=1) + if from_fw: + self.no_result(skip=2) + else: + self.no_result(skip=1) else: # We're under the development directory for this change, # so this is an Aegis invocation; pass the test (exit 0). diff --git a/testing/framework/TestCommonTests.py b/testing/framework/TestCommonTests.py index 8a93b35..03a5508 100644 --- a/testing/framework/TestCommonTests.py +++ b/testing/framework/TestCommonTests.py @@ -1,24 +1,24 @@ #!/usr/bin/env python """ -TestCommonTests.py: Unit tests for the TestCommon.py module. - -Copyright 2000-2010 Steven Knight -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +Unit tests for the TestCommon.py module. """ +# Copyright 2000-2010 Steven Knight +# This module is free software, and you may redistribute it and/or modify +# it under the same terms as Python itself, so long as this copyright message +# and disclaimer are retained in their original form. +# +# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# +# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +# AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + __author__ = "Steven Knight " __revision__ = "TestCommonTests.py 1.3.D001 2010/06/03 12:58:27 knight" diff --git a/testing/framework/TestRuntest.py b/testing/framework/TestRuntest.py index a70110c..18dcb94 100644 --- a/testing/framework/TestRuntest.py +++ b/testing/framework/TestRuntest.py @@ -1,6 +1,28 @@ +# MIT License +# +# Copyright The SCons Foundation +# +# 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. + """ -TestRuntest.py: a testing framework for the runtest.py command used to -invoke SCons tests. +A testing framework for the runtest.py command used to invoke SCons tests. A TestRuntest environment object is created via the usual invocation: @@ -12,10 +34,6 @@ from those classes, as well as any overridden or additional methods or attributes defined in this subclass. """ -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - import os import os.path import re @@ -25,10 +43,13 @@ import sys from TestCommon import * from TestCommon import __all__ -__all__.extend([ 'TestRuntest', - 'pythonstring', - 'pythonflags', - ]) +__all__.extend( + [ + 'TestRuntest', + 'pythonstring', + 'pythonflags', + ] +) if re.search(r'\s', python): pythonstring = _python_ @@ -128,7 +149,7 @@ class TestRuntest(TestCommon): TestCommon.__init__(self, **kw) dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)] - + for thing in things_to_copy: for dir in dirs: t = os.path.join(dir, thing) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 5937bad..01c1b40 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1,17 +1,3 @@ -""" -TestSCons.py: a testing framework for the SCons software construction -tool. - -A TestSCons environment object is created via the usual invocation: - - test = TestSCons() - -TestScons is a subclass of TestCommon, which in turn is a subclass -of TestCmd), and hence has available all of the methods and attributes -from those classes, as well as any overridden or additional methods or -attributes defined in this subclass. -""" - # MIT License # # Copyright The SCons Foundation @@ -35,6 +21,19 @@ attributes defined in this subclass. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +A testing framework for the SCons software construction tool. + +A TestSCons environment object is created via the usual invocation: + + test = TestSCons() + +TestScons is a subclass of TestCommon, which in turn is a subclass +of TestCmd), and hence has available all of the methods and attributes +from those classes, as well as any overridden or additional methods or +attributes defined in this subclass. +""" + import os import re import shutil @@ -57,8 +56,10 @@ from TestCmd import PIPE default_version = '4.1.1ayyyymmdd' -python_version_unsupported = (3, 4, 0) -python_version_deprecated = (3, 5, 0) +# TODO: these need to be hand-edited when there are changes +python_version_unsupported = (3, 4, 0) # highest unsupported version +python_version_deprecated = (3, 5, 0) # deprecated version +python_version_supported_str = "3.6.0" # str of lowest non-deprecated version # In the checked-in source, the value of SConsVersion in the following # line must remain "__ VERSION __" (without the spaces) so the built @@ -177,11 +178,13 @@ def deprecated_python_version(version=sys.version_info): if deprecated_python_version(): msg = r""" -scons: warning: Support for pre-2.7.0 Python version (%s) is deprecated. +scons: warning: Support for pre-%s Python version (%s) is deprecated. If this will cause hardship, contact scons-dev@scons.org """ - - deprecated_python_expr = re_escape(msg % python_version_string()) + file_expr + deprecated_python_expr = ( + re_escape(msg % (python_version_supported_str, python_version_string())) + + file_expr + ) del msg else: deprecated_python_expr = "" @@ -718,28 +721,31 @@ class TestSCons(TestCommon): return result 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 - Parameters - ---------- - name - expected name of sconsign file + """Delete the sconsign file. + + Note on python it seems to append .p3 to the file name so we take + care of that. - Returns - ------- - None + TODO the above seems to not be an issue any more. + + Args: + name: expected name of sconsign file """ if sys.version_info[0] == 3: name += '.p3' self.unlink(name) def java_ENV(self, version=None): - """ + """ Initialize JAVA SDK environment. + Initialize with a default external environment that uses a local Java SDK in preference to whatever's found in the default PATH. - :param version: if set, match only that version - :return: the new env. + Args: + version: if set, match only that version + + Returns: + the new env. """ if not self.external: try: @@ -784,11 +790,13 @@ class TestSCons(TestCommon): return None def java_where_includes(self, version=None): - """ - Find include path needed for compiling java jni code. + """ Find include path needed for compiling java jni code. - :param version: if set, match only that version - :return: path to java headers + Args: + version: if set, match only that version + + Returns: + path to java headers or None """ import sys @@ -822,13 +830,15 @@ class TestSCons(TestCommon): return result def java_where_java_home(self, version=None): - """ - Find path to what would be JAVA_HOME. + """ Find path to what would be JAVA_HOME. SCons does not read JAVA_HOME from the environment, so deduce it. - :param version: if set, match only that version - :return: path where JDK components live + Args: + version: if set, match only that version + + Returns: + path where JDK components live """ if sys.platform[:6] == 'darwin': # osx 10.11, 10.12 @@ -873,14 +883,16 @@ class TestSCons(TestCommon): stdout, stderr = sp.communicate() sp.wait() if "No Java runtime" in str(stderr): - self.skip_test("Could not find Java " + java_bin_name + ", skipping test(s).\n") + self.skip_test("Could not find Java " + java_bin_name + ", skipping test(s).\n", from_fw=True) def java_where_jar(self, version=None): - """ - Find java archiver jar. + """ Find java archiver jar. + + Args: + version: if set, match only that version - :param version: if set, match only that version - :return: path to jar + Returns: + path to jar """ ENV = self.java_ENV(version) if self.detect_tool('jar', ENV=ENV): @@ -888,35 +900,39 @@ class TestSCons(TestCommon): else: where_jar = self.where_is('jar', ENV['PATH']) if not where_jar: - self.skip_test("Could not find Java jar, skipping test(s).\n") + self.skip_test("Could not find Java jar, skipping test(s).\n", from_fw=True) elif sys.platform == "darwin": self.java_mac_check(where_jar, 'jar') return where_jar def java_where_java(self, version=None): - """ - Find java executable. + """ Find java executable. + + Args: + version: if set, match only that version - :param version: if set, match only that version - :return: path to the java rutime + Returns: + path to the java rutime """ ENV = self.java_ENV(version) where_java = self.where_is('java', ENV['PATH']) if not where_java: - self.skip_test("Could not find Java java, skipping test(s).\n") + self.skip_test("Could not find Java java, skipping test(s).\n", from_fw=True) elif sys.platform == "darwin": self.java_mac_check(where_java, 'java') return where_java def java_where_javac(self, version=None): - """ - Find java compiler. + """ Find java compiler. + + Args: + version: if set, match only that version - :param version: if set, match only that version - :return: path to javac + Returns: + path to javac """ ENV = self.java_ENV(version) if self.detect_tool('javac'): @@ -924,7 +940,7 @@ class TestSCons(TestCommon): else: where_javac = self.where_is('javac', ENV['PATH']) if not where_javac: - self.skip_test("Could not find Java javac, skipping test(s).\n") + self.skip_test("Could not find Java javac, skipping test(s).\n", from_fw=True) elif sys.platform == "darwin": self.java_mac_check(where_javac, 'javac') @@ -937,7 +953,7 @@ class TestSCons(TestCommon): verf = 'javac %s' % version if self.stderr().find(verf) == -1 and self.stdout().find(verf) == -1: fmt = "Could not find javac for Java version %s, skipping test(s).\n" - self.skip_test(fmt % version) + self.skip_test(fmt % version, from_fw=True) else: version_re = r'javac (\d*\.*\d)' m = re.search(version_re, self.stderr()) @@ -956,15 +972,17 @@ class TestSCons(TestCommon): return where_javac, version def java_where_javah(self, version=None): - """ - Find java header generation tool. + """ Find java header generation tool. TODO issue #3347 since JDK10, there is no separate javah command, 'javac -h' is used. We should not return a javah from a different installed JDK - how to detect and what to return in this case? - :param version: if set, match only that version - :return: path to javah + Args: + version: if set, match only that version + + Returns: + path to javah """ ENV = self.java_ENV(version) if self.detect_tool('javah'): @@ -972,15 +990,17 @@ class TestSCons(TestCommon): else: where_javah = self.where_is('javah', ENV['PATH']) if not where_javah: - self.skip_test("Could not find Java javah, skipping test(s).\n") + self.skip_test("Could not find Java javah, skipping test(s).\n", from_fw=True) return where_javah def java_where_rmic(self, version=None): - """ - Find java rmic tool. + """ Find java rmic tool. - :param version: if set, match only that version - :return: path to rmic + Args: + version: if set, match only that version + + Returns: + path to rmic """ ENV = self.java_ENV(version) if self.detect_tool('rmic'): @@ -988,7 +1008,7 @@ class TestSCons(TestCommon): else: where_rmic = self.where_is('rmic', ENV['PATH']) if not where_rmic: - self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n") + self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n", from_fw=True) return where_rmic def java_get_class_files(self, dir): @@ -1085,8 +1105,8 @@ void my_qt_symbol(const char *arg) { """) self.write([dir, 'lib', 'SConstruct'], r""" -env = Environment() import sys +env = Environment() if sys.platform == 'win32': env.StaticLibrary('myqt', 'my_qobject.cpp') else: @@ -1108,20 +1128,20 @@ else: if isinstance(place, list): place = test.workpath(*place) self.write(place, """\ -if ARGUMENTS.get('noqtdir', 0): QTDIR=None -else: QTDIR=r'%s' -env = Environment(QTDIR = QTDIR, - QT_LIB = r'%s', - QT_MOC = r'%s', - QT_UIC = r'%s', - tools=['default','qt']) +if ARGUMENTS.get('noqtdir', 0): + QTDIR = None +else: + QTDIR = r'%s' +env = Environment( + QTDIR=QTDIR, QT_LIB=r'%s', QT_MOC=r'%s', QT_UIC=r'%s', tools=['default', 'qt'] +) dup = 1 if ARGUMENTS.get('variant_dir', 0): if ARGUMENTS.get('chdir', 0): SConscriptChdir(1) else: SConscriptChdir(0) - dup=int(ARGUMENTS.get('dup', 1)) + dup = int(ARGUMENTS.get('dup', 1)) if dup == 0: builddir = 'build_dup0' env['QT_DEBUG'] = 1 @@ -1155,26 +1175,27 @@ SConscript(sconscript) return 'COVERAGE_PROCESS_START' in os.environ or 'COVERAGE_FILE' in os.environ def skip_if_not_msvc(self, check_platform=True): - """ Check whether we are on a Windows platform and skip the - test if not. This check can be omitted by setting - check_platform to False. - Then, for a win32 platform, additionally check - whether we have a MSVC toolchain installed - in the system, and skip the test if none can be - found (=MinGW is the only compiler available). + """ Check whether MSVC is avaliable. + + Check whether we are on a Windows platform and skip the test if + not. This check can be omitted by setting check_platform to False. + + Then, for a win32 platform, additionally check whether we have + an MSVC toolchain installed in the system, and skip the test if + none can be found (e.g. MinGW is the only compiler available). """ if check_platform: if sys.platform != 'win32': msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform - self.skip_test(msg) + self.skip_test(msg, from_fw=True) return try: import SCons.Tool.MSCommon as msc if not msc.msvc_exists(): msg = "No MSVC toolchain found...skipping test\n" - self.skip_test(msg) - except: + self.skip_test(msg, from_fw=True) + except Exception: pass def checkConfigureLogAndStdout(self, checks, @@ -1182,29 +1203,24 @@ SConscript(sconscript) sconf_dir='.sconf_temp', sconstruct="SConstruct", doCheckLog=True, doCheckStdout=True): - """ + """ Verify expected output from Configure. + 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. + If the algorithm does not succeed, the test is marked a fail + and this function does not return. 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 - ------- - + Args: + 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 """ - try: ls = '\n' nols = '([^\n])' @@ -1319,35 +1335,25 @@ SConscript(sconscript) def checkLogAndStdout(self, checks, results, cached, logfile, sconf_dir, sconstruct, doCheckLog=True, doCheckStdout=True): - """ + """ Verify expected output from Configure. + 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. + for use in comparing results. If the algorithm does not + succeed, the test is marked a fail and this function does not return. TODO: Perhaps a better API makes sense? - Parameters - ---------- - checks : The Configure checks being run - - results : The expected results for each check - - cached : If the corresponding check is expected to be cached - - 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 - ------- - + Args: + checks: The Configure checks being run + results: The expected results for each check + cached: If the corresponding check is expected to be cached + 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 """ try: @@ -1480,16 +1486,18 @@ SConscript(sconscript) print("-----------------------------------------------------") self.fail_test() - def get_python_version(self): - """ - Returns the Python version (just so everyone doesn't have to - hand-code slicing the right number of characters). + def get_python_version(self) -> str: + """ Returns the Python version. + + Convenience function so everyone doesn't have to + hand-code slicing the right number of characters """ # see also sys.prefix documentation return python_minor_version_string() def get_platform_python_info(self, python_h_required=False): - """ + """Return information about Python. + Returns a path to a Python executable suitable for testing on this platform and its associated include path, library path and library name. @@ -1497,12 +1505,12 @@ SConscript(sconscript) If the Python executable or Python header (if required) is not found, the test is skipped. - Returns a tuple: - (path to python, include path, library path, library name) + Returns: + tuple: path to python, include path, library path, library name """ python = os.environ.get('python_executable', self.where_is('python')) if not python: - self.skip_test('Can not find installed "python", skipping test.\n') + self.skip_test('Can not find installed "python", skipping test.\n', from_fw=True) # construct a program to run in the intended environment # in order to fetch the characteristics of that Python. @@ -1559,7 +1567,7 @@ else: """) incpath, libpath, libname, python_h = self.stdout().strip().split('\n') if python_h == "False" and python_h_required: - self.skip_test('Can not find required "Python.h", skipping test.\n') + self.skip_test('Can not find required "Python.h", skipping test.\n', from_fw=True) return (python, incpath, libpath, libname) @@ -1606,7 +1614,8 @@ else: waited = waited + 1.0 def get_alt_cpp_suffix(self): - """ + """Return alternate C++ file suffix. + Many CXX tests have this same logic. They all needed to determine if the current os supports files with .C and .c as different files or not diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py index 4de3aaf..1ab4136 100644 --- a/testing/framework/TestSConsMSVS.py +++ b/testing/framework/TestSConsMSVS.py @@ -1,6 +1,28 @@ +# MIT License +# +# Copyright The SCons Foundation +# +# 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. + """ -TestSConsMSVS.py: a testing framework for the SCons software construction -tool. +A testing framework for the SCons software construction tool. A TestSConsMSVS environment object is created via the usual invocation: @@ -13,10 +35,6 @@ as well as any overridden or additional methods or attributes defined in this subclass. """ -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - import os import sys import platform @@ -28,7 +46,6 @@ from TestSCons import * from TestSCons import __all__ - expected_dspfile_6_0 = '''\ # Microsoft Developer Studio Project File - Name="Test" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 diff --git a/testing/framework/TestSCons_time.py b/testing/framework/TestSCons_time.py index 0573404..a57ca88 100644 --- a/testing/framework/TestSCons_time.py +++ b/testing/framework/TestSCons_time.py @@ -1,5 +1,28 @@ +# MIT License +# +# Copyright The SCons Foundation +# +# 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. + """ -TestSCons_time.py: a testing framework for the scons-test.py script +A testing framework for the scons-time.py script A TestSCons_time environment object is created via the usual invocation: @@ -11,10 +34,6 @@ from those classes, as well as any overridden or additional methods or attributes defined in this subclass. """ -# __COPYRIGHT__ - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - import os import os.path import sys @@ -258,13 +277,9 @@ class TestSCons_time(TestCommon): import shutil try: import tarfile - except ImportError: - - self.skip_test('no tarfile module\n') - + self.skip_test('no tarfile module\n', from_framework=True) else: - base, suffix = self.archive_split(archive) mode = { diff --git a/testing/framework/TestSConsign.py b/testing/framework/TestSConsign.py index 11a764c..699e929 100644 --- a/testing/framework/TestSConsign.py +++ b/testing/framework/TestSConsign.py @@ -1,10 +1,28 @@ -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation +# +# 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__" - -__doc__ = """ -TestSConsign.py: a testing framework for the "sconsign" script -tool. +""" +A testing framework for the "sconsign" script tool. A TestSConsign environment object is created via the usual invocation: -- cgit v0.12 From a71d917a5d8da43f0d250d7fcac1a9cce3a01f2e Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 13 Apr 2021 11:34:35 -0600 Subject: Fix sider warning typo and docstring for skip_if_not_msvc Signed-off-by: Mats Wichmann --- testing/framework/TestSCons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index 01c1b40..5cd9db9 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -1175,7 +1175,7 @@ SConscript(sconscript) return 'COVERAGE_PROCESS_START' in os.environ or 'COVERAGE_FILE' in os.environ def skip_if_not_msvc(self, check_platform=True): - """ Check whether MSVC is avaliable. + """ Skip test if MSVC is not available. Check whether we are on a Windows platform and skip the test if not. This check can be omitted by setting check_platform to False. -- cgit v0.12