Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------
Expand Down
28 changes: 19 additions & 9 deletions testing/framework/TestCmd.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,25 +19,32 @@
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
# Python License: https://docs.python.org/3/license.html#psf-license
#
# This copy is vendored for the SCons project from a revision marked
# "TestCmd.py 1.3.D001 2010/06/03 12:58:27 knight"

"""
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),
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(
Expand Down Expand Up @@ -648,7 +658,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)
Expand Down
20 changes: 13 additions & 7 deletions testing/framework/TestCommon.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +19,9 @@
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
# Python License: https://docs.python.org/3/license.html#psf-license
#
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the question... that was there ("under the same terms as Python itself") and we haven't gotten it reclicensed to be under the same terms as the rest of SCons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this bit

# This copy is vendored for the SCons project from a revision marked
# "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight"```

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an attempt to explain that we've continued to change it, while it still has an old version string in it. Our current version isn't "1.3.D001", we couldn't even produce such a version from git... you can see those lines elsewhere in the two files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just remove those. My guess it was the old "__VERSION"... string which got populated and checked in when it shouldn't have.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

happy to remove. those two layers are made to seem as if they were developed indepenedently and just included in SCons as one of multiple projects that used it. If there ever were others I doubt they still are, so we could recast that whole tone - but I certainy wasn't going to presume that much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I had to guess.. some of this code originated with QMtest which was the original framework used. But I could wrong and this code is the result of removing the need to have QMtest..

# This copy is vendored for the SCons project from a revision marked
# "TestCommon.py 1.3.D001 2010/06/03 12:58:27 knight"

"""
A testing framework for commands and scripts with commonly useful error handling
Expand All @@ -27,12 +33,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
Expand Down Expand Up @@ -771,7 +777,7 @@ def start(
arguments=None,
universal_newlines=None,
**kw,
):
) -> Popen:
"""
Starts a program or script for the test environment, handling
any exceptions.
Expand Down
13 changes: 8 additions & 5 deletions testing/framework/TestRuntest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 40 additions & 32 deletions testing/framework/TestSCons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions testing/framework/TestSConsMSVS.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 13 additions & 6 deletions testing/framework/TestSConsTar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
11 changes: 6 additions & 5 deletions testing/framework/TestSCons_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading