commit fe40fd4eede7ae7f3854925e0a15386960ddb7a4 parent ecff8c82bd7bbf29d99347f82ad2c4a92fa235f6 Author: James Graham <james@hoppipolla.co.uk> Date: Fri, 9 Jan 2026 08:37:03 +0000 Bug 2008715 [wpt PR 57004] - Update to support Python 3.14, a=testonly Automatic update from web-platform-tests Ensure we have an event loop when trying to ping h3 server This seems to be the only place we're running an async client in the main thread, so we can just create an event loop here -- Update CI configuration to Python 3.14 -- Update tox configuration for Python 3.14 -- Ensure we close HTTPError objects These are open file handles and with Python3.14 we're getting a warning if they are closed in the destructor. -- Stash tests only work with the fork multiprocessing method Python 3.14 seems to have movved to forkserver as the default, so these tests are broken there, as they are with 'spawn' -- Update some dependencies for Python 3.14 -- Fix schema validation to work with Python 3.14 jsonschema started warning when it's auto-resolving references, so we instead pre-download the expected references and create a registry. -- Update zstandard package version -- wpt-commits: 3a6cb3e8302fcd68e768b026a4ef4c149f53ca68, 7149ed4ba894cabaa8212d88ffe0d24e0ea97df5, ece96fb793192b8e7a905a210d69f48551441fc8, 9014dece9d2e8c2b257b31a14cda95a6d5686bf8, 1d26659918f65f8eddfb246932ac317b9f77ea3b, 2e37ef53b7292c73dc7fdd659c29cc8bf5c772bf, 85132c31bad750034aaafd85e9e9c405e0df0480, 61f2145a9f9088ab832889e69e1c0e12cd09ed94 wpt-pr: 57004 Diffstat:
15 files changed, 129 insertions(+), 83 deletions(-)
diff --git a/testing/web-platform/tests/.azure-pipelines.yml b/testing/web-platform/tests/.azure-pipelines.yml @@ -37,7 +37,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/checkout.yml - script: | set -eux -o pipefail @@ -65,8 +65,8 @@ jobs: directory: tools/ toxenv: py38 -- job: tools_unittest_mac_py313 - displayName: 'tools/ unittests: macOS + Python 3.13' +- job: tools_unittest_mac_py314 + displayName: 'tools/ unittests: macOS + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.tools_unittest'] pool: @@ -74,12 +74,12 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/ - toxenv: py313 + toxenv: py314 - job: wptrunner_unittest_mac_py38 displayName: 'tools/wptrunner/ unittests: macOS + Python 3.8' @@ -97,8 +97,8 @@ jobs: directory: tools/wptrunner/ toxenv: py38 -- job: wptrunner_unittest_mac_py313 - displayName: 'tools/wptrunner/ unittests: macOS + Python 3.13' +- job: wptrunner_unittest_mac_py314 + displayName: 'tools/wptrunner/ unittests: macOS + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_unittest'] pool: @@ -106,12 +106,12 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wptrunner/ - toxenv: py313 + toxenv: py314 - job: wpt_integration_mac_py38 displayName: 'tools/wpt/ tests: macOS + Python 3.8' @@ -131,8 +131,8 @@ jobs: directory: tools/wpt/ toxenv: py38 -- job: wpt_integration_mac_py313 - displayName: 'tools/wpt/ tests: macOS + Python 3.13' +- job: wpt_integration_mac_py314 + displayName: 'tools/wpt/ tests: macOS + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wpt_integration'] pool: @@ -141,13 +141,13 @@ jobs: # full checkout required - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wpt/ - toxenv: py313 + toxenv: py314 - job: tools_unittest_win_py38 displayName: 'tools/ unittests: Windows + Python 3.8' @@ -168,8 +168,8 @@ jobs: directory: tools/ toxenv: py38 -- job: tools_unittest_win_py313 - displayName: 'tools/ unittests: Windows + Python 3.13' +- job: tools_unittest_win_py314 + displayName: 'tools/ unittests: Windows + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.tools_unittest'] pool: @@ -177,13 +177,13 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' addToPath: false - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/ - toxenv: py313 + toxenv: py314 - job: wptrunner_unittest_win_py38 displayName: 'tools/wptrunner/ unittests: Windows + Python 3.8' @@ -202,8 +202,8 @@ jobs: directory: tools/wptrunner/ toxenv: py38 -- job: wptrunner_unittest_win_py313 - displayName: 'tools/wptrunner/ unittests: Windows + Python 3.13' +- job: wptrunner_unittest_win_py314 + displayName: 'tools/wptrunner/ unittests: Windows + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wptrunner_unittest'] pool: @@ -211,13 +211,13 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' addToPath: false - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wptrunner/ - toxenv: py313 + toxenv: py314 - job: wpt_integration_win_py38 displayName: 'tools/wpt/ tests: Windows + Python 3.8' @@ -237,8 +237,8 @@ jobs: directory: tools/wpt/ toxenv: py38 -- job: wpt_integration_win_py313 - displayName: 'tools/wpt/ tests: Windows + Python 3.13' +- job: wpt_integration_win_py314 + displayName: 'tools/wpt/ tests: Windows + Python 3.14' dependsOn: decision condition: dependencies.decision.outputs['test_jobs.wpt_integration'] pool: @@ -247,13 +247,13 @@ jobs: # full checkout required - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - template: tools/ci/azure/tox_pytest.yml parameters: directory: tools/wpt/ - toxenv: py313 + toxenv: py314 - job: results_edge_stable displayName: 'all tests: Edge Stable' @@ -269,7 +269,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/system_info.yml - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/install_certs.yml @@ -305,7 +305,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/system_info.yml - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/install_certs.yml @@ -342,7 +342,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/install_certs.yml - template: tools/ci/azure/install_edge.yml @@ -376,7 +376,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.13' + versionSpec: '3.14' - template: tools/ci/azure/checkout.yml - template: tools/ci/azure/install_certs.yml - template: tools/ci/azure/color_profile.yml diff --git a/testing/web-platform/tests/tools/ci/tc/tasks/test.yml b/testing/web-platform/tests/tools/ci/tc/tasks/test.yml @@ -127,14 +127,14 @@ components: - python3.8-dev - python3.8-venv - tox-python3_13: + tox-python3_14: env: - TOXENV: py313 + TOXENV: py314 PY_COLORS: "0" install: - - python3.13 - - python3.13-dev - - python3.13-venv + - python3.14 + - python3.14-dev + - python3.14-venv tests-affected: options: browser: @@ -588,13 +588,13 @@ tasks: run-job: - tools_unittest - - tools/ unittests (Python 3.13): + - tools/ unittests (Python 3.14): description: >- - Unit tests for tools running under Python 3.13, excluding wptrunner + Unit tests for tools running under Python 3.14, excluding wptrunner use: - wpt-base - trigger-pr - - tox-python3_13 + - tox-python3_14 command: ./tools/ci/ci_tools_unittest.sh env: HYPOTHESIS_PROFILE: ci @@ -624,13 +624,13 @@ tasks: run-job: - wpt_integration - - tools/ integration tests (Python 3.13): + - tools/ integration tests (Python 3.14): description: >- - Integration tests for tools running under Python 3.13 + Integration tests for tools running under Python 3.14 use: - wpt-base - trigger-pr - - tox-python3_13 + - tox-python3_14 command: ./tools/ci/ci_tools_integration_test.sh install: - libnss3-tools @@ -665,13 +665,13 @@ tasks: run-job: - resources_unittest - - resources/ tests (Python 3.13): + - resources/ tests (Python 3.14): description: >- - Tests for testharness.js and other files in resources/ under Python 3.13 + Tests for testharness.js and other files in resources/ under Python 3.14 use: - wpt-base - trigger-pr - - tox-python3_13 + - tox-python3_14 command: ./tools/ci/ci_resources_unittest.sh install: - libnss3-tools diff --git a/testing/web-platform/tests/tools/ci/tc/tests/test_valid.py b/testing/web-platform/tests/tools/ci/tc/tests/test_valid.py @@ -1,4 +1,5 @@ # mypy: ignore-errors +from urllib.parse import urlsplit import json import os @@ -9,6 +10,8 @@ import jsone import pytest import yaml from jsonschema import validate +from referencing import Resource +from referencing.jsonschema import DRAFT6, SchemaRegistry from tools.ci.tc import decision @@ -86,13 +89,24 @@ def test_verify_payload(): """Verify that the decision task produces tasks with a valid payload""" from tools.ci.tc.decision import decide - r = httpx.get("https://community-tc.services.mozilla.com/schemas/queue/v1/create-task-request.json") - r.raise_for_status() - create_task_schema = r.json() + schema_urls = ["https://community-tc.services.mozilla.com/schemas/common/metaschema.json", + "https://community-tc.services.mozilla.com/schemas/queue/v1/task-metadata.json", + "https://community-tc.services.mozilla.com/schemas/queue/v1/task.json", + "https://community-tc.services.mozilla.com/schemas/queue/v1/create-task-request.json", + "https://community-tc.services.mozilla.com/references/schemas/docker-worker/v1/payload.json"] - r = httpx.get("https://community-tc.services.mozilla.com/references/schemas/docker-worker/v1/payload.json") - r.raise_for_status() - payload_schema = r.json() + schemas = {} + for schema_url in schema_urls: + name = urlsplit(schema_url).path.rsplit("/", 1)[-1].rsplit(".", 1)[0] + r = httpx.get(schema_url) + r.raise_for_status() + schemas[name] = (schema_url, r.json()) + + + registry = SchemaRegistry() + for url, schema_doc in schemas.values(): + resource = Resource.from_contents(schema_doc, default_specification=DRAFT6) + registry = registry.with_resource(url, resource) jobs = ["lint", "manifest_upload", @@ -111,8 +125,12 @@ def test_verify_payload(): task_id_map = decide(event) for name, (task_id, task_data) in task_id_map.items(): try: - validate(instance=task_data, schema=create_task_schema) - validate(instance=task_data["payload"], schema=payload_schema) + validate(instance=task_data, + schema=schemas["create-task-request"][1], + registry=registry) + validate(instance=task_data["payload"], + schema=schemas["payload"][1], + registry=registry) except Exception as e: print(f"Validation failed for task '{name}':\n{json.dumps(task_data, indent=2)}") raise e @@ -177,11 +195,11 @@ def test_verify_payload(): ("pr_event.json", True, {".taskcluster.yml", ".travis.yml", "tools/ci/start.sh"}, ['lint', 'tools/ unittests (Python 3.8)', - 'tools/ unittests (Python 3.13)', + 'tools/ unittests (Python 3.14)', 'tools/ integration tests (Python 3.8)', - 'tools/ integration tests (Python 3.13)', + 'tools/ integration tests (Python 3.14)', 'resources/ tests (Python 3.8)', - 'resources/ tests (Python 3.13)', + 'resources/ tests (Python 3.14)', 'download-firefox-nightly', 'infrastructure/ tests (firefox)', 'infrastructure/ tests (chrome)', @@ -201,7 +219,7 @@ def test_verify_payload(): ("pr_event_tests_affected.json", True, {"resources/testharness.js"}, ['lint', 'resources/ tests (Python 3.8)', - 'resources/ tests (Python 3.13)', + 'resources/ tests (Python 3.14)', 'download-firefox-nightly', 'infrastructure/ tests (firefox)', 'infrastructure/ tests (chrome)', diff --git a/testing/web-platform/tests/tools/manifest/requirements.txt b/testing/web-platform/tests/tools/manifest/requirements.txt @@ -1 +1,2 @@ -zstandard==0.23.0 +zstandard==0.23.0; python_version < '3.9' +zstandard==0.25.0; python_version >= '3.9' diff --git a/testing/web-platform/tests/tools/requirements_mypy.txt b/testing/web-platform/tests/tools/requirements_mypy.txt @@ -4,7 +4,8 @@ types-atomicwrites==1.4.5.1 types-html5lib==1.1.11.20241018 types-setuptools==75.8.0.20250110 types-ujson==5.10.0.20240515 -typing_extensions==4.12.2 +typing_extensions==4.15.0; python_version >= '3.9' +typing_extensions==4.13.2; python_version < '3.9' # Install dependencies so we get type signatures from them. -r ci/requirements_build.txt diff --git a/testing/web-platform/tests/tools/requirements_tests.txt b/testing/web-platform/tests/tools/requirements_tests.txt @@ -1,6 +1,7 @@ httpx[http2]==0.27.2 json-e==4.7.0 -jsonschema==4.17.3 +jsonschema==4.25.1; python_version >= '3.9' +jsonschema==4.23.0; python_version < '3.9' pyyaml==6.0.1 types-pyyaml==6.0.12.20241230 taskcluster==91.0.1 diff --git a/testing/web-platform/tests/tools/tox.ini b/testing/web-platform/tests/tools/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38,py39,py310,py311,py312,py313,{py38,py39,py310,py311,py312,py313}-{flake8,mypy} +envlist = py38,py39,py310,py311,py312,py313,py314,{py38,py39,py310,py311,py312,py313,py314}-{flake8,mypy} skipsdist=True skip_missing_interpreters=False diff --git a/testing/web-platform/tests/tools/webtransport/h3/webtransport_h3_server.py b/testing/web-platform/tests/tools/webtransport/h3/webtransport_h3_server.py @@ -601,7 +601,10 @@ def server_is_running(host: str, port: int, timeout: float) -> bool: Check the WebTransport over HTTP/3 server is running at the given `host` and `port`. """ - loop = asyncio.get_event_loop() + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() return loop.run_until_complete(_connect_server_with_timeout(host, port, timeout)) diff --git a/testing/web-platform/tests/tools/wpt/requirements_metadata.txt b/testing/web-platform/tests/tools/wpt/requirements_metadata.txt @@ -1 +1,2 @@ -pydantic==2.10.6 +pydantic==2.10.6; python_version < '3.9' +pydantic==2.12.5; python_version >= '3.9' diff --git a/testing/web-platform/tests/tools/wpt/tox.ini b/testing/web-platform/tests/tools/wpt/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38,py39,py310,py311,py312,py312 +envlist = py38,py39,py310,py311,py312,py313,py314 skipsdist=True skip_missing_interpreters = False diff --git a/testing/web-platform/tests/tools/wptrunner/tox.ini b/testing/web-platform/tests/tools/wptrunner/tox.ini @@ -2,7 +2,7 @@ xfail_strict=true [tox] -envlist = py313-{base,chrome,firefox,opera,safari,sauce,servo,webkit,webkitgtk_minibrowser,epiphany},{py38,py39,py310,py311,py312}-base +envlist = py314-{base,chrome,firefox,opera,safari,sauce,servo,webkit,webkitgtk_minibrowser,epiphany},{py38,py39,py310,py311,py312,py313}-base skip_missing_interpreters = False [testenv] diff --git a/testing/web-platform/tests/tools/wptserve/tests/functional/test_handlers.py b/testing/web-platform/tests/tools/wptserve/tests/functional/test_handlers.py @@ -88,13 +88,20 @@ class TestFileHandler(TestUsingServer): def test_range_invalid(self): with self.assertRaises(HTTPError) as cm: self.request("/document.txt", headers={"Range":"bytes=11-10"}) - self.assertEqual(cm.exception.code, 416) + + with cm.exception as exc: + # Ensure we read the response + exc.read() + self.assertEqual(exc.code, 416) with open(os.path.join(doc_root, "document.txt"), 'rb') as f: expected = f.read() with self.assertRaises(HTTPError) as cm: self.request("/document.txt", headers={"Range":"bytes=%i-%i" % (len(expected), len(expected) + 10)}) - self.assertEqual(cm.exception.code, 416) + with cm.exception as exc: + # Ensure we read the response + exc.read() + self.assertEqual(exc.code, 416) def test_sub_config(self): resp = self.request("/sub.sub.txt") @@ -136,8 +143,10 @@ class TestFunctionHandler(TestUsingServer): with pytest.raises(HTTPError) as cm: self.request(route[1]) - assert cm.value.code == 500 - del cm + with cm.value as exc: + # Ensure we read the response + exc.read() + assert exc.code == 500 def test_tuple_2_rv(self): @wptserve.handlers.handler @@ -188,8 +197,10 @@ class TestFunctionHandler(TestUsingServer): with pytest.raises(HTTPError) as cm: self.request(route[1]) - assert cm.value.code == 500 - del cm + with cm.value as exc: + # Ensure we read the response + exc.read() + assert exc.code == 500 def test_none_rv(self): @wptserve.handlers.handler @@ -290,22 +301,28 @@ class TestPythonHandler(TestUsingServer): with pytest.raises(HTTPError) as cm: self.request("/no_main.py") - assert cm.value.code == 500 - del cm + # Ensure we read the response + with cm.value as exc: + exc.read() + assert exc.code == 500 def test_invalid(self): with pytest.raises(HTTPError) as cm: self.request("/invalid.py") - assert cm.value.code == 500 - del cm + with cm.value as exc: + # Ensure we read the response + exc.read() + assert exc.code == 500 def test_missing(self): with pytest.raises(HTTPError) as cm: self.request("/missing.py") - assert cm.value.code == 404 - del cm + with cm.value as exc: + # Ensure we read the response + exc.read() + assert exc.code == 404 class TestDirectoryHandler(TestUsingServer): @@ -342,8 +359,10 @@ class TestAsIsHandler(TestUsingServer): with pytest.raises(HTTPError) as cm: self.request("/subdir") - assert cm.value.code == 500 - del cm + with cm.value as exc: + # Ensure we read the response + exc.read() + assert exc.code == 500 class TestH2Handler(TestUsingH2Server): diff --git a/testing/web-platform/tests/tools/wptserve/tests/functional/test_pipes.py b/testing/web-platform/tests/tools/wptserve/tests/functional/test_pipes.py @@ -86,8 +86,9 @@ sha512: r8eLGRTc7ZznZkFjeVLyo6/FyQdra9qmlYCwKKxm3kfQAswRS9+3HsYk3thLUhcFmmWhK4dX self.assertEqual(resp.read().rstrip(), expected.strip()) def test_sub_file_hash_unrecognized(self): - with self.assertRaises(urllib.error.HTTPError): + with self.assertRaises(urllib.error.HTTPError) as cm: self.request("/sub_file_hash_unrecognized.sub.txt") + cm.exception.close() def test_sub_headers(self): resp = self.request("/sub_headers.txt", query="pipe=sub", headers={"X-Test": "PASS"}) diff --git a/testing/web-platform/tests/tools/wptserve/tests/functional/test_server.py b/testing/web-platform/tests/tools/wptserve/tests/functional/test_server.py @@ -16,8 +16,8 @@ class TestFileHandler(TestUsingServer): def test_not_handled(self): with self.assertRaises(HTTPError) as cm: self.request("/not_existing") - - self.assertEqual(cm.exception.code, 404) + with cm.exception as exc: + self.assertEqual(exc.code, 404) class TestRewriter(TestUsingServer): @@ -45,7 +45,8 @@ class TestRequestHandler(TestUsingServer): with self.assertRaises(HTTPError) as cm: self.request("/test/raises") - self.assertEqual(cm.exception.code, 500) + with cm.exception as exc: + self.assertEqual(exc.code, 500) def test_many_headers(self): headers = {"X-Val%d" % i: str(i) for i in range(256)} diff --git a/testing/web-platform/tests/tools/wptserve/tests/test_stash.py b/testing/web-platform/tests/tools/wptserve/tests/test_stash.py @@ -66,7 +66,7 @@ class SlowLock(BaseManager): @pytest.mark.xfail(sys.platform == "win32" or - multiprocessing.get_start_method() == "spawn", + multiprocessing.get_start_method() != "fork", reason="https://github.com/web-platform-tests/wpt/issues/16938") def test_delayed_lock(add_cleanup): """Ensure that delays in proxied Lock retrieval do not interfere with @@ -114,7 +114,7 @@ class SlowDict(BaseManager): @pytest.mark.xfail(sys.platform == "win32" or - multiprocessing.get_start_method() == "spawn", + multiprocessing.get_start_method() != "fork", reason="https://github.com/web-platform-tests/wpt/issues/16938") def test_delayed_dict(add_cleanup): """Ensure that delays in proxied `dict` retrieval do not interfere with