diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 1ae64d499c..8ade27b113 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -187,18 +187,21 @@ jobs: - if: steps.have.outputs.ok env: {GH_TOKEN: "${{ github.token }}", CLAW_TOKEN: "${{ steps.tok.outputs.token }}", R: "${{ github.repository }}"} run: | - printf '#include "gen/c/claw/refresh.c"\n#include \n#include \nint main(void){size_t k=1<<22,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);if(n==k)return 3;size_t m=refresh_plan(b,n,o,k);if(m>k)return 4;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - - list() { gh api graphql --paginate -F owner="${R%/*}" -F name="${R#*/}" -f query='query($owner:String!,$name:String!,$endCursor:String){repository(owner:$owner,name:$name){pullRequests(states:OPEN,first:100,after:$endCursor,orderBy:{field:CREATED_AT,direction:ASC}){pageInfo{hasNextPage endCursor} nodes{number headRefOid createdAt isDraft isCrossRepository maintainerCanModify mergeable mergeStateStatus baseRefName headRepository{nameWithOwner} labels(first:50){nodes{name}}}}}}' --jq '.data.repository.pullRequests.nodes[]|[.number,.headRefOid,.createdAt,.isDraft,([.labels.nodes[].name]|join(",")),.isCrossRepository,(.headRepository.nameWithOwner//""),.maintainerCanModify,.mergeable,.mergeStateStatus,.baseRefName]|@tsv'; } + printf '#include "gen/c/claw/refresh.c"\n#include \n#include \nint main(int c,char**v){size_t k=1<<22,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);if(n==k)return 3;size_t m=refresh_plan(b,n,o,k,atoi(v[1]));if(m>k)return 4;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - + skip() { echo "::warning::claw/refresh $1"; echo "claw/refresh updated nothing: $1" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + # Pages stay small (a page of 100 PRs with 50 labels each came back as HTML). A failed page fails the jq step: try three times, keep nothing of a failed try. + list() { for t in 1 2 3; do gh api graphql --paginate -F owner="${R%/*}" -F name="${R#*/}" -f query='query($owner:String!,$name:String!,$endCursor:String){repository(owner:$owner,name:$name){pullRequests(states:OPEN,first:30,after:$endCursor,orderBy:{field:CREATED_AT,direction:ASC}){pageInfo{hasNextPage endCursor} nodes{number headRefOid createdAt isDraft isCrossRepository maintainerCanModify mergeable mergeStateStatus baseRefName headRepository{nameWithOwner} autoMergeRequest{enabledAt} labels(first:10){nodes{name}}}}}}' --jq '.data.repository.pullRequests.nodes[]|[.number,.headRefOid,.createdAt,.isDraft,([.labels.nodes[].name]|join(",")),.isCrossRepository,(.headRepository.nameWithOwner//""),.maintainerCanModify,.mergeable,.mergeStateStatus,.baseRefName,(.autoMergeRequest!=null)]|@tsv' > /tmp/page 2>/dev/null /dev/null - sleep 30 - list > /tmp/prs.tsv + list > /dev/null || :; sleep 30 + list > /tmp/prs.tsv || skip "the pull request listing failed three times, so no plan was made." + # Every update-branch starts about 26 workflows: refresh.t27 decides from the queued-run count, and an unread count is -1. + q=$(gh api "repos/$R/actions/runs?status=queued&per_page=1" -q .total_count 2>/dev/null /dev/null /tmp/plan || { echo "::warning::claw/refresh could not build a plan"; exit 0; } + printf '%s\t%s\t%s\n' "$(printf '%s' "$line" | cut -f1-10)" "${bh:--}" "$(printf '%s' "$line" | cut -f12)" + done < /tmp/prs.tsv | /tmp/feed "$q" > /tmp/plan || skip "could not build a plan." while IFS=$'\t' read -r k a b; do case "$k" in R) if err=$(GH_TOKEN="$CLAW_TOKEN" gh api -X PUT "repos/$R/pulls/$a/update-branch" -f expected_head_sha="$b" 2>&1 QUEUE_LIMIT)) { + size_t y = put_str(out, cap, 0, SKIP_HEAD, SKIP_HEAD_LEN); + y = put_count(out, cap, y, ((uint32_t)(queued))); + y = put_str(out, cap, y, SKIP_BUSY, SKIP_BUSY_LEN); + return y; + } uint8_t dec[512] = {0}; uint32_t num[512] = {0}; size_t cs[512] = {0}; size_t ss[512] = {0}; uint32_t rk[512] = {0}; - size_t f[22] = {0}; - uint32_t st[10] = {0}; + size_t f[24] = {0}; + uint32_t st[11] = {0}; size_t cnt = 0; uint32_t extra = 0; size_t ls = 0; @@ -402,8 +433,10 @@ size_t refresh_plan(uint8_t* raw, size_t n, uint8_t* out, size_t cap) { w = put_str(out, cap, w, SUM_TEXTS[5], SUM_LENS[5]); w = put_count(out, cap, w, st[((size_t)(D_CAPPED))]); w = put_str(out, cap, w, SUM_TEXTS[6], SUM_LENS[6]); - w = put_count(out, cap, w, st[((size_t)(D_BAD))]); + w = put_count(out, cap, w, st[((size_t)(D_LATER))]); w = put_str(out, cap, w, SUM_TEXTS[7], SUM_LENS[7]); + w = put_count(out, cap, w, st[((size_t)(D_BAD))]); + w = put_str(out, cap, w, SUM_TEXTS[8], SUM_LENS[8]); if ((st[((size_t)(D_CONFLICT))] > 0)) { w = put_str(out, cap, w, BLANK_LINE, BLANK_LINE_LEN); i = 0; @@ -429,7 +462,7 @@ size_t t_put2(uint8_t* dst, size_t cap, size_t w, uint32_t v) { return x; } -size_t t_rec(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t ts, bool draft, uint32_t labels, bool cross, bool modify, uint32_t mg, uint32_t sc, uint32_t behind) { +size_t t_rec(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t ts, bool draft, uint32_t labels, bool cross, bool modify, uint32_t mg, uint32_t sc, uint32_t behind, uint32_t am) { size_t x = put_count(dst, cap, w, num); x = put_byte(dst, cap, x, '\t'); x = put_byte(dst, cap, x, digit_byte((num % 10))); @@ -466,6 +499,12 @@ size_t t_rec(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t ts, bool if ((labels == 5)) { x = put_str(dst, cap, x, "bug,x", 5); } + if ((labels == 6)) { + x = put_str(dst, cap, x, "bug,bee-reviewed", 16); + } + if ((labels == 7)) { + x = put_str(dst, cap, x, "bee-reviewed-2", 14); + } x = put_byte(dst, cap, x, '\t'); if (cross) { x = put_str(dst, cap, x, "true\tsomeone/t27\t", 17); @@ -505,6 +544,16 @@ size_t t_rec(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t ts, bool } else { x = put_count(dst, cap, x, behind); } + x = put_byte(dst, cap, x, '\t'); + if ((am == 0)) { + x = put_str(dst, cap, x, "false", 5); + } + if ((am == 1)) { + x = put_str(dst, cap, x, "true", 4); + } + if ((am == 2)) { + x = put_str(dst, cap, x, "maybe", 5); + } return put_byte(dst, cap, x, '\n'); } @@ -718,42 +767,58 @@ size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len) ------------------------------------------------------- */ void test_a_behind_conflict_free_pull_request_is_refreshed(void) { - assert((decide(false, false, false, M_OK, 3) == D_REFRESH)); - assert((decide(false, false, false, M_OK, 1) == D_REFRESH)); + assert((decide(false, false, false, M_OK, 3, true) == D_REFRESH)); + assert((decide(false, false, false, M_OK, 1, true) == D_REFRESH)); } void test_a_pull_request_that_is_not_behind_is_left_alone(void) { - assert((decide(false, false, false, M_OK, 0) == D_CURRENT)); + assert((decide(false, false, false, M_OK, 0, true) == D_CURRENT)); } void test_a_conflicting_pull_request_is_never_refreshed(void) { - assert((decide(false, false, false, M_CONFLICT, 5) == D_CONFLICT)); - assert((decide(false, false, false, M_CONFLICT, 0) == D_CONFLICT)); + assert((decide(false, false, false, M_CONFLICT, 5, true) == D_CONFLICT)); + assert((decide(false, false, false, M_CONFLICT, 0, true) == D_CONFLICT)); } void test_a_held_pull_request_is_never_touched_whatever_else_is_true(void) { - assert((decide(true, false, false, M_OK, 5) == D_HELD)); - assert((decide(true, false, false, M_CONFLICT, 5) == D_HELD)); - assert((decide(true, true, true, M_UNKNOWN, 0) == D_HELD)); + assert((decide(true, false, false, M_OK, 5, true) == D_HELD)); + assert((decide(true, false, false, M_CONFLICT, 5, true) == D_HELD)); + assert((decide(true, true, true, M_UNKNOWN, 0, true) == D_HELD)); } void test_a_draft_is_never_touched(void) { - assert((decide(false, true, false, M_OK, 5) == D_DRAFT)); - assert((decide(false, true, false, M_CONFLICT, 5) == D_DRAFT)); + assert((decide(false, true, false, M_OK, 5, true) == D_DRAFT)); + assert((decide(false, true, false, M_CONFLICT, 5, true) == D_DRAFT)); } void test_a_fork_without_maintainer_edits_is_skipped_not_failed(void) { - assert((decide(false, false, true, M_OK, 5) == D_FORK)); - assert((decide(false, false, true, M_CONFLICT, 5) == D_FORK)); + assert((decide(false, false, true, M_OK, 5, true) == D_FORK)); + assert((decide(false, false, true, M_CONFLICT, 5, true) == D_FORK)); } void test_a_fork_with_maintainer_edits_is_refreshed_like_any_other(void) { - assert((decide(false, false, false, M_OK, 5) == D_REFRESH)); + assert((decide(false, false, false, M_OK, 5, true) == D_REFRESH)); } void test_unread_facts_never_become_a_refresh(void) { - assert((decide(false, false, false, M_UNKNOWN, 5) == D_UNKNOWN)); - assert((decide(false, false, false, M_OK, -1) == D_UNKNOWN)); + assert((decide(false, false, false, M_UNKNOWN, 5, true) == D_UNKNOWN)); + assert((decide(false, false, false, M_OK, -1, true) == D_UNKNOWN)); +} + +void test_a_pull_request_the_merger_will_not_land_next_waits(void) { + assert((decide(false, false, false, M_OK, 3, false) == D_LATER)); + assert((decide(false, false, false, M_OK, 1, false) == D_LATER)); + assert((decide(false, false, false, M_OK, 1, true) == D_REFRESH)); +} + +void test_every_other_reason_outranks_waiting_for_the_merger(void) { + assert((decide(true, false, false, M_OK, 5, false) == D_HELD)); + assert((decide(false, true, false, M_OK, 5, false) == D_DRAFT)); + assert((decide(false, false, true, M_OK, 5, false) == D_FORK)); + assert((decide(false, false, false, M_CONFLICT, 5, false) == D_CONFLICT)); + assert((decide(false, false, false, M_UNKNOWN, 5, false) == D_UNKNOWN)); + assert((decide(false, false, false, M_OK, -1, false) == D_UNKNOWN)); + assert((decide(false, false, false, M_OK, 0, false) == D_CURRENT)); } void test_the_held_label_matches_only_as_a_whole_entry(void) { @@ -775,6 +840,24 @@ void test_the_held_label_matches_only_as_a_whole_entry(void) { assert((rf_label_held(&b, 0, 0) == false)); } +void test_the_merger_label_matches_only_as_a_whole_entry(void) { + uint8_t b[64] = {0}; + size_t n = put_str(&b, 64, 0, "bee-reviewed", 12); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == true)); + n = put_str(&b, 64, 0, "bug,bee-reviewed,x", 18); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == true)); + n = put_str(&b, 64, 0, "bee-reviewed-2", 14); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false)); + n = put_str(&b, 64, 0, "x-bee-reviewed", 14); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false)); + n = put_str(&b, 64, 0, "bee-reviewe", 11); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false)); + n = put_str(&b, 64, 0, "bug,x", 5); + assert((rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false)); + assert((rf_has_label(&b, 0, 0, NEXT_LABEL, NEXT_LABEL_LEN) == false)); + assert((rf_label_held(&b, 0, 0) == false)); +} + void test_a_flag_is_true_false_or_refused(void) { uint8_t b[16] = {0}; size_t n = put_str(&b, 16, 0, "true", 4); @@ -873,23 +956,23 @@ void test_older_goes_by_time_then_by_number(void) { void test_a_behind_clean_pull_request_gets_an_R_line_with_its_sha(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3); + size_t n = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((refresh_fits(d, 4096) == true)); assert((out[0] == 'R')); assert((out[1] == '\t')); assert((buf_has(&out, d, "R\t4217\t7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", 48) == true)); assert((t_lines(&out, d, 'R') == 1)); - assert((buf_has(&out, d, "M\tclaw-refresh: 1 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 unreadable\n", 166) == true)); + assert((buf_has(&out, d, "M\tclaw-refresh: 1 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 not next to land, 0 unreadable\n", 186) == true)); } void test_a_conflicting_pull_request_is_listed_once_and_not_refreshed(void) { uint8_t r[512] = {0}; - size_t n = t_rec(&r, 512, 0, 300, 100, false, 0, false, false, 1, 1, 7); - n = t_rec(&r, 512, n, 301, 200, false, 0, false, false, 0, 0, 2); + size_t n = t_rec(&r, 512, 0, 300, 100, false, 0, false, false, 1, 1, 7, 1); + n = t_rec(&r, 512, n, 301, 200, false, 0, false, false, 0, 0, 2, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 1)); assert((buf_has(&out, d, "R\t301\t", 6) == true)); assert((buf_has(&out, d, "R\t300\t", 6) == false)); @@ -900,23 +983,23 @@ void test_a_conflicting_pull_request_is_listed_once_and_not_refreshed(void) { void test_a_dirty_state_word_alone_is_a_conflict(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 310, 100, false, 0, false, false, 0, 1, 4); + size_t n = t_rec(&r, 256, 0, 310, 100, false, 0, false, false, 0, 1, 4, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, "M\t- #310 ", 9) == true)); } void test_a_draft_and_a_held_pull_request_are_not_touched(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 400, 100, true, 0, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 401, 110, false, 1, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 402, 120, false, 2, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 403, 130, false, 3, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 404, 140, false, 4, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 405, 150, false, 5, false, false, 0, 0, 9); + size_t n = t_rec(&r, 1024, 0, 400, 100, true, 0, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 401, 110, false, 1, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 402, 120, false, 2, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 403, 130, false, 3, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 404, 140, false, 4, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 405, 150, false, 5, false, false, 0, 0, 9, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((buf_has(&out, d, "R\t400\t", 6) == false)); assert((buf_has(&out, d, "R\t401\t", 6) == false)); assert((buf_has(&out, d, "R\t402\t", 6) == false)); @@ -929,10 +1012,10 @@ void test_a_draft_and_a_held_pull_request_are_not_touched(void) { void test_a_fork_without_maintainer_edits_is_skipped_and_one_with_edits_is_refreshed(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 500, 100, false, 0, true, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 501, 110, false, 0, true, true, 0, 0, 9); + size_t n = t_rec(&r, 1024, 0, 500, 100, false, 0, true, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 501, 110, false, 0, true, true, 0, 0, 9, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((buf_has(&out, d, "R\t500\t", 6) == false)); assert((buf_has(&out, d, "R\t501\t", 6) == true)); assert((t_lines(&out, d, 'R') == 1)); @@ -947,20 +1030,20 @@ void test_a_deleted_head_repository_counts_as_a_fork_that_cannot_be_edited(void) n = put_byte(&r, 256, n, 'b'); k += 1; } - n = put_str(&r, 256, n, "\t2026-10-18T00:01:00Z\tfalse\t\tfalse\t\tfalse\tMERGEABLE\tBLOCKED\t5\n", 62); + n = put_str(&r, 256, n, "\t2026-10-18T00:01:00Z\tfalse\t\tfalse\t\tfalse\tMERGEABLE\tBLOCKED\t5\ttrue\n", 67); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 fork without maintainer edits, ", 34) == true)); } void test_a_pull_request_that_is_current_or_not_computed_is_counted_and_left_alone(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 600, 100, false, 0, false, false, 0, 0, 0); - n = t_rec(&r, 1024, n, 601, 110, false, 0, false, false, 2, 3, 5); - n = t_rec(&r, 1024, n, 602, 120, false, 0, false, false, 0, 0, 9999); + size_t n = t_rec(&r, 1024, 0, 600, 100, false, 0, false, false, 0, 0, 0, 1); + n = t_rec(&r, 1024, n, 601, 110, false, 0, false, false, 2, 3, 5, 1); + n = t_rec(&r, 1024, n, 602, 120, false, 0, false, false, 0, 0, 9999, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 already up to date, ", 23) == true)); assert((buf_has(&out, d, " 2 not decidable yet, ", 22) == true)); @@ -968,19 +1051,19 @@ void test_a_pull_request_that_is_current_or_not_computed_is_counted_and_left_alo void test_a_record_that_cannot_be_read_is_counted_and_never_refreshed(void) { uint8_t r[2048] = {0}; - size_t n = put_str(&r, 2048, 0, "700\tnotasha\t2026-10-18T00:01:00Z\tfalse\t\tfalse\tx/y\tfalse\tMERGEABLE\tBLOCKED\t5\n", 76); + size_t n = put_str(&r, 2048, 0, "700\tnotasha\t2026-10-18T00:01:00Z\tfalse\t\tfalse\tx/y\tfalse\tMERGEABLE\tBLOCKED\t5\ttrue\n", 81); n = put_str(&r, 2048, n, "701\tshort record\n", 17); n = put_str(&r, 2048, n, "abc\tdef\n", 8); n = put_str(&r, 2048, n, "0\t", 2); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 4 unreadable\n", 14) == true)); } void test_a_flag_that_is_not_true_or_false_makes_the_record_unreadable(void) { uint8_t r[512] = {0}; - size_t n = t_rec(&r, 512, 0, 710, 100, false, 0, false, false, 0, 0, 5); + size_t n = t_rec(&r, 512, 0, 710, 100, false, 0, false, false, 0, 0, 5, 1); size_t k = 0; while ((k < n)) { if ((((((r[k] == 'f') && (r[(k + 1)] == 'a')) && (r[(k + 2)] == 'l')) && (r[(k + 3)] == 's')) && (r[(k + 4)] == 'e'))) { @@ -990,28 +1073,28 @@ void test_a_flag_that_is_not_true_or_false_makes_the_record_unreadable(void) { k += 1; } uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); } void test_a_number_seen_twice_is_refused_the_second_time(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 800, 100, false, 0, false, false, 0, 0, 5); - n = t_rec(&r, 1024, n, 800, 200, false, 0, false, false, 0, 0, 5); + size_t n = t_rec(&r, 1024, 0, 800, 100, false, 0, false, false, 0, 0, 5, 1); + n = t_rec(&r, 1024, n, 800, 200, false, 0, false, false, 0, 0, 5, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 1)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); } void test_the_oldest_pull_request_comes_first_whatever_the_input_order(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 903, 3000, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 901, 1000, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 902, 2000, false, 0, false, false, 0, 0, 1); + size_t n = t_rec(&r, 1024, 0, 903, 3000, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 901, 1000, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 902, 2000, false, 0, false, false, 0, 0, 1, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 3)); assert((out[0] == 'R')); assert((buf_has(&out, d, "R\t901\t", 6) == true)); @@ -1028,45 +1111,45 @@ void test_the_oldest_pull_request_comes_first_whatever_the_input_order(void) { void test_equal_timestamps_are_ordered_by_number(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 952, 500, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 951, 500, false, 0, false, false, 0, 0, 1); + size_t n = t_rec(&r, 1024, 0, 952, 500, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 951, 500, false, 0, false, false, 0, 0, 1, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((out[4] == '1')); assert((out[5] == '\t')); } -void test_at_most_thirty_are_refreshed_and_they_are_the_oldest(void) { +void test_at_most_five_are_refreshed_and_they_are_the_oldest(void) { uint8_t r[8192] = {0}; size_t n = 0; - uint32_t i = 35; + uint32_t i = 8; while ((i > 0)) { - n = t_rec(&r, 8192, n, (100 + i), (i * 60), false, 0, false, false, 0, 0, 2); + n = t_rec(&r, 8192, n, (100 + i), (i * 60), false, 0, false, false, 0, 0, 2, 1); i -= 1; } uint8_t out[16384] = {0}; - size_t d = refresh_plan(&r, n, &out, 16384); + size_t d = refresh_plan(&r, n, &out, 16384, 0); assert((refresh_fits(d, 16384) == true)); - assert((t_lines(&out, d, 'R') == 30)); + assert((t_lines(&out, d, 'R') == 5)); assert((buf_has(&out, d, "R\t101\t", 6) == true)); - assert((buf_has(&out, d, "R\t130\t", 6) == true)); - assert((buf_has(&out, d, "R\t131\t", 6) == false)); - assert((buf_has(&out, d, "R\t135\t", 6) == false)); - assert((buf_has(&out, d, " 30 to update, ", 15) == true)); - assert((buf_has(&out, d, " 5 over the cap, ", 17) == true)); + assert((buf_has(&out, d, "R\t105\t", 6) == true)); + assert((buf_has(&out, d, "R\t106\t", 6) == false)); + assert((buf_has(&out, d, "R\t108\t", 6) == false)); + assert((buf_has(&out, d, " 5 to update, ", 14) == true)); + assert((buf_has(&out, d, " 3 over the cap, ", 17) == true)); } -void test_thirty_exactly_is_not_capped(void) { +void test_five_exactly_is_not_capped(void) { uint8_t r[8192] = {0}; size_t n = 0; uint32_t i = 1; - while ((i <= 30)) { - n = t_rec(&r, 8192, n, (200 + i), (i * 60), false, 0, false, false, 0, 0, 2); + while ((i <= 5)) { + n = t_rec(&r, 8192, n, (200 + i), (i * 60), false, 0, false, false, 0, 0, 2, 1); i += 1; } uint8_t out[16384] = {0}; - size_t d = refresh_plan(&r, n, &out, 16384); - assert((t_lines(&out, d, 'R') == 30)); + size_t d = refresh_plan(&r, n, &out, 16384, 0); + assert((t_lines(&out, d, 'R') == 5)); assert((buf_has(&out, d, " 0 over the cap, ", 17) == true)); } @@ -1075,51 +1158,156 @@ void test_held_and_conflicting_ones_do_not_use_up_the_cap(void) { size_t n = 0; uint32_t i = 1; while ((i <= 10)) { - n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 1, false, false, 0, 0, 2); + n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 1, false, false, 0, 0, 2, 1); i += 1; } i = 11; while ((i <= 20)) { - n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 0, false, false, 1, 1, 2); + n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 0, false, false, 1, 1, 2, 1); i += 1; } i = 21; while ((i <= 50)) { - n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 0, false, false, 0, 0, 2); + n = t_rec(&r, 16384, n, (300 + i), (i * 60), false, 0, false, false, 0, 0, 2, 1); i += 1; } uint8_t out[16384] = {0}; - size_t d = refresh_plan(&r, n, &out, 16384); - assert((t_lines(&out, d, 'R') == 30)); + size_t d = refresh_plan(&r, n, &out, 16384, 0); + assert((t_lines(&out, d, 'R') == 5)); assert((buf_has(&out, d, "R\t321\t", 6) == true)); - assert((buf_has(&out, d, "R\t350\t", 6) == true)); + assert((buf_has(&out, d, "R\t325\t", 6) == true)); + assert((buf_has(&out, d, "R\t326\t", 6) == false)); assert((t_lines(&out, d, 'M') == 12)); } +void test_the_merger_signal_is_auto_merge_armed_or_the_bee_label(void) { + uint8_t r[2048] = {0}; + size_t n = t_rec(&r, 2048, 0, 401, 100, false, 0, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 402, 200, false, 0, false, false, 0, 0, 2, 1); + n = t_rec(&r, 2048, n, 403, 300, false, 6, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 404, 400, false, 7, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 405, 500, false, 5, false, false, 0, 0, 2, 0); + uint8_t out[4096] = {0}; + size_t d = refresh_plan(&r, n, &out, 4096, 0); + assert((t_lines(&out, d, 'R') == 2)); + assert((buf_has(&out, d, "R\t402\t", 6) == true)); + assert((buf_has(&out, d, "R\t403\t", 6) == true)); + assert((buf_has(&out, d, "R\t401\t", 6) == false)); + assert((buf_has(&out, d, "R\t404\t", 6) == false)); + assert((buf_has(&out, d, "R\t405\t", 6) == false)); + assert((buf_has(&out, d, " 2 to update, ", 14) == true)); + assert((buf_has(&out, d, " 3 not next to land, ", 21) == true)); +} + +void test_pull_requests_that_wait_do_not_use_up_the_cap(void) { + uint8_t r[8192] = {0}; + size_t n = 0; + uint32_t i = 1; + while ((i <= 10)) { + n = t_rec(&r, 8192, n, (500 + i), (i * 60), false, 0, false, false, 0, 0, 2, 0); + i += 1; + } + i = 11; + while ((i <= 16)) { + n = t_rec(&r, 8192, n, (500 + i), (i * 60), false, 0, false, false, 0, 0, 2, 1); + i += 1; + } + uint8_t out[16384] = {0}; + size_t d = refresh_plan(&r, n, &out, 16384, 0); + assert((t_lines(&out, d, 'R') == 5)); + assert((buf_has(&out, d, "R\t511\t", 6) == true)); + assert((buf_has(&out, d, "R\t515\t", 6) == true)); + assert((buf_has(&out, d, "R\t516\t", 6) == false)); + assert((buf_has(&out, d, " 10 not next to land, ", 22) == true)); + assert((buf_has(&out, d, " 1 over the cap, ", 17) == true)); +} + +void test_the_auto_merge_flag_must_be_true_or_false(void) { + uint8_t r[512] = {0}; + size_t n = t_rec(&r, 512, 0, 410, 100, false, 0, false, false, 0, 0, 2, 2); + uint8_t out[4096] = {0}; + size_t d = refresh_plan(&r, n, &out, 4096, 0); + assert((t_lines(&out, d, 'R') == 0)); + assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); + assert((buf_has(&out, d, " 0 not next to land, ", 21) == true)); +} + +void test_a_queue_above_the_limit_stops_every_refresh(void) { + uint8_t r[512] = {0}; + size_t n = t_rec(&r, 512, 0, 420, 100, false, 0, false, false, 0, 0, 2, 1); + uint8_t out[4096] = {0}; + size_t d = refresh_plan(&r, n, &out, 4096, 151); + assert((t_lines(&out, d, 'R') == 0)); + assert((t_lines(&out, d, 'M') == 1)); + assert((d == ((25 + 3) + 49))); + assert((buf_has(&out, d, "M\tclaw-refresh: skipped, 151 Actions runs are queued, above the limit of 150\n", 77) == true)); + d = refresh_plan(&r, n, &out, 4096, 396); + assert((t_lines(&out, d, 'R') == 0)); + assert((buf_has(&out, d, "skipped, 396 Actions runs are queued", 36) == true)); +} + +void test_a_queue_at_the_limit_still_refreshes(void) { + uint8_t r[512] = {0}; + size_t n = t_rec(&r, 512, 0, 421, 100, false, 0, false, false, 0, 0, 2, 1); + uint8_t out[4096] = {0}; + size_t d = refresh_plan(&r, n, &out, 4096, 150); + assert((t_lines(&out, d, 'R') == 1)); + assert((buf_has(&out, d, "skipped", 7) == false)); + d = refresh_plan(&r, n, &out, 4096, 0); + assert((t_lines(&out, d, 'R') == 1)); +} + +void test_an_unread_queue_size_stops_every_refresh(void) { + uint8_t r[512] = {0}; + size_t n = t_rec(&r, 512, 0, 422, 100, false, 0, false, false, 0, 0, 2, 1); + uint8_t out[4096] = {0}; + size_t d = refresh_plan(&r, n, &out, 4096, -1); + assert((t_lines(&out, d, 'R') == 0)); + assert((t_lines(&out, d, 'M') == 1)); + assert((d == (25 + 41))); + assert((buf_has(&out, d, "M\tclaw-refresh: skipped, the Actions queue size could not be read\n", 66) == true)); +} + +void test_the_skip_texts_are_pinned_by_their_lengths(void) { + uint8_t b[96] = {0}; + size_t w = put_str(&b, 96, 0, SKIP_HEAD, SKIP_HEAD_LEN); + assert((b[(w - 1)] == ' ')); + assert((b[(w - 2)] == ',')); + w = put_str(&b, 96, 0, SKIP_BUSY, SKIP_BUSY_LEN); + assert((b[0] == ' ')); + assert((b[(w - 1)] == '\n')); + assert((b[(w - 2)] == '0')); + w = put_str(&b, 96, 0, SKIP_UNREAD, SKIP_UNREAD_LEN); + assert((b[(w - 1)] == '\n')); + assert((b[(w - 2)] == 'd')); + assert((QUEUE_LIMIT == 150)); + assert((REFRESH_CAP == 5)); +} + void test_an_empty_list_is_a_valid_empty_answer(void) { uint8_t r[4] = {0}; uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, 0, &out, 4096); + size_t d = refresh_plan(&r, 0, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); - assert((buf_has(&out, d, "M\tclaw-refresh: 0 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 unreadable\n", 166) == true)); + assert((buf_has(&out, d, "M\tclaw-refresh: 0 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 not next to land, 0 unreadable\n", 186) == true)); assert((t_lines(&out, d, 'M') == 1)); } void test_a_carriage_return_does_not_hide_a_record(void) { uint8_t r[512] = {0}; - size_t n = t_rec(&r, 512, 0, 970, 100, false, 0, false, false, 0, 0, 2); + size_t n = t_rec(&r, 512, 0, 970, 100, false, 0, false, false, 0, 0, 2, 1); r[(n - 1)] = 13; n = put_byte(&r, 512, n, '\n'); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 1)); } void test_a_buffer_too_small_is_reported_not_trusted(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 980, 100, false, 0, false, false, 0, 0, 2); + size_t n = t_rec(&r, 256, 0, 980, 100, false, 0, false, false, 0, 0, 2, 1); uint8_t out[32] = {0}; - size_t d = refresh_plan(&r, n, &out, 32); + size_t d = refresh_plan(&r, n, &out, 32, 0); assert((refresh_fits(d, 32) == false)); assert((refresh_fits(d, d) == true)); } @@ -1127,13 +1315,13 @@ void test_a_buffer_too_small_is_reported_not_trusted(void) { void test_the_summary_texts_are_pinned_by_their_lengths(void) { uint8_t b[64] = {0}; size_t k = 0; - while ((k < 8)) { + while ((k < 9)) { size_t w = put_str(&b, 64, 0, SUM_TEXTS[k], SUM_LENS[k]); assert((w == SUM_LENS[k])); assert((b[0] == ' ')); k += 1; } - size_t w = put_str(&b, 64, 0, SUM_TEXTS[7], SUM_LENS[7]); + size_t w = put_str(&b, 64, 0, SUM_TEXTS[8], SUM_LENS[8]); assert((b[(w - 1)] == '\n')); assert((b[(w - 2)] == 'e')); w = put_str(&b, 64, 0, CONFLICT_TAIL, CONFLICT_TAIL_LEN); @@ -1149,10 +1337,10 @@ void test_the_summary_texts_are_pinned_by_their_lengths(void) { assert((b[(w - 2)] == '\t')); } -void test_one_record_splits_into_eleven_fields_and_classifies_as_refresh(void) { +void test_one_record_splits_into_twelve_fields_and_classifies_as_refresh(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3); - size_t f[22] = {0}; + size_t n = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3, 1); + size_t f[24] = {0}; size_t le = line_end(&r, n, 0); assert((le == (n - 1))); assert((rf_split(&r, 0, le, &f) == true)); @@ -1165,6 +1353,8 @@ void test_one_record_splits_into_eleven_fields_and_classifies_as_refresh(void) { assert((rf_flag(&r, f[14], f[15]) == T_FALSE)); assert((rf_merge_class(&r, f[16], f[17], f[18], f[19]) == M_OK)); assert((rf_behind(&r, f[20], f[21], f[18], f[19]) == 1)); + assert((rf_flag(&r, f[22], f[23]) == T_TRUE)); + assert((f[23] == (n - 1))); assert((rf_classify(&r, &f) == D_REFRESH)); } @@ -1192,36 +1382,36 @@ void test_older_ignores_the_byte_after_the_stamp(void) { void test_one_unreadable_field_alone_makes_the_record_unreadable(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 720, 100, false, 0, false, false, 0, 0, 5); + size_t n = t_rec(&r, 256, 0, 720, 100, false, 0, false, false, 0, 0, 5, 1); assert((r[45] == '2')); assert((r[73] == 'f')); assert((r[92] == 'f')); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 1)); r[45] = 'x'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); r[45] = '2'; r[73] = 'F'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); r[73] = 'f'; r[92] = 'F'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); } void test_a_record_one_field_short_is_unreadable_not_merely_undecidable(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 730, 100, false, 0, false, false, 0, 0, 5); - r[(n - 3)] = '\n'; - n -= 2; + size_t n = t_rec(&r, 256, 0, 730, 100, false, 0, false, false, 0, 0, 5, 1); + r[(n - 6)] = '\n'; + n -= 5; uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); assert((buf_has(&out, d, " 0 not decidable yet, ", 22) == true)); @@ -1230,19 +1420,19 @@ void test_a_record_one_field_short_is_unreadable_not_merely_undecidable(void) { void test_a_blank_line_is_not_a_record(void) { uint8_t r[512] = {0}; size_t n = put_byte(&r, 512, 0, '\n'); - n = t_rec(&r, 512, n, 740, 100, false, 0, false, false, 0, 0, 5); + n = t_rec(&r, 512, n, 740, 100, false, 0, false, false, 0, 0, 5, 1); n = put_byte(&r, 512, n, '\n'); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 1)); assert((buf_has(&out, d, " 0 unreadable\n", 14) == true)); } void test_pull_request_number_zero_is_unreadable(void) { uint8_t r[256] = {0}; - size_t n = t_rec(&r, 256, 0, 0, 100, false, 0, false, false, 0, 0, 5); + size_t n = t_rec(&r, 256, 0, 0, 100, false, 0, false, false, 0, 0, 5, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 0)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); } @@ -1252,22 +1442,22 @@ void test_the_513th_record_is_counted_as_unreadable_and_not_stored(void) { size_t n = 0; uint32_t i = 1; while ((i <= 513)) { - n = t_rec(&r, 65536, n, i, 100, false, 0, false, false, 0, 0, 0); + n = t_rec(&r, 65536, n, i, 100, false, 0, false, false, 0, 0, 0, 1); i += 1; } assert((n < 65536)); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((buf_has(&out, d, "512 already up to date, ", 24) == true)); assert((buf_has(&out, d, " 1 unreadable\n", 14) == true)); } void test_the_hour_digits_decide_the_order(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 9, 39600, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 3, 43200, false, 0, false, false, 0, 0, 1); + size_t n = t_rec(&r, 1024, 0, 9, 39600, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 3, 43200, false, 0, false, false, 0, 0, 1, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 2)); assert((out[2] == '9')); assert((out[47] == '3')); @@ -1275,10 +1465,10 @@ void test_the_hour_digits_decide_the_order(void) { void test_the_second_digits_decide_the_order(void) { uint8_t r[1024] = {0}; - size_t n = t_rec(&r, 1024, 0, 9, 3610, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 3, 3659, false, 0, false, false, 0, 0, 1); + size_t n = t_rec(&r, 1024, 0, 9, 3610, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 3, 3659, false, 0, false, false, 0, 0, 1, 1); uint8_t out[4096] = {0}; - size_t d = refresh_plan(&r, n, &out, 4096); + size_t d = refresh_plan(&r, n, &out, 4096, 0); assert((t_lines(&out, d, 'R') == 2)); assert((out[2] == '9')); assert((out[47] == '3')); @@ -1300,7 +1490,10 @@ int main(void) { test_a_fork_without_maintainer_edits_is_skipped_not_failed(); test_a_fork_with_maintainer_edits_is_refreshed_like_any_other(); test_unread_facts_never_become_a_refresh(); + test_a_pull_request_the_merger_will_not_land_next_waits(); + test_every_other_reason_outranks_waiting_for_the_merger(); test_the_held_label_matches_only_as_a_whole_entry(); + test_the_merger_label_matches_only_as_a_whole_entry(); test_a_flag_is_true_false_or_refused(); test_mergeability_folds_conflict_and_unknown_apart(); test_behind_reads_the_state_word_then_the_count(); @@ -1319,14 +1512,21 @@ int main(void) { test_a_number_seen_twice_is_refused_the_second_time(); test_the_oldest_pull_request_comes_first_whatever_the_input_order(); test_equal_timestamps_are_ordered_by_number(); - test_at_most_thirty_are_refreshed_and_they_are_the_oldest(); - test_thirty_exactly_is_not_capped(); + test_at_most_five_are_refreshed_and_they_are_the_oldest(); + test_five_exactly_is_not_capped(); test_held_and_conflicting_ones_do_not_use_up_the_cap(); + test_the_merger_signal_is_auto_merge_armed_or_the_bee_label(); + test_pull_requests_that_wait_do_not_use_up_the_cap(); + test_the_auto_merge_flag_must_be_true_or_false(); + test_a_queue_above_the_limit_stops_every_refresh(); + test_a_queue_at_the_limit_still_refreshes(); + test_an_unread_queue_size_stops_every_refresh(); + test_the_skip_texts_are_pinned_by_their_lengths(); test_an_empty_list_is_a_valid_empty_answer(); test_a_carriage_return_does_not_hide_a_record(); test_a_buffer_too_small_is_reported_not_trusted(); test_the_summary_texts_are_pinned_by_their_lengths(); - test_one_record_splits_into_eleven_fields_and_classifies_as_refresh(); + test_one_record_splits_into_twelve_fields_and_classifies_as_refresh(); test_a_created_stamp_is_checked_at_every_separator_and_at_its_length(); test_older_ignores_the_byte_after_the_stamp(); test_one_unreadable_field_alone_makes_the_record_unreadable(); @@ -1336,7 +1536,7 @@ int main(void) { test_the_513th_record_is_counted_as_unreadable_and_not_stored(); test_the_hour_digits_decide_the_order(); test_the_second_digits_decide_the_order(); - printf("All %d tests passed.\n", 44); + printf("All %d tests passed.\n", 54); return 0; } #endif /* T27_TEST_MAIN */ diff --git a/scripts/gen_w661.py b/scripts/gen_w661.py deleted file mode 100644 index f7ae6d24c1..0000000000 --- a/scripts/gen_w661.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w661_bench_module_141x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w661_bench_module_141x2p6_aos_var_call_write.t27") - -OUTER = 141 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 70 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w661_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/specs/claw/refresh.t27 b/specs/claw/refresh.t27 index c69ad2b3f4..7470a6e89d 100644 --- a/specs/claw/refresh.t27 +++ b/specs/claw/refresh.t27 @@ -10,13 +10,15 @@ ; feeds this module the open pull requests, and calls the update-branch endpoint for every line this ; module answers `R`. The job holds no decision; every skip below is decided here. ; -- -; The input, one record per pull request on stdin, eleven TAB fields, written by the job from one +; The input, one record per pull request on stdin, twelve TAB fields, written by the job from one ; GraphQL listing and one compare per PR (the fields are copied, never judged): ; number, head sha, createdAt, isDraft, label names joined by commas, isCrossRepository, ; head repository name with owner (empty when the fork is gone), maintainerCanModify, -; mergeable, mergeStateStatus, commits the head is behind its base (`-` when unread) +; mergeable, mergeStateStatus, commits the head is behind its base (`-` when unread), +; autoMerge (`true` when auto-merge is armed on the PR) +; The second input is the number of workflow runs queued on the repository (negative: unread). ; The output, one line per fact, two TAB fields: -; R TAB number TAB head sha refresh this one (at most 30, oldest first) +; R TAB number TAB head sha refresh this one (at most 5, oldest first) ; M TAB text one line for the step summary ; -- ; The decision, first match wins (decide below): @@ -26,11 +28,19 @@ ; conflict CONFLICTING or DIRTY: left alone, listed once in the summary ; unknown GitHub has not computed mergeability, or the behind count is unread: next push ; current not behind -; refresh behind and conflict-free +; later behind and conflict-free, but the merger will not land it next: it has neither +; auto-merge armed nor the bee-reviewed label (the merger's two signals), so a +; refresh would start about 26 workflows that nobody is waiting for +; refresh behind, conflict-free and next to land ; Then at most REFRESH_CAP of the refresh ones are kept, oldest createdAt first (ties: lower number), ; the rest are counted as over the cap and wait for the next merge. ; -- -; Fail closed: a record that is not eleven fields, a number that is not a positive integer, a sha that +; The queue rule (#10545): every update-branch starts about 26 workflows, and on 2026-10-18 the +; Actions queue held 396 queued runs against 13 running. When more than QUEUE_LIMIT runs are queued, +; or the count is unread, the answer is one M line and no R line: a refresh would only lengthen the +; queue that is already the bottleneck. +; -- +; Fail closed: a record that is not twelve fields, a number that is not a positive integer, a sha that ; is not 40 lowercase hex digits, a createdAt that is not YYYY-MM-DDTHH:MM:SSZ, a flag that is not ; `true` or `false`, a number seen twice, or a record past MAX_PRS is never refreshed; it is counted ; as unreadable in the summary line. An unread fact never becomes an API call. @@ -45,10 +55,13 @@ use claw::text; /// Records one run reads; more are counted as unreadable, never silently dropped. pub const MAX_PRS : usize = 512; -/// FR-002: the most pull requests one run refreshes, so one merge cannot flood the Actions queue. -pub const REFRESH_CAP : u32 = 30; +/// FR-002: the most pull requests one run refreshes, so one merge cannot flood the Actions queue +/// (each update-branch starts about 26 workflows). +pub const REFRESH_CAP : u32 = 5; +/// FR-004: above this many queued workflow runs a run refreshes nothing. +pub const QUEUE_LIMIT : i32 = 150; /// Fields of one record. -pub const NFIELDS : usize = 11; +pub const NFIELDS : usize = 12; /// Decisions. D_BAD is an unreadable record. pub const D_NONE : u8 = 0; @@ -61,7 +74,8 @@ pub const D_CURRENT : u8 = 6; pub const D_UNKNOWN : u8 = 7; pub const D_CAPPED : u8 = 8; pub const D_BAD : u8 = 9; -pub const D_COUNT : usize = 10; +pub const D_LATER : u8 = 10; +pub const D_COUNT : usize = 11; /// What GitHub says about mergeability, folded: M_OK may be updated, M_CONFLICT may not, M_UNKNOWN is /// not computed yet (a draft, or a PR whose base moved a moment ago). @@ -77,15 +91,27 @@ pub const T_BAD : u8 = 2; /// The label that holds a pull request out of every automatic step (issue-gate.yml carries the same word). pub const HELD_LABEL : str = "no-auto-merge"; pub const HELD_LABEL_LEN : usize = 13; +/// The label the bee reviewer puts on a pull request it passed: the merger lands those next +/// (auto-merge-ready-prs.yml), as it lands every PR that has auto-merge armed. +pub const NEXT_LABEL : str = "bee-reviewed"; +pub const NEXT_LABEL_LEN : usize = 12; /// Summary pieces, each with its byte length (the tests pin the lengths). pub const SUM_HEAD : str = "M\tclaw-refresh: "; pub const SUM_HEAD_LEN : usize = 16; -pub const SUM_TEXTS : [8]str = [ +pub const SUM_TEXTS : [9]str = [ " to update, ", " in conflict, ", " held or draft, ", " fork without maintainer edits, ", - " already up to date, ", " not decidable yet, ", " over the cap, ", " unreadable\n", + " already up to date, ", " not decidable yet, ", " over the cap, ", " not next to land, ", + " unreadable\n", ]; -pub const SUM_LENS : [8]usize = [12, 14, 16, 32, 21, 20, 15, 12]; +pub const SUM_LENS : [9]usize = [12, 14, 16, 32, 21, 20, 15, 19, 12]; +/// The whole answer when the Actions queue is too long (SKIP_HEAD, the count, SKIP_BUSY) or unread. +pub const SKIP_HEAD : str = "M\tclaw-refresh: skipped, "; +pub const SKIP_HEAD_LEN : usize = 25; +pub const SKIP_BUSY : str = " Actions runs are queued, above the limit of 150\n"; +pub const SKIP_BUSY_LEN : usize = 49; +pub const SKIP_UNREAD : str = "the Actions queue size could not be read\n"; +pub const SKIP_UNREAD_LEN : usize = 41; pub const CONFLICT_HEAD : str = "M\t- #"; pub const CONFLICT_HEAD_LEN : usize = 5; pub const CONFLICT_TAIL : str = " conflicts with its base branch and was left alone\n"; @@ -94,8 +120,8 @@ pub const BLANK_LINE : str = "M\t\n"; pub const BLANK_LINE_LEN : usize = 3; /// The pure decision. fork_blocked is "the head is in a fork and maintainer edits are off"; mc is -/// M_*; bh is 1 behind, 0 current, -1 unread. -pub fn decide(held : bool, draft : bool, fork_blocked : bool, mc : u8, bh : i32) -> u8 { +/// M_*; bh is 1 behind, 0 current, -1 unread; next is "the merger lands this one next". +pub fn decide(held : bool, draft : bool, fork_blocked : bool, mc : u8, bh : i32, next : bool) -> u8 { if (held) { return D_HELD; } if (draft) { return D_DRAFT; } if (fork_blocked) { return D_FORK; } @@ -103,21 +129,27 @@ pub fn decide(held : bool, draft : bool, fork_blocked : bool, mc : u8, bh : i32) if (mc == M_UNKNOWN) { return D_UNKNOWN; } if (bh < 0) { return D_UNKNOWN; } if (bh == 0) { return D_CURRENT; } + if (next == false) { return D_LATER; } return D_REFRESH; } -/// Whether raw[s..e) is a comma list with the held label as one whole entry. -pub fn rf_label_held(raw : []const u8, s : usize, e : usize) -> bool { +/// Whether raw[s..e) is a comma list with the label (ll bytes) as one whole entry. +pub fn rf_has_label(raw : []const u8, s : usize, e : usize, label : str, ll : usize) -> bool { var p : usize = s; while (p <= e) { var q : usize = p; while (q < e and raw[q] != ',') { q += 1; } - if (field_eq(raw, p, q, HELD_LABEL, HELD_LABEL_LEN)) { return true; } + if (field_eq(raw, p, q, label, ll)) { return true; } p = q + 1; } return false; } +/// Whether the held label is one whole entry of the label list raw[s..e). +pub fn rf_label_held(raw : []const u8, s : usize, e : usize) -> bool { + return rf_has_label(raw, s, e, HELD_LABEL, HELD_LABEL_LEN); +} + /// `true`, `false` or neither. pub fn rf_flag(raw : []const u8, s : usize, e : usize) -> u8 { if (field_eq(raw, s, e, "true", 4)) { return T_TRUE; } @@ -228,13 +260,15 @@ fn rf_classify(raw : []const u8, f : []const usize) -> u8 { const draft : u8 = rf_flag(raw, f[6], f[7]); const cross : u8 = rf_flag(raw, f[10], f[11]); const modify : u8 = rf_flag(raw, f[14], f[15]); - if (draft == T_BAD or cross == T_BAD or modify == T_BAD) { return D_BAD; } + const armed : u8 = rf_flag(raw, f[22], f[23]); + if (draft == T_BAD or cross == T_BAD or modify == T_BAD or armed == T_BAD) { return D_BAD; } const held : bool = rf_label_held(raw, f[8], f[9]); const gone : bool = f[13] == f[12]; const fork_blocked : bool = (cross == T_TRUE or gone) and modify != T_TRUE; const mc : u8 = rf_merge_class(raw, f[16], f[17], f[18], f[19]); const bh : i32 = rf_behind(raw, f[20], f[21], f[18], f[19]); - return decide(held, draft == T_TRUE, fork_blocked, mc, bh); + const next : bool = armed == T_TRUE or rf_has_label(raw, f[8], f[9], NEXT_LABEL, NEXT_LABEL_LEN); + return decide(held, draft == T_TRUE, fork_blocked, mc, bh, next); } /// Index of the decision in the summary texts, for the counts that share one text. @@ -249,16 +283,29 @@ fn rf_put_sha(dst : []u8, cap : usize, w : usize, raw : []const u8, s : usize) - } /// The answer for the records in raw[0..n), into out: the R lines oldest first, then the summary. +/// queued is the number of workflow runs queued on the repository (negative: unread); above +/// QUEUE_LIMIT, or unread, the answer is one M line and no R line. /// Returns the bytes it needs; when that exceeds cap the buffer holds a clipped answer that must not /// be acted on. -pub fn refresh_plan(raw : []const u8, n : usize, out : []u8, cap : usize) -> usize { +pub fn refresh_plan(raw : []const u8, n : usize, out : []u8, cap : usize, queued : i32) -> usize { + if (queued < 0) { + var u : usize = put_str(out, cap, 0, SKIP_HEAD, SKIP_HEAD_LEN); + u = put_str(out, cap, u, SKIP_UNREAD, SKIP_UNREAD_LEN); + return u; + } + if (queued > QUEUE_LIMIT) { + var y : usize = put_str(out, cap, 0, SKIP_HEAD, SKIP_HEAD_LEN); + y = put_count(out, cap, y, queued as u32); + y = put_str(out, cap, y, SKIP_BUSY, SKIP_BUSY_LEN); + return y; + } var dec : [512]u8 = undefined; var num : [512]u32 = undefined; var cs : [512]usize = undefined; var ss : [512]usize = undefined; var rk : [512]u32 = undefined; - var f : [22]usize = undefined; - var st : [10]u32 = undefined; + var f : [24]usize = undefined; + var st : [11]u32 = undefined; var cnt : usize = 0; var extra : u32 = 0; var ls : usize = 0; @@ -356,8 +403,10 @@ pub fn refresh_plan(raw : []const u8, n : usize, out : []u8, cap : usize) -> usi w = put_str(out, cap, w, SUM_TEXTS[5], SUM_LENS[5]); w = put_count(out, cap, w, st[D_CAPPED as usize]); w = put_str(out, cap, w, SUM_TEXTS[6], SUM_LENS[6]); - w = put_count(out, cap, w, st[D_BAD as usize]); + w = put_count(out, cap, w, st[D_LATER as usize]); w = put_str(out, cap, w, SUM_TEXTS[7], SUM_LENS[7]); + w = put_count(out, cap, w, st[D_BAD as usize]); + w = put_str(out, cap, w, SUM_TEXTS[8], SUM_LENS[8]); if (st[D_CONFLICT as usize] > 0) { w = put_str(out, cap, w, BLANK_LINE, BLANK_LINE_LEN); i = 0; @@ -389,8 +438,9 @@ fn t_put2(dst : []u8, cap : usize, w : usize, v : u32) -> usize { /// One record. ts is seconds into 2026-10-18 (under a day); labels: 0 none, 1 no-auto-merge, /// 2 bug,no-auto-merge,x, 3 no-auto-merge-2, 4 x-no-auto-merge, 5 bug,x; mg: 0 MERGEABLE, /// 1 CONFLICTING, 2 UNKNOWN; sc: 0 BLOCKED, 1 DIRTY, 2 BEHIND, 3 UNKNOWN; behind: commits behind, -/// 9999 for unread. The sha is 40 hex digits whose first is the last digit of num. -fn t_rec(dst : []u8, cap : usize, w : usize, num : u32, ts : u32, draft : bool, labels : u32, cross : bool, modify : bool, mg : u32, sc : u32, behind : u32) -> usize { +/// 9999 for unread; am: 0 false, 1 true, 2 maybe. The sha is 40 hex digits whose first is the last +/// digit of num. Label code 6 is bug,bee-reviewed, 7 is bee-reviewed-2. +fn t_rec(dst : []u8, cap : usize, w : usize, num : u32, ts : u32, draft : bool, labels : u32, cross : bool, modify : bool, mg : u32, sc : u32, behind : u32, am : u32) -> usize { var x : usize = put_count(dst, cap, w, num); x = put_byte(dst, cap, x, '\t'); x = put_byte(dst, cap, x, digit_byte(num % 10)); @@ -413,6 +463,8 @@ fn t_rec(dst : []u8, cap : usize, w : usize, num : u32, ts : u32, draft : bool, if (labels == 3) { x = put_str(dst, cap, x, "no-auto-merge-2", 15); } if (labels == 4) { x = put_str(dst, cap, x, "x-no-auto-merge", 15); } if (labels == 5) { x = put_str(dst, cap, x, "bug,x", 5); } + if (labels == 6) { x = put_str(dst, cap, x, "bug,bee-reviewed", 16); } + if (labels == 7) { x = put_str(dst, cap, x, "bee-reviewed-2", 14); } x = put_byte(dst, cap, x, '\t'); if (cross) { x = put_str(dst, cap, x, "true\tsomeone/t27\t", 17); } else { x = put_str(dst, cap, x, "false\tgHashTag/t27\t", 19); } if (modify) { x = put_str(dst, cap, x, "true\t", 5); } else { x = put_str(dst, cap, x, "false\t", 6); } @@ -430,6 +482,10 @@ fn t_rec(dst : []u8, cap : usize, w : usize, num : u32, ts : u32, draft : bool, } else { x = put_count(dst, cap, x, behind); } + x = put_byte(dst, cap, x, '\t'); + if (am == 0) { x = put_str(dst, cap, x, "false", 5); } + if (am == 1) { x = put_str(dst, cap, x, "true", 4); } + if (am == 2) { x = put_str(dst, cap, x, "maybe", 5); } return put_byte(dst, cap, x, '\n'); } @@ -447,42 +503,58 @@ fn t_lines(out : []const u8, n : usize, b : u8) -> u32 { // ---- tests: the pure decision ---- test a_behind_conflict_free_pull_request_is_refreshed { - assert decide(false, false, false, M_OK, 3) == D_REFRESH; - assert decide(false, false, false, M_OK, 1) == D_REFRESH; + assert decide(false, false, false, M_OK, 3, true) == D_REFRESH; + assert decide(false, false, false, M_OK, 1, true) == D_REFRESH; } test a_pull_request_that_is_not_behind_is_left_alone { - assert decide(false, false, false, M_OK, 0) == D_CURRENT; + assert decide(false, false, false, M_OK, 0, true) == D_CURRENT; } test a_conflicting_pull_request_is_never_refreshed { - assert decide(false, false, false, M_CONFLICT, 5) == D_CONFLICT; - assert decide(false, false, false, M_CONFLICT, 0) == D_CONFLICT; + assert decide(false, false, false, M_CONFLICT, 5, true) == D_CONFLICT; + assert decide(false, false, false, M_CONFLICT, 0, true) == D_CONFLICT; } test a_held_pull_request_is_never_touched_whatever_else_is_true { - assert decide(true, false, false, M_OK, 5) == D_HELD; - assert decide(true, false, false, M_CONFLICT, 5) == D_HELD; - assert decide(true, true, true, M_UNKNOWN, 0) == D_HELD; + assert decide(true, false, false, M_OK, 5, true) == D_HELD; + assert decide(true, false, false, M_CONFLICT, 5, true) == D_HELD; + assert decide(true, true, true, M_UNKNOWN, 0, true) == D_HELD; } test a_draft_is_never_touched { - assert decide(false, true, false, M_OK, 5) == D_DRAFT; - assert decide(false, true, false, M_CONFLICT, 5) == D_DRAFT; + assert decide(false, true, false, M_OK, 5, true) == D_DRAFT; + assert decide(false, true, false, M_CONFLICT, 5, true) == D_DRAFT; } test a_fork_without_maintainer_edits_is_skipped_not_failed { - assert decide(false, false, true, M_OK, 5) == D_FORK; - assert decide(false, false, true, M_CONFLICT, 5) == D_FORK; + assert decide(false, false, true, M_OK, 5, true) == D_FORK; + assert decide(false, false, true, M_CONFLICT, 5, true) == D_FORK; } test a_fork_with_maintainer_edits_is_refreshed_like_any_other { - assert decide(false, false, false, M_OK, 5) == D_REFRESH; + assert decide(false, false, false, M_OK, 5, true) == D_REFRESH; } test unread_facts_never_become_a_refresh { - assert decide(false, false, false, M_UNKNOWN, 5) == D_UNKNOWN; - assert decide(false, false, false, M_OK, -1) == D_UNKNOWN; + assert decide(false, false, false, M_UNKNOWN, 5, true) == D_UNKNOWN; + assert decide(false, false, false, M_OK, -1, true) == D_UNKNOWN; +} + +test a_pull_request_the_merger_will_not_land_next_waits { + assert decide(false, false, false, M_OK, 3, false) == D_LATER; + assert decide(false, false, false, M_OK, 1, false) == D_LATER; + assert decide(false, false, false, M_OK, 1, true) == D_REFRESH; +} + +test every_other_reason_outranks_waiting_for_the_merger { + assert decide(true, false, false, M_OK, 5, false) == D_HELD; + assert decide(false, true, false, M_OK, 5, false) == D_DRAFT; + assert decide(false, false, true, M_OK, 5, false) == D_FORK; + assert decide(false, false, false, M_CONFLICT, 5, false) == D_CONFLICT; + assert decide(false, false, false, M_UNKNOWN, 5, false) == D_UNKNOWN; + assert decide(false, false, false, M_OK, -1, false) == D_UNKNOWN; + assert decide(false, false, false, M_OK, 0, false) == D_CURRENT; } // ---- tests: reading the fields ---- @@ -506,6 +578,24 @@ test the_held_label_matches_only_as_a_whole_entry { assert rf_label_held(&b, 0, 0) == false; } +test the_merger_label_matches_only_as_a_whole_entry { + var b : [64]u8 = undefined; + var n : usize = put_str(&b, 64, 0, "bee-reviewed", 12); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == true; + n = put_str(&b, 64, 0, "bug,bee-reviewed,x", 18); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == true; + n = put_str(&b, 64, 0, "bee-reviewed-2", 14); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false; + n = put_str(&b, 64, 0, "x-bee-reviewed", 14); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false; + n = put_str(&b, 64, 0, "bee-reviewe", 11); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false; + n = put_str(&b, 64, 0, "bug,x", 5); + assert rf_has_label(&b, 0, n, NEXT_LABEL, NEXT_LABEL_LEN) == false; + assert rf_has_label(&b, 0, 0, NEXT_LABEL, NEXT_LABEL_LEN) == false; + assert rf_label_held(&b, 0, 0) == false; +} + test a_flag_is_true_false_or_refused { var b : [16]u8 = undefined; var n : usize = put_str(&b, 16, 0, "true", 4); @@ -606,23 +696,23 @@ test older_goes_by_time_then_by_number { test a_behind_clean_pull_request_gets_an_R_line_with_its_sha { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3); + const n : usize = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert refresh_fits(d, 4096) == true; assert out[0] == 'R'; assert out[1] == '\t'; assert buf_has(&out, d, "R\t4217\t7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", 48) == true; assert t_lines(&out, d, 'R') == 1; - assert buf_has(&out, d, "M\tclaw-refresh: 1 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 unreadable\n", 166) == true; + assert buf_has(&out, d, "M\tclaw-refresh: 1 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 not next to land, 0 unreadable\n", 186) == true; } test a_conflicting_pull_request_is_listed_once_and_not_refreshed { var r : [512]u8 = undefined; - var n : usize = t_rec(&r, 512, 0, 300, 100, false, 0, false, false, 1, 1, 7); - n = t_rec(&r, 512, n, 301, 200, false, 0, false, false, 0, 0, 2); + var n : usize = t_rec(&r, 512, 0, 300, 100, false, 0, false, false, 1, 1, 7, 1); + n = t_rec(&r, 512, n, 301, 200, false, 0, false, false, 0, 0, 2, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 1; assert buf_has(&out, d, "R\t301\t", 6) == true; assert buf_has(&out, d, "R\t300\t", 6) == false; @@ -633,23 +723,23 @@ test a_conflicting_pull_request_is_listed_once_and_not_refreshed { test a_dirty_state_word_alone_is_a_conflict { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 310, 100, false, 0, false, false, 0, 1, 4); + const n : usize = t_rec(&r, 256, 0, 310, 100, false, 0, false, false, 0, 1, 4, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, "M\t- #310 ", 9) == true; } test a_draft_and_a_held_pull_request_are_not_touched { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 400, 100, true, 0, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 401, 110, false, 1, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 402, 120, false, 2, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 403, 130, false, 3, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 404, 140, false, 4, false, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 405, 150, false, 5, false, false, 0, 0, 9); + var n : usize = t_rec(&r, 1024, 0, 400, 100, true, 0, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 401, 110, false, 1, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 402, 120, false, 2, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 403, 130, false, 3, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 404, 140, false, 4, false, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 405, 150, false, 5, false, false, 0, 0, 9, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert buf_has(&out, d, "R\t400\t", 6) == false; assert buf_has(&out, d, "R\t401\t", 6) == false; assert buf_has(&out, d, "R\t402\t", 6) == false; @@ -662,10 +752,10 @@ test a_draft_and_a_held_pull_request_are_not_touched { test a_fork_without_maintainer_edits_is_skipped_and_one_with_edits_is_refreshed { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 500, 100, false, 0, true, false, 0, 0, 9); - n = t_rec(&r, 1024, n, 501, 110, false, 0, true, true, 0, 0, 9); + var n : usize = t_rec(&r, 1024, 0, 500, 100, false, 0, true, false, 0, 0, 9, 1); + n = t_rec(&r, 1024, n, 501, 110, false, 0, true, true, 0, 0, 9, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert buf_has(&out, d, "R\t500\t", 6) == false; assert buf_has(&out, d, "R\t501\t", 6) == true; assert t_lines(&out, d, 'R') == 1; @@ -680,20 +770,20 @@ test a_deleted_head_repository_counts_as_a_fork_that_cannot_be_edited { n = put_byte(&r, 256, n, 'b'); k += 1; } - n = put_str(&r, 256, n, "\t2026-10-18T00:01:00Z\tfalse\t\tfalse\t\tfalse\tMERGEABLE\tBLOCKED\t5\n", 62); + n = put_str(&r, 256, n, "\t2026-10-18T00:01:00Z\tfalse\t\tfalse\t\tfalse\tMERGEABLE\tBLOCKED\t5\ttrue\n", 67); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 fork without maintainer edits, ", 34) == true; } test a_pull_request_that_is_current_or_not_computed_is_counted_and_left_alone { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 600, 100, false, 0, false, false, 0, 0, 0); - n = t_rec(&r, 1024, n, 601, 110, false, 0, false, false, 2, 3, 5); - n = t_rec(&r, 1024, n, 602, 120, false, 0, false, false, 0, 0, 9999); + var n : usize = t_rec(&r, 1024, 0, 600, 100, false, 0, false, false, 0, 0, 0, 1); + n = t_rec(&r, 1024, n, 601, 110, false, 0, false, false, 2, 3, 5, 1); + n = t_rec(&r, 1024, n, 602, 120, false, 0, false, false, 0, 0, 9999, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 already up to date, ", 23) == true; assert buf_has(&out, d, " 2 not decidable yet, ", 22) == true; @@ -701,19 +791,19 @@ test a_pull_request_that_is_current_or_not_computed_is_counted_and_left_alone { test a_record_that_cannot_be_read_is_counted_and_never_refreshed { var r : [2048]u8 = undefined; - var n : usize = put_str(&r, 2048, 0, "700\tnotasha\t2026-10-18T00:01:00Z\tfalse\t\tfalse\tx/y\tfalse\tMERGEABLE\tBLOCKED\t5\n", 76); + var n : usize = put_str(&r, 2048, 0, "700\tnotasha\t2026-10-18T00:01:00Z\tfalse\t\tfalse\tx/y\tfalse\tMERGEABLE\tBLOCKED\t5\ttrue\n", 81); n = put_str(&r, 2048, n, "701\tshort record\n", 17); n = put_str(&r, 2048, n, "abc\tdef\n", 8); n = put_str(&r, 2048, n, "0\t", 2); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 4 unreadable\n", 14) == true; } test a_flag_that_is_not_true_or_false_makes_the_record_unreadable { var r : [512]u8 = undefined; - var n : usize = t_rec(&r, 512, 0, 710, 100, false, 0, false, false, 0, 0, 5); + var n : usize = t_rec(&r, 512, 0, 710, 100, false, 0, false, false, 0, 0, 5, 1); var k : usize = 0; while (k < n) { if (r[k] == 'f' and r[k + 1] == 'a' and r[k + 2] == 'l' and r[k + 3] == 's' and r[k + 4] == 'e') { @@ -723,28 +813,28 @@ test a_flag_that_is_not_true_or_false_makes_the_record_unreadable { k += 1; } var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; } test a_number_seen_twice_is_refused_the_second_time { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 800, 100, false, 0, false, false, 0, 0, 5); - n = t_rec(&r, 1024, n, 800, 200, false, 0, false, false, 0, 0, 5); + var n : usize = t_rec(&r, 1024, 0, 800, 100, false, 0, false, false, 0, 0, 5, 1); + n = t_rec(&r, 1024, n, 800, 200, false, 0, false, false, 0, 0, 5, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 1; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; } test the_oldest_pull_request_comes_first_whatever_the_input_order { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 903, 3000, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 901, 1000, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 902, 2000, false, 0, false, false, 0, 0, 1); + var n : usize = t_rec(&r, 1024, 0, 903, 3000, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 901, 1000, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 902, 2000, false, 0, false, false, 0, 0, 1, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 3; assert out[0] == 'R'; assert buf_has(&out, d, "R\t901\t", 6) == true; @@ -761,45 +851,45 @@ test the_oldest_pull_request_comes_first_whatever_the_input_order { test equal_timestamps_are_ordered_by_number { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 952, 500, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 951, 500, false, 0, false, false, 0, 0, 1); + var n : usize = t_rec(&r, 1024, 0, 952, 500, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 951, 500, false, 0, false, false, 0, 0, 1, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert out[4] == '1'; assert out[5] == '\t'; } -test at_most_thirty_are_refreshed_and_they_are_the_oldest { +test at_most_five_are_refreshed_and_they_are_the_oldest { var r : [8192]u8 = undefined; var n : usize = 0; - var i : u32 = 35; + var i : u32 = 8; while (i > 0) { - n = t_rec(&r, 8192, n, 100 + i, i * 60, false, 0, false, false, 0, 0, 2); + n = t_rec(&r, 8192, n, 100 + i, i * 60, false, 0, false, false, 0, 0, 2, 1); i -= 1; } var out : [16384]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 16384); + const d : usize = refresh_plan(&r, n, &out, 16384, 0); assert refresh_fits(d, 16384) == true; - assert t_lines(&out, d, 'R') == 30; + assert t_lines(&out, d, 'R') == 5; assert buf_has(&out, d, "R\t101\t", 6) == true; - assert buf_has(&out, d, "R\t130\t", 6) == true; - assert buf_has(&out, d, "R\t131\t", 6) == false; - assert buf_has(&out, d, "R\t135\t", 6) == false; - assert buf_has(&out, d, " 30 to update, ", 15) == true; - assert buf_has(&out, d, " 5 over the cap, ", 17) == true; + assert buf_has(&out, d, "R\t105\t", 6) == true; + assert buf_has(&out, d, "R\t106\t", 6) == false; + assert buf_has(&out, d, "R\t108\t", 6) == false; + assert buf_has(&out, d, " 5 to update, ", 14) == true; + assert buf_has(&out, d, " 3 over the cap, ", 17) == true; } -test thirty_exactly_is_not_capped { +test five_exactly_is_not_capped { var r : [8192]u8 = undefined; var n : usize = 0; var i : u32 = 1; - while (i <= 30) { - n = t_rec(&r, 8192, n, 200 + i, i * 60, false, 0, false, false, 0, 0, 2); + while (i <= 5) { + n = t_rec(&r, 8192, n, 200 + i, i * 60, false, 0, false, false, 0, 0, 2, 1); i += 1; } var out : [16384]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 16384); - assert t_lines(&out, d, 'R') == 30; + const d : usize = refresh_plan(&r, n, &out, 16384, 0); + assert t_lines(&out, d, 'R') == 5; assert buf_has(&out, d, " 0 over the cap, ", 17) == true; } @@ -808,51 +898,156 @@ test held_and_conflicting_ones_do_not_use_up_the_cap { var n : usize = 0; var i : u32 = 1; while (i <= 10) { - n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 1, false, false, 0, 0, 2); + n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 1, false, false, 0, 0, 2, 1); i += 1; } i = 11; while (i <= 20) { - n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 0, false, false, 1, 1, 2); + n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 0, false, false, 1, 1, 2, 1); i += 1; } i = 21; while (i <= 50) { - n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 0, false, false, 0, 0, 2); + n = t_rec(&r, 16384, n, 300 + i, i * 60, false, 0, false, false, 0, 0, 2, 1); i += 1; } var out : [16384]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 16384); - assert t_lines(&out, d, 'R') == 30; + const d : usize = refresh_plan(&r, n, &out, 16384, 0); + assert t_lines(&out, d, 'R') == 5; assert buf_has(&out, d, "R\t321\t", 6) == true; - assert buf_has(&out, d, "R\t350\t", 6) == true; + assert buf_has(&out, d, "R\t325\t", 6) == true; + assert buf_has(&out, d, "R\t326\t", 6) == false; assert t_lines(&out, d, 'M') == 12; } +test the_merger_signal_is_auto_merge_armed_or_the_bee_label { + var r : [2048]u8 = undefined; + var n : usize = t_rec(&r, 2048, 0, 401, 100, false, 0, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 402, 200, false, 0, false, false, 0, 0, 2, 1); + n = t_rec(&r, 2048, n, 403, 300, false, 6, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 404, 400, false, 7, false, false, 0, 0, 2, 0); + n = t_rec(&r, 2048, n, 405, 500, false, 5, false, false, 0, 0, 2, 0); + var out : [4096]u8 = undefined; + const d : usize = refresh_plan(&r, n, &out, 4096, 0); + assert t_lines(&out, d, 'R') == 2; + assert buf_has(&out, d, "R\t402\t", 6) == true; + assert buf_has(&out, d, "R\t403\t", 6) == true; + assert buf_has(&out, d, "R\t401\t", 6) == false; + assert buf_has(&out, d, "R\t404\t", 6) == false; + assert buf_has(&out, d, "R\t405\t", 6) == false; + assert buf_has(&out, d, " 2 to update, ", 14) == true; + assert buf_has(&out, d, " 3 not next to land, ", 21) == true; +} + +test pull_requests_that_wait_do_not_use_up_the_cap { + var r : [8192]u8 = undefined; + var n : usize = 0; + var i : u32 = 1; + while (i <= 10) { + n = t_rec(&r, 8192, n, 500 + i, i * 60, false, 0, false, false, 0, 0, 2, 0); + i += 1; + } + i = 11; + while (i <= 16) { + n = t_rec(&r, 8192, n, 500 + i, i * 60, false, 0, false, false, 0, 0, 2, 1); + i += 1; + } + var out : [16384]u8 = undefined; + const d : usize = refresh_plan(&r, n, &out, 16384, 0); + assert t_lines(&out, d, 'R') == 5; + assert buf_has(&out, d, "R\t511\t", 6) == true; + assert buf_has(&out, d, "R\t515\t", 6) == true; + assert buf_has(&out, d, "R\t516\t", 6) == false; + assert buf_has(&out, d, " 10 not next to land, ", 22) == true; + assert buf_has(&out, d, " 1 over the cap, ", 17) == true; +} + +test the_auto_merge_flag_must_be_true_or_false { + var r : [512]u8 = undefined; + const n : usize = t_rec(&r, 512, 0, 410, 100, false, 0, false, false, 0, 0, 2, 2); + var out : [4096]u8 = undefined; + const d : usize = refresh_plan(&r, n, &out, 4096, 0); + assert t_lines(&out, d, 'R') == 0; + assert buf_has(&out, d, " 1 unreadable\n", 14) == true; + assert buf_has(&out, d, " 0 not next to land, ", 21) == true; +} + +test a_queue_above_the_limit_stops_every_refresh { + var r : [512]u8 = undefined; + const n : usize = t_rec(&r, 512, 0, 420, 100, false, 0, false, false, 0, 0, 2, 1); + var out : [4096]u8 = undefined; + var d : usize = refresh_plan(&r, n, &out, 4096, 151); + assert t_lines(&out, d, 'R') == 0; + assert t_lines(&out, d, 'M') == 1; + assert d == 25 + 3 + 49; + assert buf_has(&out, d, "M\tclaw-refresh: skipped, 151 Actions runs are queued, above the limit of 150\n", 77) == true; + d = refresh_plan(&r, n, &out, 4096, 396); + assert t_lines(&out, d, 'R') == 0; + assert buf_has(&out, d, "skipped, 396 Actions runs are queued", 36) == true; +} + +test a_queue_at_the_limit_still_refreshes { + var r : [512]u8 = undefined; + const n : usize = t_rec(&r, 512, 0, 421, 100, false, 0, false, false, 0, 0, 2, 1); + var out : [4096]u8 = undefined; + var d : usize = refresh_plan(&r, n, &out, 4096, 150); + assert t_lines(&out, d, 'R') == 1; + assert buf_has(&out, d, "skipped", 7) == false; + d = refresh_plan(&r, n, &out, 4096, 0); + assert t_lines(&out, d, 'R') == 1; +} + +test an_unread_queue_size_stops_every_refresh { + var r : [512]u8 = undefined; + const n : usize = t_rec(&r, 512, 0, 422, 100, false, 0, false, false, 0, 0, 2, 1); + var out : [4096]u8 = undefined; + const d : usize = refresh_plan(&r, n, &out, 4096, -1); + assert t_lines(&out, d, 'R') == 0; + assert t_lines(&out, d, 'M') == 1; + assert d == 25 + 41; + assert buf_has(&out, d, "M\tclaw-refresh: skipped, the Actions queue size could not be read\n", 66) == true; +} + +test the_skip_texts_are_pinned_by_their_lengths { + var b : [96]u8 = undefined; + var w : usize = put_str(&b, 96, 0, SKIP_HEAD, SKIP_HEAD_LEN); + assert b[w - 1] == ' '; + assert b[w - 2] == ','; + w = put_str(&b, 96, 0, SKIP_BUSY, SKIP_BUSY_LEN); + assert b[0] == ' '; + assert b[w - 1] == '\n'; + assert b[w - 2] == '0'; + w = put_str(&b, 96, 0, SKIP_UNREAD, SKIP_UNREAD_LEN); + assert b[w - 1] == '\n'; + assert b[w - 2] == 'd'; + assert QUEUE_LIMIT == 150; + assert REFRESH_CAP == 5; +} + test an_empty_list_is_a_valid_empty_answer { var r : [4]u8 = undefined; var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, 0, &out, 4096); + const d : usize = refresh_plan(&r, 0, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; - assert buf_has(&out, d, "M\tclaw-refresh: 0 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 unreadable\n", 166) == true; + assert buf_has(&out, d, "M\tclaw-refresh: 0 to update, 0 in conflict, 0 held or draft, 0 fork without maintainer edits, 0 already up to date, 0 not decidable yet, 0 over the cap, 0 not next to land, 0 unreadable\n", 186) == true; assert t_lines(&out, d, 'M') == 1; } test a_carriage_return_does_not_hide_a_record { var r : [512]u8 = undefined; - var n : usize = t_rec(&r, 512, 0, 970, 100, false, 0, false, false, 0, 0, 2); + var n : usize = t_rec(&r, 512, 0, 970, 100, false, 0, false, false, 0, 0, 2, 1); r[n - 1] = 13; n = put_byte(&r, 512, n, '\n'); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 1; } test a_buffer_too_small_is_reported_not_trusted { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 980, 100, false, 0, false, false, 0, 0, 2); + const n : usize = t_rec(&r, 256, 0, 980, 100, false, 0, false, false, 0, 0, 2, 1); var out : [32]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 32); + const d : usize = refresh_plan(&r, n, &out, 32, 0); assert refresh_fits(d, 32) == false; assert refresh_fits(d, d) == true; } @@ -860,13 +1055,13 @@ test a_buffer_too_small_is_reported_not_trusted { test the_summary_texts_are_pinned_by_their_lengths { var b : [64]u8 = undefined; var k : usize = 0; - while (k < 8) { + while (k < 9) { const w : usize = put_str(&b, 64, 0, SUM_TEXTS[k], SUM_LENS[k]); assert w == SUM_LENS[k]; assert b[0] == ' '; k += 1; } - var w : usize = put_str(&b, 64, 0, SUM_TEXTS[7], SUM_LENS[7]); + var w : usize = put_str(&b, 64, 0, SUM_TEXTS[8], SUM_LENS[8]); assert b[w - 1] == '\n'; assert b[w - 2] == 'e'; w = put_str(&b, 64, 0, CONFLICT_TAIL, CONFLICT_TAIL_LEN); @@ -882,10 +1077,10 @@ test the_summary_texts_are_pinned_by_their_lengths { assert b[w - 2] == '\t'; } -test one_record_splits_into_eleven_fields_and_classifies_as_refresh { +test one_record_splits_into_twelve_fields_and_classifies_as_refresh { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3); - var f : [22]usize = undefined; + const n : usize = t_rec(&r, 256, 0, 4217, 600, false, 0, false, false, 0, 0, 3, 1); + var f : [24]usize = undefined; const le : usize = line_end(&r, n, 0); assert le == n - 1; assert rf_split(&r, 0, le, &f) == true; @@ -898,6 +1093,8 @@ test one_record_splits_into_eleven_fields_and_classifies_as_refresh { assert rf_flag(&r, f[14], f[15]) == T_FALSE; assert rf_merge_class(&r, f[16], f[17], f[18], f[19]) == M_OK; assert rf_behind(&r, f[20], f[21], f[18], f[19]) == 1; + assert rf_flag(&r, f[22], f[23]) == T_TRUE; + assert f[23] == n - 1; assert rf_classify(&r, &f) == D_REFRESH; } @@ -927,36 +1124,36 @@ test older_ignores_the_byte_after_the_stamp { test one_unreadable_field_alone_makes_the_record_unreadable { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 720, 100, false, 0, false, false, 0, 0, 5); + const n : usize = t_rec(&r, 256, 0, 720, 100, false, 0, false, false, 0, 0, 5, 1); assert r[45] == '2'; assert r[73] == 'f'; assert r[92] == 'f'; var out : [4096]u8 = undefined; - var d : usize = refresh_plan(&r, n, &out, 4096); + var d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 1; r[45] = 'x'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; r[45] = '2'; r[73] = 'F'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; r[73] = 'f'; r[92] = 'F'; - d = refresh_plan(&r, n, &out, 4096); + d = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; } test a_record_one_field_short_is_unreadable_not_merely_undecidable { var r : [256]u8 = undefined; - var n : usize = t_rec(&r, 256, 0, 730, 100, false, 0, false, false, 0, 0, 5); - r[n - 3] = '\n'; - n -= 2; + var n : usize = t_rec(&r, 256, 0, 730, 100, false, 0, false, false, 0, 0, 5, 1); + r[n - 6] = '\n'; + n -= 5; var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; assert buf_has(&out, d, " 0 not decidable yet, ", 22) == true; @@ -965,19 +1162,19 @@ test a_record_one_field_short_is_unreadable_not_merely_undecidable { test a_blank_line_is_not_a_record { var r : [512]u8 = undefined; var n : usize = put_byte(&r, 512, 0, '\n'); - n = t_rec(&r, 512, n, 740, 100, false, 0, false, false, 0, 0, 5); + n = t_rec(&r, 512, n, 740, 100, false, 0, false, false, 0, 0, 5, 1); n = put_byte(&r, 512, n, '\n'); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 1; assert buf_has(&out, d, " 0 unreadable\n", 14) == true; } test pull_request_number_zero_is_unreadable { var r : [256]u8 = undefined; - const n : usize = t_rec(&r, 256, 0, 0, 100, false, 0, false, false, 0, 0, 5); + const n : usize = t_rec(&r, 256, 0, 0, 100, false, 0, false, false, 0, 0, 5, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 0; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; } @@ -987,22 +1184,22 @@ test the_513th_record_is_counted_as_unreadable_and_not_stored { var n : usize = 0; var i : u32 = 1; while (i <= 513) { - n = t_rec(&r, 65536, n, i, 100, false, 0, false, false, 0, 0, 0); + n = t_rec(&r, 65536, n, i, 100, false, 0, false, false, 0, 0, 0, 1); i += 1; } assert n < 65536; var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert buf_has(&out, d, "512 already up to date, ", 24) == true; assert buf_has(&out, d, " 1 unreadable\n", 14) == true; } test the_hour_digits_decide_the_order { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 9, 39600, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 3, 43200, false, 0, false, false, 0, 0, 1); + var n : usize = t_rec(&r, 1024, 0, 9, 39600, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 3, 43200, false, 0, false, false, 0, 0, 1, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 2; assert out[2] == '9'; assert out[47] == '3'; @@ -1010,10 +1207,10 @@ test the_hour_digits_decide_the_order { test the_second_digits_decide_the_order { var r : [1024]u8 = undefined; - var n : usize = t_rec(&r, 1024, 0, 9, 3610, false, 0, false, false, 0, 0, 1); - n = t_rec(&r, 1024, n, 3, 3659, false, 0, false, false, 0, 0, 1); + var n : usize = t_rec(&r, 1024, 0, 9, 3610, false, 0, false, false, 0, 0, 1, 1); + n = t_rec(&r, 1024, n, 3, 3659, false, 0, false, false, 0, 0, 1, 1); var out : [4096]u8 = undefined; - const d : usize = refresh_plan(&r, n, &out, 4096); + const d : usize = refresh_plan(&r, n, &out, 4096, 0); assert t_lines(&out, d, 'R') == 2; assert out[2] == '9'; assert out[47] == '3'; diff --git a/tools/census/quiet.txt b/tools/census/quiet.txt index b42450c69a..548b02aaa5 100644 --- a/tools/census/quiet.txt +++ b/tools/census/quiet.txt @@ -2,7 +2,7 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING workflow files read 67 steps in a quiet shape 33 - named a path but not quiet 233 (--excluded prints them) + named a path but not quiet 237 (--excluded prints them) by shape: failure branch passes 14 `… 2>/dev/null … || echo PASSED`