mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-22 13:02:12 +03:00
Add e2e tests (#582)
* feature(tests): e2e tests * chore(build): github action * chore(build): fix workflow * chore(build): fix workflow * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * work * working * working * working * bring back the proper errors * make e2e flow run properly * style(fmt): make rustfmt happy * style(fmt): make rustfmt happy * run on everything just to test the workflow * bring back running behaviour on workflow
This commit is contained in:
parent
07ad2f54ea
commit
3313634fe9
49
.github/workflows/e2e.yml
vendored
Normal file
49
.github/workflows/e2e.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: End to End tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Bulild generic binary and run tests on it
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
ssh:
|
||||
image: ghcr.io/linuxserver/openssh-server
|
||||
env:
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
TZ: Europe/Vienna
|
||||
PASSWORD_ACCESS: true
|
||||
USER_PASSWORD: test
|
||||
USER_NAME: test
|
||||
ports:
|
||||
- 2222:2222
|
||||
options: -v ${{ github.workspace }}/target:/usr/src/zellij --name ssh
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Add WASM target
|
||||
run: rustup target add wasm32-wasi
|
||||
- name: Install musl-tools
|
||||
run: sudo apt-get install -y --no-install-recommends musl-tools
|
||||
- name: Add musl target
|
||||
run: rustup target add x86_64-unknown-linux-musl
|
||||
- name: Install cargo-make
|
||||
run: cargo install --debug cargo-make
|
||||
- name: Build asset
|
||||
run: cargo make build-e2e
|
||||
- name: Restart ssh container
|
||||
# we need to do this because otherwise the volume will not be mounted
|
||||
# on the docker container, since it was created before the folder existed
|
||||
uses: docker://docker
|
||||
with:
|
||||
args: docker restart ssh
|
||||
- name: Test
|
||||
run: cargo make e2e-test
|
@ -44,6 +44,18 @@ cargo make manpage
|
||||
To run `install` or `publish`, you'll need the package `binaryen` in the
|
||||
version `wasm-opt --version` > 97, for it's command `wasm-opt`.
|
||||
|
||||
## Running the end-to-end tests
|
||||
Zellij includes some end to end tests which test the whole application as a black-box from the outside.
|
||||
These tests work by running a docker container which contains the Zellij binary, connecting to it via ssh, sending some commands and comparing the output received against predefined snapshots.
|
||||
|
||||
To run these tests locally, you'll need to have both `docker` and `docker-compose` installed.
|
||||
Once you do, in the repository root:
|
||||
1. `docker-compose up -d` will start up the docker container
|
||||
2. `cargo make build-e2e` will build the generic linux executable of Zellij in the target folder, which is shared with the container
|
||||
3. `cargo make e2e-test` will run the tests
|
||||
|
||||
To re-run the tests after you've changed something in the code base, be sure to repeat steps 2 and 3.
|
||||
|
||||
## Looking for something to work on?
|
||||
|
||||
If you are new contributor to `Zellij` going through
|
||||
|
132
Cargo.lock
generated
132
Cargo.lock
generated
@ -312,6 +312,15 @@ dependencies = [
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
@ -504,7 +513,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"parking_lot 0.11.1",
|
||||
"signal-hook 0.1.17",
|
||||
"winapi",
|
||||
]
|
||||
@ -902,6 +911,7 @@ version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"console",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@ -1017,12 +1027,47 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0186af0d8f171ae6b9c4c90ec51898bad5d08a2d5e470903a50d9ad8959cbee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
@ -1184,12 +1229,35 @@ version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
||||
dependencies = [
|
||||
"lock_api 0.3.4",
|
||||
"parking_lot_core 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
@ -1197,8 +1265,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
"lock_api 0.4.4",
|
||||
"parking_lot_core 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cloudabi",
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1210,7 +1292,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.8",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
@ -1227,6 +1309,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.0.3"
|
||||
@ -1422,6 +1510,12 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.8"
|
||||
@ -1437,7 +1531,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1447,7 +1541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1622,7 +1716,19 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"lock_api 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ssh2"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d876d4d57f6bbf2245d43f7ec53759461f801a446d3693704aa6d27b257844d7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libssh2-sys",
|
||||
"parking_lot 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1749,7 +1855,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand 0.8.3",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.8",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
]
|
||||
@ -1783,7 +1889,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.8",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
@ -1929,6 +2035,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "025ce40a007e1907e58d5bc1a594def78e5573bb0b1160bc389634e8f12e4faa"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
@ -2333,6 +2445,8 @@ version = "0.14.0"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"names",
|
||||
"rand 0.8.3",
|
||||
"ssh2",
|
||||
"zellij-client",
|
||||
"zellij-server",
|
||||
"zellij-utils",
|
||||
|
@ -19,7 +19,9 @@ zellij-server = { path = "zellij-server/", version = "0.14.0" }
|
||||
zellij-utils = { path = "zellij-utils/", version = "0.14.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.6.0"
|
||||
insta = { version = "1.6.0", features = ["backtrace"] }
|
||||
ssh2 = "0.9.1"
|
||||
rand = "0.8.0"
|
||||
zellij-utils = { path = "zellij-utils/", version = "0.14.0", features = ["test"] }
|
||||
zellij-client = { path = "zellij-client/", version = "0.14.0", features = ["test"] }
|
||||
zellij-server = { path = "zellij-server/", version = "0.14.0", features = ["test"] }
|
||||
|
@ -84,6 +84,10 @@ end
|
||||
env = { "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS" = ["default-plugins/status-bar", "default-plugins/strider", "default-plugins/tab-bar"] }
|
||||
run_task = { name = "build-release", fork = true }
|
||||
|
||||
[tasks.build-plugins]
|
||||
env = { "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS" = ["default-plugins/status-bar", "default-plugins/strider", "default-plugins/tab-bar"] }
|
||||
run_task = { name = "build", fork = true }
|
||||
|
||||
[tasks.wasm-opt-plugins]
|
||||
script_runner = "@duckscript"
|
||||
script = '''
|
||||
@ -120,6 +124,19 @@ dependencies = ["setup-cross-compilation", "build-plugins-release", "wasm-opt-pl
|
||||
command = "cross"
|
||||
args = ["build", "--verbose", "--release", "--target", "${CARGO_MAKE_TASK_ARGS}"]
|
||||
|
||||
# Build e2e asset
|
||||
[tasks.build-e2e]
|
||||
workspace = false
|
||||
dependencies = ["build-plugins"]
|
||||
command = "cargo"
|
||||
args = ["build", "--verbose", "--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]
|
||||
workspace = false
|
||||
command = "cargo"
|
||||
args = ["test", "--", "--ignored", "--nocapture", "--test-threads", "1", "@@split(CARGO_MAKE_TASK_ARGS,;)"]
|
||||
|
||||
[tasks.setup-cross-compilation]
|
||||
command = "cargo"
|
||||
args = ["install", "cross"]
|
||||
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
version: "2.1"
|
||||
services:
|
||||
zellij-e2e:
|
||||
image: ghcr.io/linuxserver/openssh-server
|
||||
container_name: zellij-e2e
|
||||
hostname: zellij-e2e
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Vienna
|
||||
- PASSWORD_ACCESS=true
|
||||
- USER_PASSWORD=test
|
||||
- USER_NAME=test
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./target
|
||||
target: /usr/src/zellij
|
||||
ports:
|
||||
- 2222:2222
|
||||
restart: unless-stopped
|
716
src/tests/e2e/cases.rs
Normal file
716
src/tests/e2e/cases.rs
Normal file
@ -0,0 +1,716 @@
|
||||
use ::insta::assert_snapshot;
|
||||
use zellij_utils::pane_size::PositionAndSize;
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use super::remote_runner::{RemoteRunner, RemoteTerminal, Step};
|
||||
use crate::tests::utils::commands::{
|
||||
CLOSE_PANE_IN_PANE_MODE, DETACH_IN_SESSION_MODE, ENTER, LOCK_MODE, NEW_TAB_IN_TAB_MODE,
|
||||
PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SCROLL_MODE,
|
||||
SCROLL_UP_IN_SCROLL_MODE, SESSION_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE,
|
||||
TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE,
|
||||
};
|
||||
|
||||
// All the E2E tests are marked as "ignored" so that they can be run separately from the normal
|
||||
// tests
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn starts_with_one_terminal() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("starts_with_one_terminal", fake_win_size, None)
|
||||
.add_step(Step {
|
||||
name: "Wait for app to load",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
|
||||
{
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn split_terminals_vertically() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size, None)
|
||||
.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(2, 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 appear",
|
||||
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
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 8,
|
||||
rows: 20,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new(
|
||||
"cannot_split_terminals_vertically_when_active_terminal_is_too_small",
|
||||
fake_win_size,
|
||||
None,
|
||||
)
|
||||
.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(2, 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: "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",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(5, 2) && remote_terminal.snapshot_contains("Hi!")
|
||||
{
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn scrolling_inside_a_pane() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane", fake_win_size, None)
|
||||
.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(2, 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: "Fill terminal with text",
|
||||
instruction: |mut 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
|
||||
remote_terminal.send_key(&format!("{:0<57}", "line1 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
|
||||
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Scroll up inside pane",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20) {
|
||||
// all lines have been written to the pane
|
||||
remote_terminal.send_key(&SCROLL_MODE);
|
||||
remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for scroll to finish",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(119, 20)
|
||||
&& remote_terminal.snapshot_contains("line1 ")
|
||||
{
|
||||
// scrolled up one line
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn toggle_pane_fullscreen() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("toggle_pane_fullscreen", fake_win_size, None)
|
||||
.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(2, 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: "Change newly opened pane to be fullscreen",
|
||||
instruction: |mut 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
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
remote_terminal.send_key(&TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE);
|
||||
// back to normal mode after toggling fullscreen
|
||||
remote_terminal.send_key(&ENTER);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for pane to become fullscreen",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 0) {
|
||||
// cursor is in full screen pane now
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn open_new_tab() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("open_new_tab", fake_win_size, None)
|
||||
.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(2, 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: "Open new tab",
|
||||
instruction: |mut 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
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_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 tab to open",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 2)
|
||||
&& remote_terminal.tip_appears()
|
||||
&& remote_terminal.snapshot_contains("Tab #2")
|
||||
&& remote_terminal.status_bar_appears()
|
||||
{
|
||||
// cursor is in the newly opened second tab
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn close_pane() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("close_pane", fake_win_size, None)
|
||||
.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(2, 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: "Close pane",
|
||||
instruction: |mut 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
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
|
||||
// back to normal mode after close
|
||||
remote_terminal.send_key(&ENTER);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for pane to close",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(2, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the original pane
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn exit_zellij() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("exit_zellij", fake_win_size, None)
|
||||
.add_step(Step {
|
||||
name: "Wait for app to load",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
|
||||
{
|
||||
remote_terminal.send_key(&QUIT);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for app to exit",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if !remote_terminal.status_bar_appears()
|
||||
&& remote_terminal.snapshot_contains("Bye from Zellij!")
|
||||
{
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert!(last_snapshot.contains("Bye from Zellij!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn closing_last_pane_exits_zellij() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("closing_last_pane_exits_zellij", fake_win_size, None)
|
||||
.add_step(Step {
|
||||
name: "Close pane",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for app to exit",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("Bye from Zellij!") {
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert!(last_snapshot.contains("Bye from Zellij!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn resize_pane() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("resize_pane", fake_win_size, None)
|
||||
.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(2, 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: "Resize pane",
|
||||
instruction: |mut 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
|
||||
remote_terminal.send_key(&RESIZE_MODE);
|
||||
remote_terminal.send_key(&RESIZE_LEFT_IN_RESIZE_MODE);
|
||||
// back to normal mode after resizing
|
||||
remote_terminal.send_key(&ENTER);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for pane to be resized",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(53, 2) && remote_terminal.tip_appears() {
|
||||
// pane has been resized
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn lock_mode() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("lock_mode", fake_win_size, None)
|
||||
.add_step(Step {
|
||||
name: "Enter lock mode",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
|
||||
{
|
||||
remote_terminal.send_key(&LOCK_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Send keys that should not be intercepted by the app",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("INTERFACE LOCKED") {
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
remote_terminal.send_key(&"abc".as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for terminal to render sent keys",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(6, 2) {
|
||||
// text has been entered into the only terminal pane
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn resize_terminal_window() {
|
||||
// this checks the resizing of the whole terminal window (reaction to SIGWINCH) and not just one pane
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let last_snapshot = RemoteRunner::new("resize_terminal_window", fake_win_size, None)
|
||||
.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(2, 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: "Change terminal window size",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// new pane has been opened and focused
|
||||
remote_terminal.change_size(100, 24);
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "wait for terminal to be resized and app to be re-rendered",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(43, 2) && remote_terminal.tip_appears() {
|
||||
// size has been changed
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn detach_and_attach_session() {
|
||||
let fake_win_size = PositionAndSize {
|
||||
cols: 120,
|
||||
rows: 24,
|
||||
x: 0,
|
||||
y: 0,
|
||||
..Default::default()
|
||||
};
|
||||
let session_id = rand::thread_rng().gen_range(0..10000);
|
||||
let session_name = format!("session_{}", session_id);
|
||||
let last_snapshot = RemoteRunner::new(
|
||||
"detach_and_attach_session",
|
||||
fake_win_size,
|
||||
Some(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(2, 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: "Send some text to the active pane",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// new pane has been opened and focused
|
||||
remote_terminal.send_key(&"I am some text".as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Detach session",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(77, 2) {
|
||||
remote_terminal.send_key(&SESSION_MODE);
|
||||
remote_terminal.send_key(&DETACH_IN_SESSION_MODE);
|
||||
// text has been entered
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Reattach session",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if !remote_terminal.status_bar_appears() {
|
||||
// we don't see the toolbar, so we can assume we've already detached
|
||||
remote_terminal.attach_to_original_session();
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.add_step(Step {
|
||||
name: "Wait for session to be attached",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(77, 2) {
|
||||
// we're back inside the session
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
},
|
||||
})
|
||||
.run_all_steps();
|
||||
assert_snapshot!(last_snapshot);
|
||||
}
|
2
src/tests/e2e/mod.rs
Normal file
2
src/tests/e2e/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod cases;
|
||||
mod remote_runner;
|
284
src/tests/e2e/remote_runner.rs
Normal file
284
src/tests/e2e/remote_runner.rs
Normal file
@ -0,0 +1,284 @@
|
||||
use zellij_tile::data::Palette;
|
||||
use zellij_utils::pane_size::PositionAndSize;
|
||||
|
||||
use zellij_server::panes::TerminalPane;
|
||||
use zellij_utils::{vte, zellij_tile};
|
||||
|
||||
use ssh2::Session;
|
||||
use std::io::prelude::*;
|
||||
use std::net::TcpStream;
|
||||
|
||||
const ZELLIJ_EXECUTABLE_LOCATION: &str = "/usr/src/zellij/x86_64-unknown-linux-musl/debug/zellij";
|
||||
const CONNECTION_STRING: &str = "127.0.0.1:2222";
|
||||
const CONNECTION_USERNAME: &str = "test";
|
||||
const CONNECTION_PASSWORD: &str = "test";
|
||||
|
||||
fn ssh_connect() -> 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.set_timeout(20000);
|
||||
sess
|
||||
}
|
||||
|
||||
fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: PositionAndSize) {
|
||||
let (columns, rows) = (win_size.cols as u32, win_size.rows as u32);
|
||||
channel
|
||||
.request_pty("xterm", None, Some((columns, rows, 0, 0)))
|
||||
.unwrap();
|
||||
channel.shell().unwrap();
|
||||
channel
|
||||
.write_all(format!("export PS1=\"$ \"\n").as_bytes())
|
||||
.unwrap();
|
||||
channel.flush().unwrap();
|
||||
}
|
||||
|
||||
fn start_zellij(channel: &mut ssh2::Channel, session_name: Option<&String>) {
|
||||
match session_name.as_ref() {
|
||||
Some(name) => {
|
||||
channel
|
||||
.write_all(
|
||||
format!("{} --session {}\n", ZELLIJ_EXECUTABLE_LOCATION, name).as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
None => {
|
||||
channel
|
||||
.write_all(format!("{}\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
channel.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
|
||||
let output_lines = terminal_output.read_buffer_as_lines();
|
||||
let cursor_coordinates = terminal_output.cursor_coordinates();
|
||||
let mut snapshot = String::new();
|
||||
for (line_index, line) in output_lines.iter().enumerate() {
|
||||
for (character_index, terminal_character) in line.iter().enumerate() {
|
||||
if let Some((cursor_x, cursor_y)) = cursor_coordinates {
|
||||
if line_index == cursor_y && character_index == cursor_x {
|
||||
snapshot.push('█');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
snapshot.push(terminal_character.character);
|
||||
}
|
||||
if line_index != output_lines.len() - 1 {
|
||||
snapshot.push('\n');
|
||||
}
|
||||
}
|
||||
snapshot
|
||||
}
|
||||
|
||||
pub struct RemoteTerminal<'a> {
|
||||
channel: &'a mut ssh2::Channel,
|
||||
session_name: Option<&'a String>,
|
||||
cursor_x: usize,
|
||||
cursor_y: usize,
|
||||
current_snapshot: String,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for RemoteTerminal<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"cursor x: {}\ncursor_y: {}\ncurrent_snapshot:\n{}",
|
||||
self.cursor_x, self.cursor_y, self.current_snapshot
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoteTerminal<'a> {
|
||||
pub fn cursor_position_is(&self, x: usize, y: usize) -> bool {
|
||||
x == self.cursor_x && y == self.cursor_y
|
||||
}
|
||||
pub fn tip_appears(&self) -> bool {
|
||||
self.current_snapshot.contains("Tip:")
|
||||
}
|
||||
pub fn status_bar_appears(&self) -> bool {
|
||||
self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
|
||||
// this is a bug that happens because the app draws borders around the status bar momentarily on first render
|
||||
}
|
||||
pub fn snapshot_contains(&self, text: &str) -> bool {
|
||||
self.current_snapshot.contains(text)
|
||||
}
|
||||
pub fn send_key(&mut self, key: &[u8]) {
|
||||
self.channel.write(key).unwrap();
|
||||
self.channel.flush().unwrap();
|
||||
}
|
||||
pub fn change_size(&mut self, cols: u32, rows: u32) {
|
||||
self.channel
|
||||
.request_pty_size(cols, rows, Some(cols), Some(rows))
|
||||
.unwrap();
|
||||
}
|
||||
pub fn attach_to_original_session(&mut self) {
|
||||
self.channel
|
||||
.write_all(
|
||||
format!(
|
||||
"{} attach {}\n",
|
||||
ZELLIJ_EXECUTABLE_LOCATION,
|
||||
self.session_name.unwrap()
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
self.channel.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Step {
|
||||
pub instruction: fn(RemoteTerminal) -> bool,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
pub struct RemoteRunner {
|
||||
steps: Vec<Step>,
|
||||
current_step_index: usize,
|
||||
vte_parser: vte::Parser,
|
||||
terminal_output: TerminalPane,
|
||||
channel: ssh2::Channel,
|
||||
session_name: Option<String>,
|
||||
test_name: &'static str,
|
||||
currently_running_step: Option<String>,
|
||||
retries_left: usize,
|
||||
win_size: PositionAndSize,
|
||||
}
|
||||
|
||||
impl RemoteRunner {
|
||||
pub fn new(
|
||||
test_name: &'static str,
|
||||
win_size: PositionAndSize,
|
||||
session_name: Option<String>,
|
||||
) -> Self {
|
||||
let sess = ssh_connect();
|
||||
let mut channel = sess.channel_session().unwrap();
|
||||
let vte_parser = vte::Parser::new();
|
||||
let terminal_output = TerminalPane::new(0, win_size, Palette::default());
|
||||
setup_remote_environment(&mut channel, win_size);
|
||||
start_zellij(&mut channel, session_name.as_ref());
|
||||
RemoteRunner {
|
||||
steps: vec![],
|
||||
channel,
|
||||
terminal_output,
|
||||
vte_parser,
|
||||
session_name,
|
||||
test_name,
|
||||
currently_running_step: None,
|
||||
current_step_index: 0,
|
||||
retries_left: 3,
|
||||
win_size,
|
||||
}
|
||||
}
|
||||
pub fn add_step(mut self, step: Step) -> Self {
|
||||
self.steps.push(step);
|
||||
self
|
||||
}
|
||||
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,
|
||||
session_name: self.session_name.as_ref(),
|
||||
}
|
||||
}
|
||||
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);
|
||||
let (cursor_x, cursor_y) = self.terminal_output.cursor_coordinates().unwrap_or((0, 0));
|
||||
let remote_terminal = RemoteTerminal {
|
||||
cursor_x,
|
||||
cursor_y,
|
||||
current_snapshot,
|
||||
channel: &mut self.channel,
|
||||
session_name: self.session_name.as_ref(),
|
||||
};
|
||||
let instruction = next_step.instruction;
|
||||
self.currently_running_step = Some(String::from(next_step.name));
|
||||
if instruction(remote_terminal) {
|
||||
self.current_step_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn steps_left(&self) -> bool {
|
||||
self.steps.get(self.current_step_index).is_some()
|
||||
}
|
||||
fn restart_test(&mut self) -> String {
|
||||
let session_name = self.session_name.as_ref().map(|name| {
|
||||
// this is so that we don't try to connect to the previous session if it's still stuck
|
||||
// inside the container
|
||||
format!("{}_{}", name, self.retries_left)
|
||||
});
|
||||
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size, session_name);
|
||||
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()
|
||||
}
|
||||
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 {
|
||||
let mut buf = [0u8; 1024];
|
||||
match self.channel.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(_count) => {
|
||||
for byte in buf.iter() {
|
||||
self.vte_parser
|
||||
.advance(&mut self.terminal_output.grid, *byte);
|
||||
}
|
||||
self.run_next_step();
|
||||
if !self.steps_left() {
|
||||
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");
|
||||
}
|
||||
panic!("Error while reading remote session: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
take_snapshot(&mut self.terminal_output)
|
||||
}
|
||||
pub fn get_current_snapshot(&mut self) -> String {
|
||||
take_snapshot(&mut self.terminal_output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RemoteRunner {
|
||||
fn drop(&mut self) {
|
||||
self.channel.close().unwrap();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
|
||||
|
||||
$ Hi!█
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl +
|
||||
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ █
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ I am some text█
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ nabc█
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
-- INTERFACE LOCKED --
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1 Tab #2
|
||||
|
||||
$ █
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ line1 000000000000000000000000000000000000000000000000000
|
||||
│line2 00000000000000000000000000000000000000000000000000000
|
||||
│line3 00000000000000000000000000000000000000000000000000000
|
||||
│line4 00000000000000000000000000000000000000000000000000000
|
||||
│line5 00000000000000000000000000000000000000000000000000000
|
||||
│line6 00000000000000000000000000000000000000000000000000000
|
||||
│line7 00000000000000000000000000000000000000000000000000000
|
||||
│line8 00000000000000000000000000000000000000000000000000000
|
||||
│line9 00000000000000000000000000000000000000000000000000000
|
||||
│line10 0000000000000000000000000000000000000000000000000000
|
||||
│line11 0000000000000000000000000000000000000000000000000000
|
||||
│line12 0000000000000000000000000000000000000000000000000000
|
||||
│line13 0000000000000000000000000000000000000000000000000000
|
||||
│line14 0000000000000000000000000000000000000000000000000000
|
||||
│line15 0000000000000000000000000000000000000000000000000000
|
||||
│line16 0000000000000000000000000000000000000000000000000000
|
||||
│line17 0000000000000000000000000000000000000000000000000000
|
||||
│line18 0000000000000000000000000000000000000000000000000000
|
||||
│line19 000000000000000000000000000000000000000000000000000█
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
<↓↑> Scroll / <PgUp/PgDn> Scroll Page / <ENTER> Select pane
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ │$ █
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
Zellij Tab #1
|
||||
|
||||
$ █
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Ctrl + <g> LOCK <p> PANE <t> TAB <r> RESIZE <s> SCROLL <o> SESSION <q> QUIT
|
||||
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/tests/integration/e2e.rs
|
||||
expression: last_snapshot
|
||||
|
||||
---
|
||||
$ █
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod e2e;
|
||||
pub mod fakes;
|
||||
pub mod integration;
|
||||
pub mod possible_tty_inputs;
|
||||
|
@ -51,6 +51,7 @@ pub mod commands {
|
||||
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
||||
pub const ESC: [u8; 1] = [27];
|
||||
pub const ENTER: [u8; 1] = [10]; // char '\n'
|
||||
pub const LOCK_MODE: [u8; 1] = [7]; // ctrl-g
|
||||
|
||||
pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
|
||||
pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
|
||||
@ -85,6 +86,9 @@ pub mod commands {
|
||||
pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
|
||||
pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
|
||||
|
||||
pub const SESSION_MODE: [u8; 1] = [15]; // ctrl-o
|
||||
pub const DETACH_IN_SESSION_MODE: [u8; 1] = [100]; // d
|
||||
|
||||
pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
|
||||
pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
|
||||
pub const SLEEP: [u8; 0] = [];
|
||||
|
Loading…
Reference in New Issue
Block a user