- 仍是一个测试程序(tests/ui/main.rs,cargo test --test ui),每个文件单独成测试程序会再链接一遍 GPUI - 按功能分 11 个模块:host_dialog、host_tree、connection、terminal、workspace、settings、 sftp、transfer、forward、credential、update - 两个以上模块共用的夹具和假实现放进 support/:mod.rs 是公共的 use、种子 id 和通用助手, 其余按终端、SFTP、端口转发、测试连接、凭据分文件;只有一个模块用的留在那个模块里 - 纯搬移,测试逻辑不变:147 个测试名称与搬移前一致,全部通过 - CLAUDE.md 写明模块划分和新测试该放哪里,AGENTS.md、README 改路径
213 lines
6.3 KiB
Rust
213 lines
6.3 KiB
Rust
//! Fake terminal transports, local and remote.
|
|
|
|
use super::*;
|
|
|
|
#[derive(Clone, Copy, Default)]
|
|
pub enum FakeBehavior {
|
|
#[default]
|
|
Running,
|
|
ExitFirst,
|
|
FailFirst,
|
|
ReportsOs(HostOs),
|
|
ReportsLatency(Latency),
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct FakeTerminalFactory {
|
|
pub starts: AtomicUsize,
|
|
pub behavior: FakeBehavior,
|
|
pub writes: Arc<Mutex<Vec<Vec<u8>>>>,
|
|
pub resizes: Arc<Mutex<Vec<TerminalSize>>>,
|
|
}
|
|
|
|
impl FakeTerminalFactory {
|
|
pub fn exit_first() -> Self {
|
|
Self {
|
|
behavior: FakeBehavior::ExitFirst,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn fail_first() -> Self {
|
|
Self {
|
|
behavior: FakeBehavior::FailFirst,
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn reports_os(os: HostOs) -> Self {
|
|
Self {
|
|
behavior: FakeBehavior::ReportsOs(os),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn reports_latency(latency: Latency) -> Self {
|
|
Self {
|
|
behavior: FakeBehavior::ReportsLatency(latency),
|
|
..Self::default()
|
|
}
|
|
}
|
|
|
|
pub fn written_text(&self) -> String {
|
|
let bytes: Vec<_> = self
|
|
.writes
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.iter()
|
|
.flatten()
|
|
.copied()
|
|
.collect();
|
|
String::from_utf8(bytes).expect("fake transport receives UTF-8 test input")
|
|
}
|
|
|
|
pub fn starts(&self) -> usize {
|
|
self.starts.load(Ordering::SeqCst)
|
|
}
|
|
}
|
|
|
|
impl TerminalTransportFactory for FakeTerminalFactory {
|
|
fn create(&self) -> Box<dyn TerminalTransport> {
|
|
let run = self.starts.fetch_add(1, Ordering::SeqCst) + 1;
|
|
Box::new(FakeTerminalTransport {
|
|
run,
|
|
behavior: self.behavior,
|
|
writes: self.writes.clone(),
|
|
resizes: self.resizes.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct FakeTerminalTransport {
|
|
pub run: usize,
|
|
pub behavior: FakeBehavior,
|
|
pub writes: Arc<Mutex<Vec<Vec<u8>>>>,
|
|
pub resizes: Arc<Mutex<Vec<TerminalSize>>>,
|
|
}
|
|
|
|
impl TerminalTransport for FakeTerminalTransport {
|
|
fn run(
|
|
self: Box<Self>,
|
|
_: TerminalSize,
|
|
commands: mpsc::Receiver<TerminalTransportCommand>,
|
|
events: async_channel::Sender<TerminalTransportEvent>,
|
|
) -> anyhow::Result<()> {
|
|
if matches!(self.behavior, FakeBehavior::FailFirst) && self.run == 1 {
|
|
anyhow::bail!("测试启动失败");
|
|
}
|
|
events.send_blocking(TerminalTransportEvent::Started)?;
|
|
if let FakeBehavior::ReportsOs(os) = self.behavior {
|
|
events.send_blocking(TerminalTransportEvent::HostOsDetected(os))?;
|
|
}
|
|
if let FakeBehavior::ReportsLatency(latency) = self.behavior {
|
|
events.send_blocking(TerminalTransportEvent::Latency(latency))?;
|
|
}
|
|
events.send_blocking(TerminalTransportEvent::Output(
|
|
format!(
|
|
"\x1b]0;测试终端 {}\x07run:{}$ alpha.txt 会议纪要.md\r\n",
|
|
self.run, self.run
|
|
)
|
|
.into_bytes(),
|
|
))?;
|
|
if matches!(self.behavior, FakeBehavior::ExitFirst) && self.run == 1 {
|
|
events.send_blocking(TerminalTransportEvent::Exited {
|
|
code: 9,
|
|
signal: None,
|
|
})?;
|
|
return Ok(());
|
|
}
|
|
while let Ok(command) = commands.recv() {
|
|
match command {
|
|
TerminalTransportCommand::Write(bytes) => {
|
|
self.writes
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.push(bytes.clone());
|
|
events.send_blocking(TerminalTransportEvent::Output(bytes))?;
|
|
}
|
|
TerminalTransportCommand::Resize(size) => {
|
|
self.resizes
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.push(size);
|
|
}
|
|
TerminalTransportCommand::PromptReply { .. } => {}
|
|
TerminalTransportCommand::Shutdown => break,
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn open_workspace_with_remote_factory(
|
|
cx: &mut TestAppContext,
|
|
store: HostStore,
|
|
factory: Arc<dyn TerminalTransportFactory>,
|
|
) -> (WindowHandle<Root>, Entity<Workspace>) {
|
|
cx.update(shellrs::init);
|
|
// Dialogs still, as in every fixture: see `open_workspace_with_tester`.
|
|
cx.update(|cx| cx.set_reduce_motion(true));
|
|
let mut workspace = None;
|
|
let handle = cx.open_window(size(px(1280.), px(800.)), |window, cx| {
|
|
let store = cx.new(|_| store);
|
|
let remote = Arc::new(FixedRemoteTerminalTransportProvider::new(factory.clone()));
|
|
let view = cx.new(|cx| {
|
|
Workspace::new_with_services(
|
|
store,
|
|
cx.new(|_| SettingsStore::in_memory()),
|
|
remote,
|
|
Arc::new(FakeTerminalFactory::default()),
|
|
Arc::new(FakeSftpProvider::default()),
|
|
Arc::new(FakeLocalDirectory::default()),
|
|
Arc::new(FakeConnectionTester::default()),
|
|
Arc::new(FakeForwardProvider::default()),
|
|
window,
|
|
cx,
|
|
)
|
|
});
|
|
workspace = Some(view.clone());
|
|
Root::new(view, window, cx)
|
|
});
|
|
(handle, workspace.expect("workspace created"))
|
|
}
|
|
|
|
pub fn remote_lifecycle(
|
|
workspace: &Entity<Workspace>,
|
|
id: RemoteTerminalId,
|
|
cx: &App,
|
|
) -> TerminalLifecycle {
|
|
workspace
|
|
.read(cx)
|
|
.remote_terminal(id)
|
|
.expect("terminal exists")
|
|
.read(cx)
|
|
.lifecycle(cx)
|
|
}
|
|
|
|
/// Hands every remote terminal the same fake, recording the login each one
|
|
/// was started with.
|
|
#[derive(Default)]
|
|
pub struct RecordingRemoteProvider {
|
|
pub factory: Arc<FakeTerminalFactory>,
|
|
pub logins: Mutex<Vec<HostLogin>>,
|
|
}
|
|
|
|
impl RecordingRemoteProvider {
|
|
pub fn logins(&self) -> Vec<HostLogin> {
|
|
self.logins
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
impl RemoteTerminalTransportProvider for RecordingRemoteProvider {
|
|
fn factory_for(&self, login: &HostLogin) -> SharedTerminalTransportFactory {
|
|
self.logins
|
|
.lock()
|
|
.unwrap_or_else(|error| error.into_inner())
|
|
.push(login.clone());
|
|
self.factory.clone()
|
|
}
|
|
}
|