refactor(tabs): lay down infrastructure for multiple users (#864)

* refactor(screen): support multiple mirrored clients

* style(fmt): make rustfmt happy

* style(clippy): make clippy happy

* whitespace

* github, y u no update CI?!

* is this a cache issue?

* is it the checkout cache?

* no cache at all?

* Debug

* fix gototab

* decoment

* gototab none in wasm_vm

* gototab none in wasm_vm

* the fun never ends

* tests(e2e): update infra and add multiple user mirroring test

* refactor(tab): change structs in tabs and terminal panes to support multiple users

* style(fmt): make rustfmt happy

* style(fmt): make clippy happy
This commit is contained in:
Aram Drevekenin 2021-11-12 17:22:14 +01:00 committed by GitHub
parent 3e07040808
commit bd795a3e9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 2274 additions and 1579 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
use std::sync::{Arc, Mutex};
use zellij_tile::data::Palette; use zellij_tile::data::Palette;
use zellij_server::panes::TerminalPane; use zellij_server::panes::TerminalPane;
@ -16,6 +17,7 @@ const CONNECTION_STRING: &str = "127.0.0.1:2222";
const CONNECTION_USERNAME: &str = "test"; const CONNECTION_USERNAME: &str = "test";
const CONNECTION_PASSWORD: &str = "test"; const CONNECTION_PASSWORD: &str = "test";
const SESSION_NAME: &str = "e2e-test"; const SESSION_NAME: &str = "e2e-test";
const RETRIES: usize = 10;
fn ssh_connect() -> ssh2::Session { fn ssh_connect() -> ssh2::Session {
let tcp = TcpStream::connect(CONNECTION_STRING).unwrap(); let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
@ -24,7 +26,16 @@ fn ssh_connect() -> ssh2::Session {
sess.handshake().unwrap(); sess.handshake().unwrap();
sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD) sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
.unwrap(); .unwrap();
sess.set_timeout(3000); sess
}
fn ssh_connect_without_timeout() -> ssh2::Session {
let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD)
.unwrap();
sess sess
} }
@ -107,6 +118,76 @@ fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
channel.flush().unwrap(); channel.flush().unwrap();
} }
fn read_from_channel(
channel: &Arc<Mutex<ssh2::Channel>>,
last_snapshot: &Arc<Mutex<String>>,
cursor_coordinates: &Arc<Mutex<(usize, usize)>>,
pane_geom: &PaneGeom,
) -> (Arc<Mutex<bool>>, std::thread::JoinHandle<()>) {
let should_keep_running = Arc::new(Mutex::new(true));
let thread = std::thread::Builder::new()
.name("read_thread".into())
.spawn({
let should_keep_running = should_keep_running.clone();
let channel = channel.clone();
let last_snapshot = last_snapshot.clone();
let cursor_coordinates = cursor_coordinates.clone();
let mut vte_parser = vte::Parser::new();
let mut terminal_output = TerminalPane::new(0, *pane_geom, Palette::default(), 0); // 0 is the pane index
let mut retries_left = 3;
move || {
let mut should_sleep = false;
loop {
if !*should_keep_running.lock().unwrap() {
break;
}
if should_sleep {
std::thread::sleep(std::time::Duration::from_millis(10));
should_sleep = false;
}
let mut buf = [0u8; 1280000];
match channel.lock().unwrap().read(&mut buf) {
Ok(0) => {
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
}
Ok(count) => {
for byte in buf.iter().take(count) {
vte_parser.advance(&mut terminal_output.grid, *byte);
}
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.grid.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
}
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
let current_snapshot = take_snapshot(&mut terminal_output);
let mut last_snapshot = last_snapshot.lock().unwrap();
*cursor_coordinates.lock().unwrap() =
terminal_output.cursor_coordinates().unwrap_or((0, 0));
*last_snapshot = current_snapshot;
should_sleep = true;
} else if retries_left > 0 {
retries_left -= 1;
} else {
break;
}
}
}
}
}
})
.unwrap();
(should_keep_running, thread)
}
pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String { pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
let output_lines = terminal_output.read_buffer_as_lines(); let output_lines = terminal_output.read_buffer_as_lines();
let cursor_coordinates = terminal_output.cursor_coordinates(); let cursor_coordinates = terminal_output.cursor_coordinates();
@ -128,42 +209,44 @@ pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
snapshot snapshot
} }
pub struct RemoteTerminal<'a> { pub struct RemoteTerminal {
channel: &'a mut ssh2::Channel, channel: Arc<Mutex<ssh2::Channel>>,
cursor_x: usize, cursor_x: usize,
cursor_y: usize, cursor_y: usize,
current_snapshot: String, last_snapshot: Arc<Mutex<String>>,
} }
impl<'a> std::fmt::Debug for RemoteTerminal<'a> { impl std::fmt::Debug for RemoteTerminal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"cursor x: {}\ncursor_y: {}\ncurrent_snapshot:\n{}", "cursor x: {}\ncursor_y: {}\ncurrent_snapshot:\n{}",
self.cursor_x, self.cursor_y, self.current_snapshot self.cursor_x,
self.cursor_y,
*self.last_snapshot.lock().unwrap()
) )
} }
} }
impl<'a> RemoteTerminal<'a> { impl RemoteTerminal {
pub fn cursor_position_is(&self, x: usize, y: usize) -> bool { pub fn cursor_position_is(&self, x: usize, y: usize) -> bool {
x == self.cursor_x && y == self.cursor_y x == self.cursor_x && y == self.cursor_y
} }
pub fn tip_appears(&self) -> bool { pub fn tip_appears(&self) -> bool {
self.current_snapshot.contains("Tip:") self.last_snapshot.lock().unwrap().contains("Tip:")
} }
pub fn status_bar_appears(&self) -> bool { pub fn status_bar_appears(&self) -> bool {
self.current_snapshot.contains("Ctrl +") self.last_snapshot.lock().unwrap().contains("Ctrl +")
} }
pub fn snapshot_contains(&self, text: &str) -> bool { pub fn snapshot_contains(&self, text: &str) -> bool {
self.current_snapshot.contains(text) self.last_snapshot.lock().unwrap().contains(text)
} }
#[allow(unused)] #[allow(unused)]
pub fn current_snapshot(&self) -> String { pub fn current_snapshot(&self) -> String {
// convenience method for writing tests, // convenience method for writing tests,
// this should only be used when developing, // this should only be used when developing,
// please prefer "snapsht_contains" instead // please prefer "snapsht_contains" instead
self.current_snapshot.clone() self.last_snapshot.lock().unwrap().clone()
} }
#[allow(unused)] #[allow(unused)]
pub fn current_cursor_position(&self) -> String { pub fn current_cursor_position(&self) -> String {
@ -173,21 +256,25 @@ impl<'a> RemoteTerminal<'a> {
format!("x: {}, y: {}", self.cursor_x, self.cursor_y) format!("x: {}, y: {}", self.cursor_x, self.cursor_y)
} }
pub fn send_key(&mut self, key: &[u8]) { pub fn send_key(&mut self, key: &[u8]) {
self.channel.write_all(key).unwrap(); let mut channel = self.channel.lock().unwrap();
self.channel.flush().unwrap(); channel.write_all(key).unwrap();
channel.flush().unwrap();
} }
pub fn change_size(&mut self, cols: u32, rows: u32) { pub fn change_size(&mut self, cols: u32, rows: u32) {
self.channel self.channel
.lock()
.unwrap()
.request_pty_size(cols, rows, Some(cols), Some(rows)) .request_pty_size(cols, rows, Some(cols), Some(rows))
.unwrap(); .unwrap();
} }
pub fn attach_to_original_session(&mut self) { pub fn attach_to_original_session(&mut self) {
self.channel let mut channel = self.channel.lock().unwrap();
channel
.write_all( .write_all(
format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME).as_bytes(), format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME).as_bytes(),
) )
.unwrap(); .unwrap();
self.channel.flush().unwrap(); channel.flush().unwrap();
} }
} }
@ -200,26 +287,21 @@ pub struct Step {
pub struct RemoteRunner { pub struct RemoteRunner {
steps: Vec<Step>, steps: Vec<Step>,
current_step_index: usize, current_step_index: usize,
vte_parser: vte::Parser, channel: Arc<Mutex<ssh2::Channel>>,
terminal_output: TerminalPane,
channel: ssh2::Channel,
test_name: &'static str,
currently_running_step: Option<String>, currently_running_step: Option<String>,
retries_left: usize, retries_left: usize,
win_size: Size, retry_pause_ms: usize,
layout_file_name: Option<&'static str>,
without_frames: bool,
session_name: Option<String>,
attach_to_existing: bool,
panic_on_no_retries_left: bool, panic_on_no_retries_left: bool,
last_snapshot: Arc<Mutex<String>>,
cursor_coordinates: Arc<Mutex<(usize, usize)>>, // x, y
reader_thread: (Arc<Mutex<bool>>, std::thread::JoinHandle<()>),
pub test_timed_out: bool, pub test_timed_out: bool,
} }
impl RemoteRunner { impl RemoteRunner {
pub fn new(test_name: &'static str, win_size: Size) -> Self { pub fn new(win_size: Size) -> Self {
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows); let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols); let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows); rows.set_inner(win_size.rows);
@ -230,35 +312,38 @@ impl RemoteRunner {
rows, rows,
cols, cols,
}; };
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel); start_zellij(&mut channel);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner { RemoteRunner {
steps: vec![], steps: vec![],
channel, channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None, currently_running_step: None,
current_step_index: 0, current_step_index: 0,
retries_left: 10, retries_left: RETRIES,
win_size, retry_pause_ms: 100,
layout_file_name: None,
without_frames: false,
session_name: None,
attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true, panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
} }
} }
pub fn new_with_session_name( pub fn kill_running_sessions(win_size: Size) {
test_name: &'static str,
win_size: Size,
session_name: &str,
) -> Self {
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new(); setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel);
}
pub fn new_with_session_name(win_size: Size, session_name: &str) -> Self {
// notice that this method does not have a timeout, so use with caution!
let sess = ssh_connect_without_timeout();
let mut channel = sess.channel_session().unwrap();
let mut rows = Dimension::fixed(win_size.rows); let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols); let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows); rows.set_inner(win_size.rows);
@ -269,35 +354,31 @@ impl RemoteRunner {
rows, rows,
cols, cols,
}; };
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_in_session(&mut channel, session_name); start_zellij_in_session(&mut channel, session_name);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner { RemoteRunner {
steps: vec![], steps: vec![],
channel, channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None, currently_running_step: None,
current_step_index: 0, current_step_index: 0,
retries_left: 10, retries_left: RETRIES,
win_size, retry_pause_ms: 100,
layout_file_name: None,
without_frames: false,
session_name: Some(String::from(session_name)),
attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true, panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
} }
} }
pub fn new_existing_session( pub fn new_existing_session(win_size: Size, session_name: &str) -> Self {
test_name: &'static str, let sess = ssh_connect_without_timeout();
win_size: Size,
session_name: &str,
) -> Self {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows); let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols); let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows); rows.set_inner(win_size.rows);
@ -308,31 +389,31 @@ impl RemoteRunner {
rows, rows,
cols, cols,
}; };
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
attach_to_existing_session(&mut channel, session_name); attach_to_existing_session(&mut channel, session_name);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner { RemoteRunner {
steps: vec![], steps: vec![],
channel, channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None, currently_running_step: None,
current_step_index: 0, current_step_index: 0,
retries_left: 10, retries_left: RETRIES,
win_size, retry_pause_ms: 100,
layout_file_name: None,
without_frames: false,
session_name: Some(String::from(session_name)),
attach_to_existing: true,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true, panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
} }
} }
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self { pub fn new_without_frames(win_size: Size) -> Self {
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows); let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols); let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows); rows.set_inner(win_size.rows);
@ -343,36 +424,32 @@ impl RemoteRunner {
rows, rows,
cols, cols,
}; };
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_without_frames(&mut channel); start_zellij_without_frames(&mut channel);
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner { RemoteRunner {
steps: vec![], steps: vec![],
channel, channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None, currently_running_step: None,
current_step_index: 0, current_step_index: 0,
retries_left: 10, retries_left: RETRIES,
win_size, retry_pause_ms: 100,
layout_file_name: None,
without_frames: true,
session_name: None,
attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true, panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
} }
} }
pub fn new_with_layout( pub fn new_with_layout(win_size: Size, layout_file_name: &'static str) -> Self {
test_name: &'static str,
win_size: Size,
layout_file_name: &'static str,
) -> Self {
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name); let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name);
let sess = ssh_connect(); let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap(); let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let mut rows = Dimension::fixed(win_size.rows); let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols); let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows); rows.set_inner(win_size.rows);
@ -383,159 +460,104 @@ impl RemoteRunner {
rows, rows,
cols, cols,
}; };
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size); setup_remote_environment(&mut channel, win_size);
start_zellij_with_layout(&mut channel, &remote_path.to_string_lossy()); start_zellij_with_layout(&mut channel, &remote_path.to_string_lossy());
let channel = Arc::new(Mutex::new(channel));
let last_snapshot = Arc::new(Mutex::new(String::new()));
let cursor_coordinates = Arc::new(Mutex::new((0, 0)));
sess.set_blocking(false);
let reader_thread =
read_from_channel(&channel, &last_snapshot, &cursor_coordinates, &pane_geom);
RemoteRunner { RemoteRunner {
steps: vec![], steps: vec![],
channel, channel,
terminal_output,
vte_parser,
test_name,
currently_running_step: None, currently_running_step: None,
current_step_index: 0, current_step_index: 0,
retries_left: 10, retries_left: RETRIES,
win_size, retry_pause_ms: 100,
layout_file_name: Some(layout_file_name),
without_frames: false,
session_name: None,
attach_to_existing: false,
test_timed_out: false, test_timed_out: false,
panic_on_no_retries_left: true, panic_on_no_retries_left: true,
last_snapshot,
cursor_coordinates,
reader_thread,
} }
} }
pub fn dont_panic(mut self) -> Self { pub fn dont_panic(mut self) -> Self {
self.panic_on_no_retries_left = false; self.panic_on_no_retries_left = false;
self self
} }
pub fn retry_pause_ms(mut self, retry_pause_ms: usize) -> Self {
self.retry_pause_ms = retry_pause_ms;
self
}
pub fn add_step(mut self, step: Step) -> Self { pub fn add_step(mut self, step: Step) -> Self {
self.steps.push(step); self.steps.push(step);
self self
} }
pub fn replace_steps(&mut self, steps: Vec<Step>) {
self.steps = steps;
}
fn display_informative_error(&mut self) {
let test_name = self.test_name;
let current_step_name = self.currently_running_step.as_ref().cloned();
match current_step_name {
Some(current_step) => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Was waiting for step: {}", test_name, current_step);
eprintln!("{:?}", remote_terminal);
}
None => {
let remote_terminal = self.current_remote_terminal_state();
eprintln!("Timed out waiting for data on the SSH channel for test {}. Haven't begun running steps yet.", test_name);
eprintln!("{:?}", remote_terminal);
}
}
}
pub fn get_current_snapshot(&mut self) -> String {
take_snapshot(&mut self.terminal_output)
}
fn current_remote_terminal_state(&mut self) -> RemoteTerminal {
let current_snapshot = self.get_current_snapshot();
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
RemoteTerminal {
cursor_x,
cursor_y,
current_snapshot,
channel: &mut self.channel,
}
}
pub fn run_next_step(&mut self) { pub fn run_next_step(&mut self) {
if let Some(next_step) = self.steps.get(self.current_step_index) { if let Some(next_step) = self.steps.get(self.current_step_index) {
let current_snapshot = take_snapshot(&mut self.terminal_output); let (cursor_x, cursor_y) = *self.cursor_coordinates.lock().unwrap();
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
let remote_terminal = RemoteTerminal { let remote_terminal = RemoteTerminal {
cursor_x, cursor_x,
cursor_y, cursor_y,
current_snapshot, last_snapshot: self.last_snapshot.clone(),
channel: &mut self.channel, channel: self.channel.clone(),
}; };
let instruction = next_step.instruction; let instruction = next_step.instruction;
self.currently_running_step = Some(String::from(next_step.name)); self.currently_running_step = Some(String::from(next_step.name));
if instruction(remote_terminal) { if instruction(remote_terminal) {
self.retries_left = RETRIES;
self.current_step_index += 1; self.current_step_index += 1;
} else {
self.retries_left -= 1;
std::thread::sleep(std::time::Duration::from_millis(self.retry_pause_ms as u64));
} }
} }
} }
pub fn steps_left(&self) -> bool { pub fn steps_left(&self) -> bool {
self.steps.get(self.current_step_index).is_some() self.steps.get(self.current_step_index).is_some()
} }
fn restart_test(&mut self) -> String { pub fn take_snapshot_after(&mut self, step: Step) -> String {
if let Some(layout_file_name) = self.layout_file_name.as_ref() { let mut retries_left = RETRIES;
// let mut new_runner = RemoteRunner::new_with_layout(self.test_name, self.win_size, Path::new(&local_layout_path), session_name); let instruction = step.instruction;
let mut new_runner =
RemoteRunner::new_with_layout(self.test_name, self.win_size, layout_file_name);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else if self.without_frames {
let mut new_runner = RemoteRunner::new_without_frames(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else if self.session_name.is_some() {
let mut new_runner = if self.attach_to_existing {
RemoteRunner::new_existing_session(
self.test_name,
self.win_size,
self.session_name.as_ref().unwrap(),
)
} else {
RemoteRunner::new_with_session_name(
self.test_name,
self.win_size,
self.session_name.as_ref().unwrap(),
)
};
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
} else {
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
}
self.run_all_steps()
}
pub fn run_all_steps(&mut self) -> String {
// returns the last snapshot
loop { loop {
let mut buf = [0u8; 1024]; if retries_left == 0 {
match self.channel.read(&mut buf) { self.test_timed_out = true;
Ok(0) => break, return self.last_snapshot.lock().unwrap().clone();
Ok(_count) => {
for byte in buf.iter() {
self.vte_parser
.advance(&mut self.terminal_output.grid, *byte);
} }
let (cursor_x, cursor_y) = *self.cursor_coordinates.lock().unwrap();
let remote_terminal = RemoteTerminal {
cursor_x,
cursor_y,
last_snapshot: self.last_snapshot.clone(),
channel: self.channel.clone(),
};
if instruction(remote_terminal) {
return self.last_snapshot.lock().unwrap().clone();
} else {
retries_left -= 1;
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
}
}
pub fn run_all_steps(&mut self) {
loop {
self.run_next_step(); self.run_next_step();
if !self.steps_left() { if !self.steps_left() {
break; break;
} } else if self.retries_left == 0 {
}
Err(e) => {
if self.retries_left > 0 {
return self.restart_test();
}
self.test_timed_out = true; self.test_timed_out = true;
if self.panic_on_no_retries_left { break;
self.display_informative_error();
panic!("timed out waiting for test: {:?}", e);
} }
} }
} }
}
take_snapshot(&mut self.terminal_output)
}
} }
impl Drop for RemoteRunner { impl Drop for RemoteRunner {
fn drop(&mut self) { fn drop(&mut self) {
let _ = self.channel.close(); let _ = self.channel.lock().unwrap().close();
let reader_thread_running = &mut self.reader_thread.0;
*reader_thread_running.lock().unwrap() = false;
} }
} }

View file

@ -22,4 +22,4 @@ expression: last_snapshot
│ │ │ │
└──────┘ └──────┘
Ctrl + Ctrl +
...

View file

@ -1,29 +1,29 @@
--- ---
source: src/tests/integration/e2e.rs source: src/tests/e2e/cases.rs
expression: last_snapshot expression: last_snapshot
--- ---
Zellij  Tab #1  Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
$ │$ │$ █ │$
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes. <←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -0,0 +1,29 @@
---
source: src/tests/e2e/cases.rs
expression: second_runner_snapshot
---
Zellij (mirrored_sessions)  Tab #1  Tab #2 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ █ ││$ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
<←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -1,11 +1,11 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
expression: last_snapshot expression: first_runner_snapshot
--- ---
Zellij (mirrored_sessions)  Tab #1  Zellij (mirrored_sessions)  Tab #1  Tab #2 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ ┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ ││$ █ │$ █ ││$
│ ││ │ │ ││ │
│ ││ │ │ ││ │
│ ││ │ │ ││ │
@ -26,4 +26,4 @@ expression: last_snapshot
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane. <←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane

View file

@ -0,0 +1,29 @@
---
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│$ █ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.

View file

@ -348,7 +348,7 @@ impl Screen {
let mut output = Output::default(); let mut output = Output::default();
let mut tabs_to_close = vec![]; let mut tabs_to_close = vec![];
for (tab_index, tab) in self.tabs.iter_mut() { for (tab_index, tab) in self.tabs.iter_mut() {
if tab.get_active_pane().is_some() { if tab.has_active_panes() {
tab.render(&mut output); tab.render(&mut output);
} else { } else {
tabs_to_close.push(*tab_index); tabs_to_close.push(*tab_index);
@ -511,20 +511,20 @@ impl Screen {
{ {
self.get_active_tab_mut(client_id) self.get_active_tab_mut(client_id)
.unwrap() .unwrap()
.clear_active_terminal_scroll(); .clear_active_terminal_scroll(client_id);
} }
self.colors = mode_info.palette; self.colors = mode_info.palette;
self.mode_info = mode_info; self.mode_info = mode_info;
for tab in self.tabs.values_mut() { for tab in self.tabs.values_mut() {
tab.mode_info = self.mode_info.clone(); tab.mode_info = self.mode_info.clone();
tab.mark_active_pane_for_rerender(); tab.mark_active_pane_for_rerender(client_id);
} }
} }
pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) { pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) {
if !self if !self
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_left() .move_focus_left(client_id)
{ {
self.switch_tab_prev(client_id); self.switch_tab_prev(client_id);
} }
@ -533,7 +533,7 @@ impl Screen {
if !self if !self
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_right() .move_focus_right(client_id)
{ {
self.switch_tab_next(client_id); self.switch_tab_next(client_id);
} }
@ -597,10 +597,13 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::NewPane(pid, client_or_tab_index) => { ScreenInstruction::NewPane(pid, client_or_tab_index) => {
match client_or_tab_index { match client_or_tab_index {
ClientOrTabIndex::ClientId(client_id) => { ClientOrTabIndex::ClientId(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().new_pane(pid); screen
.get_active_tab_mut(client_id)
.unwrap()
.new_pane(pid, Some(client_id));
} }
ClientOrTabIndex::TabIndex(tab_index) => { ClientOrTabIndex::TabIndex(tab_index) => {
screen.tabs.get_mut(&tab_index).unwrap().new_pane(pid); screen.tabs.get_mut(&tab_index).unwrap().new_pane(pid, None);
} }
}; };
screen screen
@ -616,7 +619,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.horizontal_split(pid); .horizontal_split(pid, client_id);
screen screen
.bus .bus
.senders .senders
@ -630,7 +633,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.vertical_split(pid); .vertical_split(pid, client_id);
screen screen
.bus .bus
.senders .senders
@ -644,26 +647,38 @@ pub(crate) fn screen_thread_main(
let active_tab = screen.get_active_tab_mut(client_id).unwrap(); let active_tab = screen.get_active_tab_mut(client_id).unwrap();
match active_tab.is_sync_panes_active() { match active_tab.is_sync_panes_active() {
true => active_tab.write_to_terminals_on_current_tab(bytes), true => active_tab.write_to_terminals_on_current_tab(bytes),
false => active_tab.write_to_active_terminal(bytes), false => active_tab.write_to_active_terminal(bytes, client_id),
} }
} }
ScreenInstruction::ResizeLeft(client_id) => { ScreenInstruction::ResizeLeft(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_left(); screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_left(client_id);
screen.render(); screen.render();
} }
ScreenInstruction::ResizeRight(client_id) => { ScreenInstruction::ResizeRight(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_right(); screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_right(client_id);
screen.render(); screen.render();
} }
ScreenInstruction::ResizeDown(client_id) => { ScreenInstruction::ResizeDown(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_down(); screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_down(client_id);
screen.render(); screen.render();
} }
ScreenInstruction::ResizeUp(client_id) => { ScreenInstruction::ResizeUp(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().resize_up(); screen
.get_active_tab_mut(client_id)
.unwrap()
.resize_up(client_id);
screen.render(); screen.render();
} }
@ -671,7 +686,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.resize_increase(); .resize_increase(client_id);
screen.render(); screen.render();
} }
@ -679,12 +694,15 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.resize_decrease(); .resize_decrease(client_id);
screen.render(); screen.render();
} }
ScreenInstruction::SwitchFocus(client_id) => { ScreenInstruction::SwitchFocus(client_id) => {
screen.get_active_tab_mut(client_id).unwrap().move_focus(); screen
.get_active_tab_mut(client_id)
.unwrap()
.move_focus(client_id);
screen.render(); screen.render();
} }
@ -692,7 +710,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.focus_next_pane(); .focus_next_pane(client_id);
screen.render(); screen.render();
} }
@ -700,7 +718,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.focus_previous_pane(); .focus_previous_pane(client_id);
screen.render(); screen.render();
} }
@ -708,7 +726,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_left(); .move_focus_left(client_id);
screen.render(); screen.render();
} }
@ -726,7 +744,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_down(); .move_focus_down(client_id);
screen.render(); screen.render();
} }
@ -734,7 +752,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_right(); .move_focus_right(client_id);
screen.render(); screen.render();
} }
@ -752,7 +770,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_focus_up(); .move_focus_up(client_id);
screen.render(); screen.render();
} }
@ -760,7 +778,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_up(); .scroll_active_terminal_up(client_id);
screen.render(); screen.render();
} }
@ -768,7 +786,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_active_pane(); .move_active_pane(client_id);
screen.render(); screen.render();
} }
@ -776,7 +794,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_active_pane_down(); .move_active_pane_down(client_id);
screen.render(); screen.render();
} }
@ -784,7 +802,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_active_pane_up(); .move_active_pane_up(client_id);
screen.render(); screen.render();
} }
@ -792,7 +810,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_active_pane_right(); .move_active_pane_right(client_id);
screen.render(); screen.render();
} }
@ -800,7 +818,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.move_active_pane_left(); .move_active_pane_left(client_id);
screen.render(); screen.render();
} }
@ -816,7 +834,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_down(); .scroll_active_terminal_down(client_id);
screen.render(); screen.render();
} }
@ -832,7 +850,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_to_bottom(); .scroll_active_terminal_to_bottom(client_id);
screen.render(); screen.render();
} }
@ -840,7 +858,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_up_page(); .scroll_active_terminal_up_page(client_id);
screen.render(); screen.render();
} }
@ -848,7 +866,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_down_page(); .scroll_active_terminal_down_page(client_id);
screen.render(); screen.render();
} }
@ -856,7 +874,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_up_half_page(); .scroll_active_terminal_up_half_page(client_id);
screen.render(); screen.render();
} }
@ -864,7 +882,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.scroll_active_terminal_down_half_page(); .scroll_active_terminal_down_half_page(client_id);
screen.render(); screen.render();
} }
@ -872,7 +890,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.clear_active_terminal_scroll(); .clear_active_terminal_scroll(client_id);
screen.render(); screen.render();
} }
@ -880,7 +898,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.close_focused_pane(); .close_focused_pane(client_id);
screen.update_tabs(); // update_tabs eventually calls render through the plugin thread screen.update_tabs(); // update_tabs eventually calls render through the plugin thread
} }
ScreenInstruction::SetSelectable(id, selectable, tab_index) => { ScreenInstruction::SetSelectable(id, selectable, tab_index) => {
@ -917,7 +935,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.toggle_active_pane_fullscreen(); .toggle_active_pane_fullscreen(client_id);
screen.update_tabs(); screen.update_tabs();
screen.render(); screen.render();
@ -1011,7 +1029,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.handle_left_click(&point); .handle_left_click(&point, client_id);
screen.render(); screen.render();
} }
@ -1019,7 +1037,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.handle_right_click(&point); .handle_right_click(&point, client_id);
screen.render(); screen.render();
} }
@ -1027,7 +1045,7 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.handle_mouse_release(&point); .handle_mouse_release(&point, client_id);
screen.render(); screen.render();
} }
@ -1035,12 +1053,15 @@ pub(crate) fn screen_thread_main(
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)
.unwrap() .unwrap()
.handle_mouse_hold(&point); .handle_mouse_hold(&point, client_id);
screen.render(); screen.render();
} }
ScreenInstruction::Copy(client_id) => { ScreenInstruction::Copy(client_id) => {
screen.get_active_tab(client_id).unwrap().copy_selection(); screen
.get_active_tab(client_id)
.unwrap()
.copy_selection(client_id);
screen.render(); screen.render();
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,3 @@
use crate::{
logging_pipe::LoggingPipe,
panes::PaneId,
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
};
use highway::{HighwayHash, PortableHash}; use highway::{HighwayHash, PortableHash};
use log::{debug, info, warn}; use log::{debug, info, warn};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@ -25,6 +18,15 @@ use wasmer::{
}; };
use wasmer_wasi::{Pipe, WasiEnv, WasiState}; use wasmer_wasi::{Pipe, WasiEnv, WasiState};
use zellij_tile::data::{Event, EventType, PluginIds}; use zellij_tile::data::{Event, EventType, PluginIds};
use crate::{
logging_pipe::LoggingPipe,
panes::PaneId,
pty::{ClientOrTabIndex, PtyInstruction},
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
};
use zellij_utils::{ use zellij_utils::{
consts::ZELLIJ_PROJ_DIR, consts::ZELLIJ_PROJ_DIR,
errors::{ContextType, PluginContext}, errors::{ContextType, PluginContext},