From 3548a0d13b453d529b740cc2642b9f9a2ec4bf44 Mon Sep 17 00:00:00 2001 From: Paulo Lacerda Date: Thu, 13 Aug 2026 22:07:48 -0300 Subject: [PATCH] fix: pass built version to dev verification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 70 +++++++++++++++++++++++++--------- tests/unit/test_ci_workflow.py | 43 +++++++++++++++++++++ 2 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 tests/unit/test_ci_workflow.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ea0aff..7bd2965 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,6 +149,8 @@ jobs: needs: [lint, test] runs-on: ubuntu-latest environment: staging + outputs: + version: ${{ steps.package-version.outputs.version }} permissions: id-token: write # Required for PyPI Trusted Publishing (OIDC) steps: @@ -171,10 +173,44 @@ jobs: - name: Build package run: uv build + - name: Determine built version + id: package-version + run: | + VERSION=$(uv run python - <<'PY' + from email.parser import BytesParser + from pathlib import Path + from zipfile import ZipFile + + wheels = sorted(Path("dist").glob("*.whl")) + if len(wheels) != 1: + raise SystemExit( + f"Expected exactly one wheel in dist/, found {len(wheels)}." + ) + with ZipFile(wheels[0]) as archive: + metadata_files = [ + name + for name in archive.namelist() + if name.endswith(".dist-info/METADATA") + ] + if len(metadata_files) != 1: + raise SystemExit( + "Expected exactly one .dist-info/METADATA file in " + f"{wheels[0].name}, found {len(metadata_files)}." + ) + metadata = BytesParser().parsebytes(archive.read(metadata_files[0])) + version = metadata.get("Version") + if not version: + raise SystemExit(f"No Version field found in {wheels[0].name}.") + print(version) + PY + ) + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Built package version: $VERSION" + - name: Show version run: | ls -la dist/ - uv run python -c "from importlib.metadata import version; print(f'Dev version: {version(\"agentops-accelerator\")}')" + echo "Dev version: ${{ steps.package-version.outputs.version }}" - name: Publish to TestPyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -189,33 +225,27 @@ jobs: needs: publish-dev runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - name: Set up Python uses: actions/setup-python@v7 with: python-version: "3.12" - - name: Determine expected version - id: version - run: | - pip install setuptools-scm - VERSION=$(python -m setuptools_scm) - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "Expected dev version: $VERSION" - - name: Install from TestPyPI + env: + PACKAGE_VERSION: ${{ needs.publish-dev.outputs.version }} run: | + if [ -z "$PACKAGE_VERSION" ]; then + echo "::error::publish-dev did not expose the built package version." + exit 1 + fi # TestPyPI serves its simple index through a CDN, so a freshly # uploaded release is not immediately resolvable even after the # upload returns 200 OK. Allow ~6 minutes for it to propagate. ATTEMPTS=12 for i in $(seq 1 "$ATTEMPTS"); do - echo "Attempt $i/$ATTEMPTS: installing agentops-accelerator==${{ steps.version.outputs.version }}" + echo "Attempt $i/$ATTEMPTS: installing agentops-accelerator==$PACKAGE_VERSION" if pip install --no-cache-dir \ - "agentops-accelerator==${{ steps.version.outputs.version }}" \ + "agentops-accelerator==$PACKAGE_VERSION" \ --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/; then exit 0 @@ -225,7 +255,7 @@ jobs: sleep 30 fi done - echo "::error::agentops-accelerator==${{ steps.version.outputs.version }} was not available from TestPyPI after $ATTEMPTS attempts (~6 min)." + echo "::error::agentops-accelerator==$PACKAGE_VERSION was not available from TestPyPI after $ATTEMPTS attempts (~6 min)." exit 1 - name: Smoke test @@ -234,13 +264,15 @@ jobs: agentops --help - name: Summary + env: + PACKAGE_VERSION: ${{ needs.publish-dev.outputs.version }} run: | echo "## ✅ Dev build published and verified" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "- Version: \`${{ steps.version.outputs.version }}\`" >> "$GITHUB_STEP_SUMMARY" - echo "- TestPyPI: https://test.pypi.org/project/agentops-accelerator/${{ steps.version.outputs.version }}/" >> "$GITHUB_STEP_SUMMARY" + echo "- Version: \`$PACKAGE_VERSION\`" >> "$GITHUB_STEP_SUMMARY" + echo "- TestPyPI: https://test.pypi.org/project/agentops-accelerator/$PACKAGE_VERSION/" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Install: \`pip install agentops-accelerator==${{ steps.version.outputs.version }} --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/\`" >> "$GITHUB_STEP_SUMMARY" + echo "Install: \`pip install agentops-accelerator==$PACKAGE_VERSION --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/\`" >> "$GITHUB_STEP_SUMMARY" # Validate that the VSIX extension packages correctly build-vsix: diff --git a/tests/unit/test_ci_workflow.py b/tests/unit/test_ci_workflow.py new file mode 100644 index 0000000..a0beff9 --- /dev/null +++ b/tests/unit/test_ci_workflow.py @@ -0,0 +1,43 @@ +"""Regression tests for the repository's CI workflow.""" + +from pathlib import Path + +import yaml + + +_CI_WORKFLOW = Path(__file__).parents[2] / ".github" / "workflows" / "ci.yml" + + +def _jobs() -> dict: + workflow = yaml.safe_load(_CI_WORKFLOW.read_text(encoding="utf-8")) + return workflow["jobs"] + + +def test_publish_dev_exposes_version_from_built_wheel_metadata() -> None: + publish_dev = _jobs()["publish-dev"] + + assert publish_dev["outputs"]["version"] == ( + "${{ steps.package-version.outputs.version }}" + ) + version_step = next( + step + for step in publish_dev["steps"] + if step.get("id") == "package-version" + ) + script = version_step["run"] + assert "uv run python - <<'PY'" in script + assert 'Path("dist").glob("*.whl")' in script + assert '.endswith(".dist-info/METADATA")' in script + assert 'metadata.get("Version")' in script + assert 'echo "version=$VERSION" >> "$GITHUB_OUTPUT"' in script + + +def test_verify_dev_consumes_publish_dev_artifact_version() -> None: + verify_dev = _jobs()["verify-dev"] + serialized = yaml.safe_dump(verify_dev) + + assert verify_dev["needs"] == "publish-dev" + assert "${{ needs.publish-dev.outputs.version }}" in serialized + assert "PACKAGE_VERSION" in serialized + assert "setuptools_scm" not in serialized + assert "Determine expected version" not in serialized