Add basic shell tests (#39232)

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Kirill Bulatov
2025-09-30 22:45:36 +03:00
committed by GitHub
co-authored by Lukas Wirth
parent a53faff412
commit e65a9291ef
3 changed files with 149 additions and 4 deletions
+2
View File
@@ -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::*;
+1
View File
@@ -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>,
+146 -4
View File
@@ -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.