t27.aiРусский

The review jam

You will learn

Which of ten cases a stuck pull request is in, and why the earliest matching rule wins.

A pull request can stall for many reasons at once: refused, pushed again, and red on a noise check. review-jam.t27 gives the Queen one answer. jam_case checks 9 facts in a fixed order and the earliest rule that matches wins, so a PR the Queen could not measure is never reported as free, and one that was refused and then pushed is not taken for noise. There are 10 cases, from none to unmeasured.

Try it

Clear facts one by one from the start state and name each case you pass; then find a PR that is in no jam.

Open the interactive lesson →

The review jam: which case a stuck pull request is in
The review jam: which case a stuck pull request is in ↗

Nine facts about a pull request that will not move. Toggle them: the earliest rule that matches wins, and the pull request lands on that rule.

specs/automation/review-jam.t27

// Review jam: which case a stuck pull request is in, and the Queen's action.
// Part of EPIC #5388. Contract: gHashTag/t27#9032 (re-filed from #5700).
// `tri jam` (gHashTag/999-multibots-telegraf) imports these numbers instead
// of hand-copying them.

module review_jam;

// Jam case constants (u8, value = position, starting at 0)
pub const JAM_NONE: u8 = 0;
pub const JAM_NO_REVIEWER: u8 = 1;
pub const JAM_STACKED_ON_OPEN: u8 = 2;
pub const JAM_REFUSED_NOT_FIXED: u8 = 3;
pub const JAM_REFUSED_THEN_PUSHED: u8 = 4;
pub const JAM_LABELED_NOT_MERGED: u8 = 5;
pub const JAM_CONFLICTING: u8 = 6;
pub const JAM_NOISE_RED: u8 = 7;
pub const JAM_DRAFT_OR_FRESH: u8 = 8;
pub const JAM_UNMEASURED: u8 = 9;

// Queen action constants (u8, value = position, starting at 0)
pub const ACT_NOTHING: u8 = 0;
pub const ACT_DISPATCH_REVIEWER: u8 = 1;
pub const ACT_WAIT_FOR_BASE: u8 = 2;
pub const ACT_DISPATCH_CODER: u8 = 3;
pub const ACT_DISPATCH_RE_REVIEW: u8 = 4;
pub const ACT_FINISH_MERGE: u8 = 5;
pub const ACT_REBASE_BY_CODER: u8 = 6;
pub const ACT_NAME_NOISE_AND_REVIEW: u8 = 7;
pub const ACT_WAIT: u8 = 8;
pub const ACT_MEASURE_AGAIN: u8 = 9;

// The jam case of a pull request. The FIRST matching rule wins:
//  1. not measured                      -> UNMEASURED
//  2. draft or fresh                    -> DRAFT_OR_FRESH
//  3. stacked on an open PR             -> STACKED_ON_OPEN
//  4. conflicting                       -> CONFLICTING
//  5. labeled reviewed (and still open) -> LABELED_NOT_MERGED
//  6. refused and pushed after          -> REFUSED_THEN_PUSHED
//  7. refused                           -> REFUSED_NOT_FIXED
//  8. only noise checks red             -> NOISE_RED
//  9. no reviewer                       -> NO_REVIEWER
// 10. otherwise                         -> NONE
pub fn jam_case(measured: bool, draft_or_fresh: bool, stacked_on_open: bool, conflicting: bool, labeled_reviewed: bool, refused: bool, pushed_after_refusal: bool, has_reviewer: bool, only_noise_red: bool) -> u8 {
    if !measured {
        return JAM_UNMEASURED;
    }
    if draft_or_fresh {
        return JAM_DRAFT_OR_FRESH;
    }
    if stacked_on_open {
        return JAM_STACKED_ON_OPEN;
    }
    if conflicting {
        return JAM_CONFLICTING;
    }
    if labeled_reviewed {
        return JAM_LABELED_NOT_MERGED;
    }
    if refused && pushed_after_refusal {
        return JAM_REFUSED_THEN_PUSHED;
    }
    if refused {
        return JAM_REFUSED_NOT_FIXED;
    }
    if only_noise_red {
        return JAM_NOISE_RED;
    }
    if !has_reviewer {
        return JAM_NO_REVIEWER;
    }
    JAM_NONE
}

// The Queen's action for a case: the action with the SAME position.
// Any value above 9 is an unknown case, and an unknown case is never
// "nothing to do": it maps to ACT_MEASURE_AGAIN.
pub fn jam_action(case: u8) -> u8 {
    if case > 9 {
        return ACT_MEASURE_AGAIN;
    }
    case
}

// Whether the case is the Queen's turn to act: true for every case except
// NONE, DRAFT_OR_FRESH and STACKED_ON_OPEN.
pub fn jam_is_queens_turn(case: u8) -> bool {
    case != JAM_NONE && case != JAM_DRAFT_OR_FRESH && case != JAM_STACKED_ON_OPEN
}

// ---- jam_case: one test per case ----

test "unmeasured beats every other flag" {
    assert jam_case(false, true, true, true, true, true, true, true, true) == JAM_UNMEASURED;
}

test "unmeasured with every other flag clear" {
    assert jam_case(false, false, false, false, false, false, false, false, false) == JAM_UNMEASURED;
}

test "draft or fresh" {
    assert jam_case(true, true, false, false, false, false, false, false, false) == JAM_DRAFT_OR_FRESH;
}

test "stacked on open PR" {
    assert jam_case(true, false, true, false, false, false, false, true, false) == JAM_STACKED_ON_OPEN;
}

test "conflicting" {
    assert jam_case(true, false, false, true, false, false, false, true, false) == JAM_CONFLICTING;
}

test "labeled not merged" {
    assert jam_case(true, false, false, false, true, false, false, true, false) == JAM_LABELED_NOT_MERGED;
}

