Skip to content

Commit 08e2b52

Browse files
msyycCopilotCopilot
authored
Use sdkReleaseType for tagIsStable in TypeSpec projects (#45768)
* Use sdkReleaseType for tagIsStable in TypeSpec projects For TypeSpec projects, determine tagIsStable from sdkReleaseType input instead of using judge_tag_preview. Swagger projects continue to use judge_tag_preview as before. - Extract sdkReleaseType from pipeline input data - Add is_tsp flag to distinguish TypeSpec from Swagger codegen - TypeSpec: tagIsStable = (sdkReleaseType == 'stable') - Swagger: tagIsStable = not judge_tag_preview(...) - Add test_sdk_generator.py with 9 test cases covering the new logic * Use sdkReleaseType for tagIsStable in TypeSpec projects (#45769) * Initial plan * Fix Black formatting in test_sdk_generator.py Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: msyyc <70930885+msyyc@users.noreply.github.com> * update * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent db215f5 commit 08e2b52

2 files changed

Lines changed: 233 additions & 1 deletion

File tree

eng/tools/azure-sdk-tools/packaging_tools/sdk_generator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def main(generate_input, generate_output):
107107
readme_and_tsp = [("relatedReadmeMdFiles", item) for item in data.get("relatedReadmeMdFiles", [])] + [
108108
("relatedTypeSpecProjectFolder", item) for item in data.get("relatedTypeSpecProjectFolder", [])
109109
]
110+
sdk_release_type = data.get("sdkReleaseType")
110111
run_in_pipeline = data.get("runMode") is not None
111112
for input_type, readme_or_tsp in readme_and_tsp:
112113
_LOGGER.info(f"[CODEGEN]({readme_or_tsp})codegen begin")
@@ -176,14 +177,17 @@ def main(generate_input, generate_output):
176177
_LOGGER.info(f"remove additional file when roll back to swagger: {file_path}")
177178

178179
try:
180+
is_tsp = "readme.md" not in readme_or_tsp
179181
package_total.add(package_name)
180182
sdk_code_path = str(Path(sdk_folder, folder_name, package_name))
181183
if package_name not in result:
182184
package_entry = {}
183185
package_entry["packageName"] = package_name
184186
package_entry["path"] = [folder_name]
185187
package_entry[spec_word] = [readme_or_tsp]
186-
package_entry["tagIsStable"] = not judge_tag_preview(sdk_code_path, package_name)
188+
package_entry["tagIsStable"] = (
189+
sdk_release_type == "stable" if is_tsp else (not judge_tag_preview(sdk_code_path, package_name))
190+
)
187191
package_entry["targetReleaseDate"] = data.get("targetReleaseDate", "")
188192
result[package_name] = package_entry
189193
else:
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import pytest
2+
import json
3+
import tempfile
4+
import shutil
5+
from pathlib import Path
6+
from unittest.mock import patch
7+
8+
9+
MODULE = "packaging_tools.sdk_generator"
10+
11+
12+
@pytest.fixture
13+
def io_paths():
14+
"""Create temp input/output JSON files and clean up after test."""
15+
tmp_dir = tempfile.mkdtemp()
16+
input_path = str(Path(tmp_dir) / "input.json")
17+
output_path = str(Path(tmp_dir) / "output.json")
18+
yield input_path, output_path
19+
shutil.rmtree(tmp_dir)
20+
21+
22+
def _write_input(path, data):
23+
with open(path, "w") as f:
24+
json.dump(data, f)
25+
26+
27+
def _read_output(path):
28+
with open(path, "r") as f:
29+
return json.load(f)
30+
31+
32+
# ── helpers for the heavy mocking needed by main() ──────────────────────
33+
34+
35+
def _common_patches():
36+
"""Return a dict of patch targets used by most tests."""
37+
return {
38+
"gen_typespec": patch(f"{MODULE}.gen_typespec", return_value=None),
39+
"gen_dpg": patch(f"{MODULE}.gen_dpg", return_value=None),
40+
"generate": patch(f"{MODULE}.generate", return_value=None),
41+
"get_package_names": patch(f"{MODULE}.get_package_names"),
42+
"judge_tag_preview": patch(f"{MODULE}.judge_tag_preview"),
43+
"format_samples_and_tests": patch(f"{MODULE}.format_samples_and_tests"),
44+
"check_call": patch(f"{MODULE}.check_call"),
45+
"sdk_changelog_generate": patch(f"{MODULE}.sdk_changelog_generate"),
46+
"sdk_update_version": patch(f"{MODULE}.sdk_update_version"),
47+
"sdk_update_metadata": patch(f"{MODULE}.sdk_update_metadata"),
48+
"create_package": patch(f"{MODULE}.create_package"),
49+
"check_file": patch(f"{MODULE}.check_file"),
50+
"del_outdated_generated_files": patch(f"{MODULE}.del_outdated_generated_files"),
51+
"del_outdated_files": patch(f"{MODULE}.del_outdated_files"),
52+
}
53+
54+
55+
class TestTagIsStableForTypeSpec:
56+
"""Tests for the tagIsStable logic change: TypeSpec projects use sdkReleaseType,
57+
while Swagger projects still use judge_tag_preview."""
58+
59+
def _run_main(self, input_path, output_path, input_data, package_names, judge_return=False):
60+
"""Helper that wires up all the mocks and calls main()."""
61+
_write_input(input_path, input_data)
62+
63+
patches = _common_patches()
64+
mocks = {name: p.start() for name, p in patches.items()}
65+
66+
# get_package_names returns our controlled list
67+
mocks["get_package_names"].return_value = package_names
68+
mocks["judge_tag_preview"].return_value = judge_return
69+
70+
# create_package needs a dist folder with a .whl
71+
def fake_create_package(folder, pkg):
72+
# Use a temporary base directory to avoid polluting the repo workspace
73+
base_dir = Path(tempfile.mkdtemp())
74+
dist = base_dir / folder / pkg / "dist"
75+
dist.mkdir(parents=True, exist_ok=True)
76+
(dist / f"{pkg}-0.0.0-py3-none-any.whl").touch()
77+
78+
mocks["create_package"].side_effect = fake_create_package
79+
80+
try:
81+
from packaging_tools.sdk_generator import main
82+
83+
main(input_path, output_path)
84+
finally:
85+
for p in patches.values():
86+
p.stop()
87+
88+
return _read_output(output_path)
89+
90+
# ── TypeSpec: sdkReleaseType == "stable" → tagIsStable = True ────
91+
92+
def test_typespec_stable_release_type(self, io_paths):
93+
input_path, output_path = io_paths
94+
input_data = {
95+
"specFolder": "spec",
96+
"headSha": "abc123",
97+
"repoHttpsUrl": "https://github.com/test/repo",
98+
"relatedTypeSpecProjectFolder": ["Microsoft.Test/stable"],
99+
"sdkReleaseType": "stable",
100+
}
101+
result = self._run_main(
102+
input_path,
103+
output_path,
104+
input_data,
105+
package_names=[("sdk/test", "azure-mgmt-test")],
106+
)
107+
pkg = result["packages"][0]
108+
assert pkg["tagIsStable"] is True
109+
110+
# ── TypeSpec: sdkReleaseType == "preview" → tagIsStable = False ──
111+
112+
def test_typespec_preview_release_type(self, io_paths):
113+
input_path, output_path = io_paths
114+
input_data = {
115+
"specFolder": "spec",
116+
"headSha": "abc123",
117+
"repoHttpsUrl": "https://github.com/test/repo",
118+
"relatedTypeSpecProjectFolder": ["Microsoft.Test/preview"],
119+
"sdkReleaseType": "preview",
120+
}
121+
result = self._run_main(
122+
input_path,
123+
output_path,
124+
input_data,
125+
package_names=[("sdk/test", "azure-mgmt-test")],
126+
)
127+
pkg = result["packages"][0]
128+
assert pkg["tagIsStable"] is False
129+
130+
# ── TypeSpec: sdkReleaseType missing → tagIsStable = False ───────
131+
132+
def test_typespec_no_release_type(self, io_paths):
133+
input_path, output_path = io_paths
134+
input_data = {
135+
"specFolder": "spec",
136+
"headSha": "abc123",
137+
"repoHttpsUrl": "https://github.com/test/repo",
138+
"relatedTypeSpecProjectFolder": ["Microsoft.Test/preview"],
139+
# no sdkReleaseType key
140+
}
141+
result = self._run_main(
142+
input_path,
143+
output_path,
144+
input_data,
145+
package_names=[("sdk/test", "azure-mgmt-test")],
146+
)
147+
pkg = result["packages"][0]
148+
assert pkg["tagIsStable"] is False
149+
150+
# ── Swagger: tagIsStable delegates to judge_tag_preview ──────────
151+
152+
def test_swagger_stable_by_judge(self, io_paths):
153+
"""When judge_tag_preview returns False (not preview), tagIsStable should be True."""
154+
input_path, output_path = io_paths
155+
input_data = {
156+
"specFolder": "spec",
157+
"relatedReadmeMdFiles": ["specification/test/resource-manager/readme.md"],
158+
"sdkReleaseType": "stable",
159+
}
160+
result = self._run_main(
161+
input_path,
162+
output_path,
163+
input_data,
164+
package_names=[("sdk/test", "azure-mgmt-test")],
165+
judge_return=False, # not preview → tagIsStable = True
166+
)
167+
pkg = result["packages"][0]
168+
assert pkg["tagIsStable"] is True
169+
170+
def test_swagger_preview_by_judge(self, io_paths):
171+
"""When judge_tag_preview returns True (preview), tagIsStable should be False,
172+
regardless of sdkReleaseType."""
173+
input_path, output_path = io_paths
174+
input_data = {
175+
"specFolder": "spec",
176+
"relatedReadmeMdFiles": ["specification/test/resource-manager/readme.md"],
177+
"sdkReleaseType": "stable", # even though release type says "stable"
178+
}
179+
result = self._run_main(
180+
input_path,
181+
output_path,
182+
input_data,
183+
package_names=[("sdk/test", "azure-mgmt-test")],
184+
judge_return=True, # preview → tagIsStable = False
185+
)
186+
pkg = result["packages"][0]
187+
assert pkg["tagIsStable"] is False
188+
189+
190+
class TestExtractSdkFolder:
191+
def test_extracts_folder(self):
192+
from packaging_tools.sdk_generator import extract_sdk_folder
193+
194+
lines = [
195+
"``` yaml $(python)\n",
196+
" output-folder: $(python-sdks-folder)/network/azure-mgmt-network\n",
197+
"```\n",
198+
]
199+
assert extract_sdk_folder(lines) == "network/azure-mgmt-network"
200+
201+
def test_returns_empty_when_no_match(self):
202+
from packaging_tools.sdk_generator import extract_sdk_folder
203+
204+
lines = ["some random line\n"]
205+
assert extract_sdk_folder(lines) == ""
206+
207+
208+
class TestGetReadmePythonContent:
209+
def test_returns_content_when_file_exists(self, tmp_path):
210+
from packaging_tools.sdk_generator import get_readme_python_content
211+
212+
readme = tmp_path / "readme.md"
213+
readme.touch()
214+
python_readme = tmp_path / "readme.python.md"
215+
python_readme.write_text("line1\nline2\n")
216+
217+
result = get_readme_python_content(str(readme))
218+
assert len(result) == 2
219+
assert "line1\n" in result
220+
221+
def test_returns_empty_when_no_python_readme(self, tmp_path):
222+
from packaging_tools.sdk_generator import get_readme_python_content
223+
224+
readme = tmp_path / "readme.md"
225+
readme.touch()
226+
227+
result = get_readme_python_content(str(readme))
228+
assert result == []

0 commit comments

Comments
 (0)