Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 84 additions & 30 deletions .github/workflows/scripts/crane_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,18 @@
def parse_machine_state(content):
"""Parse the [*] Machine State table from a state file. Returns a dict."""
state = {}
m = re.search(r"## [*] Machine State.*?\n(.*?)(?=\n## |\Z)", content, re.DOTALL)
m = re.search(
r"##\s+(?:\[\*\]|\*)\s+Machine State.*?\n(.*?)(?=\n## |\Z)",
content,
re.DOTALL,
)
if not m:
return state
section = m.group(0)
for row in re.finditer(r"\|\s*(.+?)\s*\|\s*(.+?)\s*\|", section):
raw_key = row.group(1).strip()
raw_val = row.group(2).strip()
if raw_key.lower() in ("field", "---", ":---", ":---:", "---:"):
if raw_key.lower() == "field" or re.fullmatch(r":?-+:?", raw_key):
continue
key = raw_key.lower().replace(" ", "_")
val = None if raw_val in ("--", "-", "") else raw_val
Expand Down Expand Up @@ -231,6 +235,60 @@ def check_skip_conditions(state, issue_active=False):
return False, None


def evaluate_completed_label_recovery(
name,
state,
issue_active,
issue_completed_label,
repo,
github_token,
find_pr=None,
check_gate=None,
):
"""Return stale-completion recovery state for issue-based migrations.

Completed-label issues are only trustworthy when Crane can positively
confirm the current PR-head gate. A missing PR, pending checks, failing
checks, or unavailable gate evidence means the completed state is stale and
the migration should be selected again.
"""
if find_pr is None:
find_pr = find_existing_pr_for_branch
if check_gate is None:
check_gate = get_pr_head_check_gate

has_stale_completed_state = issue_active and is_completed_state(state)
recovered_completed_issue = False
recovery_event = None

if issue_completed_label and is_completed_state(state) and not issue_active:
existing_pr_for_recovery = find_pr(repo, name, github_token)
if existing_pr_for_recovery:
gate_passed, gate_reason = check_gate(
repo, existing_pr_for_recovery, github_token
)
if gate_passed is True:
recovery_event = (
"confirmed",
existing_pr_for_recovery,
gate_reason,
)
else:
has_stale_completed_state = True
recovered_completed_issue = True
recovery_event = (
"stale_gate",
existing_pr_for_recovery,
gate_reason or "gate-unavailable",
)
else:
has_stale_completed_state = True
recovered_completed_issue = True
recovery_event = ("stale_no_pr", None, "no-open-migration-pr")

return has_stale_completed_state, recovered_completed_issue, recovery_event


# ---------------------------------------------------------------------------
# I/O helpers
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -765,37 +823,33 @@ def main():
else:
print(" {}: no state found (first run)".format(name))

has_stale_completed_state = issue_active and is_completed_state(state)
recovered_completed_issue = False
if issue_completed_label and is_completed_state(state) and not issue_active:
existing_pr_for_recovery = find_existing_pr_for_branch(repo, name, github_token)
if existing_pr_for_recovery:
gate_passed, gate_reason = get_pr_head_check_gate(
repo, existing_pr_for_recovery, github_token
)
if gate_passed is False:
has_stale_completed_state = True
recovered_completed_issue = True
print(
" {}: crane-completed label is stale; PR #{} gate is {}".format(
name, existing_pr_for_recovery, gate_reason
)
)
elif gate_passed is True:
print(
" {}: crane-completed label confirmed by PR #{} gate {}".format(
name, existing_pr_for_recovery, gate_reason
)
has_stale_completed_state, recovered_completed_issue, recovery_event = (
evaluate_completed_label_recovery(
name,
state,
issue_active,
issue_completed_label,
repo,
github_token,
)
)
if recovery_event:
event_kind, recovery_pr, gate_reason = recovery_event
if event_kind == "confirmed":
print(
" {}: crane-completed label confirmed by PR #{} gate {}".format(
name, recovery_pr, gate_reason
)
else:
print(
" {}: could not evaluate completed-label recovery for PR #{} ({})".format(
name, existing_pr_for_recovery, gate_reason
)
)
elif event_kind == "stale_gate":
print(
" {}: crane-completed label is stale; PR #{} gate is {}".format(
name, recovery_pr, gate_reason
)
else:
)
elif event_kind == "stale_no_pr":
print(
" {}: crane-completed label present, but no open migration PR was found".format(
" {}: crane-completed label is stale; no open migration PR was found".format(
name
)
)
Expand Down
67 changes: 67 additions & 0 deletions tests/unit/test_crane_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ def test_machine_state_completed_string_is_recognized() -> None:
assert crane_scheduler.is_completed_state({"completed": "true"}) is True


def test_parse_machine_state_accepts_bracketed_status_heading() -> None:
state = crane_scheduler.parse_machine_state(
"""# Crane: sample

## [*] Machine State

| Field | Value |
|-------|-------|
| Last Run | 2026-06-05T16:10:36Z |
| Iteration Count | 67 |
| PR | #104 |
| Completed | true |
| Recent Statuses | accepted, rejected |

---

## [list] Migration Info
"""
)

assert state["last_run"] == "2026-06-05T16:10:36Z"
assert state["iteration_count"] == 67
assert state["completed"] is True
assert state["recent_statuses"] == ["accepted", "rejected"]
assert "-------" not in state


def test_issue_label_detection_accepts_github_label_payloads() -> None:
issue = {"labels": [{"name": "crane-completed"}, "automation"]}

Expand All @@ -52,6 +79,46 @@ def test_issue_label_detection_accepts_github_label_payloads() -> None:
assert crane_scheduler._issue_has_label(issue, "crane-migration") is False


def test_completed_label_without_open_pr_is_recovered_as_stale() -> None:
stale, recovered, event = crane_scheduler.evaluate_completed_label_recovery(
"crane-migration-python-to-go-full-apm-cli-rewrite",
{"completed": True},
issue_active=False,
issue_completed_label=True,
repo="githubnext/apm",
github_token="token",
find_pr=lambda *_args: None,
)

assert stale is True
assert recovered is True
assert event == ("stale_no_pr", None, "no-open-migration-pr")

should_skip, reason = crane_scheduler.check_skip_conditions(
{"completed": True},
issue_active=recovered,
)
assert should_skip is False
assert reason is None


def test_completed_label_with_unknown_pr_gate_is_recovered_as_stale() -> None:
stale, recovered, event = crane_scheduler.evaluate_completed_label_recovery(
"crane-migration-python-to-go-full-apm-cli-rewrite",
{"completed": True},
issue_active=False,
issue_completed_label=True,
repo="githubnext/apm",
github_token="token",
find_pr=lambda *_args: 104,
check_gate=lambda *_args: (None, "checks-unavailable:2699b7d"),
)

assert stale is True
assert recovered is True
assert event == ("stale_gate", 104, "checks-unavailable:2699b7d")


def test_pr_head_gate_fails_when_any_check_is_not_success() -> None:
def fake_http_get_json(url, _headers, timeout=30):
del timeout
Expand Down
Loading