From 01c5503a19a5e35e262c2801aa6668b95d7fc089 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Sat, 19 Sep 2026 20:58:49 -0700 Subject: [PATCH] Compat: a copy's own listeners aren't ours to bind The first run against INBUXA's store failed all eight tests identically, before reading a single record: the copy carries that server's listeners on 25, 443, 465, 587, 110, 143, 993 and 995, and nothing in a test run is root, so each one failed with "Permission denied (os error 13)". The builder now remembers the listeners it adds, and under NO_INSERT drops build errors for any it didn't. Every other error still stands, including a bind failing on one of its own, so this can't hide the case where the harness's own port is taken. The copy isn't edited for this: its listeners are simply not what a compat run needs, and it reaches the server over the compat- ones instead. Checked that a NO_INSERT run still boots and that scim_tests, which takes the ordinary path, still passes. --- docs/spec/compat-tests.md | 10 +++++++++ tests/src/utils/server.rs | 46 +++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/spec/compat-tests.md b/docs/spec/compat-tests.md index 66c7a0f..4276fa9 100644 --- a/docs/spec/compat-tests.md +++ b/docs/spec/compat-tests.md @@ -99,6 +99,16 @@ copy of INBUXA's data any more. The script copies from the source for each test and removes the copy afterwards, so the source stays clean; take it from a stopped server or a snapshot, never from under a running one. +**Why the copy's own listeners are ignored.** A real server listens on 25, +443, 993 and the rest. Nothing in a test run is root, so every one of those +fails to bind and the run died before reading any data, with eight +`Permission denied (os error 13)` errors and nothing about what to do. +Under `NO_INSERT` the harness now drops build errors belonging to listeners +it did not add itself, and keeps every other error, including a bind that +fails on one of its own (`tests/src/utils/server.rs`). The copy is not +edited to achieve this; its listeners are simply not what a compat run +needs. + **Why `compat-` listeners appear in the copy.** The harness needs listeners on its own ports, and the registry keys listeners by name. A real server has its own, and a production listener called `jmap` or `imap` collided diff --git a/tests/src/utils/server.rs b/tests/src/utils/server.rs index c575c6f..c39800e 100644 --- a/tests/src/utils/server.rs +++ b/tests/src/utils/server.rs @@ -83,6 +83,9 @@ pub struct TestServer { pub struct TestServerBuilder { bootstrap: Bootstrap, temp_dir: TempDir, + // inbuxa: the listeners this builder added, so a compat run can tell + // them from the ones that came with a copy of someone else's store. + own_listeners: Vec, http_listener_port: u16, reset: bool, logging_enabled: bool, @@ -126,6 +129,7 @@ impl TestServerBuilder { ) .await, http_listener_port: 8899, + own_listeners: Vec::new(), temp_dir, reset, logging_enabled: false, @@ -212,17 +216,19 @@ impl TestServerBuilder { } else { name.to_string() }; - self.insert_object(NetworkListener { - bind: Map::new(vec![ - SocketAddr::from_str(&format!("0.0.0.0:{port}")).unwrap(), - ]), - name, - protocol, - use_tls: true, - tls_implicit, - ..Default::default() - }) - .await; + let id = self + .insert_object(NetworkListener { + bind: Map::new(vec![ + SocketAddr::from_str(&format!("0.0.0.0:{port}")).unwrap(), + ]), + name, + protocol, + use_tls: true, + tls_implicit, + ..Default::default() + }) + .await; + self.own_listeners.push(id); self } @@ -390,6 +396,24 @@ impl TestServerBuilder { .parse_tcp_acceptors(&mut self.bootstrap, inner.clone()) .await; + // inbuxa: a compat run opens a copy of a real server's store, which + // carries that server's listeners: 25, 443, 993 and the rest. Nothing + // here runs as root, so every one of them fails to bind and the run + // dies before it reads any data. Those are the copy's, not ours, and + // the tests reach the server over the compat- listeners added above, + // so drop their errors and keep every other one — including a bind + // that fails on a listener this builder added. + if std::env::var("NO_INSERT").is_ok() { + let own = &self.own_listeners; + self.bootstrap.errors.retain(|error| match error { + registry::types::error::Error::Build { object_id, .. } => { + object_id.object() != ObjectType::NetworkListener + || own.contains(&object_id.id()) + } + _ => true, + }); + } + // Start services self.bootstrap.assert_no_errors(); if !self.disable_services {