diff --git a/src/terminal/view.rs b/src/terminal/view.rs index 316d10cd..41249aad 100644 --- a/src/terminal/view.rs +++ b/src/terminal/view.rs @@ -98,7 +98,7 @@ pub fn declare_displayed(cx: &App, panes: impl IntoIterator Option { cx.try_global::()? .0 @@ -8848,34 +8848,54 @@ mod tests { } } +/// A connected pair of [`crate::daemon::transport::Stream`]s, one for each end +/// of a pane's link to its daemon. +/// +/// The client half is what a pane really reads and writes; the daemon half is +/// the test's, to speak protocol into. +/// +/// This is the one thing a pane harness needs that Unix and Windows spell +/// differently — `socketpair` there, a loopback connect here — and every gpui +/// test in this crate is portable once it goes through this instead of naming +/// `UnixStream` itself. +#[cfg(test)] +pub(crate) fn test_stream_pair() -> ( + crate::daemon::transport::Stream, + crate::daemon::transport::Stream, +) { + #[cfg(unix)] + { + std::os::unix::net::UnixStream::pair().unwrap() + } + #[cfg(windows)] + { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let client_side = std::net::TcpStream::connect(addr).unwrap(); + let (daemon_side, _) = listener.accept().unwrap(); + (client_side, daemon_side) + } +} + #[cfg(test)] pub(crate) fn quiet_test_pane( pane_id: u64, window: &mut Window, cx: &mut gpui::App, ) -> (gpui::Entity, crate::daemon::transport::Stream) { - #[cfg(unix)] - let (client_side, daemon_side) = std::os::unix::net::UnixStream::pair().unwrap(); - #[cfg(windows)] - let (client_side, daemon_side) = { - let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); - let client_side = std::net::TcpStream::connect(addr).unwrap(); - let (daemon_side, _) = listener.accept().unwrap(); - (client_side, daemon_side) - }; + let (client_side, daemon_side) = test_stream_pair(); let terminal = RemoteTerminal::from_stream(client_side, TermSize::new(80, 24)) .expect("quiet test terminal"); let view = cx.new(|cx| TerminalView::with_terminal(terminal, pane_id, window, cx)); (view, daemon_side) } -#[cfg(all(test, unix))] +#[cfg(test)] pub(crate) fn quiet_test_ssh_pane( pane_id: u64, window: &mut Window, cx: &mut gpui::App, -) -> (gpui::Entity, std::os::unix::net::UnixStream) { +) -> (gpui::Entity, crate::daemon::transport::Stream) { let (view, stream) = quiet_test_pane(pane_id, window, cx); view.update(cx, |view, _| { view.ssh_spec = Some(Box::new( @@ -8888,20 +8908,20 @@ pub(crate) fn quiet_test_ssh_pane( (view, stream) } -#[cfg(all(test, unix))] +#[cfg(test)] mod gpui_tests { use super::*; use crate::daemon::protocol::{ClientMsg, DaemonMsg}; + use crate::daemon::transport::Stream; use gpui::{Entity, TestAppContext, point}; - use std::os::unix::net::UnixStream; - fn harness(cx: &mut TestAppContext) -> (gpui::WindowHandle, UnixStream) { + fn harness(cx: &mut TestAppContext) -> (gpui::WindowHandle, Stream) { // Building a view reads the config. Whether that hit the real user // directory used to come down to which test happened to pin the // scratch dir first. crate::core::config::pin_test_config_dir(); cx.executor().allow_parking(); - let (client_side, daemon_side) = UnixStream::pair().unwrap(); + let (client_side, daemon_side) = super::test_stream_pair(); cx.update(|cx| { gpui_component::init(cx); cx.set_global(Config::default()); @@ -8926,11 +8946,11 @@ mod gpui_tests { ) -> ( gpui::WindowHandle, Entity, - UnixStream, + Stream, ) { crate::core::config::pin_test_config_dir(); cx.executor().allow_parking(); - let (client_side, daemon_side) = UnixStream::pair().unwrap(); + let (client_side, daemon_side) = super::test_stream_pair(); cx.update(|cx| { gpui_component::init(cx); cx.set_global(Config::default()); @@ -8956,7 +8976,7 @@ mod gpui_tests { fn prompt_ready( window: &gpui::WindowHandle, cx: &mut TestAppContext, - daemon: &mut UnixStream, + daemon: &mut Stream, ) { DaemonMsg::Prompt { active: true, @@ -8980,7 +9000,7 @@ mod gpui_tests { fn alt_screen_ready( window: &gpui::WindowHandle, cx: &mut TestAppContext, - daemon: &mut UnixStream, + daemon: &mut Stream, ) { DaemonMsg::Output(b"\x1b[?1049h".to_vec()) .encode(daemon) @@ -9008,7 +9028,7 @@ mod gpui_tests { .encode(&mut daemon) .unwrap(); - let report = |status: AgentStatus, daemon: &mut UnixStream| { + let report = |status: AgentStatus, daemon: &mut Stream| { DaemonMsg::AgentStatus(Some(AgentSessionState { status, message: None, @@ -9115,7 +9135,7 @@ mod gpui_tests { status: crate::core::cli_agent::AgentStatus, pane: &gpui::Entity, cx: &mut TestAppContext, - daemon: &mut UnixStream, + daemon: &mut Stream, ) { use crate::core::cli_agent::AgentSessionState; @@ -9565,6 +9585,15 @@ mod gpui_tests { /// must not have made the promise. Otherwise those paths sit "not answered /// yet" for the life of the pane — no underline, and a click that says /// nothing, which is the silence this whole path exists to remove. + /// + /// Unix-only because the path it prints is: `Path::new("/etc/hosts")` + /// is not absolute on Windows, so `FileCandidate::paths` measures it + /// from the roots rather than letting it stand alone — and a workspace + /// that never connected has no roots, so nothing is ever wanted. Which + /// is itself the divergence: a Windows tty7 looking at a *remote* Linux + /// pane never probes the POSIX paths that pane prints, and so never + /// underlines them. + #[cfg(unix)] #[gpui::test] fn a_probe_with_no_host_to_ask_stays_wanted(cx: &mut TestAppContext) { let (window, mut daemon) = harness(cx); @@ -9756,7 +9785,7 @@ mod gpui_tests { .unwrap(); } - fn next_input(daemon: &mut UnixStream) -> Vec { + fn next_input(daemon: &mut Stream) -> Vec { loop { match ClientMsg::read(daemon).expect("client socket stays open") { ClientMsg::Input(bytes) => return bytes, @@ -9788,7 +9817,7 @@ mod gpui_tests { } } - fn next_input_until_timeout(daemon: &mut UnixStream) -> Option> { + fn next_input_until_timeout(daemon: &mut Stream) -> Option> { use std::io::ErrorKind; daemon @@ -12533,7 +12562,7 @@ mod gpui_tests { } assert_eq!(seen, "before", "the pre-drop screen is what we relink over"); - let (new_client, mut new_daemon) = UnixStream::pair().unwrap(); + let (new_client, mut new_daemon) = super::test_stream_pair(); window .update(cx, |view, _, cx| { view.adopt_relink( diff --git a/src/ui/app.rs b/src/ui/app.rs index dea55890..ab6dad2b 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -9282,14 +9282,13 @@ pub(crate) mod test_window { } /// A window carrying `n` quiet tabs, active on the first. - #[cfg(unix)] pub(crate) fn harness_with_tabs( cx: &mut TestAppContext, n: usize, ) -> ( Entity, VisualTestContext, - Vec, + Vec, ) { use crate::terminal::view::quiet_test_pane; use crate::ui::pane::{Pane, PaneSlot}; @@ -9316,13 +9315,12 @@ pub(crate) mod test_window { (app, vcx, streams) } - #[cfg(unix)] pub(crate) fn harness_with_pane( cx: &mut TestAppContext, ) -> ( Entity, VisualTestContext, - std::os::unix::net::UnixStream, + crate::daemon::transport::Stream, ) { use crate::terminal::view::quiet_test_pane; use crate::ui::pane::{Pane, PaneSlot}; @@ -9371,7 +9369,6 @@ pub(crate) mod test_window { /// another frame 250ms later. So the sleep below is load-bearing too, and /// a round that drew nothing is not on its own enough to stop on — a burst /// still open is a frame already owed. - #[cfg(unix)] pub(crate) fn quiesce(vcx: &mut VisualTestContext, cwd: Option<&std::path::Path>) { use crate::terminal::git_data::ScmData; use crate::terminal::git_status::GitStatusCache; @@ -9483,7 +9480,7 @@ mod cursor_blink_gpui_tests { } } -#[cfg(all(test, unix))] +#[cfg(test)] mod ssh_rebuild_gpui_tests { use super::test_window::harness_with_pane; use crate::core::session::{ @@ -9920,9 +9917,7 @@ mod shell_menu_gpui_tests { } } -// `harness_with_tabs` hands back the panes' `UnixStream`s, so it exists only -// on unix — same as `ssh_rebuild_gpui_tests` below it. -#[cfg(all(test, unix))] +#[cfg(test)] mod rename_gpui_tests { use gpui::TestAppContext; @@ -10006,7 +10001,7 @@ mod rename_gpui_tests { // everything else hidden; a pane nobody has declared — or whose id nobody // registered — must err toward displayed, because the failure direction that // matters is a visible pane that stops repainting. -#[cfg(all(test, unix))] +#[cfg(test)] mod displayed_gpui_tests { use gpui::TestAppContext; @@ -10107,7 +10102,7 @@ mod displayed_gpui_tests { // Zoom is a tab's view state: it rides with the tab across a switch, while a // layout change (drag, split, close) still clears it. -#[cfg(all(test, unix))] +#[cfg(test)] mod zoom_gpui_tests { use gpui::TestAppContext; @@ -10155,7 +10150,7 @@ mod zoom_gpui_tests { // test config dir and nothing is listening on it — so every forward request // fails. That is exactly the case these are about: what the panel and the form // are left holding when the far side does not answer. -#[cfg(all(test, unix))] +#[cfg(test)] mod managed_forward_gpui_tests { use gpui::TestAppContext; use gpui_component::input::InputState; diff --git a/src/ui/diff_overlay.rs b/src/ui/diff_overlay.rs index 2e869e31..016d3fd7 100644 --- a/src/ui/diff_overlay.rs +++ b/src/ui/diff_overlay.rs @@ -2191,7 +2191,7 @@ mod tests { } } -#[cfg(all(test, unix))] +#[cfg(test)] mod overlay_gpui_tests { use super::*; use crate::ui::app::test_window; @@ -2469,8 +2469,14 @@ mod overlay_gpui_tests { /// terminal, an editor, a worktree command — and the cached branch is a branch /// the repository has left. That is what the stale entry below stands for. /// -/// Unix-gated like every other window harness in this tree: `harness_with_tabs` -/// hands back a `std::os::unix::net::UnixStream` for the pane. +/// Unix-only, and not for the harness: on Windows the repository root git +/// reports (`C:/Users/—`, forward slashes, straight out of MSYS2 git) is +/// not the root the seeded cache below holds, so `scm_epoch` never agrees +/// with the landing snapshot and the overlay re-probes on every frame — +/// `load` reaches `Ready` and `loading` goes straight back to `true`, +/// which is the exact spin this test exists to catch. That is a real +/// divergence in the SCM layer's path comparisons rather than a test +/// artefact, so the gate stays until those roots are compared normalised. #[cfg(all(test, unix))] mod render_idle_gpui_tests { use super::*; diff --git a/src/ui/file_tree.rs b/src/ui/file_tree.rs index f5408ec8..0d379823 100644 --- a/src/ui/file_tree.rs +++ b/src/ui/file_tree.rs @@ -2931,7 +2931,7 @@ mod tests { } } -#[cfg(all(test, unix))] +#[cfg(test)] mod render_idle_gpui_tests { use super::*; use crate::daemon::protocol::DaemonMsg; @@ -2959,7 +2959,7 @@ mod render_idle_gpui_tests { ) -> ( Entity, VisualTestContext, - std::os::unix::net::UnixStream, + crate::daemon::transport::Stream, ) { let (app, mut vcx, mut pane) = test_window::harness_with_pane(cx); DaemonMsg::Cwd(root.to_path_buf()) @@ -3558,7 +3558,7 @@ mod render_idle_gpui_tests { /// What these cannot reach is the hit test — whether the row under the cursor /// is the one that gets the drop is decided by gpui's hitbox stack, and there /// is no headless way to put a cursor over a row. -#[cfg(all(test, unix))] +#[cfg(test)] mod drop_gpui_tests { use super::render_idle_gpui_tests::{files_panel_on, rows, scratch, serial, settle}; use super::*; diff --git a/src/ui/scm/detail.rs b/src/ui/scm/detail.rs index d1ffc073..16c27337 100644 --- a/src/ui/scm/detail.rs +++ b/src/ui/scm/detail.rs @@ -974,6 +974,16 @@ mod tests { /// here — a missing global, a theme token, a slice through the middle of a /// character — goes wrong during layout and paint, so these arm the render /// probe and insist something was actually drawn. +/// +/// Still unix-only, and for a reason worth naming rather than a harness +/// one: on Windows `git rev-parse --show-toplevel` (Git for Windows is +/// MSYS2) prints `C:/Users/—` with forward slashes, and that string is +/// what `tty7_core::core::git::probe` stores as `RepoSnapshot::root`. +/// Every comparison here — and in the SCM cache — is a plain `PathBuf` +/// equality against a path the OS spelled `C:\Users\`, so the two never +/// match and the panel never settles on the directory it is already +/// showing. Taking the gate off needs those roots compared normalised, +/// the way `ui::path_display` already normalises for display. #[cfg(all(test, unix))] mod detail_gpui_tests { use super::*; @@ -1043,7 +1053,7 @@ mod detail_gpui_tests { ) -> ( Entity, VisualTestContext, - std::os::unix::net::UnixStream, + crate::daemon::transport::Stream, ) { let (app, mut vcx, mut pane) = test_window::harness_with_pane(cx); DaemonMsg::Cwd(root.to_path_buf()) diff --git a/src/ui/scm/graph.rs b/src/ui/scm/graph.rs index 553c9a61..99cdcb32 100644 --- a/src/ui/scm/graph.rs +++ b/src/ui/scm/graph.rs @@ -2186,11 +2186,7 @@ mod tests { /// way to know is to settle the window and count frames. Same shape as the file /// tree's own idle tests, including the serial lock: the render probe is /// thread-local and two of these at once would count each other's frames. -/// -/// `unix` for the same reason `panel.rs`, `detail.rs` and `file_tree.rs` gate -/// theirs: a real pane means `test_window::harness_with_pane`, and that harness -/// hands back a `std::os::unix::net::UnixStream`. -#[cfg(all(test, unix))] +#[cfg(test)] mod render_idle_gpui_tests { use super::*; use crate::ui::app::{render_probe, test_window}; diff --git a/src/ui/scm/panel.rs b/src/ui/scm/panel.rs index bf3dc850..abb15b97 100644 --- a/src/ui/scm/panel.rs +++ b/src/ui/scm/panel.rs @@ -2888,7 +2888,7 @@ mod tests { /// `default_global`, which fires the global observers whether or not anything /// changed, and it is called every frame. A watcher that notified on every one /// of those would request a frame from inside a frame and never stop. -#[cfg(all(test, unix))] +#[cfg(test)] mod render_idle_gpui_tests { use super::*; use crate::daemon::protocol::DaemonMsg; @@ -2927,7 +2927,7 @@ mod render_idle_gpui_tests { ) -> ( Entity, VisualTestContext, - std::os::unix::net::UnixStream, + crate::daemon::transport::Stream, ) { let (app, mut vcx, mut pane) = test_window::harness_with_pane(cx); DaemonMsg::Cwd(root.to_path_buf()) @@ -2974,6 +2974,12 @@ mod render_idle_gpui_tests { render_probe::draws() } + /// The only test in this module that waits on `repo.root`, and so the + /// only one Windows cannot run: git spells that root `C:/Users/—` and + /// the pane's own cwd is spelled `C:\Users\`, so the equality below + /// never holds there. Its sibling keys off the pane's cwd instead, and + /// runs everywhere. + #[cfg(unix)] #[gpui::test] fn a_settled_source_control_panel_reaches_render_idle(cx: &mut TestAppContext) { let _serial = serial(); diff --git a/src/ui/switcher.rs b/src/ui/switcher.rs index 2468a40c..b69a2349 100644 --- a/src/ui/switcher.rs +++ b/src/ui/switcher.rs @@ -3687,7 +3687,7 @@ mod tests { } } -#[cfg(all(test, unix))] +#[cfg(test)] mod gpui_tests { use gpui::{Modifiers, TestAppContext}; diff --git a/src/ui/tree_sync.rs b/src/ui/tree_sync.rs index 5ea14edc..ba193b4e 100644 --- a/src/ui/tree_sync.rs +++ b/src/ui/tree_sync.rs @@ -3686,7 +3686,6 @@ mod tests { /// the window shows one, and the one it could not put up must not come out /// of a `Full` diff as `TabClose` — that op deleted from the machine exactly /// the tabs a restart had failed to bring back, panes and all (#672). - #[cfg(unix)] #[gpui::test] fn the_next_sync_leaves_a_tab_the_rebuild_could_not_put_up_on_the_machine( cx: &mut gpui::TestAppContext, @@ -3775,7 +3774,6 @@ mod tests { /// straight after clears the queue, so by the time a test can look the /// ops are gone either way — while `informed` outliving the arrival is /// both durable and the thing that made them possible. - #[cfg(unix)] #[gpui::test] fn arriving_at_a_workspace_does_not_prune_what_is_already_in_it(cx: &mut gpui::TestAppContext) { let (app, mut vcx, _pane_stream) = crate::ui::app::test_window::harness_with_pane(cx);