#![cfg(feature = "query")]
#![cfg(feature = "test-support")]
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use libtmux::query::{Filterable as _, QueryIteratorExt as _};
use libtmux::test::TestServer;
use libtmux::{Command, Server, TmuxText};
async fn run(server: &Server, command: Command) {
let result = server.cmd(command).await.expect("setup command executes");
assert!(result.success(), "setup command succeeds");
}
async fn new_session(server: &Server, name: &str) {
run(
server,
Command::new("new-session")
.arg("-d")
.arg("-s")
.arg(name)
.arg("exec sleep 300"),
)
.await;
}
#[tokio::test]
async fn a_typed_expression_filters_the_handles_a_listing_returned() {
let guard = TestServer::builder().start().await.expect("tmux starts");
let server = guard.server();
for name in ["build", "build-docs", "test"] {
new_session(server, name).await;
}
let sessions = server.sessions().await.expect("sessions list");
let fields = libtmux::Session::filter_fields();
assert_eq!(
sessions
.iter()
.filter(|session| session.window_count() == 1)
.count(),
3,
);
let starts_with_build = fields.session_name.starts_with("build");
let names = sessions
.iter()
.matching(&starts_with_build)
.map(libtmux::Session::name)
.map(TmuxText::as_bytes)
.collect::<Vec<_>>();
assert_eq!(names, [&b"build"[..], &b"build-docs"[..]]);
let exactly_build = starts_with_build
.clone()
.and(fields.session_name.eq("build"));
let found = sessions
.iter()
.matching(&exactly_build)
.exactly_one()
.expect("exactly one session is named build");
assert_eq!(found.name().as_bytes(), &b"build"[..]);
let none = fields.session_name.eq("absent");
assert!(sessions.iter().matching(&none).exactly_one().is_err());
assert_eq!(sessions.iter().matching(&none).one_or_none(), Ok(None));
assert!(
sessions
.iter()
.matching(&starts_with_build)
.one_or_none()
.is_err()
);
assert_eq!(
sessions
.iter()
.matching(&starts_with_build.clone().not())
.count(),
1
);
guard.shutdown().await.expect("tmux fixture shuts down");
}
#[tokio::test]
async fn every_hierarchy_handle_is_filterable() {
let guard = TestServer::builder().start().await.expect("tmux starts");
let server = guard.server();
new_session(server, "work").await;
run(
server,
Command::new("split-window")
.arg("-d")
.arg("-t")
.arg("work")
.arg("exec sleep 300"),
)
.await;
let windows = server.windows().await.expect("windows list");
let window_fields = libtmux::Window::filter_fields();
assert_eq!(
windows
.iter()
.matching(&window_fields.window_panes.eq(2_u32))
.count(),
1,
);
let pane_fields = libtmux::Pane::filter_fields();
let running_sleep = pane_fields.pane_current_command.contains("sleep");
let panes = poll_until(server, |panes| {
panes.iter().matching(&running_sleep).count() == 2
})
.await;
let active = panes
.iter()
.matching(&pane_fields.pane_active.eq(true))
.exactly_one()
.expect("exactly one pane is active");
assert!(active.is_active());
assert_eq!(panes.iter().matching(&running_sleep).count(), 2);
guard.shutdown().await.expect("tmux fixture shuts down");
}
async fn poll_until(
server: &Server,
ready: impl Fn(&[libtmux::Pane]) -> bool,
) -> Vec<libtmux::Pane> {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
loop {
let panes = server.panes().await.expect("panes list");
if ready(&panes) {
return panes;
}
assert!(
std::time::Instant::now() < deadline,
"panes did not reach the expected state before the deadline",
);
tokio::task::yield_now().await;
}
}
#[tokio::test]
async fn a_filter_expression_is_inert_and_reusable() {
let guard = TestServer::builder().start().await.expect("tmux starts");
let server = guard.server();
new_session(server, "alpha").await;
let fields = libtmux::Session::filter_fields();
let expression = fields.session_name.eq("alpha");
let before = server.sessions().await.expect("sessions list");
assert_eq!(before.iter().matching(&expression).count(), 1);
run(
server,
Command::new("rename-session")
.arg("-t")
.arg("alpha")
.arg("renamed"),
)
.await;
let after = server.sessions().await.expect("sessions list");
assert_eq!(after.iter().matching(&expression).count(), 0);
assert_eq!(before.iter().matching(&expression).count(), 1);
guard.shutdown().await.expect("tmux fixture shuts down");
}
#[tokio::test]
async fn searching_filters_a_listing_and_keeps_the_lenient_loud_pair() {
let guard = TestServer::builder().start().await.expect("tmux starts");
let server = guard.server();
let session = server.new_session("searched").await.expect("session");
for name in ["build", "build-docs", "test"] {
session
.new_window(libtmux::NewWindowOptions::new(name))
.await
.expect("window");
}
let fields = libtmux::Window::filter_fields();
let building = session
.search_windows(&fields.window_name.starts_with("build"))
.await
.expect("search");
assert_eq!(building.len(), 2);
let none = session
.search_windows(&fields.window_name.eq("absent"))
.await
.expect("search");
assert!(none.is_empty());
assert_eq!(
session
.search_windows_or_empty(&fields.window_name.starts_with("build"))
.await
.len(),
building.len(),
);
guard.shutdown().await.expect("tmux fixture shuts down");
}