1use crate::tests::TestServer;
2use call::ActiveCall;
3use fs::{FakeFs, Fs as _};
4use gpui::{Context as _, TestAppContext};
5use language::language_settings::all_language_settings;
6use project::ProjectPath;
7use remote::SshRemoteClient;
8use remote_server::HeadlessProject;
9use serde_json::json;
10use std::{path::Path, sync::Arc};
11
12#[gpui::test(iterations = 10)]
13async fn test_sharing_an_ssh_remote_project(
14 cx_a: &mut TestAppContext,
15 cx_b: &mut TestAppContext,
16 server_cx: &mut TestAppContext,
17) {
18 let executor = cx_a.executor();
19 let mut server = TestServer::start(executor.clone()).await;
20 let client_a = server.create_client(cx_a, "user_a").await;
21 let client_b = server.create_client(cx_b, "user_b").await;
22 server
23 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
24 .await;
25
26 // Set up project on remote FS
27 let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
28 let remote_fs = FakeFs::new(server_cx.executor());
29 remote_fs
30 .insert_tree(
31 "/code",
32 json!({
33 "project1": {
34 ".zed": {
35 "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
36 },
37 "README.md": "# project 1",
38 "src": {
39 "lib.rs": "fn one() -> usize { 1 }"
40 }
41 },
42 "project2": {
43 "README.md": "# project 2",
44 },
45 }),
46 )
47 .await;
48
49 // User A connects to the remote project via SSH.
50 server_cx.update(HeadlessProject::init);
51 let _headless_project =
52 server_cx.new_model(|cx| HeadlessProject::new(server_ssh, remote_fs.clone(), cx));
53
54 let (project_a, worktree_id) = client_a
55 .build_ssh_project("/code/project1", client_ssh, cx_a)
56 .await;
57
58 // While the SSH worktree is being scanned, user A shares the remote project.
59 let active_call_a = cx_a.read(ActiveCall::global);
60 let project_id = active_call_a
61 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
62 .await
63 .unwrap();
64
65 // User B joins the project.
66 let project_b = client_b.join_remote_project(project_id, cx_b).await;
67 let worktree_b = project_b
68 .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
69 .unwrap();
70
71 let worktree_a = project_a
72 .update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
73 .unwrap();
74
75 executor.run_until_parked();
76
77 worktree_a.update(cx_a, |worktree, _cx| {
78 assert_eq!(
79 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
80 vec![
81 Path::new(".zed"),
82 Path::new(".zed/settings.json"),
83 Path::new("README.md"),
84 Path::new("src"),
85 Path::new("src/lib.rs"),
86 ]
87 );
88 });
89
90 worktree_b.update(cx_b, |worktree, _cx| {
91 assert_eq!(
92 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
93 vec![
94 Path::new(".zed"),
95 Path::new(".zed/settings.json"),
96 Path::new("README.md"),
97 Path::new("src"),
98 Path::new("src/lib.rs"),
99 ]
100 );
101 });
102
103 // User B can open buffers in the remote project.
104 let buffer_b = project_b
105 .update(cx_b, |project, cx| {
106 project.open_buffer((worktree_id, "src/lib.rs"), cx)
107 })
108 .await
109 .unwrap();
110 buffer_b.update(cx_b, |buffer, cx| {
111 assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
112 let ix = buffer.text().find('1').unwrap();
113 buffer.edit([(ix..ix + 1, "100")], None, cx);
114 });
115
116 executor.run_until_parked();
117
118 cx_b.read(|cx| {
119 let file = buffer_b.read(cx).file();
120 assert_eq!(
121 all_language_settings(file, cx)
122 .language(Some(&("Rust".into())))
123 .language_servers,
124 ["override-rust-analyzer".to_string()]
125 )
126 });
127
128 project_b
129 .update(cx_b, |project, cx| {
130 project.save_buffer_as(
131 buffer_b.clone(),
132 ProjectPath {
133 worktree_id: worktree_id.to_owned(),
134 path: Arc::from(Path::new("src/renamed.rs")),
135 },
136 cx,
137 )
138 })
139 .await
140 .unwrap();
141 assert_eq!(
142 remote_fs
143 .load("/code/project1/src/renamed.rs".as_ref())
144 .await
145 .unwrap(),
146 "fn one() -> usize { 100 }"
147 );
148 cx_b.run_until_parked();
149 cx_b.update(|cx| {
150 assert_eq!(
151 buffer_b
152 .read(cx)
153 .file()
154 .unwrap()
155 .path()
156 .to_string_lossy()
157 .to_string(),
158 "src/renamed.rs".to_string()
159 );
160 });
161}