Skip to content

Commit d602760

Browse files
committed
verify: run stdlib tests
1 parent e0502bd commit d602760

12 files changed

Lines changed: 3433 additions & 26 deletions

File tree

.github/workflows/linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ jobs:
329329
unset LD_LIBRARY_PATH
330330
fi
331331
332-
./test-distribution.py dist/*.tar.zst
332+
./test-distribution.py --stdlib dist/*.tar.zst
333333
fi
334334
env:
335335
MATRIX_RUN: ${{ matrix.run }}

.github/workflows/macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ jobs:
181181
build/pythonbuild validate-distribution --macos-sdks-path macosx-sdks dist/*.tar.zst
182182
183183
if [ "${MATRIX_RUN}" == "true" ]; then
184-
./test-distribution.py dist/*.tar.zst
184+
./test-distribution.py --stdlib dist/*.tar.zst
185185
fi
186186
env:
187187
MATRIX_RUN: ${{ matrix.run }}

.github/workflows/windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,4 @@ jobs:
186186
run: |
187187
$Dists = Resolve-Path -Path "dist/*.tar.zst" -Relative
188188
.\pythonbuild.exe validate-distribution $Dists
189-
uv run --no-dev test-distribution.py $Dists
189+
uv run --no-dev test-distribution.py --stdlib $Dists

cpython-unix/build-cpython.sh

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ tar -xf "Python-${PYTHON_VERSION}.tar.xz"
5151
PIP_WHEEL="${ROOT}/pip-${PIP_VERSION}-py3-none-any.whl"
5252
SETUPTOOLS_WHEEL="${ROOT}/setuptools-${SETUPTOOLS_VERSION}-py3-none-any.whl"
5353

54-
cat Setup.local
55-
mv Setup.local "Python-${PYTHON_VERSION}/Modules/Setup.local"
54+
# Put critical config files in logs to aid debugging.
55+
for f in Setup.local Makefile.extra stdlib-test-annotations.json; do
56+
echo "BEGIN $f"
57+
cat $f
58+
echo "END $f"
59+
done
5660

57-
cat Makefile.extra
61+
mv Setup.local "Python-${PYTHON_VERSION}/Modules/Setup.local"
5862

5963
pushd "Python-${PYTHON_VERSION}"
6064

@@ -1386,14 +1390,18 @@ cp -av Modules/config.c.in "${ROOT}/out/python/build/Modules/"
13861390
cp -av Python/frozen.c "${ROOT}/out/python/build/Python/"
13871391
cp -av Modules/Setup* "${ROOT}/out/python/build/Modules/"
13881392

1389-
# Copy the test hardness runner for convenience.
1393+
# Copy the test harness runner for convenience.
13901394
# As of Python 3.13, the test harness runner has been removed so we provide a compatibility script
13911395
if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_13}" ]; then
13921396
cp -av "${ROOT}/run_tests-13.py" "${ROOT}/out/python/build/run_tests.py"
13931397
else
13941398
cp -av Tools/scripts/run_tests.py "${ROOT}/out/python/build/"
13951399
fi
13961400

1401+
# Copy standard library test annotations so it is in the artifact and the
1402+
# distribution self-describes expected test wonkiness.
1403+
cp -av "${ROOT}/stdlib-test-annotations.json" "${ROOT}/out/python/build/stdlib-test-annotations.json"
1404+
13971405
# Don't hard-code the build-time prefix into the pkg-config files. See
13981406
# the description of `pcfiledir` in `man pkg-config`.
13991407
find "${ROOT}/out/python/install/lib/pkgconfig" -name \*.pc -type f -exec \

cpython-unix/build.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
meets_python_maximum_version,
2525
meets_python_minimum_version,
2626
parse_setup_line,
27+
stdlib_test_annotations,
2728
)
2829
from pythonbuild.docker import build_docker_image, get_image, write_dockerfiles
2930
from pythonbuild.downloads import DOWNLOADS
@@ -50,6 +51,7 @@
5051
SUPPORT = ROOT / "cpython-unix"
5152
EXTENSION_MODULES = SUPPORT / "extension-modules.yml"
5253
TARGETS_CONFIG = SUPPORT / "targets.yml"
54+
STDLIB_TEST_ANNOTATIONS = ROOT / "stdlib-test-annotations.yml"
5355

5456
LINUX_ALLOW_SYSTEM_LIBRARIES = {
5557
"c",
@@ -732,6 +734,13 @@ def build_cpython(
732734
setuptools_archive = download_entry("setuptools", DOWNLOADS_PATH)
733735
pip_archive = download_entry("pip", DOWNLOADS_PATH)
734736

737+
test_annotations = stdlib_test_annotations(
738+
STDLIB_TEST_ANNOTATIONS,
739+
python_version,
740+
target_triple,
741+
parsed_build_options,
742+
)
743+
735744
ems = extension_modules_config(EXTENSION_MODULES)
736745

737746
setup = derive_setup_local(
@@ -804,6 +813,14 @@ def build_cpython(
804813

805814
build_env.copy_file(fh.name, dest_name="Makefile.extra")
806815

816+
# Install the derived test annotations.
817+
with tempfile.NamedTemporaryFile("w", encoding="utf-8") as fh:
818+
os.chmod(fh.name, 0o644)
819+
test_annotations.json_dump(fh)
820+
fh.flush()
821+
822+
build_env.copy_file(fh.name, dest_name="stdlib-test-annotations.json")
823+
807824
env = {
808825
"PIP_VERSION": DOWNLOADS["pip"]["version"],
809826
"PYTHON_VERSION": python_version,
@@ -1070,7 +1087,7 @@ def main():
10701087
write_dockerfiles(SUPPORT, BUILD)
10711088
elif action == "makefiles":
10721089
targets = get_targets(TARGETS_CONFIG)
1073-
write_triples_makefiles(targets, BUILD, SUPPORT)
1090+
write_triples_makefiles(targets, ROOT, BUILD, SUPPORT)
10741091
write_target_settings(targets, BUILD / "targets")
10751092
write_package_versions(BUILD / "versions")
10761093

cpython-windows/build.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
meets_python_maximum_version,
2323
meets_python_minimum_version,
2424
parse_config_c,
25+
stdlib_test_annotations,
2526
)
2627
from pythonbuild.downloads import DOWNLOADS
2728
from pythonbuild.utils import (
@@ -39,6 +40,7 @@
3940
BUILD = ROOT / "build"
4041
DIST = ROOT / "dist"
4142
SUPPORT = ROOT / "cpython-windows"
43+
STDLIB_TEST_ANNOTATIONS = ROOT / "stdlib-test-annotations.yml"
4244

4345
LOG_PREFIX = [None]
4446
LOG_FH = [None]
@@ -128,7 +130,6 @@
128130
"_zstd": ["zstd"],
129131
}
130132

131-
132133
# Tests to run during PGO profiling.
133134
#
134135
# This set was copied from test.libregrtest.pgo in the CPython source
@@ -1407,6 +1408,13 @@ def build_cpython(
14071408
else:
14081409
raise Exception("unhandled architecture: %s" % arch)
14091410

1411+
test_annotations = stdlib_test_annotations(
1412+
STDLIB_TEST_ANNOTATIONS,
1413+
python_version,
1414+
target_triple,
1415+
parsed_build_options,
1416+
)
1417+
14101418
tempdir_opts = (
14111419
{"ignore_cleanup_errors": True} if sys.version_info >= (3, 12) else {}
14121420
)
@@ -1763,6 +1771,12 @@ def build_cpython(
17631771
out_dir / "python" / "build" / "run_tests.py",
17641772
)
17651773

1774+
# Install a JSON file annotating tests.
1775+
with (out_dir / "python" / "build" / "stdlib-test-annotations.json").open(
1776+
"w", encoding="utf-8"
1777+
) as fh:
1778+
test_annotations.json_dump(fh)
1779+
17661780
licenses_dir = out_dir / "python" / "licenses"
17671781
licenses_dir.mkdir()
17681782
for f in sorted(os.listdir(ROOT)):

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies = [
1010
"docker>=7.1.0",
1111
"jinja2>=3.1.5",
1212
"jsonschema>=4.23.0",
13+
"junitparser>=4.0.2",
1314
"pyyaml>=6.0.2",
1415
"six>=1.17.0",
1516
"tomli>=2.2.1",

pythonbuild/cpython.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
import dataclasses
6+
import json
57
import pathlib
68
import re
79
import tarfile
10+
from typing import Optional
811

912
import jsonschema
1013
import yaml
@@ -137,6 +140,65 @@
137140
},
138141
}
139142

143+
STDLIB_TEST_ANNOTATION_COMMON_PROPERTIES = {
144+
"reason": {"type": "string"},
145+
"targets": {
146+
"type": "array",
147+
"items": {"type": "string"},
148+
},
149+
"ignore-targets": {
150+
"type": "array",
151+
"items": {"type": "string"},
152+
},
153+
"minimum-python-version": {"type": "string"},
154+
"maximum-python-version": {"type": "string"},
155+
"build-option": {"type": "string"},
156+
"no-build-option": {"type": "string"},
157+
}
158+
159+
STDLIB_TEST_ANNOTATIONS_SCHEMA = {
160+
"type": "object",
161+
"properties": {
162+
"harness-skips": {
163+
"type": "array",
164+
"items": {
165+
"type": "object",
166+
"properties": {
167+
"name": {"type": "string"},
168+
**STDLIB_TEST_ANNOTATION_COMMON_PROPERTIES,
169+
},
170+
"additionalProperties": False,
171+
"required": ["name", "reason"],
172+
},
173+
},
174+
"module-excludes": {
175+
"type": "array",
176+
"items": {
177+
"type": "object",
178+
"properties": {
179+
"module": {"type": "string"},
180+
**STDLIB_TEST_ANNOTATION_COMMON_PROPERTIES,
181+
},
182+
"additionalProperties": False,
183+
"required": ["module", "reason"],
184+
},
185+
},
186+
"expected-failures": {
187+
"type": "array",
188+
"items": {
189+
"type": "object",
190+
"properties": {
191+
"name": {"type": "string"},
192+
"dont-verify": {"type": "boolean"},
193+
"intermittent": {"type": "boolean"},
194+
**STDLIB_TEST_ANNOTATION_COMMON_PROPERTIES,
195+
},
196+
"additionalProperties": False,
197+
"required": ["name", "reason"],
198+
},
199+
},
200+
},
201+
}
140202

141203
# Packages that define tests.
142204
STDLIB_TEST_PACKAGES = {
@@ -750,3 +812,139 @@ def extension_modules_config(yaml_path: pathlib.Path):
750812
jsonschema.validate(data, EXTENSION_MODULES_SCHEMA)
751813

752814
return data
815+
816+
817+
TEST_ANNOTATION_HARNESS_SKIP = "harness-skip"
818+
TEST_ANNOTATION_MODULE_EXCLUDE = "module-exclude"
819+
TEST_ANNOTATION_TEST_FAILURE = "test-failure"
820+
821+
822+
@dataclasses.dataclass
823+
class TestAnnotation:
824+
# Describes the type of annotation.
825+
flavor: str
826+
# Name/pattern of test.
827+
name: str
828+
# Describes why the annotation exists.
829+
reason: str
830+
# Whether the test is expected to fail.
831+
expect_test_failure: bool
832+
# Whether to skip verification of failures.
833+
dont_verify: bool
834+
# Whether test failure is intermittent. Should only be true if
835+
# expect_test_failure also true.
836+
intermittent_test_failure: bool
837+
# Whether to exclude loading the test module when running tests.
838+
exclude_testing: bool
839+
840+
841+
@dataclasses.dataclass
842+
class TestAnnotations:
843+
annotations: list[TestAnnotation]
844+
845+
def json_dump(self, of):
846+
data = [dataclasses.asdict(a) for a in self.annotations]
847+
json.dump(data, of, indent=2)
848+
849+
850+
def filter_stdlib_test_entry(
851+
flavor: str,
852+
test,
853+
python_version: str,
854+
target_triple: str,
855+
build_options: set[str],
856+
) -> Optional[TestAnnotation]:
857+
name = test["name"]
858+
859+
if targets := test.get("targets"):
860+
matches_target = any(re.match(p, target_triple) for p in targets)
861+
else:
862+
matches_target = True
863+
864+
for m in test.get("ignore-targets", []):
865+
if re.match(m, target_triple):
866+
matches_target = False
867+
868+
if not matches_target:
869+
log(f"ignoring {flavor} rule (target doesn't match): {name}")
870+
return None
871+
872+
python_minimum_version = test.get("minimum-python-version", "1.0")
873+
python_maximum_version = test.get("maximum-python-version", "100.0")
874+
875+
if not meets_python_minimum_version(python_version, python_minimum_version):
876+
log(
877+
f"ignoring {flavor} rule ({python_version} < {python_minimum_version} (min)): {name}"
878+
)
879+
return None
880+
881+
if not meets_python_maximum_version(python_version, python_maximum_version):
882+
log(
883+
f"ignoring {flavor} rule ({python_version} > {python_maximum_version} (max)): {name}"
884+
)
885+
return None
886+
887+
if option := test.get("build-option"):
888+
if option not in build_options:
889+
log(f"ignoring {flavor} rule (build option {option} not present): {name}")
890+
return None
891+
892+
if option := test.get("no-build-option"):
893+
if option in build_options:
894+
log(f"ignoring {flavor} rule (build option {option} is present): {name}")
895+
return None
896+
897+
# Filtering complete. This rule applies to the current build.
898+
899+
log(f"relevant {flavor} test rule: {name}: {test['reason']}")
900+
901+
return TestAnnotation(
902+
flavor=flavor,
903+
name=name,
904+
reason=test["reason"],
905+
expect_test_failure=True,
906+
dont_verify=test.get("dont-verify", False),
907+
intermittent_test_failure=test.get("intermittent", False),
908+
exclude_testing=test.get("exclude", False),
909+
)
910+
911+
912+
def stdlib_test_annotations(
913+
yaml_path: pathlib.Path,
914+
python_version: str,
915+
target_triple: str,
916+
build_options: set[str],
917+
) -> TestAnnotations:
918+
"""Processes the test-annotations.yml file for a given build configuration."""
919+
with yaml_path.open("r", encoding="utf-8") as fh:
920+
data = yaml.load(fh, Loader=yaml.SafeLoader)
921+
922+
jsonschema.validate(data, STDLIB_TEST_ANNOTATIONS_SCHEMA)
923+
924+
annotations = []
925+
926+
log(f"processing {len(data['expected-failures'])} stdlib test annotations")
927+
928+
raw_entries = []
929+
930+
for entry in data["harness-skips"]:
931+
raw_entries.append((TEST_ANNOTATION_HARNESS_SKIP, entry))
932+
933+
for entry in data["module-excludes"]:
934+
entry["name"] = entry["module"]
935+
raw_entries.append((TEST_ANNOTATION_MODULE_EXCLUDE, entry))
936+
937+
for entry in data["expected-failures"]:
938+
raw_entries.append((TEST_ANNOTATION_TEST_FAILURE, entry))
939+
940+
for flavor, entry in raw_entries:
941+
if a := filter_stdlib_test_entry(
942+
flavor,
943+
entry,
944+
python_version,
945+
target_triple,
946+
build_options,
947+
):
948+
annotations.append(a)
949+
950+
return TestAnnotations(annotations)

0 commit comments

Comments
 (0)