|
6 | 6 | import re |
7 | 7 | import tarfile |
8 | 8 |
|
| 9 | +from dataclasses import dataclass |
| 10 | + |
9 | 11 | import jsonschema |
10 | 12 | import yaml |
11 | 13 |
|
|
137 | 139 | }, |
138 | 140 | } |
139 | 141 |
|
| 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 | +} |
140 | 173 |
|
141 | 174 | # Packages that define tests. |
142 | 175 | STDLIB_TEST_PACKAGES = { |
@@ -750,3 +783,92 @@ def extension_modules_config(yaml_path: pathlib.Path): |
750 | 783 | jsonschema.validate(data, EXTENSION_MODULES_SCHEMA) |
751 | 784 |
|
752 | 785 | 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 |
0 commit comments