diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 11e425dac4fa9f..23887751ef71ce 100644 --- a/.azure-pipelines.yml +++ b/.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/tools/ci/tc/tasks/test.yml b/tools/ci/tc/tasks/test.yml index 56349667d9f7e4..6fe02c9abc42fb 100644 --- a/tools/ci/tc/tasks/test.yml +++ b/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/tools/ci/tc/tests/test_valid.py b/tools/ci/tc/tests/test_valid.py index 4a12ff8927c34d..2510082c1fc732 100644 --- a/tools/ci/tc/tests/test_valid.py +++ b/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 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/tools/manifest/requirements.txt b/tools/manifest/requirements.txt index ca872b12c41955..faa0ea2bfb9940 100644 --- a/tools/manifest/requirements.txt +++ b/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/tools/requirements_mypy.txt b/tools/requirements_mypy.txt index a846a21ac4f4ef..9da309f8da402b 100644 --- a/tools/requirements_mypy.txt +++ b/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/tools/requirements_tests.txt b/tools/requirements_tests.txt index e966b94f0f2163..0e8eb6197d7aaa 100644 --- a/tools/requirements_tests.txt +++ b/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/tools/tox.ini b/tools/tox.ini index 8fd54b68939945..e320b5a8f5c132 100644 --- a/tools/tox.ini +++ b/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/tools/webtransport/h3/webtransport_h3_server.py b/tools/webtransport/h3/webtransport_h3_server.py index eca779a3f58995..a6e1d5d06c8987 100644 --- a/tools/webtransport/h3/webtransport_h3_server.py +++ b/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/tools/wpt/requirements_metadata.txt b/tools/wpt/requirements_metadata.txt index 3052e99b4fcef9..aaf469b5f5c902 100644 --- a/tools/wpt/requirements_metadata.txt +++ b/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/tools/wpt/tox.ini b/tools/wpt/tox.ini index 7c8ab17890f452..5b15e2330a5479 100644 --- a/tools/wpt/tox.ini +++ b/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/tools/wptrunner/tox.ini b/tools/wptrunner/tox.ini index 44fe708c86ecd6..bacbee58d7c194 100644 --- a/tools/wptrunner/tox.ini +++ b/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/tools/wptserve/tests/functional/test_handlers.py b/tools/wptserve/tests/functional/test_handlers.py index 91b05f4fe46eda..56893ef4515c69 100644 --- a/tools/wptserve/tests/functional/test_handlers.py +++ b/tools/wptserve/tests/functional/test_handlers.py @@ -88,13 +88,20 @@ def test_multiple_ranges(self): 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 @@ def handler(request, response): 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 @@ def handler(request, response): 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 @@ def test_no_main(self): 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 @@ def test_directory_fails(self): 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/tools/wptserve/tests/functional/test_pipes.py b/tools/wptserve/tests/functional/test_pipes.py index e868b6121016f5..dcade73ea81d39 100644 --- a/tools/wptserve/tests/functional/test_pipes.py +++ b/tools/wptserve/tests/functional/test_pipes.py @@ -86,8 +86,9 @@ def test_sub_file_hash(self): 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/tools/wptserve/tests/functional/test_server.py b/tools/wptserve/tests/functional/test_server.py index 39ef5889bea818..2dd8f8da1e469b 100644 --- a/tools/wptserve/tests/functional/test_server.py +++ b/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 @@ def handler(request, response): 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/tools/wptserve/tests/test_stash.py b/tools/wptserve/tests/test_stash.py index 7806dc1c9b2f0a..c6966eeaaf6a06 100644 --- a/tools/wptserve/tests/test_stash.py +++ b/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