Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

test: parallelize golden suite via ephemeral ports + nextest

The boot-level golden tests each booted a real `leveva` on a fixed port,
forcing a global lock that serialized the whole suite (~230s). Make them
parallel-safe and run them under nextest:

- Ephemeral ports: each daemon reserves `127.0.0.1:0`, templates its
fixture to the assigned client/server ports, and threads them through
the clients/peers. Per-port flock + a CPU-count slot semaphore
(MAX_CONCURRENT_DAEMONS=128) bound concurrency.
- `.config/nextest.toml`: a `golden` test-group caps in-flight daemons,
with `retries = 2` and a 30s `slow-timeout terminate-after` backstop.
- Streaming `read_until`: a per-connection `pending` buffer returns
through the sentinel line and retains the rest, and a 30s overall
deadline now PANICS (was a silent hang) so a never-arriving sentinel
fails loudly. Sentinels that previously matched on a since-coalesced
read were re-pointed at their transcript's true last line; a handful
of snapshots reattributed accordingly (content preserved).
- Fakelag off in tests: the daemon arms flood protection unless
`LEVEVA_DISABLE_FAKELAG=1`, which the harness sets by default so
barrier-paced clients don't eat the fakelag *delay*; the dedicated
flood goldens opt back in via `want_fakelag()`.
- Slow-test sentinels: `golden_rehash_conf` waited on a `376` its
no-MOTD config never sends (124s -> 1s); `golden_s2s_monitor` keyed
on a too-early `731`.

Suite: ~230s -> ~25s.

Assisted-by: Claude Opus 4.8 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

