From d18b2281e86b7c47599eb27fbf624c724279321a Mon Sep 17 00:00:00 2001 From: msyyc Date: Thu, 14 May 2026 07:45:07 +0000 Subject: [PATCH 1/2] fix(http-client-python): regenerate README.md during baseline reset Delete README.md files during baseline reset so regeneration recreates them for both azure and unbranded flavors. Extend unit test to assert README.md exists for every generated SDK package. --- .../fix-ci-readme-regeneration-2026-5-14.md | 7 +++++ .../eng/scripts/ci/regenerate-common.ts | 27 +++++++++++------- packages/http-client-python/package.json | 2 +- .../mock_api/shared/unittests/test_readme.py | 28 +++++++++++++++++++ 4 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 .chronus/changes/fix-ci-readme-regeneration-2026-5-14.md create mode 100644 packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py diff --git a/.chronus/changes/fix-ci-readme-regeneration-2026-5-14.md b/.chronus/changes/fix-ci-readme-regeneration-2026-5-14.md new file mode 100644 index 00000000000..a5fd88faafd --- /dev/null +++ b/.chronus/changes/fix-ci-readme-regeneration-2026-5-14.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-python" +--- + +Fix CI on main by deleting README.md files during baseline reset so regeneration recreates them; extend unit test to assert README.md exists for every generated SDK package (azure & unbranded). diff --git a/packages/http-client-python/eng/scripts/ci/regenerate-common.ts b/packages/http-client-python/eng/scripts/ci/regenerate-common.ts index f21c4013616..529270f1d1d 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate-common.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate-common.ts @@ -732,16 +732,23 @@ export async function prepareBaselineOfGeneratedCode(generatedFolder: string): P rmSync(tempDir, { recursive: true, force: true }); } - // Delete a couple of fully-generated package folders so regeneration has to - // recreate them from scratch (smoke test of full-emit path). - const targetsToDelete = [ - join(testsGeneratedDir, "azure", "authentication-http-custom"), - join(testsGeneratedDir, "unbranded", "encode-array"), - ]; - for (const target of targetsToDelete) { - if (existsSync(target)) { - console.log(pc.dim(`Deleting ${target}`)); - rmSync(target, { recursive: true, force: true }); + // Smoke test the full-emit path: delete a couple of fully-generated package + // folders and every README.md so regeneration has to recreate them. + const deleteIfExists = (path: string) => { + if (!existsSync(path)) return; + console.log(pc.dim(`Deleting ${path}`)); + rmSync(path, { recursive: true, force: true }); + }; + + deleteIfExists(join(testsGeneratedDir, "azure", "authentication-http-custom")); + deleteIfExists(join(testsGeneratedDir, "unbranded", "encode-array")); + + if (existsSync(testsGeneratedDir)) { + const entries = await readdir(testsGeneratedDir, { recursive: true, withFileTypes: true }); + for (const entry of entries) { + if (entry.isFile() && entry.name === "README.md") { + deleteIfExists(join(entry.parentPath, entry.name)); + } } } } diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index b4edd33cca2..a6c894ec804 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -26,7 +26,7 @@ } }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "scripts": { "clean": "rimraf ./dist ./temp ./venv ./tests/generated", diff --git a/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py b/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py new file mode 100644 index 00000000000..6d9384e8d94 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py @@ -0,0 +1,28 @@ +from pathlib import Path + +import pytest + +GENERATED_DIR = Path(__file__).parent / "../../../generated" +FLAVORS = ("azure", "unbranded") + + +def _package_dirs(): + base = GENERATED_DIR.resolve() + dirs = [] + for flavor in FLAVORS: + flavor_dir = base / flavor + if not flavor_dir.is_dir(): + continue + dirs.extend(sorted(p for p in flavor_dir.iterdir() if p.is_dir())) + return dirs + + +@pytest.mark.parametrize( + "package_dir", + _package_dirs(), + ids=lambda p: f"{p.parent.name}/{p.name}", +) +def test_readme_exists(package_dir: Path): + """Each generated SDK package folder (azure & unbranded) must contain a README.md.""" + readme_path = package_dir / "README.md" + assert readme_path.is_file(), f"README.md not found at {readme_path}" From 0efb8fcdcdff1137503577d00365bfbc94b64ce7 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Thu, 14 May 2026 08:28:36 +0000 Subject: [PATCH 2/2] fix test case --- .../mock_api/shared/unittests/test_readme.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py b/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py index 6d9384e8d94..133f680d9e7 100644 --- a/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py +++ b/packages/http-client-python/tests/mock_api/shared/unittests/test_readme.py @@ -5,6 +5,19 @@ GENERATED_DIR = Path(__file__).parent / "../../../generated" FLAVORS = ("azure", "unbranded") +# Folders that exist in the azure-sdk-for-python baseline checkout but are not +# regenerated by the emitter, so their README.md is never (re)written. Skip +# them to keep this test focused on real generated packages. +# - "azure/service-multiple-services": listed in SKIP_SPECS in +# eng/scripts/ci/regenerate-common.ts (no emitter run for this spec). +# - "azure/azure-client-generator-core-client-initialization": orphan parent +# folder; only its sub-specs (.../default, .../individually, +# .../individuallyParent) are actually emitted as packages. +SKIP_PACKAGES = { + ("azure", "service-multiple-services"), + ("azure", "azure-client-generator-core-client-initialization"), +} + def _package_dirs(): base = GENERATED_DIR.resolve() @@ -13,7 +26,12 @@ def _package_dirs(): flavor_dir = base / flavor if not flavor_dir.is_dir(): continue - dirs.extend(sorted(p for p in flavor_dir.iterdir() if p.is_dir())) + for p in sorted(flavor_dir.iterdir()): + if not p.is_dir(): + continue + if (flavor, p.name) in SKIP_PACKAGES: + continue + dirs.append(p) return dirs