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:
@@ -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.
|
||||
///
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use soulseek_rs::{Client, ClientSettings, PeerAddress};
|
||||
use std::net::TcpStream;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Every environment variable the binary reads, cleared for each run so a
|
||||
/// developer's shell (or a stray `.env`) cannot change what a test observes —
|
||||
@@ -44,3 +48,116 @@ pub fn free_port() -> Option<u16> {
|
||||
.ok()
|
||||
.map(|addr| addr.port())
|
||||
}
|
||||
|
||||
/// A soulfind spawned for one test, on a port nobody else holds, with a
|
||||
/// throwaway database that goes with it.
|
||||
pub struct Soulfind {
|
||||
child: Child,
|
||||
db: PathBuf,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Soulfind {
|
||||
pub fn start() -> Option<Self> {
|
||||
let port = free_port()?;
|
||||
let db = std::env::temp_dir().join(format!("soulfind-{port}.db"));
|
||||
let _ = std::fs::remove_file(&db);
|
||||
let child = Self::spawn(port, &db)?;
|
||||
Some(Self { child, db, port })
|
||||
}
|
||||
|
||||
fn spawn(port: u16, db: &Path) -> Option<Child> {
|
||||
let mut child = Command::new(soulfind_binary()?)
|
||||
.args(["-d", db.to_str()?, "-p", &port.to_string()])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()?;
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while Instant::now() < deadline {
|
||||
// Whoever answers on the port has to be this child: one that
|
||||
// lost the port and died would otherwise pass as up.
|
||||
if child.try_wait().ok().flatten().is_some() {
|
||||
return None;
|
||||
}
|
||||
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
||||
return Some(child);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
None
|
||||
}
|
||||
|
||||
pub const fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
|
||||
pub fn address(&self) -> String {
|
||||
format!("127.0.0.1:{}", self.port)
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
}
|
||||
|
||||
/// Bring it back on the same port and database.
|
||||
pub fn restart(&mut self) -> Option<()> {
|
||||
self.stop();
|
||||
self.child = Self::spawn(self.port, &self.db)?;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// A client logged in to this server as `user`, sharing `shares`.
|
||||
pub fn client(&self, user: &str, shares: Vec<String>) -> Client {
|
||||
login("127.0.0.1", self.port, user, shares)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Soulfind {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
let _ = std::fs::remove_file(&self.db);
|
||||
}
|
||||
}
|
||||
|
||||
/// An in-process client logged in to the server at `host:port` as `user`,
|
||||
/// sharing `shares`: the other end of a test.
|
||||
///
|
||||
/// The listener stays on: a peer delivers search results and transfers over
|
||||
/// a connection it opens back to us, so a client without one hears nothing.
|
||||
pub fn login(host: &str, port: u16, user: &str, shares: Vec<String>) -> Client {
|
||||
let mut client = Client::with_settings(ClientSettings {
|
||||
username: user.to_string(),
|
||||
password: "pw".to_string(),
|
||||
server_address: PeerAddress::new(host.to_string(), port),
|
||||
enable_listen: true,
|
||||
listen_port: free_port().expect("peer 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
|
||||
}
|
||||
|
||||
/// Wait out the SetWaitPort registrations so peer lookups resolve.
|
||||
pub fn settle() {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
/// What a suite does without a server: fail when the run said one is
|
||||
/// required, otherwise say it skipped. The caller returns afterwards.
|
||||
pub fn no_server(suite: &str) {
|
||||
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 for the {suite} suite (set SOULFIND_BIN)"
|
||||
);
|
||||
println!("{suite} skipped: no soulfind server (set SOULFIND_BIN to run)");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{CLI_ENV_VARS, free_port, soulfind_binary};
|
||||
use common::{CLI_ENV_VARS, Soulfind, free_port};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -215,17 +215,7 @@ macro_rules! daemon_or_skip {
|
||||
match Daemon::start() {
|
||||
Some(daemon) => daemon,
|
||||
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 daemon could be \
|
||||
started (set SOULFIND_BIN)"
|
||||
);
|
||||
println!(
|
||||
"daemon protocol e2e skipped: no soulfind server (set \
|
||||
SOULFIND_BIN to run)"
|
||||
);
|
||||
common::no_server("daemon protocol e2e");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -431,66 +421,6 @@ fn discover_serves_a_contract_whose_parameters_match_what_is_accepted() {
|
||||
// Fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Soulfind {
|
||||
child: Child,
|
||||
db: PathBuf,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Soulfind {
|
||||
fn start() -> Option<Self> {
|
||||
let port = free_port()?;
|
||||
let db = std::env::temp_dir().join(format!("soulfind-proto-{port}.db"));
|
||||
let _ = std::fs::remove_file(&db);
|
||||
let child = Self::spawn(port, &db)?;
|
||||
Some(Self { child, db, port })
|
||||
}
|
||||
|
||||
fn spawn(port: u16, db: &Path) -> Option<Child> {
|
||||
let bin = soulfind_binary()?;
|
||||
|
||||
let mut child = Command::new(bin)
|
||||
.args(["-d", db.to_str()?, "-p", &port.to_string()])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()?;
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while Instant::now() < deadline {
|
||||
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
||||
return Some(child);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
None
|
||||
}
|
||||
|
||||
fn address(&self) -> String {
|
||||
format!("127.0.0.1:{}", self.port)
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
}
|
||||
|
||||
fn restart(&mut self) -> Option<()> {
|
||||
self.stop();
|
||||
self.child = Self::spawn(self.port, &self.db)?;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Soulfind {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
let _ = std::fs::remove_file(&self.db);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_until(timeout: Duration, mut f: impl FnMut() -> bool) -> bool {
|
||||
let deadline = Instant::now() + timeout;
|
||||
while Instant::now() < deadline {
|
||||
|
||||
+38
-154
@@ -14,17 +14,15 @@
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{free_port, soulfind_binary};
|
||||
use common::{Soulfind, settle};
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::TestBackend;
|
||||
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use soulseek_rs::{Client, ClientSettings, PeerAddress};
|
||||
use soulseek_rs::Client;
|
||||
use soulseek_rs_tui::MainTui;
|
||||
use soulseek_rs_tui::models::{FocusedPane, SearchStatus};
|
||||
use soulseek_rs_tui::models::SearchStatus;
|
||||
use soulseek_rs_tui::persist::state::StateStore;
|
||||
use std::net::TcpStream;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -32,98 +30,18 @@ use std::time::{Duration, Instant};
|
||||
/// it out; long enough for a peer on loopback to answer many times over.
|
||||
const SEARCH_WINDOW: Duration = Duration::from_secs(3);
|
||||
|
||||
/// A soulfind spawned for one test, on a port nobody else holds.
|
||||
struct Soulfind {
|
||||
child: Child,
|
||||
db: std::path::PathBuf,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Soulfind {
|
||||
fn start() -> Option<Self> {
|
||||
let bin = soulfind_binary()?;
|
||||
let port = free_port()?;
|
||||
let db = std::env::temp_dir().join(format!("soulfind-tui-{port}.db"));
|
||||
let _ = std::fs::remove_file(&db);
|
||||
|
||||
let mut child = Command::new(bin)
|
||||
.args(["-d", db.to_str()?, "-p", &port.to_string()])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()?;
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while Instant::now() < deadline {
|
||||
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
||||
return Some(Self { child, db, port });
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
None
|
||||
}
|
||||
|
||||
/// A client logged in to this server as `user`, sharing `shares`.
|
||||
///
|
||||
/// The listener stays on: a peer delivers search results over a
|
||||
/// connection it opens back to the searcher, so a searcher without one
|
||||
/// hears nothing.
|
||||
fn client(&self, user: &str, shares: Vec<String>) -> Client {
|
||||
let mut client = Client::with_settings(ClientSettings {
|
||||
username: user.to_string(),
|
||||
password: "pw".to_string(),
|
||||
server_address: PeerAddress::new(
|
||||
"127.0.0.1".to_string(),
|
||||
self.port,
|
||||
),
|
||||
enable_listen: true,
|
||||
listen_port: free_port().expect("peer 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 Soulfind {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
let _ = std::fs::remove_file(&self.db);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! server_or_skip {
|
||||
() => {
|
||||
match Soulfind::start() {
|
||||
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)"
|
||||
);
|
||||
println!(
|
||||
"tui e2e skipped: no soulfind server (set SOULFIND_BIN to \
|
||||
run)"
|
||||
);
|
||||
common::no_server("tui e2e");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Wait out the SetWaitPort registrations so peer lookups resolve.
|
||||
fn settle() {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
/// The window under test: the real `MainTui` over a real session, drawn onto
|
||||
/// a buffer instead of a terminal.
|
||||
struct Window {
|
||||
@@ -162,23 +80,22 @@ impl Window {
|
||||
self.terminal.backend().to_string()
|
||||
}
|
||||
|
||||
/// Keep the window running until `done` says so, or `timeout` runs out.
|
||||
/// Returns the last screen either way, so a failing test shows what the
|
||||
/// user would have been looking at.
|
||||
/// Keep the window running until `done` says so, and hand back the
|
||||
/// screen it stopped on. Past `timeout` the test fails showing the last
|
||||
/// screen, what the user would have been looking at, under `what`.
|
||||
fn run_until(
|
||||
&mut self,
|
||||
timeout: Duration,
|
||||
what: &str,
|
||||
mut done: impl FnMut(&MainTui, &str) -> bool,
|
||||
) -> (bool, String) {
|
||||
) -> String {
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
let screen = self.frame();
|
||||
if done(&self.tui, &screen) {
|
||||
return (true, screen);
|
||||
}
|
||||
if Instant::now() > deadline {
|
||||
return (false, screen);
|
||||
return screen;
|
||||
}
|
||||
assert!(Instant::now() <= deadline, "{what}:\n{screen}");
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
@@ -192,10 +109,6 @@ impl Window {
|
||||
.map(|file| file.filename.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn search_status(&self, index: usize) -> SearchStatus {
|
||||
self.tui.state().searches[index].status.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the file the sharer offers has reached the Results pane, on
|
||||
@@ -208,6 +121,11 @@ fn shows_the_shared_file(tui: &MainTui, screen: &str) -> bool {
|
||||
&& screen.contains("tui_probe_rerun")
|
||||
}
|
||||
|
||||
/// Whether the one search has run its window out.
|
||||
fn finished(tui: &MainTui, _: &str) -> bool {
|
||||
tui.state().searches[0].status == SearchStatus::Completed
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_s_runs_a_restored_query_again_and_the_answers_land_on_screen() {
|
||||
let server = server_or_skip!();
|
||||
@@ -215,11 +133,8 @@ fn shift_s_runs_a_restored_query_again_and_the_answers_land_on_screen() {
|
||||
// The other end of the wire: a peer sharing one file with a name only
|
||||
// this test searches for.
|
||||
let share = tempfile::tempdir().expect("share dir");
|
||||
std::fs::write(
|
||||
share.path().join("tui_probe_rerun_vexq.bin"),
|
||||
(0..4096u32).map(|i| (i % 251) as u8).collect::<Vec<u8>>(),
|
||||
)
|
||||
.expect("share file");
|
||||
std::fs::write(share.path().join("tui_probe_rerun_vexq.bin"), [7u8; 64])
|
||||
.expect("share file");
|
||||
let _sharer = server
|
||||
.client("tui_e2e_sharer", vec![share.path().display().to_string()]);
|
||||
settle();
|
||||
@@ -240,46 +155,21 @@ fn shift_s_runs_a_restored_query_again_and_the_answers_land_on_screen() {
|
||||
screen.contains("vexq"),
|
||||
"the old query is listed:\n{screen}"
|
||||
);
|
||||
assert_eq!(window.tui.state().searches.len(), 1);
|
||||
assert_eq!(window.search_status(0), SearchStatus::Completed);
|
||||
assert!(
|
||||
window.result_names().is_empty(),
|
||||
"and it has nothing under it"
|
||||
);
|
||||
assert_eq!(
|
||||
window.tui.state().focused_pane,
|
||||
FocusedPane::Searches,
|
||||
"the window opens on the list, so the query is already highlighted"
|
||||
);
|
||||
|
||||
// One key, no typing.
|
||||
// One key, no typing: the window opens on the list with the query
|
||||
// highlighted.
|
||||
window.press(KeyCode::Char('S'));
|
||||
|
||||
assert_eq!(window.tui.state().searches.len(), 1, "no second row");
|
||||
assert_eq!(window.search_status(0), SearchStatus::Active);
|
||||
assert_eq!(window.tui.state().focused_pane, FocusedPane::Results);
|
||||
|
||||
let (found, screen) =
|
||||
window.run_until(Duration::from_secs(15), shows_the_shared_file);
|
||||
assert!(
|
||||
found,
|
||||
"the sharer's file should reach the screen:\n{screen}"
|
||||
window.run_until(
|
||||
Duration::from_secs(15),
|
||||
"the sharer's file should reach the screen",
|
||||
shows_the_shared_file,
|
||||
);
|
||||
assert!(
|
||||
window
|
||||
.result_names()
|
||||
.iter()
|
||||
.all(|name| name.contains("tui_probe_rerun")),
|
||||
"only what the network answered: {:?}",
|
||||
window.result_names()
|
||||
);
|
||||
|
||||
// The search runs its window out and settles, keeping what it found.
|
||||
let (done, screen) = window
|
||||
.run_until(SEARCH_WINDOW + Duration::from_secs(5), |tui, _| {
|
||||
tui.state().searches[0].status == SearchStatus::Completed
|
||||
});
|
||||
assert!(done, "the search should finish:\n{screen}");
|
||||
let screen = window.run_until(
|
||||
SEARCH_WINDOW + Duration::from_secs(5),
|
||||
"the search should finish",
|
||||
finished,
|
||||
);
|
||||
assert!(shows_the_shared_file(&window.tui, &screen));
|
||||
let first_run = window.result_names();
|
||||
|
||||
@@ -287,23 +177,17 @@ fn shift_s_runs_a_restored_query_again_and_the_answers_land_on_screen() {
|
||||
// the same peer has to answer the same searcher a second time, and the
|
||||
// window has to start the display over rather than stack the answers.
|
||||
window.press(KeyCode::Char('1'));
|
||||
assert_eq!(window.tui.state().focused_pane, FocusedPane::Searches);
|
||||
window.press(KeyCode::Char('S'));
|
||||
assert_eq!(window.search_status(0), SearchStatus::Active);
|
||||
assert!(
|
||||
window.result_names().is_empty(),
|
||||
"the display starts over: {:?}",
|
||||
window.result_names()
|
||||
window.run_until(
|
||||
Duration::from_secs(15),
|
||||
"the second run should be answered too",
|
||||
shows_the_shared_file,
|
||||
);
|
||||
window.run_until(
|
||||
SEARCH_WINDOW + Duration::from_secs(5),
|
||||
"the second run should finish",
|
||||
finished,
|
||||
);
|
||||
|
||||
let (found, screen) =
|
||||
window.run_until(Duration::from_secs(15), shows_the_shared_file);
|
||||
assert!(found, "the second run should be answered too:\n{screen}");
|
||||
let (done, _) = window
|
||||
.run_until(SEARCH_WINDOW + Duration::from_secs(5), |tui, _| {
|
||||
tui.state().searches[0].status == SearchStatus::Completed
|
||||
});
|
||||
assert!(done);
|
||||
assert_eq!(
|
||||
window.result_names(),
|
||||
first_run,
|
||||
|
||||
Reference in New Issue
Block a user