Skip to content

[Crane: crane-migration-python-to-go-full-apm-cli-rewrite] #61

[Crane: crane-migration-python-to-go-full-apm-cli-rewrite]

[Crane: crane-migration-python-to-go-full-apm-cli-rewrite] #61

Workflow file for this run

name: Migration Parity and Benchmarks
on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
enforce_completion:
description: "Fail unless migration completion gates are fully satisfied"
required: false
default: false
type: boolean
permissions:
contents: read
issues: write
pull-requests: write
env:
PYTHON_VERSION: "3.12"
jobs:
detect-changes:
name: Detect Migration Changes
runs-on: ubuntu-24.04
outputs:
should-run: ${{ steps.filter.outputs.should-run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changed paths
id: filter
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ github.event_name }}" = "pull_request" ] && [[ "$HEAD_REF" == crane/* ]]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
git diff --name-only \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}" \
| tee "$RUNNER_TEMP/changed-files.txt"
if grep -Eq '^(\.crane/|\.github/workflows/migration-ci\.yml$|cmd/|internal/|pkg/|go\.mod$|go\.sum$|pyproject\.toml$|scripts/ci/|src/|tests/benchmarks/|tests/parity/|tests/unit/test_crane_score\.py$)' "$RUNNER_TEMP/changed-files.txt"; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
else
echo "should-run=false" >> "$GITHUB_OUTPUT"
fi
parity:
name: Python-vs-Go Parity Gate
needs: [detect-changes]
if: needs.detect-changes.outputs.should-run == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install Python reference CLI
run: |
uv sync --extra dev
test -x "$GITHUB_WORKSPACE/.venv/bin/apm"
echo "APM_PYTHON_BIN=$GITHUB_WORKSPACE/.venv/bin/apm" >> "$GITHUB_ENV"
- name: Extract Python behavior contracts
run: |
uv run python scripts/ci/python_behavior_contracts.py extract \
--output "$RUNNER_TEMP/python-behavior-contracts.json"
echo "APM_PYTHON_CONTRACT_INVENTORY=$RUNNER_TEMP/python-behavior-contracts.json" >> "$GITHUB_ENV"
- name: Run CLI-agnostic Python behavior tests
shell: bash
run: |
go build -o "$RUNNER_TEMP/apm-go" ./cmd/apm
set +e
APM_GO_BIN="$RUNNER_TEMP/apm-go" \
uv run pytest tests/parity/test_python_behavior_contracts.py -q --tb=short \
| tee "$RUNNER_TEMP/python-cli-contract-tests.txt"
status=${PIPESTATUS[0]}
set -e
echo "PYTHON_CLI_CONTRACT_STATUS=$status" >> "$GITHUB_ENV"
- name: Run Go parity tests
shell: bash
run: |
enforce_completion=false
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.enforce_completion == true }}" = "true" ]; then
enforce_completion=true
elif [ "${{ github.event_name }}" = "pull_request" ] && [[ "${{ github.event.pull_request.head.ref }}" == crane/* ]]; then
enforce_completion=true
fi
echo "MIGRATION_COMPLETION_ENFORCED=$enforce_completion" >> "$GITHUB_ENV"
if [ "$enforce_completion" = "true" ]; then
export APM_ENFORCE_COMPLETION_GATES=1
fi
set +e
go test -json -skip '^TestGoCutover' ./... | tee "$RUNNER_TEMP/go-test-events.json"
status=${PIPESTATUS[0]}
set -e
echo "GO_TEST_STATUS=$status" >> "$GITHUB_ENV"
- name: Run Go-only cutover gate
shell: bash
run: |
set +e
APM_PYTHON_BIN= \
APM_PYTHON_CONTRACT_INVENTORY= \
PYTHONPATH= \
VIRTUAL_ENV= \
go test -json ./cmd/apm -run '^TestGoCutover' \
| tee "$RUNNER_TEMP/go-cutover-events.json"
status=${PIPESTATUS[0]}
set -e
cat "$RUNNER_TEMP/go-cutover-events.json" >> "$RUNNER_TEMP/go-test-events.json"
echo "GO_CUTOVER_STATUS=$status" >> "$GITHUB_ENV"
- name: Compute migration score
run: |
go run .crane/scripts/score.go < "$RUNNER_TEMP/go-test-events.json" | tee "$RUNNER_TEMP/migration-score.json"
uv run python scripts/ci/python_behavior_contracts.py check \
--inventory "$RUNNER_TEMP/python-behavior-contracts.json" \
--coverage tests/parity/python_contract_coverage.yml \
--allow-intentionally-incomplete \
--summary "$RUNNER_TEMP/python-contract-coverage.md" || true
python - "$RUNNER_TEMP/migration-score.json" "${MIGRATION_COMPLETION_ENFORCED:-false}" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as fh:
score = json.load(fh)
enforce_completion = sys.argv[2].lower() == "true"
print(json.dumps(score, indent=2, sort_keys=True))
if not enforce_completion:
print(
"::notice::Non-enforcing migration evidence run; "
"completion gates are enforced only for crane/* PRs and "
"manual runs with enforce_completion=true."
)
raise SystemExit(0)
if score.get("progress") != 1.0:
raise SystemExit("progress must be 1.0 for completion parity")
if score.get("migration_score") == 1.0 and not score.get("deletion_grade_ready"):
raise SystemExit("migration_score 1.0 requires deletion_grade_ready")
PY
if [ "${MIGRATION_COMPLETION_ENFORCED:-false}" = "true" ]; then
test "${PYTHON_CLI_CONTRACT_STATUS:-1}" = "0"
test "${GO_TEST_STATUS:-1}" = "0"
test "${GO_CUTOVER_STATUS:-1}" = "0"
else
if [ "${PYTHON_CLI_CONTRACT_STATUS:-1}" != "0" ]; then
echo "::notice::Python behavior contract tests are incomplete in collection mode."
fi
if [ "${GO_TEST_STATUS:-1}" != "0" ]; then
echo "::notice::Go parity tests are incomplete in collection mode."
fi
if [ "${GO_CUTOVER_STATUS:-1}" != "0" ]; then
echo "::notice::Go-only cutover gate is incomplete in collection mode."
fi
fi
- name: Upload parity evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: migration-parity-evidence
path: |
${{ runner.temp }}/go-test-events.json
${{ runner.temp }}/go-cutover-events.json
${{ runner.temp }}/migration-score.json
${{ runner.temp }}/python-behavior-contracts.json
${{ runner.temp }}/python-contract-coverage.md
${{ runner.temp }}/python-cli-contract-tests.txt
if-no-files-found: ignore
retention-days: 14
benchmarks:
name: Migration Benchmarks
needs: [detect-changes, parity]
if: always() && needs.detect-changes.outputs.should-run == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install dependencies
run: uv sync --extra dev
- name: Build Go CLI
run: go build -o "$RUNNER_TEMP/apm-go" ./cmd/apm
- name: Run Python-vs-Go CLI benchmark
shell: bash
run: |
enforce_completion=false
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.enforce_completion == true }}" = "true" ]; then
enforce_completion=true
elif [ "${{ github.event_name }}" = "pull_request" ] && [[ "${{ github.event.pull_request.head.ref }}" == crane/* ]]; then
enforce_completion=true
fi
extra_args=()
if [ "$enforce_completion" != "true" ]; then
extra_args+=(--allow-failures)
fi
python scripts/ci/migration_cli_benchmark.py \
--python-bin "$GITHUB_WORKSPACE/.venv/bin/apm" \
--go-bin "$RUNNER_TEMP/apm-go" \
--json-out "$RUNNER_TEMP/migration-cli-benchmark.json" \
--markdown-out "$RUNNER_TEMP/migration-cli-benchmark.md" \
--max-ratio 5.0 \
"${extra_args[@]}"
- name: Run Python scaling guards
run: uv run pytest tests/benchmarks/test_scaling_guards.py -v
- name: Add benchmark summary
if: always()
run: |
if [ -f "$RUNNER_TEMP/migration-cli-benchmark.md" ]; then
cat "$RUNNER_TEMP/migration-cli-benchmark.md" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Post benchmark PR comment
if: always() && github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [ ! -f "$RUNNER_TEMP/migration-cli-benchmark.md" ]; then
echo "No migration benchmark markdown found; skipping PR comment."
exit 0
fi
marker="<!-- apm-migration-benchmark:${HEAD_SHA} -->"
{
echo "$marker"
echo "## Migration Benchmark Results"
echo
echo "- **Commit**: \`${HEAD_SHA}\`"
echo "- **Run**: ${RUN_URL}"
echo
cat "$RUNNER_TEMP/migration-cli-benchmark.md"
} > "$RUNNER_TEMP/migration-benchmark-pr-comment.md"
comment_id=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \
--jq ".[] | select(.body | contains(\"${marker}\")) | .id" | tail -n 1)
if [ -n "$comment_id" ]; then
gh api \
--method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" \
--field body@"$RUNNER_TEMP/migration-benchmark-pr-comment.md"
else
gh pr comment "$PR_NUMBER" --body-file "$RUNNER_TEMP/migration-benchmark-pr-comment.md"
fi
- name: Upload benchmark evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: migration-benchmark-evidence
path: |
${{ runner.temp }}/migration-cli-benchmark.json
${{ runner.temp }}/migration-cli-benchmark.md
if-no-files-found: ignore
retention-days: 14