329 lines
14 KiB
Rust
329 lines
14 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use crate::Builder;
|
|
use regex::Regex;
|
|
use settings::Setting as _;
|
|
use galaxy::{
|
|
features::FeatureFlag,
|
|
integration_testing::{
|
|
step::new_step_with_default_assertions,
|
|
subshell::{
|
|
accept_tmux_install, assert_subshell_banner_is_showing,
|
|
assert_subshell_is_bootstrapped, enter_ssh_command, enter_ssh_password,
|
|
run_exit_command, setup_gcloud_sdk, trigger_subshell_bootstrap,
|
|
wait_for_password_prompt,
|
|
},
|
|
terminal::{
|
|
assert_active_block_output_for_single_terminal_in_tab,
|
|
assert_long_running_block_executing_for_single_terminal_in_tab,
|
|
execute_command_for_single_terminal_in_tab,
|
|
util::{current_shell_starter_and_version, nonce, ExactLine, ExpectedExitStatus},
|
|
validate_block_output, wait_until_bootstrapped_single_pane_for_tab,
|
|
},
|
|
view_getters::{single_terminal_view, single_terminal_view_for_tab},
|
|
},
|
|
terminal::{
|
|
model::bootstrap::BootstrapStage,
|
|
session_settings::{StartupShell, StartupShellOverride},
|
|
shell::ShellType,
|
|
},
|
|
};
|
|
use galaxyui::{
|
|
async_assert, async_assert_eq,
|
|
integration::{AssertionCallback, AssertionOutcome, TestStep},
|
|
};
|
|
|
|
use super::new_builder;
|
|
|
|
/// Verifies that the active block is part of a remote session.
|
|
fn assert_active_block_is_remote(user: &'static str, host: &'static str) -> AssertionCallback {
|
|
Box::new(move |app, window_id| {
|
|
let terminal_view = single_terminal_view_for_tab(app, window_id, 0);
|
|
terminal_view.read(app, |view, ctx| {
|
|
let model = view.model.lock();
|
|
let active_block = model.block_list().active_block();
|
|
|
|
let Some(session_id) = active_block.session_id() else {
|
|
return AssertionOutcome::PreconditionFailed(
|
|
"Active block returned None from shell_host()".into(),
|
|
);
|
|
};
|
|
let Some(session) = view.sessions(ctx).get(session_id) else {
|
|
return AssertionOutcome::PreconditionFailed(
|
|
"Active block should be part of a known session".into(),
|
|
);
|
|
};
|
|
|
|
match async_assert!(
|
|
!session.is_local(),
|
|
"Active block should be part of a remote session"
|
|
) {
|
|
AssertionOutcome::Success => {}
|
|
failure => return failure,
|
|
};
|
|
|
|
let Some(shell_host) = active_block.shell_host() else {
|
|
return AssertionOutcome::PreconditionFailed(
|
|
"Active block returned None from shell_host()".into(),
|
|
);
|
|
};
|
|
|
|
match async_assert_eq!(
|
|
shell_host.user,
|
|
user,
|
|
"Remote session did not have the expected user"
|
|
) {
|
|
AssertionOutcome::Success => {}
|
|
failure => return failure,
|
|
};
|
|
|
|
async_assert_eq!(
|
|
shell_host.hostname,
|
|
host,
|
|
"Remote session did not have the expected host"
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Assertion that the MotD message is shown. How we expect it to be shown
|
|
/// depends on whether or not the remote shell can be bootstrapped.
|
|
fn assert_motd_shown(bootstrapped: bool) -> AssertionCallback {
|
|
let motd_regex = Regex::new("Welcome to Ubuntu").expect("Regex should compile");
|
|
Box::new(move |app, window_id| {
|
|
let terminal_view = single_terminal_view(app, window_id);
|
|
terminal_view.read(app, |view, _| {
|
|
let model = view.model.lock();
|
|
|
|
let motd_output = if bootstrapped {
|
|
let motd_block = model
|
|
.block_list()
|
|
.blocks()
|
|
.iter()
|
|
.rev()
|
|
.find(|block| block.bootstrap_stage() == BootstrapStage::ScriptExecution)
|
|
.expect("MotD block should exist");
|
|
// Because of how we move blocks through their stages, output from
|
|
// RC files and the MotD goes into the command grid.
|
|
motd_block.command_to_string()
|
|
} else {
|
|
// For non-bootstrapped blocks, the MotD is part of the SSH
|
|
// session output in the active block.
|
|
model.block_list().active_block().output_to_string()
|
|
};
|
|
|
|
async_assert!(
|
|
motd_regex.is_match(&motd_output),
|
|
"Expected output to match {motd_regex:?}, but was:\n{motd_output:?}"
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Verifies that the current session is using a login shell.
|
|
fn verify_login_shell(shell: &str) -> TestStep {
|
|
let command = match shell {
|
|
"zsh" => "[[ -o login ]]",
|
|
"fish" => "status --is-login",
|
|
// For other shells, we don't actually start a login shell but do source /etc/profile.
|
|
_ => "test \"$WARP_PROFILE_LOADED\" = true",
|
|
};
|
|
|
|
match shell {
|
|
"bash" | "zsh" => execute_command_for_single_terminal_in_tab(
|
|
0,
|
|
command.into(),
|
|
ExpectedExitStatus::Success,
|
|
(),
|
|
)
|
|
.add_assertion(assert_motd_shown(true /* bootstrapped */)),
|
|
_ => {
|
|
// For non-bootstrapped shells, run the command directly and verify
|
|
// the exit status.
|
|
let nonce = nonce();
|
|
let expected_output = ExactLine::from(format!("{nonce}: 0"));
|
|
|
|
TestStep::new("Verify login shell")
|
|
.with_typed_characters(&[&format!("{command}; echo \"{nonce}\": $?")])
|
|
.with_keystrokes(&["enter"])
|
|
.add_assertion(assert_active_block_output_for_single_terminal_in_tab(
|
|
expected_output,
|
|
0,
|
|
))
|
|
.add_assertion(assert_motd_shown(false /* bootstrapped */))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A macro to generate a test function to validate that we are able to
|
|
/// bootstrap a given remote shell when using ssh.
|
|
macro_rules! generate_can_bootstrap_legacy_ssh_test_for_shell {
|
|
($fn_name:ident, $shell:literal) => {
|
|
/// Ensure we can successfully ssh into a $shell remote shell and bootstrap it
|
|
/// successfully.
|
|
pub fn $fn_name() -> Builder {
|
|
new_builder()
|
|
// TODO(CORE-2333) PowerShell has no SSH wrapper.
|
|
.set_should_run_test(|| {
|
|
if FeatureFlag::SSHTmuxWrapper.is_enabled() {
|
|
return false;
|
|
}
|
|
let (starter, _) = current_shell_starter_and_version();
|
|
starter.shell_type() != ShellType::PowerShell
|
|
})
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(setup_gcloud_sdk())
|
|
.with_step(enter_ssh_command($shell))
|
|
.with_step(wait_for_password_prompt(0 /*tab_idx*/, $shell))
|
|
.with_step(
|
|
enter_ssh_password().set_post_step_pause(std::time::Duration::from_millis(250)),
|
|
)
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(
|
|
new_step_with_default_assertions(
|
|
"Assert active block is part of a remote session",
|
|
)
|
|
.add_assertion(assert_active_block_is_remote($shell, "ubuntu-14-04")),
|
|
)
|
|
.with_step(verify_login_shell($shell))
|
|
}
|
|
};
|
|
}
|
|
|
|
/// A macro to generate a test function to validate that we are able to
|
|
/// bootstrap a given remote shell when using ssh.
|
|
macro_rules! generate_can_bootstrap_tmux_ssh_test_for_shell {
|
|
($fn_name:ident, $shell:literal, $install_tmux:literal) => {
|
|
/// Ensure we can successfully ssh into a $shell remote shell and bootstrap it
|
|
/// successfully.
|
|
pub fn $fn_name() -> Builder {
|
|
fn warpify(builder: Builder) -> Builder {
|
|
builder
|
|
.with_step(enter_ssh_command($shell))
|
|
.with_step(wait_for_password_prompt(0 /*tab_idx*/, $shell))
|
|
.with_step(
|
|
enter_ssh_password()
|
|
.set_post_step_pause(std::time::Duration::from_millis(250)),
|
|
)
|
|
.with_step(assert_subshell_banner_is_showing())
|
|
.with_step(trigger_subshell_bootstrap())
|
|
}
|
|
|
|
fn assert_warpification(builder: Builder) -> Builder {
|
|
builder
|
|
.with_step(assert_subshell_is_bootstrapped(0, 0))
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(
|
|
new_step_with_default_assertions(
|
|
"Assert active block is part of a remote session",
|
|
)
|
|
.add_assertion(assert_active_block_is_remote($shell, "ubuntu-14-04")),
|
|
)
|
|
.with_step(verify_login_shell($shell))
|
|
}
|
|
|
|
let builder = new_builder()
|
|
// TODO(CORE-2333) PowerShell has no SSH wrapper.
|
|
.set_should_run_test(|| {
|
|
if !FeatureFlag::SSHTmuxWrapper.is_enabled() {
|
|
return false;
|
|
}
|
|
let (starter, _) = current_shell_starter_and_version();
|
|
starter.shell_type() != ShellType::PowerShell
|
|
})
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(setup_gcloud_sdk());
|
|
// Install Tmux
|
|
let builder = warpify(builder).with_step(
|
|
accept_tmux_install().set_post_step_pause(std::time::Duration::from_secs(3)),
|
|
);
|
|
// Quit SSH Session once we validate warpificaiton works with Tmux Install
|
|
let builder = assert_warpification(builder).with_step(run_exit_command());
|
|
|
|
// Validate we can Warpify when Tmux is already installed
|
|
assert_warpification(warpify(builder))
|
|
}
|
|
};
|
|
}
|
|
|
|
/// A macro to generate a test function to validate that we are able to
|
|
/// successfully start (but not bootstrap) an ssh connection with the given
|
|
/// remote shell. Verifies that the ssh connection worked by asserting that
|
|
/// there is still a long-running block after entering the password, and that
|
|
/// attempting to run `exit` returns us to the bootstrapped local shell.
|
|
macro_rules! generate_long_running_block_ssh_test_for_shell {
|
|
($fn_name:ident, $shell:literal, prompt_regex: $prompt_regex:literal) => {
|
|
/// Ensure we can successfully ssh into a $shell remote shell and bootstrap it
|
|
/// successfully.
|
|
pub fn $fn_name() -> Builder {
|
|
new_builder()
|
|
// TODO(CORE-2333) PowerShell has no SSH wrapper.
|
|
.set_should_run_test(|| {
|
|
let (starter, _) = current_shell_starter_and_version();
|
|
starter.shell_type() != ShellType::PowerShell
|
|
})
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(setup_gcloud_sdk())
|
|
.with_step(enter_ssh_command($shell))
|
|
.with_step(wait_for_password_prompt(0 /*tab_idx*/, $shell))
|
|
.with_step(enter_ssh_password())
|
|
.with_step(
|
|
TestStep::new("Assert prompt is awaiting input")
|
|
.add_assertion(
|
|
assert_long_running_block_executing_for_single_terminal_in_tab(true, 0),
|
|
)
|
|
.add_assertion(move |app, window_id| {
|
|
let regex = Regex::new($prompt_regex)
|
|
.expect("regex should not fail to compile");
|
|
validate_block_output(®ex, 0, 0, window_id, app)
|
|
}),
|
|
)
|
|
.with_step(verify_login_shell($shell))
|
|
.with_step(TestStep::new("Exit ssh session").with_typed_characters(&["exit\n"]))
|
|
.with_step(new_step_with_default_assertions(
|
|
"Assert ssh session has completed",
|
|
))
|
|
}
|
|
};
|
|
}
|
|
|
|
// Generate test methods to validate expected ssh behavior for a variety of
|
|
// remote shells.
|
|
generate_can_bootstrap_legacy_ssh_test_for_shell!(test_legacy_ssh_into_bash, "bash");
|
|
generate_can_bootstrap_legacy_ssh_test_for_shell!(test_legacy_ssh_into_zsh, "zsh");
|
|
generate_can_bootstrap_tmux_ssh_test_for_shell!(test_tmux_ssh_into_bash, "bash", false);
|
|
generate_can_bootstrap_tmux_ssh_test_for_shell!(test_tmux_ssh_into_zsh, "zsh", false);
|
|
generate_can_bootstrap_tmux_ssh_test_for_shell!(test_install_tmux_ssh_into_bash, "bash", true);
|
|
generate_can_bootstrap_tmux_ssh_test_for_shell!(test_install_tmux_ssh_into_zsh, "zsh", true);
|
|
generate_long_running_block_ssh_test_for_shell!(test_ssh_into_fish, "fish", prompt_regex: r"\nfish@ubuntu-14-04 ~>$");
|
|
generate_long_running_block_ssh_test_for_shell!(test_ssh_into_sh, "sh", prompt_regex: r"\n\$ $");
|
|
generate_long_running_block_ssh_test_for_shell!(test_ssh_into_ash, "ash", prompt_regex: r"\n\$ $");
|
|
|
|
/// Tests a regression with the startup shell setting and SSH proxies.
|
|
/// See WAR-6337 for details - if `$SHELL` is not set to a valid executable file
|
|
/// path, SSH fails to execute proxy commands (like the one this test uses for
|
|
/// gcloud).
|
|
pub fn test_ssh_with_shell_override() -> Builder {
|
|
new_builder()
|
|
// TODO(CORE-2333) PowerShell has no SSH wrapper.
|
|
.set_should_run_test(|| {
|
|
let (starter, _) = current_shell_starter_and_version();
|
|
starter.shell_type() != ShellType::PowerShell
|
|
})
|
|
.with_user_defaults(HashMap::from([(
|
|
StartupShellOverride::storage_key().to_owned(),
|
|
serde_json::to_string(&StartupShell::Zsh).expect("Can serialize setting as JSON"),
|
|
)]))
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(setup_gcloud_sdk())
|
|
.with_step(enter_ssh_command("bash"))
|
|
.with_step(wait_for_password_prompt(0, "bash"))
|
|
.with_step(enter_ssh_password())
|
|
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
|
|
.with_step(
|
|
new_step_with_default_assertions("Assert active block is part of a remote session")
|
|
.add_assertion(assert_active_block_is_remote("bash", "ubuntu-14-04")),
|
|
)
|
|
.with_step(verify_login_shell("bash"))
|
|
}
|