Add basic shell tests (#39232)
Release Notes: - N/A --------- Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
co-authored by
Lukas Wirth
parent
a53faff412
commit
e65a9291ef
@@ -159,6 +159,8 @@ pub use taffy::{AvailableSpace, LayoutId};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test::*;
|
||||
pub use text_system::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use util::smol_timeout;
|
||||
pub use util::{FutureExt, Timeout, arc_cow::ArcCow};
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
@@ -114,6 +114,7 @@ impl<T: Future> Future for WithTimeout<T> {
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
/// Uses smol executor to run a given future no longer than the timeout specified.
|
||||
pub async fn smol_timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
|
||||
@@ -121,7 +121,7 @@ const DEBUG_CELL_WIDTH: Pixels = px(5.);
|
||||
const DEBUG_LINE_HEIGHT: Pixels = px(5.);
|
||||
|
||||
///Upward flowing events, for changing the title and such
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
TitleChanged,
|
||||
BreadcrumbsChanged,
|
||||
@@ -134,7 +134,7 @@ pub enum Event {
|
||||
Open(MaybeNavigationTarget),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PathLikeTarget {
|
||||
/// File system path, absolute or relative, existing or not.
|
||||
/// Might have line and column number(s) attached as `file.rs:1:23`
|
||||
@@ -144,7 +144,7 @@ pub struct PathLikeTarget {
|
||||
}
|
||||
|
||||
/// A string inside terminal, potentially useful as a URI that can be opened.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MaybeNavigationTarget {
|
||||
/// HTTP, git, etc. string determined by the `URL_REGEX` regex.
|
||||
Url(String),
|
||||
@@ -2210,6 +2210,8 @@ pub fn rgba_color(r: u8, g: u8, b: u8) -> Hsla {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
IndexedCell, TerminalBounds, TerminalBuilder, TerminalContent, content_index_for_mouse,
|
||||
@@ -2220,7 +2222,7 @@ mod tests {
|
||||
term::cell::Cell,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use gpui::{Pixels, Point, TestAppContext, bounds, point, size};
|
||||
use gpui::{Pixels, Point, TestAppContext, bounds, point, size, smol_timeout};
|
||||
use rand::{Rng, distr, rngs::ThreadRng};
|
||||
use task::ShellBuilder;
|
||||
|
||||
@@ -2263,6 +2265,146 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO should be tested on Linux too, but does not work there well
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_terminal_eof(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let (completion_tx, completion_rx) = smol::channel::unbounded();
|
||||
// Build an empty command, which will result in a tty shell spawned.
|
||||
let terminal = cx.new(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::System,
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
});
|
||||
|
||||
let (event_tx, event_rx) = smol::channel::unbounded::<Event>();
|
||||
cx.update(|cx| {
|
||||
cx.subscribe(&terminal, move |_, e, _| {
|
||||
event_tx.send_blocking(e.clone()).unwrap();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
cx.background_spawn(async move {
|
||||
assert_eq!(
|
||||
completion_rx.recv().await.unwrap(),
|
||||
Some(ExitStatus::default()),
|
||||
"EOF should result in the tty shell exiting successfully",
|
||||
);
|
||||
})
|
||||
.detach();
|
||||
|
||||
let wakeup = event_rx.recv().await.expect("No wakeup event received");
|
||||
assert_eq!(wakeup, Event::Wakeup, "Expected wakeup, got {wakeup:?}");
|
||||
|
||||
terminal.update(cx, |terminal, _| {
|
||||
let success = terminal.try_keystroke(&Keystroke::parse("ctrl-c").unwrap(), false);
|
||||
assert!(success, "Should have registered ctrl-c sequence");
|
||||
});
|
||||
terminal.update(cx, |terminal, _| {
|
||||
let success = terminal.try_keystroke(&Keystroke::parse("ctrl-d").unwrap(), false);
|
||||
assert!(success, "Should have registered ctrl-d sequence");
|
||||
});
|
||||
|
||||
let mut all_events = vec![Event::Wakeup];
|
||||
while let Ok(Ok(new_event)) =
|
||||
smol_timeout(Duration::from_millis(500), event_rx.recv()).await
|
||||
{
|
||||
all_events.push(new_event.clone());
|
||||
}
|
||||
|
||||
assert!(
|
||||
all_events.contains(&Event::CloseTerminal),
|
||||
"EOF command sequence should have triggered a TTY terminal exit, but got events: {all_events:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_terminal_no_exit_on_spawn_failure(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let (completion_tx, completion_rx) = smol::channel::unbounded();
|
||||
let (program, args) = ShellBuilder::new(None, &Shell::System)
|
||||
.build(Some("asdasdasdasd".to_owned()), &["@@@@@".to_owned()]);
|
||||
let terminal = cx.new(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
});
|
||||
|
||||
let (event_tx, event_rx) = smol::channel::unbounded::<Event>();
|
||||
cx.update(|cx| {
|
||||
cx.subscribe(&terminal, move |_, e, _| {
|
||||
event_tx.send_blocking(e.clone()).unwrap();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
cx.background_spawn(async move {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let exit_status = completion_rx.recv().await.ok().flatten();
|
||||
if let Some(exit_status) = exit_status {
|
||||
assert_eq!(exit_status.code(), Some(1));
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let exit_status = completion_rx.recv().await.unwrap().unwrap();
|
||||
assert!(
|
||||
!exit_status.success(),
|
||||
"Wrong shell command should result in a failure"
|
||||
);
|
||||
assert_eq!(exit_status.code(), None);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let mut all_events = Vec::new();
|
||||
while let Ok(Ok(new_event)) =
|
||||
smol_timeout(Duration::from_millis(500), event_rx.recv()).await
|
||||
{
|
||||
all_events.push(new_event.clone());
|
||||
}
|
||||
|
||||
assert!(
|
||||
!all_events
|
||||
.iter()
|
||||
.any(|event| event == &Event::CloseTerminal),
|
||||
"Wrong shell command should update the title but not should not close the terminal to show the error message, but got events: {all_events:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_for_index() {
|
||||
// Test every possible value in the color cube.
|
||||
|
||||
Reference in New Issue
Block a user