test "refused then pushed" {
    assert jam_case(true, false, false, false, false, true, true, true, false) == JAM_REFUSED_THEN_PUSHED;
}

test "refused not fixed" {
    assert jam_case(true, false, false, false, false, true, false, true, false) == JAM_REFUSED_NOT_FIXED;
}

test "noise red" {
    assert jam_case(true, false, false, false, false, false, false, true, true) == JAM_NOISE_RED;
}

test "no reviewer" {
    assert jam_case(true, false, false, false, false, false, false, false, false) == JAM_NO_REVIEWER;
}

test "none" {
    assert jam_case(true, false, false, false, false, false, false, true, false) == JAM_NONE;
}

// ---- jam_case: first-match order, each rule against the next one ----

test "draft beats stacked" {
    assert jam_case(true, true, true, false, false, false, false, true, false) == JAM_DRAFT_OR_FRESH;
}

test "stacked and unreviewed is stacked, not no reviewer" {
    assert jam_case(true, false, true, false, false, false, false, false, false) == JAM_STACKED_ON_OPEN;
}

test "stacked beats conflicting" {
    assert jam_case(true, false, true, true, false, false, false, true, false) == JAM_STACKED_ON_OPEN;
}

test "conflicting beats labeled" {
    assert jam_case(true, false, false, true, true, false, false, true, false) == JAM_CONFLICTING;
}

test "labeled beats refused then pushed" {
    assert jam_case(true, false, false, false, true, true, true, true, false) == JAM_LABELED_NOT_MERGED;
}

test "refused then pushed beats noise red" {
    assert jam_case(true, false, false, false, false, true, true, true, true) == JAM_REFUSED_THEN_PUSHED;
}

test "refused not fixed beats noise red" {
    assert jam_case(true, false, false, false, false, true, false, true, true) == JAM_REFUSED_NOT_FIXED;
}

test "noise red beats no reviewer" {
    assert jam_case(true, false, false, false, false, false, false, false, true) == JAM_NOISE_RED;
}

test "pushed without a refusal is not refused then pushed" {
    assert jam_case(true, false, false, false, false, false, true, true, false) == JAM_NONE;
}

// ---- jam_action: same position, unknown -> measure again ----

test "action none to nothing" {
    assert jam_action(JAM_NONE) == ACT_NOTHING;
}

test "action no reviewer to dispatch reviewer" {
    assert jam_action(JAM_NO_REVIEWER) == ACT_DISPATCH_REVIEWER;
}

test "action stacked to wait for base" {
    assert jam_action(JAM_STACKED_ON_OPEN) == ACT_WAIT_FOR_BASE;
}

test "action refused not fixed to dispatch coder" {
    assert jam_action(JAM_REFUSED_NOT_FIXED) == ACT_DISPATCH_CODER;
}

test "action refused then pushed to dispatch re-review" {
    assert jam_action(JAM_REFUSED_THEN_PUSHED) == ACT_DISPATCH_RE_REVIEW;
}

test "action labeled not merged to finish merge" {
    assert jam_action(JAM_LABELED_NOT_MERGED) == ACT_FINISH_MERGE;
}

test "action conflicting to rebase by coder" {
    assert jam_action(JAM_CONFLICTING) == ACT_REBASE_BY_CODER;
}

test "action noise red to name noise and review" {
    assert jam_action(JAM_NOISE_RED) == ACT_NAME_NOISE_AND_REVIEW;
}

test "action draft or fresh to wait" {
    assert jam_action(JAM_DRAFT_OR_FRESH) == ACT_WAIT;
}

test "action unmeasured to measure again" {
    assert jam_action(JAM_UNMEASURED) == ACT_MEASURE_AGAIN;
}

test "action first unknown case measures again" {
    assert jam_action(10) == ACT_MEASURE_AGAIN;
}

test "action unknown case 200 measures again" {
    assert jam_action(200) == ACT_MEASURE_AGAIN;
}

// ---- jam_is_queens_turn ----

test "queens turn for no reviewer" {
    assert jam_is_queens_turn(JAM_NO_REVIEWER) == true;
}

test "queens turn for refused not fixed" {
    assert jam_is_queens_turn(JAM_REFUSED_NOT_FIXED) == true;
}

test "queens turn for refused then pushed" {
    assert jam_is_queens_turn(JAM_REFUSED_THEN_PUSHED) == true;
}

test "queens turn for labeled not merged" {
    assert jam_is_queens_turn(JAM_LABELED_NOT_MERGED) == true;
}

test "queens turn for conflicting" {
    assert jam_is_queens_turn(JAM_CONFLICTING) == true;
}

test "queens turn for noise red" {
    assert jam_is_queens_turn(JAM_NOISE_RED) == true;
}

test "queens turn for unmeasured" {
    assert jam_is_queens_turn(JAM_UNMEASURED) == true;
}

test "not queens turn for none" {
    assert jam_is_queens_turn(JAM_NONE) == false;
}

test "not queens turn for draft or fresh" {
    assert jam_is_queens_turn(JAM_DRAFT_OR_FRESH) == false;
}

test "not queens turn for stacked on open" {
    assert jam_is_queens_turn(JAM_STACKED_ON_OPEN) == false;
}

// ---- the two scenarios of #9032, end to end ----

test "scenario bee-reviewed but still open finishes the merge" {
    assert jam_action(jam_case(true, false, false, false, true, false, false, true, false)) == ACT_FINISH_MERGE;
}

test "scenario failed read is never reported as free" {
    assert jam_action(jam_case(false, false, false, false, false, false, false, true, false)) == ACT_MEASURE_AGAIN;
}

Open the lesson's spec in the player ↗

All lessons