Skip to content

Commit c0df756

Browse files
authored
Fix repeatable APIStub changelog generation (#48566)
* Fix repeatable APIStub changelog generation * Strengthen APIStub cleanup ordering test * update test case
1 parent b1665f1 commit c0df756

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

scripts/breaking_changes_checker/detect_breaking_changes.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,15 @@ def build_report_from_apistub(
775775
return report
776776

777777

778+
def _uninstall_package(package_name: str, pkg_dir: str) -> None:
779+
"""Remove an installed package so APIStub cannot reuse a same-version distribution."""
780+
subprocess.run(
781+
[sys.executable, "-m", "pip", "uninstall", "-y", package_name],
782+
cwd=pkg_dir,
783+
check=False,
784+
)
785+
786+
778787
def _resolve_pypi_version(package_name: str, latest_pypi_version: bool) -> str:
779788
"""Resolve the PyPI version to compare against.
780789
@@ -838,12 +847,16 @@ def main(
838847
# match "current", producing an empty changelog.
839848
if not version:
840849
version = _resolve_pypi_version(package_name, latest_pypi_version)
841-
# "current" is generated from the local source, "stable" from the
842-
# resolved PyPI version.
843-
current = build_report_from_apistub(package_name, pkg_dir, debug=debug, label="current", from_pypi=False)
850+
# APIStub installs each target into this Python environment, and pip skips
851+
# replacement when local and PyPI distributions have the same name/version.
852+
# Clear both snapshots for repeatable runs, then install local last so it
853+
# remains available to downstream SDK generation steps.
854+
_uninstall_package(package_name, pkg_dir)
844855
stable = build_report_from_apistub(
845856
package_name, pkg_dir, version=version, debug=debug, label="stable", from_pypi=True
846857
)
858+
_uninstall_package(package_name, pkg_dir)
859+
current = build_report_from_apistub(package_name, pkg_dir, debug=debug, label="current", from_pypi=False)
847860
checker = compare_report_dicts(stable, current, package_name, changelog)
848861
print(checker.report_changes())
849862
if not changelog and checker.breaking_changes:

scripts/breaking_changes_checker/tests/test_code_report_changelog.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,19 @@ def test_compare_code_reports_for_azure_mgmt_apimanagement_apistub():
414414
)
415415

416416

417+
def test_uninstall_package_uses_active_python_environment():
418+
from breaking_changes_checker import detect_breaking_changes
419+
420+
with mock.patch.object(detect_breaking_changes.subprocess, "run") as run:
421+
detect_breaking_changes._uninstall_package("azure-mgmt-network", "/tmp/azure-mgmt-network")
422+
423+
run.assert_called_once_with(
424+
[sys.executable, "-m", "pip", "uninstall", "-y", "azure-mgmt-network"],
425+
cwd="/tmp/azure-mgmt-network",
426+
check=False,
427+
)
428+
429+
417430
def test_use_apistub_changelog_resolves_stable_from_pypi_and_current_from_local():
418431
"""``--use-apistub`` without ``-s`` must diff local source against the previous PyPI release.
419432
@@ -432,12 +445,17 @@ def test_use_apistub_changelog_resolves_stable_from_pypi_and_current_from_local(
432445
checker = mock.MagicMock()
433446
checker.report_changes.return_value = ""
434447
checker.breaking_changes = []
448+
events = mock.Mock()
435449

436450
with mock.patch("pypi_tools.pypi.PyPIClient", return_value=pypi_client) as pypi_client_cls, mock.patch.object(
451+
detect_breaking_changes, "_uninstall_package"
452+
) as uninstall_package, mock.patch.object(
437453
detect_breaking_changes, "build_report_from_apistub", return_value={}
438454
) as build_report, mock.patch.object(
439455
detect_breaking_changes, "compare_report_dicts", return_value=checker
440456
) as compare:
457+
events.attach_mock(uninstall_package, "uninstall")
458+
events.attach_mock(build_report, "build_report")
441459
detect_breaking_changes.main(
442460
package_name="azure-mgmt-network",
443461
target_module="azure.mgmt.network",
@@ -453,6 +471,25 @@ def test_use_apistub_changelog_resolves_stable_from_pypi_and_current_from_local(
453471
)
454472

455473
assert build_report.call_count == 2, "Expected separate apistub reports for current and stable"
474+
assert events.mock_calls == [
475+
mock.call.uninstall("azure-mgmt-network", "/tmp/azure-mgmt-network"),
476+
mock.call.build_report(
477+
"azure-mgmt-network",
478+
"/tmp/azure-mgmt-network",
479+
version="30.2.0",
480+
debug=False,
481+
label="stable",
482+
from_pypi=True,
483+
),
484+
mock.call.uninstall("azure-mgmt-network", "/tmp/azure-mgmt-network"),
485+
mock.call.build_report(
486+
"azure-mgmt-network",
487+
"/tmp/azure-mgmt-network",
488+
debug=False,
489+
label="current",
490+
from_pypi=False,
491+
),
492+
]
456493

457494
# The resolver must force the public PyPI backend: in CI PIP_INDEX_URL points
458495
# at the curated Azure Artifacts feed, which is not a full mirror of PyPI.

0 commit comments

Comments
 (0)