diff --git a/taskcluster/config.yml b/taskcluster/config.yml index 4e47debc8..d9d32aac2 100644 --- a/taskcluster/config.yml +++ b/taskcluster/config.yml @@ -38,3 +38,8 @@ workers: implementation: docker-worker os: linux worker-type: linux-docker + t-windows: + provisioner: 'taskgraph-t' + implementation: generic-worker + os: windows + worker-type: win11-64-24h2 diff --git a/taskcluster/kinds/test/kind.yml b/taskcluster/kinds/test/kind.yml index a0833dbda..95adbfc76 100644 --- a/taskcluster/kinds/test/kind.yml +++ b/taskcluster/kinds/test/kind.yml @@ -18,18 +18,14 @@ task-defaults: artifact_prefix: public retrigger: true code-review: true - worker-type: t-linux worker: - docker-image: {in-tree: python} max-run-time: 1800 env: - LC_ALL: "C" PYTHONUTF8: "0" PYTHONCOERCECLOCALE: "0" HGENCODING: "utf-8" treeherder: kind: test - platform: test/opt tier: 1 run: using: run-task @@ -37,23 +33,44 @@ task-defaults: use-caches: [checkout, uv] tasks: - unit: - description: "Run unit tests with py{matrix[python]}" + unit-linux: + description: "Run unit tests with py{matrix[python]} on Linux" matrix: set-name: "unit-py{matrix[python]}" substitution-fields: [description, run.command, treeherder, worker, attributes] python: ["314t", "314", "313", "312", "311", "310", "39"] attributes: python: "{matrix[python]}" + platform: linux + worker-type: t-linux worker: + docker-image: {in-tree: python} artifacts: - type: file path: "/builds/worker/artifacts/coverage" name: "public/coverage.py{matrix[python]}" env: + LC_ALL: "C" UV_PYTHON: "{matrix[python]}" treeherder: + platform: test-linux/opt symbol: unit(py{matrix[python]}) run: command: >- uv run coverage run --data-file /builds/worker/artifacts/coverage --context=py{matrix[python]} -m pytest -vv + + unit-win: + description: "Run unit tests with python on Windows" + attributes: + platform: windows + worker-type: t-windows + worker: + taskcluster-proxy: true + treeherder: + platform: test-win/opt + symbol: unit(py) + run: + command: >- + python3 -V && + powershell -ExecutionPolicy ByPass -c 'irm https://astral.sh/uv/install.ps1 | iex' && + ~/.local/bin/uv.exe run -m pytest -vv diff --git a/test/conftest.py b/test/conftest.py index 3e0714e0b..0a3dcb693 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,4 +1,5 @@ import os +import platform from pathlib import Path import pytest @@ -16,6 +17,12 @@ # no way to ignore ~/.gitconfig other than overriding $HOME, which is overkill. os.environ["GIT_CONFIG_NOSYSTEM"] = "1" +# Some of the tests marked with this may be fixable on Windows; we should +# look into these in more depth at some point. +nowin = pytest.mark.skipif( + platform.system() == "Windows", reason="test currently broken on Windows" +) + @pytest.fixture def responses(): diff --git a/test/test_config.py b/test/test_config.py index 4392e35ba..9a2e941a0 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -10,6 +10,8 @@ from taskgraph.config import GraphConfig +from .conftest import nowin + def test_graph_config_basic(): graph_config = GraphConfig({"foo": "bar"}, "root") @@ -50,6 +52,7 @@ def test_vcs_root_with_non_standard_dir(): assert vcs_root == expected_path +@nowin def test_vcs_root_fallback(mocker): mocker.patch("os.getcwd", return_value="/path/to/repo") diff --git a/test/test_docker.py b/test/test_docker.py index 2e4003c45..c75957bbf 100644 --- a/test/test_docker.py +++ b/test/test_docker.py @@ -10,6 +10,8 @@ from taskgraph.transforms.docker_image import IMAGE_BUILDER_IMAGE from taskgraph.util.vcs import get_repository +from .conftest import nowin + @pytest.fixture def root_url(): @@ -112,6 +114,7 @@ def test_load_task_invalid_task(run_load_task): assert run_load_task(task)[0] == 1 +@nowin def test_load_task(run_load_task): image_task_id = "def" task = { @@ -172,6 +175,7 @@ def test_load_task(run_load_task): assert exp == actual[i] +@nowin def test_load_task_env_init_and_remove(mocker, run_load_task): # Mock NamedTemporaryFile to capture what's written to it mock_envfile = mocker.MagicMock() @@ -252,6 +256,7 @@ def test_load_task_env_init_and_remove(mocker, run_load_task): assert actual[6:8] == ["-v", "/tmp/test_initfile:/builds/worker/.bashrc"] +@nowin @pytest.mark.parametrize( "image", [ @@ -292,6 +297,7 @@ def test_load_task_with_different_image_types( mocks["load_image_by_task_id"].assert_called_once_with(image_task_id) +@nowin def test_load_task_with_local_image( mocker, run_load_task, @@ -322,6 +328,7 @@ def test_load_task_with_local_image( assert mocks["build_image"].call_args[0][1] == "hello-world" +@nowin def test_load_task_with_unsupported_image_type(caplog, run_load_task): caplog.set_level(logging.DEBUG) task = { @@ -343,6 +350,7 @@ def test_load_task_with_unsupported_image_type(caplog, run_load_task): assert "Tasks with unsupported-type images are not supported!" in caplog.text +@nowin def test_load_task_with_task_definition(run_load_task, caplog): # Test passing a task definition directly instead of a task ID caplog.set_level(logging.INFO) @@ -372,6 +380,7 @@ def test_load_task_with_task_definition(run_load_task, caplog): assert "Loading 'test-task-direct' from provided definition" in caplog.text +@nowin def test_load_task_with_interactive_false(run_load_task): # Test non-interactive mode that doesn't require run-task # Task that doesn't use run-task (would fail in interactive mode) @@ -427,6 +436,7 @@ def task(): } +@nowin def test_load_task_with_custom_image_in_tree(run_load_task, task): image = "hello-world" ret, mocks = run_load_task(task, custom_image=image) @@ -453,6 +463,7 @@ def test_load_task_with_custom_image_in_tree(run_load_task, task): assert tag == f"taskcluster/{image}:latest" +@nowin def test_load_task_with_custom_image_task_id(run_load_task, task): image = "task-id=abc" ret, mocks = run_load_task(task, custom_image=image) @@ -460,6 +471,7 @@ def test_load_task_with_custom_image_task_id(run_load_task, task): mocks["load_image_by_task_id"].assert_called_once_with("abc") +@nowin def test_load_task_with_custom_image_index(mocker, run_load_task, task): image = "index=abc" mocker.patch.object(docker, "find_task_id", return_value="abc") @@ -468,6 +480,7 @@ def test_load_task_with_custom_image_index(mocker, run_load_task, task): mocks["load_image_by_task_id"].assert_called_once_with("abc") +@nowin def test_load_task_with_custom_image_registry(mocker, run_load_task, task): image = "ubuntu:latest" ret, mocks = run_load_task(task, custom_image=image) @@ -476,6 +489,7 @@ def test_load_task_with_custom_image_registry(mocker, run_load_task, task): assert not mocks["build_image"].called +@nowin def test_load_task_with_develop(mocker, run_load_task, task): repo_name = "foo" repo_path = "/workdir/vcs" @@ -623,6 +637,7 @@ def mock_path_constructor(path_arg): return inner +@nowin def test_build_image(run_build_image): # Test building image without save_image result, mocks = run_build_image("hello-world") @@ -654,6 +669,7 @@ def test_build_image(run_build_image): assert result == "hello-world:latest" +@nowin def test_build_image_with_parent(mocker, responses, root_url, run_build_image): parent_task_id = "abc" responses.get(f"{root_url}/api/queue/v1/task/{parent_task_id}/status") @@ -687,6 +703,7 @@ def test_build_image_with_parent(mocker, responses, root_url, run_build_image): assert docker_load_args[:3] == ["docker", "load", "-i"] +@nowin def test_build_image_with_parent_not_found( mocker, responses, root_url, run_build_image ): @@ -725,6 +742,7 @@ def test_build_image_with_parent_not_found( assert docker_load_args[:3] == ["docker", "load", "-i"] +@nowin def test_build_image_with_save_image(run_build_image): save_path = "/path/to/save.tar" @@ -741,6 +759,7 @@ def test_build_image_with_save_image(run_build_image): assert save_path in str(result) +@nowin def test_build_image_context_only(run_build_image): context_path = "/path/to/context.tar" diff --git a/test/test_scripts_run_task.py b/test/test_scripts_run_task.py index 933e7b2e3..83fd4e7b7 100644 --- a/test/test_scripts_run_task.py +++ b/test/test_scripts_run_task.py @@ -15,6 +15,8 @@ import taskgraph from taskgraph.util.caches import CACHES +from .conftest import nowin + @pytest.fixture(scope="module") def run_task_mod(): @@ -617,6 +619,7 @@ def inner(extra_args=None, env=None): return inner +@nowin def test_main_abspath_environment(mocker, run_main): envvars = ["GECKO_PATH", "MOZ_FETCHES_DIR", "UPLOAD_DIR"] envvars += [cache["env"] for cache in CACHES.values() if "env" in cache] diff --git a/test/test_transforms_run_toolchain.py b/test/test_transforms_run_toolchain.py index 3ec843dff..46554ea00 100644 --- a/test/test_transforms_run_toolchain.py +++ b/test/test_transforms_run_toolchain.py @@ -11,6 +11,8 @@ from taskgraph.util.templates import merge from taskgraph.util.vcs import GitRepository +from .conftest import nowin + TASK_DEFAULTS = { "description": "fake description", "label": "fake-task-label", @@ -205,6 +207,7 @@ def assert_relative_script(task, taskdesc): assert_docker_worker(task, taskdesc) +@nowin @pytest.mark.parametrize( "task", ( diff --git a/test/test_util_archive.py b/test/test_util_archive.py index ee256214b..6b1998a2e 100644 --- a/test/test_util_archive.py +++ b/test/test_util_archive.py @@ -15,6 +15,8 @@ create_tar_gz_from_files, ) +from .conftest import nowin + MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH @@ -72,6 +74,7 @@ def test_dirs_refused(tmp_path): create_tar_from_files(fh, {"test": str(tmp_path)}) +@nowin def test_setuid_setgid_refused(tmp_path): uid = tmp_path / "setuid" uid.touch() @@ -89,6 +92,7 @@ def test_setuid_setgid_refused(tmp_path): create_tar_from_files(fh, {"test": str(gid)}) +@nowin def test_create_tar_basic(tmp_path, create_files): files = create_files() @@ -103,6 +107,7 @@ def test_create_tar_basic(tmp_path, create_files): verify_basic_tarfile(tf) +@nowin def test_executable_preserved(tmp_path): p = tmp_path / "exec" p.write_bytes(b"#!/bin/bash\n") @@ -129,6 +134,7 @@ def test_executable_preserved(tmp_path): assert extracted_content == b"#!/bin/bash\n" +@nowin def test_create_tar_gz_basic(tmp_path, create_files): gp = tmp_path / "test.tar.gz" with open(gp, "wb") as fh: @@ -156,6 +162,7 @@ def test_create_tar_gz_basic(tmp_path, create_files): verify_basic_tarfile(tf) +@nowin def test_tar_gz_name(tmp_path, create_files): gp = tmp_path / "test.tar.gz" with open(gp, "wb") as fh: diff --git a/test/test_util_docker.py b/test/test_util_docker.py index f140b1668..98ad607cd 100644 --- a/test/test_util_docker.py +++ b/test/test_util_docker.py @@ -17,6 +17,7 @@ from taskgraph.config import GraphConfig from taskgraph.util import docker +from .conftest import nowin from .mockedopen import MockedOpen MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH @@ -24,6 +25,7 @@ @mock.patch.dict("os.environ", {"TASKCLUSTER_ROOT_URL": liburls.test_root_url()}) class TestDocker(unittest.TestCase): + @nowin def test_generate_context_hash(self): tmpdir = tempfile.mkdtemp() try: @@ -45,6 +47,7 @@ def test_generate_context_hash(self): finally: shutil.rmtree(tmpdir) + @nowin def test_docker_image_explicit_registry(self): files = {} files[f"{docker.IMAGE_DIR}/myimage/REGISTRY"] = "cool-images" @@ -55,6 +58,7 @@ def test_docker_image_explicit_registry(self): docker.docker_image("myimage"), "cool-images/myimage@sha256:434..." ) + @nowin def test_docker_image_explicit_registry_by_tag(self): files = {} files[f"{docker.IMAGE_DIR}/myimage/REGISTRY"] = "myreg" @@ -65,6 +69,7 @@ def test_docker_image_explicit_registry_by_tag(self): docker.docker_image("myimage", by_tag=True), "myreg/myimage:1.2.3" ) + @nowin def test_docker_image_default_registry(self): files = {} files[f"{docker.IMAGE_DIR}/REGISTRY"] = "mozilla" @@ -75,6 +80,7 @@ def test_docker_image_default_registry(self): docker.docker_image("myimage"), "mozilla/myimage@sha256:434..." ) + @nowin def test_docker_image_default_registry_by_tag(self): files = {} files[f"{docker.IMAGE_DIR}/REGISTRY"] = "mozilla" @@ -85,6 +91,7 @@ def test_docker_image_default_registry_by_tag(self): docker.docker_image("myimage", by_tag=True), "mozilla/myimage:1.2.3" ) + @nowin def test_create_context_tar_basic(self): tmp = tempfile.mkdtemp() try: @@ -116,6 +123,7 @@ def test_create_context_tar_basic(self): finally: shutil.rmtree(tmp) + @nowin def test_create_context_topsrcdir_files(self): tmp = tempfile.mkdtemp() try: @@ -148,6 +156,7 @@ def test_create_context_topsrcdir_files(self): finally: shutil.rmtree(tmp) + @nowin def test_create_context_absolute_path(self): tmp = tempfile.mkdtemp() try: @@ -163,6 +172,7 @@ def test_create_context_absolute_path(self): finally: shutil.rmtree(tmp) + @nowin def test_create_context_outside_topsrcdir(self): tmp = tempfile.mkdtemp() try: @@ -177,6 +187,7 @@ def test_create_context_outside_topsrcdir(self): finally: shutil.rmtree(tmp) + @nowin def test_create_context_missing_extra(self): tmp = tempfile.mkdtemp() try: @@ -191,6 +202,7 @@ def test_create_context_missing_extra(self): finally: shutil.rmtree(tmp) + @nowin def test_create_context_extra_directory(self): tmp = tempfile.mkdtemp() try: @@ -235,6 +247,7 @@ def test_create_context_extra_directory(self): finally: shutil.rmtree(tmp) + @nowin def test_stream_context_tar(self): tmp = tempfile.mkdtemp() try: @@ -271,6 +284,7 @@ def test_stream_context_tar(self): finally: shutil.rmtree(tmp) + @nowin def test_image_paths_with_custom_kind(self): """Test image_paths function with graph_config parameter.""" temp_dir = tempfile.mkdtemp() @@ -309,6 +323,7 @@ def test_image_paths_with_custom_kind(self): finally: shutil.rmtree(temp_dir) + @nowin def test_parse_volumes_with_graph_config(self): """Test parse_volumes function with graph_config parameter.""" temp_dir = tempfile.mkdtemp() diff --git a/test/test_util_vcs.py b/test/test_util_vcs.py index 2e2327342..e68821bce 100644 --- a/test/test_util_vcs.py +++ b/test/test_util_vcs.py @@ -11,6 +11,8 @@ from taskgraph.util.vcs import HgRepository, Repository, get_repository +from .conftest import nowin + def test_get_repository(repo): r = get_repository(repo.path) @@ -215,6 +217,7 @@ def assert_files(actual, expected): assert set(actual) == set(expected) +@nowin def test_get_tracked_files(repo): assert_files(repo.get_tracked_files(), ["first_file"]) @@ -370,6 +373,7 @@ def test_get_changed_files_two_revisions(repo): ) +@nowin def test_workdir_outgoing(repo_with_upstream): repo, upstream_location = repo_with_upstream