Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ jobs:
run: uv run poe test -A --junitxml=pytest.xml
working-directory: ./python

- name: Run workspace script routing tests
run: uv run --locked --group dev --group tool-runtime python -m pytest scripts/tests --import-mode=importlib -q
working-directory: ./python

# Surface failing tests
- name: Surface failing tests
if: always()
Expand Down
13 changes: 7 additions & 6 deletions python/scripts/run_tasks_in_changed_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path

from rich import print

from scripts.task_runner import build_work_items, discover_projects, run_tasks

# Tasks that need to run in all packages when core changes (type info propagates)
Expand All @@ -24,15 +25,15 @@ def get_changed_packages(
core_package_changed = False

for file_path in changed_files:
# Strip 'python/' prefix if present (when git diff is run from repo root)
file_path_str = str(file_path)
if file_path_str.startswith("python/"):
file_path_str = file_path_str[7:] # Remove 'python/' prefix
# Strip the repository prefix using native separators, preserving POSIX filenames.
file_path_obj = Path(file_path)
if file_path_obj.parts and file_path_obj.parts[0] == "python":
file_path_obj = Path(*file_path_obj.parts[1:])

# Convert to absolute path if relative
abs_path = Path(file_path_str)
abs_path = file_path_obj
if not abs_path.is_absolute():
abs_path = workspace_root / file_path_str
abs_path = workspace_root / file_path_obj

# Check which package this file belongs to
for project in projects:
Expand Down
116 changes: 116 additions & 0 deletions python/scripts/tests/test_changed_package_native_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Copyright (c) Microsoft. All rights reserved.

"""Exercise native changed-package paths and task routing."""

from __future__ import annotations

import os
from pathlib import Path
from unittest.mock import Mock

import pytest

from scripts import run_tasks_in_changed_packages as runner

PROJECTS = [Path("packages/core"), Path("packages/redis")]


@pytest.fixture
def workspace(tmp_path: Path) -> Path:
root = tmp_path / "python"
root.mkdir()
return root


@pytest.mark.parametrize("package", ["core", "redis"])
@pytest.mark.parametrize("spelling", ["repo-forward", "repo-native", "dot-forward", "dot-native"])
def test_repository_relative_path_selects_package(workspace: Path, package: str, spelling: str) -> None:
relative = Path("python") / "packages" / package / "example.py"
paths = {
"repo-forward": relative.as_posix(),
"repo-native": str(relative),
"dot-forward": "./" + relative.as_posix(),
"dot-native": "." + os.sep + str(relative),
}

assert runner.get_changed_packages(PROJECTS, [paths[spelling]], workspace) == (
{Path("packages") / package},
package == "core",
)


@pytest.mark.parametrize("spelling", ["workspace-forward", "workspace-native", "absolute"])
def test_existing_path_forms_keep_selecting_core(workspace: Path, spelling: str) -> None:
relative = Path("packages/core/example.py")
paths = {
"workspace-forward": relative.as_posix(),
"workspace-native": str(relative),
"absolute": str(workspace / relative),
}

assert runner.get_changed_packages(PROJECTS, [paths[spelling]], workspace) == ({PROJECTS[0]}, True)


def test_unrelated_path_stays_unselected(workspace: Path) -> None:
assert runner.get_changed_packages(PROJECTS, ["dotnet/example.cs"], workspace) == (set(), False)


@pytest.mark.skipif(os.name == "nt", reason="Backslash is a filename character on POSIX.")
def test_literal_backslash_package_keeps_its_identity(workspace: Path) -> None:
literal_package = Path(r"packages/core\notes")
projects = [*PROJECTS, literal_package]
file_path = workspace / literal_package / "example.py"
file_path.parent.mkdir(parents=True)
file_path.write_text("", encoding="utf-8")

assert runner.get_changed_packages(projects, [str(file_path)], workspace) == ({literal_package}, False)
assert runner.get_changed_packages(projects, [str(Path("python") / literal_package / "example.py")], workspace) == (
{literal_package},
False,
)


@pytest.mark.skipif(os.name != "nt", reason="UNC paths use native Windows semantics.")
def test_absolute_unc_path_keeps_selecting_core() -> None:
workspace = Path(r"\\server\share\repo\python")
file_path = workspace / PROJECTS[0] / "example.py"

assert runner.get_changed_packages(PROJECTS, [str(file_path)], workspace) == ({PROJECTS[0]}, True)


@pytest.mark.parametrize("package", ["core", "redis"])
def test_cli_native_path_routes_local_and_type_tasks(
workspace: Path, package: str, monkeypatch: pytest.MonkeyPatch
) -> None:
(workspace / "pyproject.toml").write_text(
'[tool.uv.workspace]\nmembers = ["packages/core", "packages/redis"]\n', encoding="utf-8"
)
for project in PROJECTS:
directory = workspace / project
directory.mkdir(parents=True)
(directory / "pyproject.toml").write_text(
'[tool.poe.tasks]\nlint = "echo unused"\npyright = "echo unused"\n', encoding="utf-8"
)
monkeypatch.chdir(workspace)
monkeypatch.setattr(runner, "__file__", str(workspace / "scripts/run_tasks_in_changed_packages.py"))
monkeypatch.setattr(
"sys.argv",
[
"run_tasks_in_changed_packages.py",
"lint",
"pyright",
"--files",
"." + os.sep + str(Path("python/packages") / package / "example.py"),
"--seq",
],
)
run_tasks = Mock()
monkeypatch.setattr(runner, "run_tasks", run_tasks)

runner.main()

selected = Path("packages") / package
typed = PROJECTS if package == "core" else [selected]
run_tasks.assert_called_once_with(
[(selected, "lint"), *((project, "pyright") for project in typed)], workspace, sequential=True
)
Loading