Skip to content

Commit 02046a1

Browse files
committed
verify: run stdlib tests
1 parent e1430de commit 02046a1

7 files changed

Lines changed: 2766 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ rustls = { version = "0.23" , default-features = false, features = ["aws_lc_rs"]
3030
semver = "1.0.27"
3131
serde = { version = "1.0.228", features = ["derive"] }
3232
serde_json = "1.0.145"
33+
serde_yaml = "0.9.33"
3334
sha2 = "0.10.9"
3435
tar = "0.4.44"
3536
tempfile = "3.23.0"

cpython-unix/build.py

Lines changed: 9 additions & 0 deletions
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 (
2930
build_docker_image,
@@ -56,6 +57,7 @@
5657
SUPPORT = ROOT / "cpython-unix"
5758
EXTENSION_MODULES = SUPPORT / "extension-modules.yml"
5859
TARGETS_CONFIG = SUPPORT / "targets.yml"
60+
STDLIB_TEST_ANNOTATIONS = ROOT / "stdlib-test-annotations.yml"
5961

6062
LINUX_ALLOW_SYSTEM_LIBRARIES = {
6163
"c",
@@ -764,6 +766,13 @@ def build_cpython(
764766
setup_local_content = setup["setup_local"]
765767
extra_make_content = setup["make_data"]
766768

769+
test_annotations = stdlib_test_annotations(
770+
STDLIB_TEST_ANNOTATIONS,
771+
python_version,
772+
target_triple,
773+
parsed_build_options,
774+
)
775+
767776
with build_environment(client, image) as build_env:
768777
if settings.get("needs_toolchain"):
769778
build_env.install_toolchain(

pythonbuild/cpython.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import re
77
import tarfile
88

9+
from dataclasses import dataclass
10+
911
import jsonschema
1012
import yaml
1113

@@ -137,6 +139,37 @@
137139
},
138140
}
139141

142+
STDLIB_TEST_ANNOTATIONS_SCHEMA = {
143+
"type": "object",
144+
"properties": {
145+
"expected-failures": {
146+
"type": "array",
147+
"items": {
148+
"type": "object",
149+
"properties": {
150+
"name": {"type": "string"},
151+
"reason": {"type": "string"},
152+
"targets": {
153+
"type": "array",
154+
"items": {"type": "string"},
155+
},
156+
"ignore-targets": {
157+
"type": "array",
158+
"items": {"type": "string"},
159+
},
160+
"minimum-python-version": {"type": "string"},
161+
"maximum-python-version": {"type": "string"},
162+
"build-option": {"type": "string"},
163+
"no-build-option": {"type": "string"},
164+
"exclude": {"type": "boolean"},
165+
"dont-verify": {"type": "boolean"},
166+
},
167+
"additionalProperties": False,
168+
"required": ["name", "reason"],
169+
},
170+
},
171+
},
172+
}
140173

141174
# Packages that define tests.
142175
STDLIB_TEST_PACKAGES = {
@@ -750,3 +783,92 @@ def extension_modules_config(yaml_path: pathlib.Path):
750783
jsonschema.validate(data, EXTENSION_MODULES_SCHEMA)
751784

752785
return data
786+
787+
788+
@dataclass
789+
class TestAnnotation:
790+
# Name/pattern of test.
791+
name: str
792+
# Describes why the annotation exists.
793+
reason: str
794+
# Whether the test is expected to fail.
795+
expect_test_failure: bool
796+
# Whether test failure is intermittent. Should only be true if
797+
# expect_test_failure also true.
798+
intermittent_test_failure: bool
799+
# Whether to exclude loading the test module when running tests.
800+
exclude_testing: bool
801+
802+
803+
def stdlib_test_annotations(
804+
yaml_path: pathlib.Path,
805+
python_version: str,
806+
target_triple: str,
807+
build_options: set[str],
808+
) -> list[TestAnnotation]:
809+
"""Processes the test-annotations.yml file for a given build configuration."""
810+
with yaml_path.open("r", encoding="utf-8") as fh:
811+
data = yaml.load(fh, Loader=yaml.SafeLoader)
812+
813+
jsonschema.validate(data, STDLIB_TEST_ANNOTATIONS_SCHEMA)
814+
815+
annotations = []
816+
817+
log(f"processing {len(data['expected-failures'])} stdlib test annotations")
818+
819+
for test in data["expected-failures"]:
820+
name = test["name"]
821+
822+
if targets := test.get("targets"):
823+
matches_target = any(re.match(p, target_triple) for p in targets)
824+
else:
825+
matches_target = True
826+
827+
for m in test.get("ignore-targets", []):
828+
if re.match(m, target_triple):
829+
matches_target = False
830+
831+
if not matches_target:
832+
log(f"ignoring rule (target doesn't match): {name}")
833+
continue
834+
835+
python_minimum_version = test.get("minimum-python-version", "1.0")
836+
python_maximum_version = test.get("maximum-python-version", "100.0")
837+
838+
if not meets_python_minimum_version(python_version, python_minimum_version):
839+
log(
840+
f"ignoring rule ({python_version} < {python_minimum_version} (min)): {name}"
841+
)
842+
continue
843+
844+
if not meets_python_maximum_version(python_version, python_maximum_version):
845+
log(
846+
f"ignoring rule ({python_version} > {python_maximum_version} (max)): {name}"
847+
)
848+
continue
849+
850+
if option := test.get("build-option"):
851+
if option not in build_options:
852+
log(f"ignoring rule (build option {option} not present): {name}")
853+
continue
854+
855+
if option := test.get("no-build-option"):
856+
if option in build_options:
857+
log(f"ignoring rule (build option {option} is present): {name}")
858+
continue
859+
860+
# Filtering complete. This rule applies to the current build.
861+
862+
log(f"relevant test annotation: {name}: {test['reason']}")
863+
864+
annotations.append(
865+
TestAnnotation(
866+
name=name,
867+
reason=test["reason"],
868+
expect_test_failure=True,
869+
intermittent_test_failure=test.get("dont-verify", False),
870+
exclude_testing=test.get("exclude", False),
871+
)
872+
)
873+
874+
return annotations

src/validation.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ const SHARED_LIBRARY_EXTENSIONS: &[&str] = &[
855855
];
856856

857857
const PYTHON_VERIFICATIONS: &str = include_str!("verify_distribution.py");
858+
const TEST_ANNOTATIONS: &str = include_str!("../stdlib-test-annotations.yml");
858859

859860
fn allowed_dylibs_for_triple(triple: &str) -> Vec<MachOAllowedDylib> {
860861
match triple {
@@ -2171,6 +2172,12 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
21712172
let test_file = temp_dir.path().join("verify.py");
21722173
std::fs::write(&test_file, PYTHON_VERIFICATIONS.as_bytes())?;
21732174

2175+
// Normalize annotations to JSON so the Python stdlib can read it.
2176+
let annotations_json: serde_json::Value = serde_yaml::from_str(TEST_ANNOTATIONS)?;
2177+
let annotations_pretty = serde_json::to_vec_pretty(&annotations_json)?;
2178+
let test_annotations_file = temp_dir.path().join("test-annotations.json");
2179+
std::fs::write(&test_annotations_file, annotations_pretty)?;
2180+
21742181
eprintln!(" running interpreter tests (output should follow)");
21752182
let output = duct::cmd(&python_exe, [test_file.display().to_string()])
21762183
.stdout_to_stderr()

0 commit comments

Comments
 (0)