From 2d2e465cbd4836bde46ee43dd28234aaf36bafc0 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Thu, 30 Apr 2026 10:49:48 +0800 Subject: [PATCH 1/6] update regenerate --- .../eng/scripts/ci/regenerate.ts | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/http-client-python/eng/scripts/ci/regenerate.ts b/packages/http-client-python/eng/scripts/ci/regenerate.ts index a65230db578..4f570d07f28 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate.ts @@ -9,8 +9,8 @@ import { compile, NodeHost } from "@typespec/compiler"; import { execSync } from "child_process"; import { existsSync, rmSync } from "fs"; -import { access, mkdir, readdir, writeFile } from "fs/promises"; -import { platform } from "os"; +import { access, cp, mkdir, mkdtemp, readdir, writeFile } from "fs/promises"; +import { platform, tmpdir } from "os"; import { dirname, join, relative, resolve } from "path"; import pc from "picocolors"; import { fileURLToPath } from "url"; @@ -424,6 +424,62 @@ async function getSubdirectories(baseDir: string, flags: RegenerateFlags): Promi return subdirectories; } +/** + * Resets the local `tests/generated` folder to the baseline checked into + * Azure/azure-sdk-for-python at `eng/tools/emitter/gen`. This ensures + * regeneration starts from a clean, known-good baseline which also contains + * necessary customized code. + */ +async function resetBaselineFromSdkRepo(generatedFolder: string): Promise { + const repoUrl = "https://github.com/Azure/azure-sdk-for-python.git"; + const branch = "main"; + const sourceSubdir = "eng/tools/emitter/gen"; + const testsGeneratedDir = resolve(generatedFolder, "../tests/generated"); + + console.log(pc.cyan(`\n${"=".repeat(60)}`)); + console.log(pc.cyan(`Resetting baseline from ${repoUrl} (${branch}/${sourceSubdir})`)); + console.log(pc.cyan(`${"=".repeat(60)}\n`)); + + // Wipe tests/generated + if (existsSync(testsGeneratedDir)) { + console.log(pc.dim(`Removing ${testsGeneratedDir}`)); + rmSync(testsGeneratedDir, { recursive: true, force: true }); + } + await mkdir(testsGeneratedDir, { recursive: true }); + + // Sparse-checkout the baseline folder into a temp directory + const tempDir = await mkdtemp(join(tmpdir(), "azsdk-baseline-")); + try { + console.log(pc.dim(`Cloning into ${tempDir}`)); + const run = (cmd: string) => + execSync(cmd, { cwd: tempDir, stdio: ["ignore", "ignore", "inherit"] }); + + run(`git init`); + run(`git remote add origin ${repoUrl}`); + run(`git config core.sparseCheckout true`); + run(`git sparse-checkout init --cone`); + run(`git sparse-checkout set ${sourceSubdir}`); + run(`git fetch --depth 1 origin ${branch}`); + run(`git checkout FETCH_HEAD`); + + const sourceRoot = join(tempDir, ...sourceSubdir.split("/")); + for (const flavor of ["azure", "unbranded"]) { + const src = join(sourceRoot, flavor); + const dest = join(testsGeneratedDir, flavor); + if (!existsSync(src)) { + console.warn(pc.yellow(`Baseline folder not found: ${src}`)); + continue; + } + console.log(pc.dim(`Copying ${flavor}/ -> ${dest}`)); + await cp(src, dest, { recursive: true }); + } + + console.log(pc.green(`Baseline reset complete.\n`)); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } +} + async function preprocess(flavor: string, generatedFolder: string): Promise { if (flavor === "azure") { const testsGeneratedDir = resolve(generatedFolder, "../tests/generated/azure"); @@ -827,6 +883,8 @@ async function main() { const startTime = performance.now(); let success: boolean; + await resetBaselineFromSdkRepo(GENERATED_FOLDER); + if (flavor) { success = await regenerateFlavor(flavor, name, debug, jobs); } else { From d4005e3b1bd62cd003ae4ed0cf05190229b8e8c2 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Thu, 30 Apr 2026 10:57:51 +0800 Subject: [PATCH 2/6] add changelog --- ...-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md diff --git a/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md b/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md new file mode 100644 index 00000000000..66338e867ef --- /dev/null +++ b/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +add baseline of generated code before regenerate \ No newline at end of file From c408b98e8ff1e65176406b1815b5da7036f008d0 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 6 May 2026 06:16:03 +0000 Subject: [PATCH 3/6] add new test case --- .../eng/scripts/ci/regenerate.ts | 26 ++++++++++++- ...ation_subdir2_for_customized_code_async.py | 37 +++++++++++++++++++ ...ration_subdir_for_customized_code_async.py | 19 ++++++++++ ..._generation_subdir2_for_customized_code.py | 32 ++++++++++++++++ ...t_generation_subdir_for_customized_code.py | 16 ++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py create mode 100644 packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py create mode 100644 packages/http-client-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py create mode 100644 packages/http-client-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py diff --git a/packages/http-client-python/eng/scripts/ci/regenerate.ts b/packages/http-client-python/eng/scripts/ci/regenerate.ts index 4f570d07f28..864d801d527 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate.ts @@ -861,6 +861,30 @@ async function regenerateFlavor( return pySuccess; } + +/** + * Deletes a couple of fully-generated package folders from the baseline so that + * regeneration has to recreate them from scratch. + */ +function deleteSomeGeneratedFiles() { + const testsGeneratedDir = resolve(GENERATED_FOLDER, "../tests/generated"); + const targets = [ + join(testsGeneratedDir, "azure", "authentication-http-custom"), + join(testsGeneratedDir, "unbranded", "encode-array"), + ]; + for (const target of targets) { + if (existsSync(target)) { + console.log(pc.dim(`Deleting ${target}`)); + rmSync(target, { recursive: true, force: true }); + } + } +} + +async function preProcess() { + await resetBaselineFromSdkRepo(GENERATED_FOLDER); + deleteSomeGeneratedFiles(); +} + async function main() { const isWindows = platform() === "win32"; const flavor = argv.values.flavor; @@ -883,7 +907,7 @@ async function main() { const startTime = performance.now(); let success: boolean; - await resetBaselineFromSdkRepo(GENERATED_FOLDER); + await preProcess(); if (flavor) { success = await regenerateFlavor(flavor, name, debug, jobs); diff --git a/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py b/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py new file mode 100644 index 00000000000..bbc02dba59b --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py @@ -0,0 +1,37 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from generation.subdir2.aio import AddedClient +from generation.subdir2 import ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest_asyncio.fixture +async def client(): + async with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +@pytest.mark.asyncio +async def test_v1(client: AddedClient): + assert await client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +@pytest.mark.asyncio +async def test_v2(client: AddedClient): + assert await client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +@pytest.mark.asyncio +async def test_interface_v2(client: AddedClient): + assert await client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") diff --git a/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py b/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py new file mode 100644 index 00000000000..f992bd973dc --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from generation.subdir import Extension +from generation.subdir.aio import CustomizedClient + + +@pytest.mark.asyncio +async def test_custom_method(): + client = CustomizedClient() + assert (await client.customized_get()) == Extension( + { + "level": 0, + "extension": [{"level": 1, "extension": [{"level": 2}]}, {"level": 1}], + } + ) diff --git a/packages/http-client-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py b/packages/http-client-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py new file mode 100644 index 00000000000..9a90f290d36 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py @@ -0,0 +1,32 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from generation.subdir2 import AddedClient, ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest.fixture +def client(): + with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +def test_v1(client: AddedClient): + assert client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +def test_v2(client: AddedClient): + assert client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +def test_interface_v2(client: AddedClient): + assert client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") diff --git a/packages/http-client-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py b/packages/http-client-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py new file mode 100644 index 00000000000..d2b74608188 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from generation.subdir import CustomizedClient, Extension + + +def test_custom_method(): + client = CustomizedClient() + assert client.customized_get() == Extension( + { + "level": 0, + "extension": [{"level": 1, "extension": [{"level": 2}]}, {"level": 1}], + } + ) From 9e190996b812425b0e2e2a3fe5384d7c8f76ff68 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 6 May 2026 06:19:36 +0000 Subject: [PATCH 4/6] format --- packages/http-client-python/eng/scripts/ci/regenerate.ts | 1 - packages/http-client-python/eng/scripts/setup/run_batch.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/http-client-python/eng/scripts/ci/regenerate.ts b/packages/http-client-python/eng/scripts/ci/regenerate.ts index 864d801d527..4620fa0f603 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate.ts @@ -861,7 +861,6 @@ async function regenerateFlavor( return pySuccess; } - /** * Deletes a couple of fully-generated package folders from the baseline so that * regeneration has to recreate them from scratch. diff --git a/packages/http-client-python/eng/scripts/setup/run_batch.py b/packages/http-client-python/eng/scripts/setup/run_batch.py index 6d6bef5b0d2..9b978ec0319 100644 --- a/packages/http-client-python/eng/scripts/setup/run_batch.py +++ b/packages/http-client-python/eng/scripts/setup/run_batch.py @@ -51,9 +51,7 @@ def _coerce(value): return False return value - pygen_args = { - k: _coerce(v) for k, v in command_args.items() if k not in ["emit-yaml-only"] - } + pygen_args = {k: _coerce(v) for k, v in command_args.items() if k not in ["emit-yaml-only"]} # Run preprocess and codegen (black is batched at the end for performance) preprocess.PreProcessPlugin(output_folder=output_dir, tsp_file=yaml_path, **pygen_args).process() From e9b59c1fa715bafbb96ad0b7c79250d50718b9c8 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 6 May 2026 06:41:22 +0000 Subject: [PATCH 5/6] add missing test files --- .../tests/mock_api/shared/test_patch.py | 12 +++ .../shared/unittests/test_parse_pyproject.py | 95 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 packages/http-client-python/tests/mock_api/shared/test_patch.py create mode 100644 packages/http-client-python/tests/mock_api/shared/unittests/test_parse_pyproject.py diff --git a/packages/http-client-python/tests/mock_api/shared/test_patch.py b/packages/http-client-python/tests/mock_api/shared/test_patch.py new file mode 100644 index 00000000000..8410e4046b5 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/test_patch.py @@ -0,0 +1,12 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + + +def test_patch_mixin_operation_group_in_operations_folder(key_credential): + from authentication.apikey import ApiKeyClient, aio + + assert hasattr(ApiKeyClient(key_credential), "patch_added_operation") + assert hasattr(aio.ApiKeyClient(key_credential), "patch_added_operation") \ No newline at end of file diff --git a/packages/http-client-python/tests/mock_api/shared/unittests/test_parse_pyproject.py b/packages/http-client-python/tests/mock_api/shared/unittests/test_parse_pyproject.py new file mode 100644 index 00000000000..8ebf108f297 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/unittests/test_parse_pyproject.py @@ -0,0 +1,95 @@ +from pathlib import Path +from typing import Dict, Any + +try: + import tomllib +except ImportError: + import tomli as tomllib + + +def check_no_setup_py(package_path: str) -> None: + """ + Check that setup.py does not exist in the package directory. + + Args: + package_path: Relative path to the package directory + + Raises: + AssertionError: If setup.py exists in the package directory + """ + package_dir = Path(__file__).parent / package_path + setup_py_path = package_dir / "setup.py" + + assert not setup_py_path.exists(), f"setup.py should not exist at {setup_py_path} when using pyproject.toml" + + +def get_pyproject_section(package_path: str, section_name: str) -> Dict[str, Any]: + """ + Get a specific section from a package's pyproject.toml file. + + Args: + package_path: Relative path to the package directory containing pyproject.toml + section_name: Dot-separated section name (e.g., "tool.azure-sdk-build") + + Returns: + Dictionary containing the section data + + Raises: + AssertionError: If pyproject.toml not found or section missing + """ + try: + # Convert to absolute path and find pyproject.toml + package_dir = Path(__file__).parent / package_path + pyproject_path = package_dir / "pyproject.toml" + + # Assert pyproject.toml exists + assert pyproject_path.exists(), f"pyproject.toml not found at {pyproject_path}" + + # Parse pyproject.toml + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + + # Check that the project name matches the folder name + if "project" in data and "name" in data["project"]: + expected_name = package_dir.name + actual_name = data["project"]["name"] + assert ( + actual_name == expected_name + ), f"Project name '{actual_name}' in pyproject.toml does not match folder name '{expected_name}'" + + # Navigate to the requested section + section_parts = section_name.split(".") + current_data = data + + for part in section_parts: + assert ( + part in current_data + ), f"pyproject.toml does not contain [{'.'.join(section_parts[:section_parts.index(part)+1])}] section" + current_data = current_data[part] + + return current_data + + except Exception as e: + raise AssertionError(f"Error checking pyproject.toml at '{package_path}': {e}") + + +def test_azure_sdk_build(): + """Test that authentication-union packages have pyproject.toml with [tool.azure-sdk-build] pyright = false.""" + + # Need to check the file directly, since installed distribution metadata won't include custom sections. + test_paths = ["../../../generated/azure/authentication-union"] + + for package_path in test_paths: + # First check that setup.py doesn't exist + check_no_setup_py(package_path) + + # Get the [tool.azure-sdk-build] section + azure_sdk_build = get_pyproject_section(package_path, "tool.azure-sdk-build") + + # Check for pyright = false + assert ( + "pyright" in azure_sdk_build + ), f"[tool.azure-sdk-build] section does not contain 'pyright' setting in {package_path}" + assert ( + azure_sdk_build["pyright"] is False + ), f"Expected pyright = false, but got pyright = {azure_sdk_build['pyright']} in {package_path}" \ No newline at end of file From 686f8fd6070edfb9eb2cde0192490407be780d1d Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 6 May 2026 19:27:23 +0800 Subject: [PATCH 6/6] Update optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md --- ...imize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md b/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md index 66338e867ef..03709e26889 100644 --- a/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md +++ b/.chronus/changes/optimize-pipeline-for-complete-test-cases-2026-3-30-10-57-36.md @@ -4,4 +4,4 @@ packages: - "@typespec/http-client-python" --- -add baseline of generated code before regenerate \ No newline at end of file +add baseline of generated code before regenerate to contain necessary customized code for some specific test cases