diff --git a/CHANGES.txt b/CHANGES.txt index d472eed252..a6963f268b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -59,6 +59,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Google style. - Additional small tweaks to Environment.py type hints, fold some overly long function signature lines, and some linting-insipired cleanups. + - Test framework: tweak module docstrings RELEASE 4.10.1 - Sun, 16 Nov 2025 10:51:57 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index ef9fd7607e..571cc35d17 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -60,6 +60,8 @@ IMPROVEMENTS - Additional small tweaks to Environment.py type hints, fold some overly long function signature lines, and some linting-insipired cleanups. +- Test framework: tweak module docstrings + PACKAGING --------- diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py index b4edc1b624..dc1d2667ff 100644 --- a/testing/framework/TestCmd.py +++ b/testing/framework/TestCmd.py @@ -1,4 +1,7 @@ -# Copyright 2000-2024 Steven Knight +# SPDX-License-Identifier: PSF-2.0 +# +# Copyright 2000-2010 Steven Knight +# Copyright The SCons Foundation # # 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 @@ -22,19 +25,23 @@ The TestCmd module provides a framework for portable automated testing of executable commands and scripts (in any language, not just Python), -especially commands and scripts that require file system interaction. +particularly those that require file system interaction. -In addition to running tests and evaluating conditions, the TestCmd -module manages and cleans up one or more temporary workspace -directories, and provides methods for creating files and directories in -those workspace directories from in-line data, here-documents), allowing -tests to be completely self-contained. +Beyond running tests and evaluating conditions, the TestCmd module manages +temporary workspace directories, providing methods to create files and +directories from inline data (here-documents) and from fixture files. -A TestCmd environment object is created via the usual invocation: +Create a TestCmd environment object by instantiating the class: import TestCmd test = TestCmd.TestCmd() +TestCmd is the bottom layer of an extensible stack. You will probably +encounter it via derived classes such as the companion :class:`TestCommon` +class, or, if used in the SCons project, via :class:`TestSCons` and +other specializations, with the functionality implemented here provided +via inheritance. + There are a bunch of keyword arguments available at instantiation: test = TestCmd.TestCmd( @@ -648,7 +655,7 @@ def match_re( def match_re_dotall( lines: str | list[str] | None = None, res: str | list[str] | None = None, -) -> Match[str] | None: +) -> re.Match[str] | None: """Match function using regular expression match. Unlike :math:`match_re`, the arguments are converted to strings (if necessary) diff --git a/testing/framework/TestCommon.py b/testing/framework/TestCommon.py index 33a901399a..2c21d820ca 100644 --- a/testing/framework/TestCommon.py +++ b/testing/framework/TestCommon.py @@ -1,4 +1,7 @@ +# SPDX-License-Identifier: PSF-2.0 +# # Copyright 2000-2010 Steven Knight +# Copyright The SCons Foundation # # 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 @@ -27,12 +30,12 @@ explicit checks unnecessary, making the test scripts themselves simpler to write and easier to read. -The TestCommon class is a subclass of the TestCmd class. In essence, -TestCommon is a wrapper that handles common TestCmd error conditions in -useful ways. You can use TestCommon directly, or subclass it for your -program and add additional (or override) methods to tailor it to your -program's specific needs. Alternatively, the TestCommon class serves -as a useful example of how to define your own TestCmd subclass. +The TestCommon class is a subclass of the :mod:`TestCmd:mod:` class. +In essence, TestCommon` is a wrapper that handles common TestCmd` error +conditions in useful ways. You can use TestCommon directly, or subclass +it for your program and add additional (or override) methods to tailor +it to your program's specific needs. Alternatively, the TestCommon class +serves as a useful example of how to define your own TestCmd subclass. As a subclass of TestCmd, TestCommon provides access to all of the variables and methods from the TestCmd module. Consequently, you can @@ -771,7 +774,7 @@ def start( arguments=None, universal_newlines=None, **kw, - ): + ) -> Popen: """ Starts a program or script for the test environment, handling any exceptions. diff --git a/testing/framework/TestRuntest.py b/testing/framework/TestRuntest.py index 52bba9c9ef..b62a829553 100644 --- a/testing/framework/TestRuntest.py +++ b/testing/framework/TestRuntest.py @@ -24,14 +24,17 @@ """ A testing framework for the runtest.py command used to invoke SCons tests. -A TestRuntest environment object is created via the usual invocation: +Create a TestRuntest environment object by instantiating the class: + import TestRuntest test = TestRuntest() -TestRuntest is a subclass of TestCommon, which is 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. + test = TestRuntest() + +TestRuntest is a subclass of :class:`TestCommon`, which is in turn is a +subclass of :class:`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 diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py index a3ada082fe..d471bd66e6 100644 --- a/testing/framework/TestSCons.py +++ b/testing/framework/TestSCons.py @@ -24,14 +24,19 @@ """ A testing framework for the SCons software construction tool. -A TestSCons environment object is created via the usual invocation: +Create a TestSCons environment object by instantiating the class: - test = TestSCons() + import TestSCons + test = TestSCons.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. +:class:`TestScons' is a subclass of :class:`TestCommon,` which in turn +is a subclass of :class:`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. + +This class is further specialized for specific testing purposes in +modules :mod:`TestSConsMSVS`, :mod:`TestSConsTar`, :mod:`TestSCons_time`, +:mod:`TestSConsign` as well as in this file in :class:`TimeSCons`. """ from __future__ import annotations @@ -47,7 +52,7 @@ from SCons.Util import get_hash_format, get_current_hash_algorithm_used from TestCommon import * -from TestCommon import __all__, _python_ +from TestCommon import __all__ # Some tests which verify that SCons has been packaged properly need to # look for specific version file names. Replicating the version number @@ -146,7 +151,7 @@ def re_escape(str): # Helper functions that we use as a replacement to the default re.match # when searching for special strings in stdout/stderr. # -def search_re(out, l): +def search_re(out: str, l: str) -> int | None: """Search the regular expression 'l' in the output 'out' and return the start index when successful. """ @@ -157,7 +162,7 @@ def search_re(out, l): return None -def search_re_in_list(out, l): +def search_re_in_list(out: str, l: str) -> int | None: """Search the regular expression 'l' in each line of the given string list 'out' and return the line's index when successful. @@ -203,12 +208,20 @@ def deprecated_python_version(version=sys.version_info): deprecated_python_msg = "" -def initialize_sconsflags(ignore_python_version): - """ - Add the --warn=no-python-version option to SCONSFLAGS for every - command so test scripts don't have to filter out Python version - deprecation warnings. - Same for --warn=no-visual-c-missing. +def initialize_sconsflags(ignore_python_version: bool) -> str | None: + """Set up SCONSFLAGS for every command. + + Used so test scripts don't need to worry about unexpected warnings + in their output. + + ``--warn=no-python-version`` is used to suppress Python version + deprecation warnings while such are active. + + ``--warn=no-visual-c-missing`` is used so Windows systems which don't + have Visual C++ installed don't get warnings about it. + + Returns: + the original ``SCONSFLAGS`` value, if any, so it can be restored """ save_sconsflags = os.environ.get('SCONSFLAGS') if save_sconsflags: @@ -229,7 +242,8 @@ def initialize_sconsflags(ignore_python_version): return save_sconsflags -def restore_sconsflags(sconsflags) -> None: +def restore_sconsflags(sconsflags: str | None) -> None: + """Restore the original SCONSFLAGS value, if any.""" if sconsflags is None: del os.environ['SCONSFLAGS'] else: @@ -249,18 +263,14 @@ def restore_sconsflags(sconsflags) -> None: class NoMatch(Exception): - """ - Exception for matchPart to indicate there was no match found in the passed logfile - """ + """There was no match in the passed logfile.""" def __init__(self, p) -> None: self.pos = p def match_part_of_configlog(log, logfile, lastEnd, NoMatch=NoMatch): - """ - Match part of the logfile - """ + """Match part of the logfile.""" # print("Match:\n%s\n==============\n%s" % (log , logfile[lastEnd:])) m = re.match(log, logfile[lastEnd:]) if not m: @@ -368,8 +378,7 @@ def Environment(self, ENV=None, *args, **kw): return None def detect(self, var, prog=None, ENV=None, norm=None): - """ - Return the detected path to a tool program. + """Return the detected path to a tool program. Searches first the named construction variable, then the SCons path. @@ -401,7 +410,7 @@ def detect(self, var, prog=None, ENV=None, norm=None): return self.where_is(prog) - def detect_tool(self, tool, prog=None, ENV=None): + def detect_tool(self, tool, prog=None, ENV=None) -> bool: """ Given a tool (i.e., tool specification that would be passed to the "tools=" parameter of Environment()) and a program that @@ -410,12 +419,11 @@ def detect_tool(self, tool, prog=None, ENV=None): By default, prog is set to the value passed into the tools parameter. """ - if not prog: prog = tool env = self.Environment(ENV, tools=[tool]) if env is None: - return None + return False return env.Detect([prog]) def where_is(self, prog, path=None, pathext=None): @@ -476,8 +484,9 @@ def wrap_stdout( ) def run(self, *args, **kw) -> None: - """ - Set up SCONSFLAGS for every command so test scripts don't need + """Run a command. + + Sets up ``SCONSFLAGS`` first so test scripts don't need to worry about unexpected warnings in their output. """ sconsflags = initialize_sconsflags(self.ignore_python_version) @@ -1810,9 +1819,8 @@ def venv_path(): return (python, incpath, libpath, libname + _lib) - def start(self, *args, **kw): - """ - Starts SCons in the test environment. + def start(self, *args, **kw) -> Popen: + """Starts SCons in the test environment. This method exists to tell Test{Cmd,Common} that we're going to use standard input without forcing every .start() call in the diff --git a/testing/framework/TestSConsMSVS.py b/testing/framework/TestSConsMSVS.py index 0c4b510d21..23e2744fc2 100644 --- a/testing/framework/TestSConsMSVS.py +++ b/testing/framework/TestSConsMSVS.py @@ -24,15 +24,16 @@ """ A testing framework for the SCons software construction tool. -A TestSConsMSVS environment object is created via the usual invocation: +Create a TestSConsMSVS environment object by instantiating the class: + import TestSConsMSVS test = TestSConsMSVS() -TestSConsMSVS is a subsclass of TestSCons, which is in turn a subclass -of TestCommon, which is 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. +TestSConsMSVS is a subsclass of :class:`TestSCons`, which is a subclass of +:class:`TestCommon`, which is in turn is a subclass of :class:`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 diff --git a/testing/framework/TestSConsTar.py b/testing/framework/TestSConsTar.py index 1c3e0490d2..68ef879092 100644 --- a/testing/framework/TestSConsTar.py +++ b/testing/framework/TestSConsTar.py @@ -24,15 +24,19 @@ """ A testing framework for the SCons software construction tool. -A TestSConsTar environment object is created via the usual invocation: +Create a TestSConsTar environment object by instantiating the class: + import TestSConsTar test = TestSConsTar() -TestSConsTar is a subsclass of TestSCons, which is in turn a subclass -of TestCommon, which is 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. +TestSConsTar is a subsclass of :class:`TestSCons`, which is in turn +a subclass of :class:`TestCommon`, which is in turn is a subclass +of :class:`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. + +This module exists to provide support for using ``tar`` on Windows, +where the state is a bit unpredictable. """ import os @@ -69,6 +73,9 @@ # tar.exe --version (GH windows-2025): # bsdtar 3.7.7 - libarchive 3.7.7 zlib/1.2.13.1-motley liblzma/5.4.3 bz2lib/1.0.8 libzstd/1.5.5 + # Later versions may support even more, e.g; + # bsdtar 3.8.4 - libarchive 3.8.4 zlib/1.2.13.1-motley liblzma/5.8.1 bz2lib/1.0.8 libzstd/1.5.7 cng/2.0 libb2/bundles + def _is_windows_system_tar(tar): if not tar: diff --git a/testing/framework/TestSCons_time.py b/testing/framework/TestSCons_time.py index be447e17c5..50825c1cf7 100644 --- a/testing/framework/TestSCons_time.py +++ b/testing/framework/TestSCons_time.py @@ -24,14 +24,15 @@ """ A testing framework for the scons-time.py script -A TestSCons_time environment object is created via the usual invocation: +Create a TestSCons_time environment object by instantiating the class: + import TestSCons_time test = TestSCons_time() -TestSCons_time is a subclass of TestCommon, which is 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. +TestSCons_time is a subclass of :class:`TestCommon`, which is in turn a +subclass of :class:`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 diff --git a/testing/framework/TestSConsign.py b/testing/framework/TestSConsign.py index f84ed53881..eb665cd3f4 100644 --- a/testing/framework/TestSConsign.py +++ b/testing/framework/TestSConsign.py @@ -22,17 +22,18 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -A testing framework for the "sconsign" script tool. +A testing framework for the "sconsign" tool. -A TestSConsign environment object is created via the usual invocation: +Create a TestSConsign environment object by instantiating the class: + import TestSConsign test = TestSConsign() -TestSconsign is a subclass of TestSCons, which is a subclass of -TestCommon, which is 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. +TestSConsign is a subsclass of :class:`TestSCons`, which is a subclass of +:class:`TestCommon`, which is in turn is a subclass of :class:`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