diff options
author | Aram Drevekenin <aram@poor.dev> | 2021-09-27 11:29:13 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-27 11:29:13 +0200 |
commit | 5c54bf18c21e60d82ef063b62337f6b545d914d3 (patch) | |
tree | 4af736bfd7b05ae2402930f8f41e3e9f669d3a6f | |
parent | c93a4f1f67d04750dfc883d01a8d58b44f493a43 (diff) |
feat(sessions): mirrored sessions (#740)
* feat(sessions): mirrored sessions
* fix(tests): input units
* style(fmt): make rustfmt happy
* fix(tests): make mirrored sessions e2e test more robust
* refactor(sessions): remove force attach
* style(fmt): rustfmtify
* docs(changelog): update change
* fix(e2e): retry on all errors
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Makefile.toml | 2 | ||||
-rw-r--r-- | src/main.rs | 15 | ||||
-rw-r--r-- | src/tests/e2e/cases.rs | 96 | ||||
-rw-r--r-- | src/tests/e2e/remote_runner.rs | 178 | ||||
-rw-r--r-- | src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap | 4 | ||||
-rw-r--r-- | src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap | 29 | ||||
-rw-r--r-- | zellij-client/Cargo.toml | 1 | ||||
-rw-r--r-- | zellij-client/src/input_handler.rs | 30 | ||||
-rw-r--r-- | zellij-client/src/lib.rs | 50 | ||||
-rw-r--r-- | zellij-client/src/unit/input_handler_tests.rs | 79 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 217 | ||||
-rw-r--r-- | zellij-server/src/os_input_output.rs | 101 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 81 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 13 | ||||
-rw-r--r-- | zellij-server/src/tab.rs | 6 | ||||
-rw-r--r-- | zellij-server/src/unit/screen_tests.rs | 43 | ||||
-rw-r--r-- | zellij-server/src/unit/tab_tests.rs | 44 | ||||
-rw-r--r-- | zellij-utils/src/cli.rs | 5 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 33 | ||||
-rw-r--r-- | zellij-utils/src/ipc.rs | 9 |
22 files changed, 722 insertions, 316 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e311d8c..4481475cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Feature: Add ability to solely specify the tab name in the `tabs` section (https://github.com/zellij-org/zellij/pull/722) * Feature: Plugins can be configured and the groundwork for "Headless" plugins has been laid (https://github.com/zellij-org/zellij/pull/660) * Automatically update `example/default.yaml` on release (https://github.com/zellij-org/zellij/pull/736) +* Feature: allow mirroring sessions in multiple terminal windows (https://github.com/zellij-org/zellij/pull/740) ## [0.17.0] - 2021-09-15 * New panes/tabs now open in CWD of focused pane (https://github.com/zellij-org/zellij/pull/691) diff --git a/Cargo.lock b/Cargo.lock index bf4ae0fd9..27d844651 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2736,6 +2736,7 @@ dependencies = [ "log", "mio", "termbg", + "zellij-tile", "zellij-utils", ] diff --git a/Makefile.toml b/Makefile.toml index a5bb174e4..79392a647 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -142,7 +142,7 @@ args = ["build", "--verbose", "--release", "--target", "${CARGO_MAKE_TASK_ARGS}" workspace = false dependencies = ["build-plugins", "build-dev-data-dir"] command = "cargo" -args = ["build", "--verbose", "--target", "x86_64-unknown-linux-musl"] +args = ["build", "--verbose", "--release", "--target", "x86_64-unknown-linux-musl"] # Run e2e tests - we mark the e2e tests as "ignored" so they will not be run with the normal ones [tasks.e2e-test] diff --git a/src/main.rs b/src/main.rs index 0a91ca652..90eeb96b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,6 @@ pub fn main() { }; if let Some(Command::Sessions(Sessions::Attach { session_name, - force, create, options, })) = opts.command.clone() @@ -73,22 +72,14 @@ pub fn main() { (ClientInfo::New(session_name.unwrap()), layout) } else { ( - ClientInfo::Attach( - session_name.unwrap(), - force, - config_options.clone(), - ), + ClientInfo::Attach(session_name.unwrap(), config_options.clone()), None, ) } } else { assert_session(session); ( - ClientInfo::Attach( - session_name.unwrap(), - force, - config_options.clone(), - ), + ClientInfo::Attach(session_name.unwrap(), config_options.clone()), None, ) } @@ -106,7 +97,7 @@ pub fn main() { } } ActiveSession::One(session_name) => ( - ClientInfo::Attach(session_name, force, config_options.clone()), + ClientInfo::Attach(session_name, config_options.clone()), None, ), ActiveSession::Many => { diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 307986c1a..77892463c 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -153,20 +153,12 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { }, }) .add_step(Step { - name: "Send text to terminal", - instruction: |mut remote_terminal: RemoteTerminal| -> bool { - // this is just normal input that should be sent into the one terminal so that we can make - // sure we silently failed to split in the previous step - remote_terminal.send_key("Hi!".as_bytes()); - true - }, - }) - .add_step(Step { - name: "Wait for text to appear", + name: "Make sure only one pane appears", instruction: |remote_terminal: RemoteTerminal| -> bool { let mut step_is_complete = false; - if remote_terminal.cursor_position_is(6, 2) && remote_terminal.snapshot_contains("Hi!") + if remote_terminal.cursor_position_is(3, 2) && remote_terminal.snapshot_contains("...") { + // ... is the truncated tip line step_is_complete = true; } step_is_complete @@ -917,3 +909,85 @@ pub fn start_without_pane_frames() { .run_all_steps(); assert_snapshot!(last_snapshot); } + +#[test] +#[ignore] +pub fn mirrored_sessions() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + let mut test_attempts = 10; + let session_name = "mirrored_sessions"; + let mut last_snapshot = None; + loop { + // we run this test in a loop because there are some edge cases (especially in the CI) + // where the second runner times out and then we also need to restart the first runner + // if no test timed out, we break the loop and assert the snapshot + let mut first_runner = + RemoteRunner::new_with_session_name("mirrored_sessions", fake_win_size, session_name) + .add_step(Step { + name: "Split pane to the right", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.status_bar_appears() + && remote_terminal.cursor_position_is(3, 2) + { + remote_terminal.send_key(&PANE_MODE); + remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE); + // back to normal mode after split + remote_terminal.send_key(&ENTER); + step_is_complete = true; + } + step_is_complete + }, + }) + .add_step(Step { + name: "Wait for new pane to open", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.tip_appears() + { + // cursor is in the newly opened second pane + step_is_complete = true; + } + step_is_complete + }, + }); + first_runner.run_all_steps(); + + let mut second_runner = + RemoteRunner::new_existing_session("mirrored_sessions", fake_win_size, session_name) + .add_step(Step { + name: "Make sure session appears correctly", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) + && remote_terminal.tip_appears() + { + // cursor is in the newly opened second pane + step_is_complete = true; + } + step_is_complete + }, + }); + let last_test_snapshot = second_runner.run_all_steps(); + + if (first_runner.test_timed_out || second_runner.test_timed_out) && test_attempts >= 0 { + test_attempts -= 1; + continue; + } else { + last_snapshot = Some(last_test_snapshot); + break; + } + } + match last_snapshot { + Some(last_snapshot) => { + assert_snapshot!(last_snapshot); + } + None => { + panic!("test timed out before completing"); + } + } +} diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 7c4587f3e..924babf68 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -10,7 +10,7 @@ use std::net::TcpStream; use std::path::Path; -const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/debug/zellij"; +const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/release/zellij"; const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts"; const CONNECTION_STRING: &str = "127.0.0.1:2222"; const CONNECTION_USERNAME: &str = "test"; @@ -24,7 +24,7 @@ fn ssh_connect() -> ssh2::Session { sess.handshake().unwrap(); sess.userauth_password(CONNECTION_USERNAME, CONNECTION_PASSWORD) .unwrap(); - sess.set_timeout(20000); + sess.set_timeout(3000); sess } @@ -58,6 +58,27 @@ fn start_zellij(channel: &mut ssh2::Channel) { channel.flush().unwrap(); } +fn start_zellij_in_session(channel: &mut ssh2::Channel, session_name: &str) { + stop_zellij(channel); + channel + .write_all( + format!( + "{} --session {}\n", + ZELLIJ_EXECUTABLE_LOCATION, session_name + ) + .as_bytes(), + ) + .unwrap(); + channel.flush().unwrap(); +} + +fn attach_to_existing_session(channel: &mut ssh2::Channel, session_name: &str) { + channel + .write_all(format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, session_name).as_bytes()) + .unwrap(); + channel.flush().unwrap(); +} + fn start_zellij_without_frames(channel: &mut ssh2::Channel) { stop_zellij(channel); channel @@ -188,6 +209,9 @@ pub struct RemoteRunner { win_size: Size, layout_file_name: Option<&'static str>, without_frames: bool, + session_name: Option<String>, + attach_to_existing: bool, + pub test_timed_out: bool, } impl RemoteRunner { @@ -216,10 +240,89 @@ impl RemoteRunner { test_name, currently_running_step: None, current_step_index: 0, - retries_left: 3, + retries_left: 10, + win_size, + layout_file_name: None, + without_frames: false, + session_name: None, + attach_to_existing: false, + test_timed_out: false, + } + } + pub fn new_with_session_name( + test_name: &'static str, + win_size: Size, + session_name: &str, + ) -> Self { + let sess = ssh_connect(); + let mut channel = sess.channel_session().unwrap(); + let vte_parser = vte::Parser::new(); + let mut rows = Dimension::fixed(win_size.rows); + let mut cols = Dimension::fixed(win_size.cols); + rows.set_inner(win_size.rows); + cols.set_inner(win_size.cols); + let pane_geom = PaneGeom { + x: 0, + y: 0, + rows, + cols, + }; + let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index + setup_remote_environment(&mut channel, win_size); + start_zellij_in_session(&mut channel, &session_name); + RemoteRunner { + steps: vec![], + channel, + terminal_output, + vte_parser, + test_name, + currently_running_step: None, + current_step_index: 0, + retries_left: 10, + win_size, + layout_file_name: None, + without_frames: false, + session_name: Some(String::from(session_name)), + attach_to_existing: false, + test_timed_out: false, + } + } + pub fn new_existing_session( + test_name: &'static str, + win_size: Size, + session_name: &str, + ) -> Self { + let sess = ssh_connect(); + let mut channel = sess.channel_session().unwrap(); + let vte_parser = vte::Parser::new(); + let mut rows = Dimension::fixed(win_size.rows); + let mut cols = Dimension::fixed(win_size.cols); + rows.set_inner(win_size.rows); + cols.set_inner(win_size.cols); + let pane_geom = PaneGeom { + x: 0, + y: 0, + rows, + cols, + }; + let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index + setup_remote_environment(&mut channel, win_size); + attach_to_existing_session(&mut channel, &session_name); + RemoteRunner { + steps: vec![], + channel, + terminal_output, + vte_parser, + test_name, + currently_running_step: None, + current_step_index: 0, + retries_left: 10, win_size, layout_file_name: None, without_frames: false, + session_name: Some(String::from(session_name)), + attach_to_existing: true, + test_timed_out: false, } } pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self { @@ -247,10 +350,13 @@ impl RemoteRunner { test_name, currently_running_step: None, current_step_index: 0, - retries_left: 3, + retries_left: 10, win_size, layout_file_name: None, without_frames: true, + session_name: None, + attach_to_existing: false, + test_timed_out: false, } } pub fn new_with_layout( @@ -283,10 +389,13 @@ impl RemoteRunner { test_name, currently_running_step: None, current_step_index: 0, - retries_left: 3, + retries_left: 10, win_size, layout_file_name: Some(layout_file_name), without_frames: false, + session_name: None, + attach_to_existing: false, + test_timed_out: false, } } pub fn add_step(mut self, step: Step) -> Self { @@ -296,16 +405,6 @@ impl RemoteRunner { pub fn replace_steps(&mut self, steps: Vec<Step>) { self.steps = steps; } - 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) { if let Some(next_step) = self.steps.get(self.current_step_index) { let current_snapshot = take_snapshot(&mut self.terminal_output); @@ -341,6 +440,24 @@ impl RemoteRunner { new_runner.replace_steps(self.steps.clone()); drop(std::mem::replace(self, new_runner)); self.run_all_steps() + } 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)); + self.run_all_steps() } else { let mut new_runner = RemoteRunner::new(self.test_name, self.win_size); new_runner.retries_left = self.retries_left - 1; @@ -349,22 +466,6 @@ impl RemoteRunner { self.run_all_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 run_all_steps(&mut self) -> String { // returns the last snapshot loop { @@ -381,23 +482,16 @@ impl RemoteRunner { break; } } - Err(e) => { - if e.kind() == std::io::ErrorKind::TimedOut { - if self.retries_left > 0 { - return self.restart_test(); - } - self.display_informative_error(); - panic!("Timed out waiting for test"); + Err(_e) => { + if self.retries_left > 0 { + return self.restart_test(); } - panic!("Error while reading remote session: {}", e); + self.test_timed_out = true; } } } take_snapshot(&mut self.terminal_output) } - pub fn get_current_snapshot(&mut self) -> String { - take_snapshot(&mut self.terminal_output) - } } impl Drop for RemoteRunner { diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap index d37f23e33..796a596b9 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__cannot_split_terminals_vertically_when_active_terminal_is_too_small.snap @@ -5,7 +5,7 @@ expression: last_snapshot --- Zellij ┌──────┐ -│$ Hi!█│ +│$ █ │ │ │ │ │ │ │ @@ -22,4 +22,4 @@ expression: last_snapshot │ │ └──────┘ Ctrl + - + ... diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap new file mode 100644 index 000000000..3223680c8 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot + +--- + Zellij (mirrored_sessions) Tab #1 +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ ││$ █ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ |