diff --git a/eng/scripts/dispatch_checks.py b/eng/scripts/dispatch_checks.py index e896eab69604..d9196d881337 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,15 @@ 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 +231,10 @@ 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] + 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}" @@ -500,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 new file mode 100644 index 000000000000..c286d38c2e68 --- /dev/null +++ b/eng/tools/azure-sdk-tools/tests/test_dispatch_checks.py @@ -0,0 +1,47 @@ +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() 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..d998dbdd1d89 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,45 @@ 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")