Files
galaxy/app/src/integration_testing/terminal/util.rs
T

200 lines
6.2 KiB
Rust

use std::borrow::Cow;
use std::iter;
use std::path::{Path, PathBuf};
use async_io::block_on;
use command::blocking::Command;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use regex::Regex;
use galaxy_core::command::ExitCode;
#[cfg(windows)]
use galaxy_core::paths::base_config_dir;
use crate::terminal::local_tty::shell::{DirectShellStarter, ShellStarter, ShellStarterSource};
use crate::terminal::shell;
use crate::terminal::shell::ShellType;
/// Returns the shell starter along with the version of the shell about to be run.
pub fn current_shell_starter_and_version() -> (DirectShellStarter, String) {
let shell_starter_or_wsl_name = ShellStarter::init(Default::default())
.expect("Could not create a shell starter or wsl name");
let shell_starter_source =
block_on(async { shell_starter_or_wsl_name.to_shell_starter_source().await })
.expect("Could not create a shell starter source");
let starter = match shell_starter_source {
ShellStarterSource::Override(starter) => match starter {
ShellStarter::Direct(direct_shell_starter) => direct_shell_starter,
ShellStarter::Wsl(_) => {
// TODO(CORE-2302): Support integration tests on Windows (including WSL).
todo!("We don't yet support integration tests for WSL shells")
}
// TODO(CORE-2302): Support integration tests on Windows (including WSL).
ShellStarter::MSYS2(_) => {
todo!("We don't yet support integration tests for MSYS2")
}
ShellStarter::DockerSandbox(_) => {
todo!("We don't yet support integration tests for Docker sandbox shells")
}
},
ShellStarterSource::Environment(starter)
| ShellStarterSource::UserDefault(starter)
| ShellStarterSource::Fallback { starter, .. } => starter,
};
let version = match starter.shell_type() {
shell::ShellType::Zsh => {
let stdout = Command::new(starter.logical_shell_path())
.args(["-c", "echo $ZSH_VERSION"])
.output()
.expect("version command should run")
.stdout;
String::from_utf8_lossy(&stdout).into_owned()
}
shell::ShellType::Bash => {
let stdout = Command::new(starter.logical_shell_path())
.args(["-c", "echo $BASH_VERSION"])
.output()
.expect("version command should run")
.stdout;
String::from_utf8_lossy(&stdout).into_owned()
}
shell::ShellType::Fish => {
let stdout = Command::new(starter.logical_shell_path())
.args(["-c", "echo $FISH_VERSION"])
.output()
.expect("version command should run")
.stdout;
String::from_utf8_lossy(&stdout).into_owned()
}
shell::ShellType::PowerShell => {
let stdout = Command::new(starter.logical_shell_path())
.args(["-Version"])
.output()
.expect("version command should run")
.stdout;
String::from_utf8_lossy(&stdout).into_owned()
}
};
assert!(!version.is_empty());
(starter, version)
}
/// Returns the directory for the default histfile location for the ShellType in this
/// ShellStarter based on the given user `home_dir`.
pub fn default_histfile_directory(shell: &ShellType, home_dir: &Path) -> PathBuf {
match shell {
ShellType::Fish => home_dir.join(".local/share/fish"),
#[cfg(not(windows))]
ShellType::PowerShell => home_dir.join(".local/share/powershell/PSReadLine"),
#[cfg(windows)]
ShellType::PowerShell => base_config_dir().join("Microsoft/Windows/PowerShell/PSReadLine"),
_ => home_dir.to_owned(),
}
}
/// Generates a random nonce to distinguish between commands.
pub fn nonce() -> String {
let mut rng = thread_rng();
iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(7)
.collect()
}
/// Different options for asserting the value of the exit code.
pub enum ExpectedExitStatus {
/// Checks code == 0
Success,
/// Checks code != 0
Failure,
/// Checks code == expected
ExactCode(ExitCode),
/// Any exit status is considered valid.
Any,
}
/// A representation of the expected output from running a command.
pub trait ExpectedOutput: std::fmt::Debug {
/// Returns whether the given result matches the expected output.
fn matches(&self, result: &str) -> bool;
}
#[derive(Debug)]
pub struct ExactLine<'a>(Cow<'a, str>);
impl<'a, T: Into<Cow<'a, str>>> From<T> for ExactLine<'a> {
fn from(value: T) -> Self {
ExactLine(value.into())
}
}
impl ExpectedOutput for str {
fn matches(&self, result: &str) -> bool {
self == result
}
}
impl<T: ExpectedOutput + ?Sized> ExpectedOutput for &T {
fn matches(&self, result: &str) -> bool {
(*self).matches(result)
}
}
impl ExpectedOutput for String {
fn matches(&self, result: &str) -> bool {
self == result
}
}
impl ExpectedOutput for ExactLine<'_> {
fn matches(&self, result: &str) -> bool {
result.lines().any(|line| line == self.0)
}
}
impl ExpectedOutput for Regex {
fn matches(&self, result: &str) -> bool {
self.is_match(result)
}
}
impl ExpectedOutput for Path {
fn matches(&self, result: &str) -> bool {
self.to_str() == Some(result)
}
}
impl ExpectedOutput for PathBuf {
fn matches(&self, result: &str) -> bool {
self.as_path().matches(result)
}
}
impl ExpectedOutput for () {
fn matches(&self, _result: &str) -> bool {
true
}
}
impl<T: ExpectedOutput> ExpectedOutput for Option<T> {
fn matches(&self, result: &str) -> bool {
match self {
Some(expected) => expected.matches(result),
None => true,
}
}
}
#[derive(Debug)]
pub struct JsonEq(pub serde_json::Value);
impl ExpectedOutput for JsonEq {
fn matches(&self, result: &str) -> bool {
match serde_json::from_str::<serde_json::Value>(result) {
Ok(actual) => actual == self.0,
Err(_) => false,
}
}
}