From 36b51750da6579c6d95a8dcbbd7a6fb0f93ab237 Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Wed, 1 Jul 2026 10:33:17 -0700 Subject: [PATCH 1/7] Fix issue with --dest-dir for CI usage. --- eng/scripts/dispatch_checks.py | 14 ++++-- .../tests/test_dispatch_checks.py | 48 +++++++++++++++++++ .../create_api_review_pr_test.py | 35 ++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py diff --git a/eng/scripts/dispatch_checks.py b/eng/scripts/dispatch_checks.py index e896eab69604..89d72d07efe0 100644 --- a/eng/scripts/dispatch_checks.py +++ b/eng/scripts/dispatch_checks.py @@ -16,7 +16,7 @@ from ci_tools.scenario.generation import build_whl_for_req, replace_dev_reqs from ci_tools.logging import configure_logging, logger from ci_tools.environment_exclusions import is_check_enabled, CHECK_DEFAULTS -from ci_tools.parsing import get_config_setting +from ci_tools.parsing import ParsedSetup, get_config_setting from devtools_testutils.proxy_startup import prepare_local_tool from packaging.requirements import Requirement @@ -77,6 +77,13 @@ def _normalize_newlines(text: str) -> str: return text.replace("\r\n", "\n").replace("\r", "\n") +def get_check_dest_dir(package: str, check: str, dest_dir: Optional[str]) -> Optional[str]: + if dest_dir and check == "apistub": + package_name = ParsedSetup.from_path(package).name + return os.path.join(dest_dir, package_name) + return dest_dir + + async def _tee_stream( proc: "asyncio.subprocess.Process", package: str, check: str ) -> tuple: @@ -222,8 +229,9 @@ async def run_check( cmd += ["--service", service] if mark_arg: cmd += ["--mark_arg", mark_arg] - if dest_dir and check == "apistub": - cmd += ["--dest-dir", dest_dir] + check_dest_dir = get_check_dest_dir(package, check, dest_dir) + if check_dest_dir and check == "apistub": + cmd += ["--dest-dir", check_dest_dir] logger.info(f"[START {idx}/{total}] {check} :: {package}\nCMD: {' '.join(cmd)}") env = os.environ.copy() env["PROXY_URL"] = f"http://localhost:{proxy_port}" diff --git a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py new file mode 100644 index 000000000000..38e0d773105b --- /dev/null +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -0,0 +1,48 @@ +import os +import sys +from types import SimpleNamespace +from unittest.mock import patch + + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) +TOOLS_ROOT = os.path.join(REPO_ROOT, "eng", "tools", "azure-sdk-tools") +if TOOLS_ROOT not in sys.path: + sys.path.insert(0, TOOLS_ROOT) +if REPO_ROOT not in sys.path: + sys.path.insert(0, REPO_ROOT) + +from eng.scripts.dispatch_checks import get_check_dest_dir + + +def test_apistub_dest_dir_uses_package_subdirectory(): + package_dir = os.path.join(REPO_ROOT, "sdk", "core", "azure-core") + artifact_dir = os.path.join(REPO_ROOT, "artifacts") + + with patch( + "eng.scripts.dispatch_checks.ParsedSetup.from_path", + return_value=SimpleNamespace(name="azure-core"), + ): + result = get_check_dest_dir(package_dir, "apistub", artifact_dir) + + assert result == os.path.join(artifact_dir, "azure-core") + + +def test_non_apistub_dest_dir_is_unchanged(): + package_dir = os.path.join(REPO_ROOT, "sdk", "core", "azure-core") + artifact_dir = os.path.join(REPO_ROOT, "artifacts") + + with patch("eng.scripts.dispatch_checks.ParsedSetup.from_path") as parsed_setup: + result = get_check_dest_dir(package_dir, "pylint", artifact_dir) + + assert result == artifact_dir + parsed_setup.assert_not_called() + + +def test_empty_dest_dir_is_unchanged(): + package_dir = os.path.join(REPO_ROOT, "sdk", "core", "azure-core") + + with patch("eng.scripts.dispatch_checks.ParsedSetup.from_path") as parsed_setup: + result = get_check_dest_dir(package_dir, "apistub", None) + + assert result is None + parsed_setup.assert_not_called() \ No newline at end of file diff --git a/scripts/api_md_workflow/create_api_review_pr_test.py b/scripts/api_md_workflow/create_api_review_pr_test.py index a0e2e45a201f..47cbac739b77 100644 --- a/scripts/api_md_workflow/create_api_review_pr_test.py +++ b/scripts/api_md_workflow/create_api_review_pr_test.py @@ -605,6 +605,41 @@ def test_main_preflights_azsdk_first(self): find_package_dir.assert_not_called() + def test_generate_api_for_package_uses_package_dir_destination(self): + package_dir = Path("sdk/example/azure-example") + with ( + patch.object(workflow, "find_package_dir", return_value=package_dir), + patch.object(workflow, "run", return_value=command_result()) as run, + ): + workflow.generate_api_for_package("azure-example", runtime_executable=None) + + run.assert_called_once_with( + ["azpysdk", "apistub", "--dest-dir", str(package_dir), "azure-example"], + check=True, + shell=workflow.sys.platform == "win32", + ) + + def test_generate_api_for_package_runtime_executable_uses_package_dir_destination(self): + package_dir = Path("sdk/example/azure-example") + with ( + patch.object(workflow, "find_package_dir", return_value=package_dir), + patch.object(workflow, "run", return_value=command_result()) as run, + ): + workflow.generate_api_for_package("azure-example", runtime_executable="python") + + run.assert_called_once_with( + [ + "python", + "-m", + "azpysdk.main", + "apistub", + "--dest-dir", + str(package_dir), + "azure-example", + ], + check=True, + ) + def test_resolve_azsdk_uses_path_first(self): with patch.object(workflow.shutil, "which", return_value="C:/tools/azsdk.exe"): self.assertEqual(workflow.resolve_azsdk_executable(), "C:/tools/azsdk.exe") From 3ac1c9ff9ca73549bf91cb0d4b04867dee7cb35e Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Wed, 1 Jul 2026 10:53:40 -0700 Subject: [PATCH 2/7] Run black. --- eng/scripts/dispatch_checks.py | 4 +++- eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py | 6 ++++-- scripts/api_md_workflow/create_api_review_pr_test.py | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/eng/scripts/dispatch_checks.py b/eng/scripts/dispatch_checks.py index 89d72d07efe0..2c3afddb74b4 100644 --- a/eng/scripts/dispatch_checks.py +++ b/eng/scripts/dispatch_checks.py @@ -77,7 +77,9 @@ def _normalize_newlines(text: str) -> str: return text.replace("\r\n", "\n").replace("\r", "\n") -def get_check_dest_dir(package: str, check: str, dest_dir: Optional[str]) -> Optional[str]: +def get_check_dest_dir( + package: str, check: str, dest_dir: Optional[str] +) -> Optional[str]: if dest_dir and check == "apistub": package_name = ParsedSetup.from_path(package).name return os.path.join(dest_dir, package_name) diff --git a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py index 38e0d773105b..893465ac8d60 100644 --- a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -4,7 +4,9 @@ from unittest.mock import patch -REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) +REPO_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "..", "..", "..") +) TOOLS_ROOT = os.path.join(REPO_ROOT, "eng", "tools", "azure-sdk-tools") if TOOLS_ROOT not in sys.path: sys.path.insert(0, TOOLS_ROOT) @@ -45,4 +47,4 @@ def test_empty_dest_dir_is_unchanged(): result = get_check_dest_dir(package_dir, "apistub", None) assert result is None - parsed_setup.assert_not_called() \ No newline at end of file + parsed_setup.assert_not_called() diff --git a/scripts/api_md_workflow/create_api_review_pr_test.py b/scripts/api_md_workflow/create_api_review_pr_test.py index 47cbac739b77..d998dbdd1d89 100644 --- a/scripts/api_md_workflow/create_api_review_pr_test.py +++ b/scripts/api_md_workflow/create_api_review_pr_test.py @@ -619,13 +619,17 @@ def test_generate_api_for_package_uses_package_dir_destination(self): shell=workflow.sys.platform == "win32", ) - def test_generate_api_for_package_runtime_executable_uses_package_dir_destination(self): + def test_generate_api_for_package_runtime_executable_uses_package_dir_destination( + self, + ): package_dir = Path("sdk/example/azure-example") with ( patch.object(workflow, "find_package_dir", return_value=package_dir), patch.object(workflow, "run", return_value=command_result()) as run, ): - workflow.generate_api_for_package("azure-example", runtime_executable="python") + workflow.generate_api_for_package( + "azure-example", runtime_executable="python" + ) run.assert_called_once_with( [ From 212f3fda956880327124e877d1d372c7fcbd5158 Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Wed, 1 Jul 2026 11:08:17 -0700 Subject: [PATCH 3/7] Code review feedback. --- eng/scripts/dispatch_checks.py | 13 ++++++------- .../azure-sdk-tools/tests/test_dispatch_checks.py | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/eng/scripts/dispatch_checks.py b/eng/scripts/dispatch_checks.py index 2c3afddb74b4..d9196d881337 100644 --- a/eng/scripts/dispatch_checks.py +++ b/eng/scripts/dispatch_checks.py @@ -231,9 +231,10 @@ async def run_check( cmd += ["--service", service] if mark_arg: cmd += ["--mark_arg", mark_arg] - check_dest_dir = get_check_dest_dir(package, check, dest_dir) - if check_dest_dir and check == "apistub": - cmd += ["--dest-dir", check_dest_dir] + if check == "apistub": + check_dest_dir = get_check_dest_dir(package, check, dest_dir) + if check_dest_dir: + cmd += ["--dest-dir", check_dest_dir] logger.info(f"[START {idx}/{total}] {check} :: {package}\nCMD: {' '.join(cmd)}") env = os.environ.copy() env["PROXY_URL"] = f"http://localhost:{proxy_port}" @@ -510,14 +511,12 @@ def handler(signum, frame): if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=""" + parser = argparse.ArgumentParser(description=""" This script is the single point for all checks invoked by CI within this repo. It works in two phases. 1. Identify which packages in the repo are in scope for this script invocation, based on a glob string and a service directory. 2. Invoke one or multiple `checks` environments for each package identified as in scope. In the case of an environment invoking `pytest`, results can be collected in a junit xml file, and test markers can be selected via --mark_arg. -""" - ) +""") parser.add_argument( "glob_string", diff --git a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py index 893465ac8d60..8c8cafeedf38 100644 --- a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -3,7 +3,6 @@ from types import SimpleNamespace from unittest.mock import patch - REPO_ROOT = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "..", "..") ) From c54e2cc1f1eaa19a3fa1cff36d961304fa3e1a35 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:25:40 +0000 Subject: [PATCH 4/7] Fix black formatting: restore blank line in test_dispatch_checks.py Co-authored-by: tjprescott <5723682+tjprescott@users.noreply.github.com> --- eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py index 8c8cafeedf38..893465ac8d60 100644 --- a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -3,6 +3,7 @@ from types import SimpleNamespace from unittest.mock import patch + REPO_ROOT = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "..", "..") ) From 3b642d029a9b5f8694d4d17703b44c8a290e3d53 Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Wed, 1 Jul 2026 11:08:17 -0700 Subject: [PATCH 5/7] Code review feedback. --- eng/scripts/dispatch_checks.py | 13 ++++++------- .../azure-sdk-tools/tests/test_dispatch_checks.py | 5 +---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/eng/scripts/dispatch_checks.py b/eng/scripts/dispatch_checks.py index 2c3afddb74b4..d9196d881337 100644 --- a/eng/scripts/dispatch_checks.py +++ b/eng/scripts/dispatch_checks.py @@ -231,9 +231,10 @@ async def run_check( cmd += ["--service", service] if mark_arg: cmd += ["--mark_arg", mark_arg] - check_dest_dir = get_check_dest_dir(package, check, dest_dir) - if check_dest_dir and check == "apistub": - cmd += ["--dest-dir", check_dest_dir] + if check == "apistub": + check_dest_dir = get_check_dest_dir(package, check, dest_dir) + if check_dest_dir: + cmd += ["--dest-dir", check_dest_dir] logger.info(f"[START {idx}/{total}] {check} :: {package}\nCMD: {' '.join(cmd)}") env = os.environ.copy() env["PROXY_URL"] = f"http://localhost:{proxy_port}" @@ -510,14 +511,12 @@ def handler(signum, frame): if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=""" + parser = argparse.ArgumentParser(description=""" This script is the single point for all checks invoked by CI within this repo. It works in two phases. 1. Identify which packages in the repo are in scope for this script invocation, based on a glob string and a service directory. 2. Invoke one or multiple `checks` environments for each package identified as in scope. In the case of an environment invoking `pytest`, results can be collected in a junit xml file, and test markers can be selected via --mark_arg. -""" - ) +""") parser.add_argument( "glob_string", diff --git a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py index 893465ac8d60..c286d38c2e68 100644 --- a/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -3,10 +3,7 @@ from types import SimpleNamespace from unittest.mock import patch - -REPO_ROOT = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "..", "..", "..") -) +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")) TOOLS_ROOT = os.path.join(REPO_ROOT, "eng", "tools", "azure-sdk-tools") if TOOLS_ROOT not in sys.path: sys.path.insert(0, TOOLS_ROOT) From 16c0ddbf4afd4c73abae2c316ec4c3a44192ff3e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:43:23 +0000 Subject: [PATCH 6/7] Fix verify-azpysdk-checks mypy feed auth failure Co-authored-by: tjprescott <5723682+tjprescott@users.noreply.github.com> --- .github/workflows/azure-sdk-tools.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/azure-sdk-tools.yml b/.github/workflows/azure-sdk-tools.yml index e4a132167652..09cd26a61f35 100644 --- a/.github/workflows/azure-sdk-tools.yml +++ b/.github/workflows/azure-sdk-tools.yml @@ -69,9 +69,6 @@ jobs: with: python-version: 3.13 - - name: Configure package indexes - uses: ./.github/actions/configure-package-indexes - - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh From 1456875b101625817c2d6e6638b99cf6d8c8b667 Mon Sep 17 00:00:00 2001 From: Travis Prescott Date: Wed, 1 Jul 2026 12:27:15 -0700 Subject: [PATCH 7/7] Revert "Fix verify-azpysdk-checks mypy feed auth failure" This reverts commit 16c0ddbf4afd4c73abae2c316ec4c3a44192ff3e. --- .github/workflows/azure-sdk-tools.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/azure-sdk-tools.yml b/.github/workflows/azure-sdk-tools.yml index 09cd26a61f35..e4a132167652 100644 --- a/.github/workflows/azure-sdk-tools.yml +++ b/.github/workflows/azure-sdk-tools.yml @@ -69,6 +69,9 @@ jobs: with: python-version: 3.13 + - name: Configure package indexes + uses: ./.github/actions/configure-package-indexes + - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh