refactor(tests): share one soulfind fixture across the e2e suites

The TUI suite had grown its own copy of the soulfind spawner the protocol
suite already carried, and its own copy of the CLI suite's in-process
client and settle helpers. All four suites now spawn, log in, settle and
skip through `tests/common`: the CLI suite's own server goes too, and
the fixture checks its child is still alive before calling the port
answered, so a lost port cannot pass another server off as ours.
This commit is contained in:
Michel de Graaf
2026-09-08 13:12:12 +02:00
parent 76f4854169
commit 3ef78c6de1
7 changed files with 189 additions and 332 deletions
+11 -73
View File
@@ -11,8 +11,8 @@
mod common;
use common::{CLI_ENV_VARS, free_port, soulfind_binary};
use soulseek_rs::{Client, ClientSettings, PeerAddress};
use common::{CLI_ENV_VARS, Soulfind, free_port, login, settle};
use soulseek_rs::Client;
use std::io::Write;
use std::net::{TcpStream, ToSocketAddrs};
use std::path::{Path, PathBuf};
@@ -508,8 +508,9 @@ static SERVER_GATE: std::sync::Mutex<()> = std::sync::Mutex::new(());
struct TestServer {
host: String,
port: u16,
child: Option<Child>,
db: Option<PathBuf>,
/// The soulfind spawned for this run; `None` against a server named by
/// `SOULSEEK_TEST_SERVER`.
_server: Option<Soulfind>,
_gate: std::sync::MutexGuard<'static, ()>,
/// Daemons started for this server, one per (user, session flags). Dropped
/// with the server, which kills them.
@@ -640,41 +641,17 @@ impl TestServer {
return Some(Self {
host: host.to_string(),
port,
child: None,
db: None,
_server: None,
_gate: gate,
daemons: std::sync::Mutex::new(std::collections::HashMap::new()),
});
}
let bin = soulfind_binary()?;
let port = free_port()?;
let db = std::env::temp_dir().join(format!("soulfind-cli-{port}.db"));
let _ = std::fs::remove_file(&db);
let mut child = Command::new(&bin)
.arg("-p")
.arg(port.to_string())
.arg("-d")
.arg(&db)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
if wait_until_listening("127.0.0.1", port, Duration::from_secs(5))
.is_none()
{
let _ = child.kill();
let _ = child.wait();
return None;
}
let server = Soulfind::start()?;
Some(Self {
host: "127.0.0.1".to_string(),
port,
child: Some(child),
db: Some(db),
port: server.port(),
_server: Some(server),
_gate: gate,
daemons: std::sync::Mutex::new(std::collections::HashMap::new()),
})
@@ -777,31 +754,7 @@ impl TestServer {
/// An in-process client, used to stand up the other end of a test.
fn client(&self, user: &str, shares: Vec<String>) -> Client {
let port = free_port().expect("peer port");
let mut client = Client::with_settings(ClientSettings {
username: user.to_string(),
password: "pw".to_string(),
server_address: PeerAddress::new(self.host.clone(), self.port),
enable_listen: true,
listen_port: port,
shared_directories: shares,
version: soulseek_rs::ClientVersion::default(),
});
client.connect().expect("peer connect");
assert!(client.login().expect("peer login"), "peer should log in");
client
}
}
impl Drop for TestServer {
fn drop(&mut self) {
if let Some(child) = self.child.as_mut() {
let _ = child.kill();
let _ = child.wait();
}
if let Some(db) = self.db.as_ref() {
let _ = std::fs::remove_file(db);
}
login(&self.host, self.port, user, shares)
}
}
@@ -829,17 +782,7 @@ macro_rules! server_or_skip {
match TestServer::resolve() {
Some(server) => server,
None => {
let required = std::env::var("SOULSEEK_E2E_REQUIRED")
.is_ok_and(|v| v != "0" && !v.is_empty());
assert!(
!required,
"SOULSEEK_E2E_REQUIRED is set but no soulfind server could \
be started (set SOULFIND_BIN or SOULSEEK_TEST_SERVER)"
);
println!(
"cli e2e skipped: no soulfind server (set SOULFIND_BIN or \
SOULSEEK_TEST_SERVER to run)"
);
common::no_server("cli e2e");
return;
}
}
@@ -936,11 +879,6 @@ fn split_session_flags(args: &[&str]) -> (Vec<String>, Vec<String>) {
(session, command)
}
/// Wait out the SetWaitPort registrations so peer lookups resolve.
fn settle() {
std::thread::sleep(Duration::from_secs(1));
}
/// Like [`cli`], but keeping a config file so a command that reads or writes
/// one (the wishlist) sees it.
///