1ad4be3e
test: DELAY_MS is pinned, in the unit test and on the wire
a73x 2026-08-08 16:43
Commit message
test/delaypipe.zig
| Old | New | ||
|---|---|---|---|
| @@ -45,12 +45,21 @@ fn writeAll(fd: std.posix.fd_t, data: []const u8) !void { | |||
| 45 | while (i < data.len) i += try std.posix.write(fd, data[i..]); | 45 | while (i < data.len) i += try std.posix.write(fd, data[i..]); |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | /// `DELAY_MS` from the environment, or the default. An unparseable value is | 48 | /// Interpret a `DELAY_MS` value. Absent or unparseable is the default: this |
| 49 | /// the default too: this is a test helper, and failing to start would look | 49 | /// is a test helper, and failing to start would look like a broken transport |
| 50 | /// like a broken transport rather than a typo. | 50 | /// rather than a typo. |
| 51 | /// | ||
| 52 | /// Split from the lookup so the interpretation is testable on its own — | ||
| 53 | /// std has no portable setenv, so a test that went through the environment | ||
| 54 | /// could only ever exercise the unset case, which is the one branch that | ||
| 55 | /// returns the default no matter how badly the lookup is broken. | ||
| 56 | pub fn parseDelay(raw: ?[]const u8) u64 { | ||
| 57 | const s = raw orelse return default_delay_ms; | ||
| 58 | return std.fmt.parseInt(u64, s, 10) catch default_delay_ms; | ||
| 59 | } | ||
| 60 | |||
| 51 | pub fn delayFromEnv() u64 { | 61 | pub fn delayFromEnv() u64 { |
| 52 | const raw = std.posix.getenv("DELAY_MS") orelse return default_delay_ms; | 62 | return parseDelay(std.posix.getenv("DELAY_MS")); |
| 53 | return std.fmt.parseInt(u64, raw, 10) catch default_delay_ms; | ||
| 54 | } | 63 | } |
| 55 | 64 | ||
| 56 | pub fn main() void { | 65 | pub fn main() void { |
| @@ -125,8 +134,23 @@ test "a payload larger than one write survives the crossing in order" { | |||
| 125 | try std.testing.expectEqualStrings("alpha-beta-gamma", got.items); | 134 | try std.testing.expectEqualStrings("alpha-beta-gamma", got.items); |
| 126 | } | 135 | } |
| 127 | 136 | ||
| 128 | test "DELAY_MS is read from the environment, and a bad value is not fatal" { | 137 | test "DELAY_MS is honoured when set, and never fatal when it is nonsense" { |
| 129 | // Nothing set in the test environment, so this is the default path — | 138 | // The case that matters: a value that is NOT the default, because a |
| 130 | // the one a bare `delaypipe` in a pipeline takes. | 139 | // lookup that silently failed would fall back to the default and every |
| 140 | // measurement taken through this pipe would quietly be of the wrong | ||
| 141 | // path. e2e types into a 400ms path and asserts the round trip is | ||
| 142 | // inconsistent with 150, which is the other half of this. | ||
| 143 | try std.testing.expectEqual(@as(u64, 250), parseDelay("250")); | ||
| 144 | try std.testing.expectEqual(@as(u64, 0), parseDelay("0")); | ||
| 145 | |||
| 146 | // Nonsense is the default rather than a failure to start: a helper that | ||
| 147 | // refused to run would read as a broken transport in the suite that | ||
| 148 | // used it, which is a much longer way round to finding a typo. | ||
| 149 | try std.testing.expectEqual(default_delay_ms, parseDelay("abc")); | ||
| 150 | try std.testing.expectEqual(default_delay_ms, parseDelay("")); | ||
| 151 | try std.testing.expectEqual(default_delay_ms, parseDelay("-5")); | ||
| 152 | |||
| 153 | // Unset, which is what a bare `delaypipe` in a pipeline takes. | ||
| 154 | try std.testing.expectEqual(default_delay_ms, parseDelay(null)); | ||
| 131 | try std.testing.expectEqual(default_delay_ms, delayFromEnv()); | 155 | try std.testing.expectEqual(default_delay_ms, delayFromEnv()); |
| 132 | } | 156 | } |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -688,10 +688,23 @@ rm -f "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKE | |||
| 688 | 688 | ||
| 689 | # Delay per direction. The round trip is twice this, and every assertion | 689 | # Delay per direction. The round trip is twice this, and every assertion |
| 690 | # below about "before the daemon could have answered" is measured against | 690 | # below about "before the daemon could have answered" is measured against |
| 691 | # it. Deliberately larger than the plan's 150ms: the margin between "the | 691 | # it. Chosen against three constraints at once, which is why it is not a |
| 692 | # prediction is painted" and "the echo could have arrived" is what keeps | 692 | # round number picked for looks: |
| 693 | # this scenario from being a race, and 300ms each way makes that margin | 693 | # |
| 694 | # half a second rather than a tenth. | 694 | # - Larger than delaypipe's built-in default of 150, and far enough from |
| 695 | # it to be told apart. The snapshot below is taken 450ms after the | ||
| 696 | # keystroke: the echo cannot have returned over this 600ms round trip, | ||
| 697 | # but WOULD have over the 300ms one the default gives. A DELAY_MS lookup | ||
| 698 | # that silently stopped working — leaving every scenario here measuring | ||
| 699 | # a path twice as fast as it claimed — fails that assertion rather than | ||
| 700 | # passing quietly. | ||
| 701 | # - Small enough that the round trip stays clear of the overlay's 1000ms | ||
| 702 | # expiry bound. This is a real ceiling, not a test artefact: delaypipe | ||
| 703 | # delays each chunk serially, so a burst's later keystrokes queue behind | ||
| 704 | # the earlier ones and age while they wait. At 400ms each way the burst | ||
| 705 | # scenario went to confirmed=1 — the predictions were expiring before | ||
| 706 | # their echo could arrive, exactly as the overlay is designed to do. | ||
| 707 | # - Leaving margin on both sides, so neither is a race under load. | ||
| 695 | PDELAY=300 | 708 | PDELAY=300 |
| 696 | 709 | ||
| 697 | "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.p1" 2>&1 & | 710 | "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.p1" 2>&1 & |
| @@ -709,9 +722,10 @@ set +e | |||
| 709 | > "$OUT.p1" 2>&1 & | 722 | > "$OUT.p1" 2>&1 & |
| 710 | P1PID=$! | 723 | P1PID=$! |
| 711 | set -e | 724 | set -e |
| 712 | # Snapshot the client's output a quarter second after the keystroke — long | 725 | # Snapshot the client output 450ms after the keystroke: well |
| 713 | # before the 600ms round trip could bring the pty's own echo back. | 726 | # before this 600ms round trip could bring the pty own echo back, and well |
| 714 | sleep 2.25 | 727 | # AFTER the 300ms round trip delaypipe default of 150 would have given. |
| 728 | sleep 2.45 | ||
| 715 | cp "$OUT.p1" "$OUT.p1.early" 2>/dev/null || true | 729 | cp "$OUT.p1" "$OUT.p1.early" 2>/dev/null || true |
| 716 | set +e | 730 | set +e |
| 717 | wait "$P1PID" | 731 | wait "$P1PID" |
| @@ -723,15 +737,15 @@ set -e | |||
| 723 | # This is the whole claim of the milestone, as an effect rather than a | 737 | # This is the whole claim of the milestone, as an effect rather than a |
| 724 | # counter: the character was on the screen while it was still in flight. | 738 | # counter: the character was on the screen while it was still in flight. |
| 725 | grep -q "$(printf '\033\[4mz')" "$OUT.p1.early" || { | 739 | grep -q "$(printf '\033\[4mz')" "$OUT.p1.early" || { |
| 726 | echo "e2e FAIL: no predicted glyph 250ms after the keystroke (RTT is $((PDELAY * 2))ms)" | 740 | echo "e2e FAIL: no predicted glyph 450ms after the keystroke (RTT is $((PDELAY * 2))ms)" |
| 727 | echo "--- early snapshot ---"; cat -v "$OUT.p1.early"; exit 1; | 741 | echo "--- early snapshot ---"; cat -v "$OUT.p1.early"; exit 1; |
| 728 | } | 742 | } |
| 729 | # ...and the daemon's own answer was NOT there yet, which is what makes the | 743 | # ...and the daemon's own answer was NOT there yet, which is what makes the |
| 730 | # line above mean anything. A delta paints a row with EL(2) before its | 744 | # line above mean anything. A delta paints a row with EL(2) before its |
| 731 | # content; the prediction never does. | 745 | # content; the prediction never does. |
| 732 | if grep -q "$(printf '\033\[2K\033\[0mz')" "$OUT.p1.early"; then | 746 | if grep -q "$(printf '\033\[2K\033\[0mz')" "$OUT.p1.early"; then |
| 733 | echo "e2e FAIL: the pty's echo arrived within 250ms; the delay pipe is not delaying," | 747 | echo "e2e FAIL: the echo arrived within 450ms, so the path is not $((PDELAY * 2))ms;" |
| 734 | echo " so the assertion above proves nothing about prediction" | 748 | echo " either DELAY_MS was ignored (default is 150) or delaypipe is not delaying" |
| 735 | exit 1 | 749 | exit 1 |
| 736 | fi | 750 | fi |
| 737 | want_stat "$OUT.p1" contradicted 0 "line mode" | 751 | want_stat "$OUT.p1" contradicted 0 "line mode" |