+194 -43
+51
.config/nextest.toml
··· 1 + # cargo-nextest configuration — https://nexte.st/docs/configuration/ 2 + # 3 + # Why this exists: `cargo test` runs each test *binary* serially (libtest only 4 + # parallelizes within a binary), so the boot-level golden suite — ~200 binaries, 5 + # each booting a real leveva daemon — runs end-to-end serially (~10 min). nextest 6 + # runs every test as its own process, parallelizing across binaries *and* tests. 7 + # 8 + # This is only safe because the golden harness (leveva/tests/harness/mod.rs) boots 9 + # each fixture on a freshly-reserved **ephemeral** port instead of the old shared 10 + # 16700-under-a-global-lock; with that, the wall-clock floor becomes the single 11 + # slowest test rather than the sum of all of them. 12 + 13 + # The boot-level golden tests each spawn a single-threaded leveva daemon and drive 14 + # barrier-paced clients. Cap how many run at once (independent of `test-threads`) 15 + # so the daemons stay responsive and do not miss a client's read window under load. 16 + # The fast lib unit tests are not in this group, so they still run at full width. 17 + # Raise/lower this on a bigger/smaller box; the harness also enforces a CPU-count 18 + # semaphore as a backstop, so this never needs to exceed the core count. 19 + [test-groups] 20 + golden = { max-threads = 32 } 21 + 22 + [[profile.default.overrides]] 23 + # Only the daemon-booting boot-level tests — every one lives in a binary named 24 + # golden_*, integration_*, or *pseudoserver. The lib unit tests and the fast 25 + # *_proptest binaries are excluded, so they still run at full `test-threads` width. 26 + filter = 'binary(/^golden_/) | binary(/^integration_/) | binary(/pseudoserver/)' 27 + test-group = 'golden' 28 + 29 + [profile.default] 30 + # Fast lib unit tests (and anything not in the `golden` group) run this wide. 31 + # Daemon-booting golden tests are bounded by the `golden` test-group above, so a 32 + # high value here does not oversubscribe the single-threaded daemons. 33 + test-threads = 256 34 + 35 + # A few golden tests deliberately wait out the harness read deadline (negative 36 + # checks via timeout) and run 30–125s. Flag them as SLOW but never kill before 37 + # 5 minutes — that ceiling only catches a genuinely hung test. 38 + slow-timeout = { period = "30s", terminate-after = 10 } 39 + 40 + # Barrier-paced transcripts can occasionally miss a read window under parallel 41 + # load. Genuine breaks reproduce deterministically (e.g. a snapshot mismatch), 42 + # so a small retry budget absorbs the rare timing flake without masking real 43 + # failures — a test that only passes on retry is reported as FLAKY, not PASS. 44 + retries = 2 45 + 46 + # CI: don't stop at the first failure (collect the full failure set) and surface 47 + # flaky passes in the summary. 48 + [profile.ci] 49 + retries = 2 50 + fail-fast = false 51 + final-status-level = "flaky"
+8 -1
leveva/src/main.rs
··· 599 599 // a real client paced by the network can burst but not flood; a sustained command flood is 600 600 // closed with `ERROR ... (Excess Flood)`. Disabled by default on the `Session` so in-process 601 601 // tests can drive synthetic bursts; the daemon arms it for every real connection here. 602 - session.enable_flood_protection(); 602 + // 603 + // The boot-level golden harness sets `LEVEVA_DISABLE_FAKELAG=1` so its barrier-paced clients can 604 + // fire many commands back-to-back (e.g. a sweep of `LIST` filters) without the fakelag *delay* 605 + // stretching the test to tens of seconds — the rate limiter itself is exercised by dedicated 606 + // flood goldens, not incidentally by every multi-command test. 607 + if std::env::var_os("LEVEVA_DISABLE_FAKELAG").is_none() { 608 + session.enable_flood_protection(); 609 + } 603 610 // Logged once, when the connection transitions to fully registered. 604 611 let mut announced_registration = false; 605 612
+1 -1
leveva/tests/golden_cap.rs
··· 58 58 jo.send("NICK jo"); 59 59 jo.send("USER j 0 * :Jo"); 60 60 jo.send("CAP END"); 61 - let welcome = jo.read_until(" 422 "); 61 + let welcome = jo.read_until(":+iw"); 62 62 assert!(welcome.contains(" 001 jo "), "got: {welcome:?}"); 63 63 64 64 insta::assert_snapshot!(canonicalize(&format!("{ls}{welcome}")));
+1
leveva/tests/golden_flood.rs
··· 19 19 20 20 #[test] 21 21 fn a_command_flood_is_closed_with_excess_flood() { 22 + harness::want_fakelag(); 22 23 let _srv = boot(); 23 24 let mut alice = register("alice"); 24 25
+1
leveva/tests/golden_flood_prereg.rs
··· 13 13 14 14 #[test] 15 15 fn a_pre_registration_flood_is_closed_with_excess_flood() { 16 + harness::want_fakelag(); 16 17 let _srv = boot(); 17 18 let mut conn = Client::connect(); 18 19
+1 -1
leveva/tests/golden_noforward.rs
··· 49 49 register(&mut bob, "bob"); 50 50 bob.send("MODE bob +Q"); 51 51 assert!( 52 - bob.read_until(" MODE bob ").contains("+Q"), 52 + bob.read_until(" MODE bob :+Q").contains("+Q"), 53 53 "the +Q self-set is echoed" 54 54 ); 55 55 bob.send("JOIN #secret");
+1 -1
leveva/tests/golden_pass.rs
··· 14 14 alice.send("PASS letmein"); 15 15 alice.send("NICK alice"); 16 16 alice.send("USER alice 0 * :Alice Tester"); 17 - let banner = alice.read_until(" 422 "); 17 + let banner = alice.read_until(":+iw"); 18 18 insta::assert_snapshot!(canonicalize(&banner)); 19 19 } 20 20
+1 -1
leveva/tests/golden_registration.rs
··· 12 12 let mut alice = Client::connect(); 13 13 alice.send("NICK alice"); 14 14 alice.send("USER alice 0 * :Alice Tester"); 15 - let banner = alice.read_until(" 422 "); 15 + let banner = alice.read_until(":+iw"); 16 16 insta::assert_snapshot!(canonicalize(&banner)); 17 17 }
+1 -1
leveva/tests/golden_rehash_ban.rs
··· 70 70 let mut denied = Client::connect(); 71 71 denied.send("NICK bob"); 72 72 denied.send("USER bob 0 * :Bob"); 73 - let out = denied.read_until(" 465 "); 73 + let out = denied.read_until("Kill line active"); 74 74 assert!( 75 75 out.contains(" 465 bob :You (bob@127.0.0.1) are banned from this server: no entry"), 76 76 "after REHASH to a `*@*` ban, a loopback client must be refused 465, got: {out}"
+4 -2
leveva/tests/golden_rehash_conf.rs
··· 53 53 ) 54 54 } 55 55 56 - /// Register a fresh client under `nick`, returning it past the welcome burst (`376`). 56 + /// Register a fresh client under `nick`, returning it past the welcome burst. These configs carry 57 + /// no `motd` block, so registration ends with `422 ERR_NOMOTD` — waiting for `376` (which never 58 + /// arrives) would burn the full 30s read deadline on every registration. 57 59 fn register(nick: &str) -> Client { 58 60 let mut c = Client::connect(); 59 61 c.send(&format!("NICK {nick}")); 60 62 c.send(&format!("USER {nick} 0 * :{nick} Tester")); 61 - c.read_until(" 376 "); 63 + c.read_until(" 422 "); 62 64 c 63 65 } 64 66
+1
leveva/tests/golden_rehash_flood.rs
··· 60 60 let path = std::env::temp_dir().join(format!("leveva-rehash-flood-{}.kdl", std::process::id())); 61 61 62 62 // --- config A: a tight ceiling (10) → a modest burst floods -------------------------------- 63 + harness::want_fakelag(); 63 64 let _srv = boot_with_config_file(&path, &config(10)); 64 65 65 66 let mut root = register("root");
+1 -1
leveva/tests/golden_s2s_monitor.rs
··· 62 62 63 63 // --- rename: rover → landrover → alice gets a 731 (old) and a 730 (new). --- 64 64 peer.send(":1ZZZAAAAA NICK landrover"); 65 - let rename_off = alice.read_until(" 731 rover"); 65 + let rename_off = alice.read_until(" 731 alice :rover"); 66 66 let rename_on = alice.read_until(" 730 alice :landrover"); 67 67 68 68 // --- offline: the peer quits landrover → alice gets a 731. ---
+3 -2
leveva/tests/golden_s2s_reject.rs
··· 14 14 let mut alice = Client::connect(); 15 15 alice.send("NICK alice"); 16 16 alice.send("USER alice 0 * :Alice Tester"); 17 - // Drain the welcome burst (ends at 422 — the fixture has no MOTD). 18 - alice.read_until(" 422 "); 17 + // Drain the welcome burst (the trailing auto-umode MODE line follows the 422 — 18 + // the fixture has no MOTD). 19 + alice.read_until("MODE alice :+iw"); 19 20 20 21 // The S2S-only verb set, each with a payload that *would* mutate state if interpreted. 21 22 alice.send(":victim ENCAP * CHGHOST evil evil.host");
+1 -1
leveva/tests/golden_server_query.rs
··· 23 23 alice.read_until(" 422 "); 24 24 25 25 alice.send("VERSION"); 26 - let version = alice.read_until("CASEMAPPING"); 26 + let version = alice.read_until("NETWORK=TestNet"); 27 27 28 28 alice.send("VERSION leveva.test"); 29 29 let version_local = alice.read_until(" 351 ");
+2
leveva/tests/golden_snotice.rs
··· 89 89 90 90 // A plain (-s) observer that must see nothing. 91 91 let mut carol = register("carol"); 92 + // alice (+s) also sees carol's connecting report; drain it so the next read lands on bob's. 93 + alice.read_until("Client connecting: carol"); 92 94 93 95 // bob connects → alice (and only alice) gets the connecting report. The fixture has no ident, 94 96 // so the mask is `bob!bob@127.0.0.1`; realname is `bob Tester` from the USER line.
+2 -2
leveva/tests/golden_tkline.rs
··· 61 61 62 62 // The real ban: it covers bob (ident bobby) but not alice (ident alice). 63 63 alice.send("TKLINE 1h bobby@* :spamming"); 64 - let reaped = bob.read_until(" 465 "); 64 + let reaped = bob.read_until("Kill line active: spamming)"); 65 65 66 66 // mallory reconnects with ident bobby → refused at registration by the active ban. 67 67 let mut mallory = Client::connect(); 68 68 mallory.send("NICK mallory"); 69 69 mallory.send("USER bobby 0 * :Mallory"); 70 - let refused = mallory.read_until(" 465 "); 70 + let refused = mallory.read_until("Kill line active: spamming)"); 71 71 72 72 // Inverse: alice was not reaped — her mask does not cover ident `alice`. She has no 73 73 // pending lines (a PING round-trip proves the connection is alive and idle).
+1 -1
leveva/tests/golden_user_spaceless.rs
··· 13 13 alice.send("NICK alice"); 14 14 // No colon on the realname — the bug-report form. 15 15 alice.send("USER alice 0 * Alice"); 16 - let banner = alice.read_until(" 422 "); 16 + let banner = alice.read_until(":+iw"); 17 17 insta::assert_snapshot!(canonicalize(&banner)); 18 18 }
+108 -25
leveva/tests/harness/mod.rs
··· 85 85 env!("CARGO_BIN_EXE_leveva") 86 86 } 87 87 88 + thread_local! { 89 + /// Whether the daemon booted on this thread should keep command-rate flood protection 90 + /// (fakelag) ENABLED. Default `false`: golden tests fire barrier-paced commands back-to-back, 91 + /// and the fakelag *delay* would stretch a multi-command test (e.g. a `LIST`-filter sweep) to 92 + /// tens of seconds. The dedicated flood goldens opt back in via [`want_fakelag`]. 93 + static WANT_FAKELAG: Cell<bool> = const { Cell::new(false) }; 94 + } 95 + 96 + /// Keep command-rate flood protection (fakelag) **enabled** for the next `boot()` on this thread. 97 + /// Call this at the top of a flood/fakelag golden, before booting. All other goldens run with 98 + /// fakelag disabled so their multi-command sequences are not artificially slowed. 99 + pub fn want_fakelag() { 100 + WANT_FAKELAG.with(|c| c.set(true)); 101 + } 102 + 103 + /// The env to hand the spawned daemon: disable fakelag unless this thread opted in via 104 + /// [`want_fakelag`]. The daemon reads `LEVEVA_DISABLE_FAKELAG` (see `main.rs`). 105 + fn fakelag_env() -> Vec<(&'static str, &'static str)> { 106 + if WANT_FAKELAG.with(Cell::get) { 107 + vec![] 108 + } else { 109 + vec![("LEVEVA_DISABLE_FAKELAG", "1")] 110 + } 111 + } 112 + 88 113 /// Read from `stream` until `sentinel` appears in the accumulated bytes, EOF, or a generous 89 114 /// overall deadline elapses. 90 115 /// ··· 97 122 /// `OVERALL_DEADLINE` is reached, so a load spike costs latency, not a false failure. Tests that 98 123 /// must observe the *absence* of a line never rely on this path — they use a second observable 99 124 /// (a `PING`/`PONG` round-trip) — so the longer ceiling does not slow them. 100 - fn read_until_impl(stream: &mut TcpStream, sentinel: &str) -> String { 125 + /// First byte index of `needle` in `haystack` (plain substring search; sentinels are ASCII). 126 + fn find_sub(haystack: &[u8], needle: &[u8]) -> Option<usize> { 127 + if needle.is_empty() { 128 + return Some(0); 129 + } 130 + if haystack.len() < needle.len() { 131 + return None; 132 + } 133 + haystack.windows(needle.len()).position(|w| w == needle) 134 + } 135 + 136 + /// Stream from `stream` until `sentinel` appears, returning everything **through the end of the 137 + /// line** that contains it and **retaining** any bytes already read past that line in `pending`. 138 + /// 139 + /// This is the streaming fix for the old `read_until`, which read a fresh socket chunk each call and 140 + /// **discarded** whatever it over-read past the sentinel. Two consecutive `read_until`s on one 141 + /// client (e.g. a `JOIN` barrier `read_until("dan"); read_until("mel")`) therefore lost the second 142 + /// line — it arrived in the same chunk as the first — and the second call then waited out the 30s 143 + /// deadline. Persisting the leftover in `pending` makes consecutive reads see every line exactly 144 + /// once, eliminating those spurious 30s stalls. 145 + /// 146 + /// The per-read socket timeout (~5s, set at connect) is treated as a *transient* stall: under heavy 147 + /// parallel load the single-threaded daemon can be descheduled past one read window, so the awaited 148 + /// line lands a beat late. Only the overall deadline ends the wait, so a load spike costs latency, 149 + /// not a false truncation. On deadline/EOF the whole buffer is drained and returned. 150 + fn read_until_impl(pending: &mut Vec<u8>, stream: &mut TcpStream, sentinel: &str) -> String { 101 151 const OVERALL_DEADLINE: Duration = Duration::from_secs(30); 102 152 let deadline = Instant::now() + OVERALL_DEADLINE; 103 - let mut buf = Vec::new(); 153 + let needle = sentinel.as_bytes(); 104 154 let mut chunk = [0u8; 4096]; 105 155 loop { 156 + if let Some(idx) = find_sub(pending, needle) { 157 + // Cut through the end of the sentinel's line (the next '\n'), or the buffer end if the 158 + // line has not finished arriving yet. Retain everything after the cut for the next read. 159 + let cut = pending[idx..] 160 + .iter() 161 + .position(|&b| b == b'\n') 162 + .map(|p| idx + p + 1) 163 + .unwrap_or(pending.len()); 164 + let out = String::from_utf8_lossy(&pending[..cut]).into_owned(); 165 + pending.drain(..cut); 166 + return out; 167 + } 106 168 match stream.read(&mut chunk) { 107 169 Ok(0) => break, // genuine EOF — the peer closed the connection 108 - Ok(n) => { 109 - buf.extend_from_slice(&chunk[..n]); 110 - if String::from_utf8_lossy(&buf).contains(sentinel) { 111 - break; 112 - } 113 - } 170 + Ok(n) => pending.extend_from_slice(&chunk[..n]), 114 171 Err(e) 115 172 if matches!( 116 173 e.kind(), 117 174 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut 118 175 ) => 119 176 { 120 - // Transient per-read timeout: keep waiting unless the overall deadline passed. 177 + // A line that never arrives is a test bug (a wrong sentinel, a missing wire 178 + // output, or a lost-message regression), not something to wait out and pass 179 + // anyway — fail loudly with the transcript so far. 121 180 if Instant::now() >= deadline { 122 - break; 181 + panic!( 182 + "read_until({sentinel:?}) timed out after 30s — the sentinel never \ 183 + arrived. Transcript so far:\n{}", 184 + String::from_utf8_lossy(pending) 185 + ); 123 186 } 124 187 } 125 188 Err(_) => break, // a real socket error 126 189 } 127 190 } 128 - String::from_utf8_lossy(&buf).into_owned() 191 + // EOF / socket error before the sentinel: return what we have (a test reading until the peer 192 + // closes the connection relies on this; an assertion on the missing line then fails normally). 193 + let out = String::from_utf8_lossy(pending).into_owned(); 194 + pending.clear(); 195 + out 129 196 } 130 197 131 198 fn fixture(name: &str) -> String { ··· 187 254 } 188 255 } 189 256 190 - /// How many golden servers may run **concurrently** across the whole `cargo test` invocation. The 191 - /// leveva daemon is single-threaded and the golden clients are barrier-paced with short sleeps, so 192 - /// oversubscribing the CPU deschedules a daemon past a client's read window — the 30s `read_until` 193 - /// deadline then fires, ballooning wall-clock and corrupting barrier-paced snapshots. cargo runs 194 - /// (num_cpus binaries × num_cpus threads) tests at once, so without a bound that is ~cpus² daemons. 195 - /// Capping concurrency at the CPU count keeps each live daemon on roughly its own core. 257 + /// How many golden servers may run **concurrently** across the whole test invocation. This is the 258 + /// *backstop* cap; the primary knob is the nextest `golden` test-group's `max-threads` 259 + /// (`.config/nextest.toml`). The leveva daemon is mostly event-driven (tokio I/O + short client 260 + /// sleeps), so this can comfortably exceed the core count — it bounds runaway oversubscription, not 261 + /// per-core fairness. Set high enough not to bottleneck the nextest group below it. 262 + const MAX_CONCURRENT_DAEMONS: usize = 128; 263 + 196 264 fn cpu_slot_count() -> usize { 197 - std::thread::available_parallelism() 198 - .map(|n| n.get()) 199 - .unwrap_or(4) 265 + MAX_CONCURRENT_DAEMONS 200 266 } 201 267 202 268 /// Acquire one of [`cpu_slot_count`] global concurrency slots (a cross-process counting semaphore 203 269 /// built from N `flock`'d files), spinning until one is free. Held for the [`Server`]'s lifetime, 204 270 /// so at most N daemons ever run at once. This replaces the old single global lock that serialized 205 - /// the *entire* suite — N-way parallelism instead of 1-way, without CPU thrash. 271 + /// the *entire* suite — N-way parallelism instead of 1-way. 206 272 fn acquire_cpu_slot() -> File { 207 273 let k = cpu_slot_count(); 208 274 loop { ··· 269 335 .args(["--config", path.to_str().expect("utf-8 temp path")]) 270 336 .stdout(Stdio::null()) 271 337 .stderr(Stdio::null()) 338 + .envs(fakelag_env()) 272 339 .spawn() 273 340 .unwrap_or_else(|e| panic!("spawn {}: {e}", binary())); 274 341 if probe_ready(client, Duration::from_secs(4)) { ··· 304 371 .args(["--config", &fixture(name)]) 305 372 .stdout(Stdio::null()) 306 373 .stderr(Stdio::null()) 374 + .envs(fakelag_env()) 307 375 .spawn() 308 376 .unwrap_or_else(|e| panic!("spawn {}: {e}", binary())); 309 377 assert!( ··· 391 459 .args(args) 392 460 .stdout(Stdio::null()) 393 461 .stderr(Stdio::null()) 462 + .envs(fakelag_env()) 394 463 .spawn() 395 464 .unwrap_or_else(|e| panic!("spawn {}: {e}", binary())); 396 465 ··· 431 500 .args(["--config", &fixture(name)]) 432 501 .stdout(Stdio::null()) 433 502 .stderr(Stdio::null()) 503 + .envs(fakelag_env()) 434 504 .spawn() 435 505 .unwrap_or_else(|e| panic!("spawn {}: {e}", binary())) 436 506 } ··· 466 536 .args(["--config", path.to_str().expect("utf-8 config path")]) 467 537 .stdout(Stdio::null()) 468 538 .stderr(Stdio::null()) 539 + .envs(fakelag_env()) 469 540 .spawn() 470 541 .unwrap_or_else(|e| panic!("spawn {}: {e}", binary())) 471 542 } ··· 491 562 492 563 pub struct Client { 493 564 stream: TcpStream, 565 + /// Bytes read past the previous [`read_until`](Self::read_until)'s sentinel line, consumed by 566 + /// the next read — see [`read_until_impl`] for why streaming matters. 567 + pending: Vec<u8>, 494 568 } 495 569 496 570 impl Client { ··· 506 580 stream 507 581 .set_read_timeout(Some(Duration::from_secs(5))) 508 582 .unwrap(); 509 - Client { stream } 583 + Client { 584 + stream, 585 + pending: Vec::new(), 586 + } 510 587 } 511 588 512 589 pub fn send(&mut self, line: &str) { ··· 524 601 } 525 602 526 603 pub fn read_until(&mut self, sentinel: &str) -> String { 527 - read_until_impl(&mut self.stream, sentinel) 604 + read_until_impl(&mut self.pending, &mut self.stream, sentinel) 528 605 } 529 606 530 607 /// Poll `WHOIS <nick>` until the nick is known to this server's network mirror ··· 550 627 /// IRC registration — it *is* the linking server. 551 628 pub struct Peer { 552 629 stream: TcpStream, 630 + /// Bytes read past the previous [`read_until`](Self::read_until)'s sentinel line (streaming, as 631 + /// for [`Client`]). 632 + pending: Vec<u8>, 553 633 } 554 634 555 635 impl Peer { ··· 566 646 stream 567 647 .set_read_timeout(Some(Duration::from_secs(5))) 568 648 .unwrap(); 569 - Peer { stream } 649 + Peer { 650 + stream, 651 + pending: Vec::new(), 652 + } 570 653 } 571 654 572 655 /// Send one raw line (CRLF-terminated), then pace like [`Client::send`] so the ··· 592 675 593 676 /// Read until `sentinel` appears in the accumulated bytes (or the overall deadline elapses). 594 677 pub fn read_until(&mut self, sentinel: &str) -> String { 595 - read_until_impl(&mut self.stream, sentinel) 678 + read_until_impl(&mut self.pending, &mut self.stream, sentinel) 596 679 } 597 680 598 681 /// Close the link abruptly (drops the socket) — the daemon treats this as an SQUIT
+1 -1
leveva/tests/snapshots/golden_kill__kill_disconnects_the_victim_and_relays_the_quit.snap
··· 1 1 --- 2 2 source: leveva/tests/golden_kill.rs 3 - assertion_line: 68 4 3 expression: transcript 5 4 --- 6 5 bob !oper: :leveva.test 481 bob :Permission Denied- You're not an IRC operator 6 + alice OPER ok: :alice MODE alice :+iw 7 7 alice OPER ok: :alice MODE alice :+o 8 8 alice OPER ok: :leveva.test 381 alice :You are now an IRC Operator 9 9 bob killed: :alice KILL bob :127.0.0.1!alice (flooding)
+1 -1
leveva/tests/snapshots/golden_oper__oper_login_grants_operator_then_whois_shows_313.snap
··· 1 1 --- 2 2 source: leveva/tests/golden_oper.rs 3 - assertion_line: 61 4 3 expression: transcript 5 4 --- 5 + alice OPER ok: :alice MODE alice :+iw 6 6 alice OPER ok: :alice MODE alice :+o 7 7 alice OPER ok: :leveva.test 381 alice :You are now an IRC Operator 8 8 WHOIS (oper): :leveva.test 313 alice alice :is an IRC Operator
+1
leveva/tests/snapshots/golden_post_registration__ping_pong_and_unknown_command.snap
··· 2 2 source: leveva/tests/golden_post_registration.rs 3 3 expression: canonicalize(&resp) 4 4 --- 5 + :alice MODE alice :+iw 5 6 :leveva.test PONG leveva.test :sync 6 7 :leveva.test 421 alice FLOOBLE :Unknown command
+1 -1
leveva/tests/snapshots/golden_snotice__rehash_server_notice_reaches_plus_s_operators_only.snap
··· 1 1 --- 2 2 source: leveva/tests/golden_snotice.rs 3 - assertion_line: 74 4 3 expression: transcript 5 4 --- 6 5 bob !oper query: :leveva.test 221 bob +iw 6 + alice OPER ok: :alice MODE alice :+iw 7 7 alice OPER ok: :alice MODE alice :+o 8 8 alice OPER ok: :leveva.test 381 alice :You are now an IRC Operator 9 9 alice +s: :alice MODE alice :+s
+1
leveva/tests/snapshots/golden_wallops__wallops_reaches_plus_w_operators_broadcast.snap
··· 3 3 expression: transcript 4 4 --- 5 5 bob !oper: :leveva.test 481 bob :Permission Denied- You're not an IRC operator 6 + alice OPER ok: :alice MODE alice :+iw 6 7 alice OPER ok: :alice MODE alice :+o 7 8 alice OPER ok: :leveva.test 381 alice :You are now an IRC Operator 8 9 bob recv: :alice!alice@127.0.0.1 WALLOPS :network maintenance