1use crate::{
2 db::{self, NewUserParams, TestDb, UserId},
3 executor::Executor,
4 rpc::{Server, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
5 AppState,
6};
7use anyhow::anyhow;
8use call::{room, ActiveCall, ParticipantLocation, Room};
9use client::{
10 self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials,
11 EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT,
12};
13use collections::{BTreeMap, HashMap, HashSet};
14use editor::{
15 self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, ExcerptRange, MultiBuffer,
16 Redo, Rename, ToOffset, ToggleCodeActions, Undo,
17};
18use fs::{FakeFs, Fs as _, HomeDir, LineEnding, RemoveOptions};
19use futures::{channel::oneshot, StreamExt as _};
20use gpui::{
21 executor::Deterministic, geometry::vector::vec2f, test::EmptyView, ModelHandle, Task,
22 TestAppContext, ViewHandle,
23};
24use language::{
25 range_to_lsp, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
26 LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, PointUtf16, Rope,
27};
28use live_kit_client::MacOSDisplay;
29use lsp::{self, FakeLanguageServer};
30use parking_lot::Mutex;
31use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath, WorktreeId};
32use rand::prelude::*;
33use serde_json::json;
34use settings::{Formatter, Settings};
35use std::{
36 cell::{Cell, RefCell},
37 env, future, mem,
38 ops::Deref,
39 path::{Path, PathBuf},
40 rc::Rc,
41 sync::{
42 atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
43 Arc,
44 },
45};
46use theme::ThemeRegistry;
47use unindent::Unindent as _;
48use util::post_inc;
49use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace};
50
51#[ctor::ctor]
52fn init_logger() {
53 if std::env::var("RUST_LOG").is_ok() {
54 env_logger::init();
55 }
56}
57
58#[gpui::test(iterations = 10)]
59async fn test_basic_calls(
60 deterministic: Arc<Deterministic>,
61 cx_a: &mut TestAppContext,
62 cx_b: &mut TestAppContext,
63 cx_b2: &mut TestAppContext,
64 cx_c: &mut TestAppContext,
65) {
66 deterministic.forbid_parking();
67 let mut server = TestServer::start(&deterministic).await;
68
69 let client_a = server.create_client(cx_a, "user_a").await;
70 let client_b = server.create_client(cx_b, "user_b").await;
71 let client_c = server.create_client(cx_c, "user_c").await;
72 server
73 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
74 .await;
75
76 let active_call_a = cx_a.read(ActiveCall::global);
77 let active_call_b = cx_b.read(ActiveCall::global);
78 let active_call_c = cx_c.read(ActiveCall::global);
79
80 // Call user B from client A.
81 active_call_a
82 .update(cx_a, |call, cx| {
83 call.invite(client_b.user_id().unwrap(), None, cx)
84 })
85 .await
86 .unwrap();
87 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
88 deterministic.run_until_parked();
89 assert_eq!(
90 room_participants(&room_a, cx_a),
91 RoomParticipants {
92 remote: Default::default(),
93 pending: vec!["user_b".to_string()]
94 }
95 );
96
97 // User B receives the call.
98 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
99 let call_b = incoming_call_b.next().await.unwrap().unwrap();
100 assert_eq!(call_b.calling_user.github_login, "user_a");
101
102 // User B connects via another client and also receives a ring on the newly-connected client.
103 let _client_b2 = server.create_client(cx_b2, "user_b").await;
104 let active_call_b2 = cx_b2.read(ActiveCall::global);
105 let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
106 deterministic.run_until_parked();
107 let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
108 assert_eq!(call_b2.calling_user.github_login, "user_a");
109
110 // User B joins the room using the first client.
111 active_call_b
112 .update(cx_b, |call, cx| call.accept_incoming(cx))
113 .await
114 .unwrap();
115 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
116 assert!(incoming_call_b.next().await.unwrap().is_none());
117
118 deterministic.run_until_parked();
119 assert_eq!(
120 room_participants(&room_a, cx_a),
121 RoomParticipants {
122 remote: vec!["user_b".to_string()],
123 pending: Default::default()
124 }
125 );
126 assert_eq!(
127 room_participants(&room_b, cx_b),
128 RoomParticipants {
129 remote: vec!["user_a".to_string()],
130 pending: Default::default()
131 }
132 );
133
134 // Call user C from client B.
135 let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
136 active_call_b
137 .update(cx_b, |call, cx| {
138 call.invite(client_c.user_id().unwrap(), None, cx)
139 })
140 .await
141 .unwrap();
142
143 deterministic.run_until_parked();
144 assert_eq!(
145 room_participants(&room_a, cx_a),
146 RoomParticipants {
147 remote: vec!["user_b".to_string()],
148 pending: vec!["user_c".to_string()]
149 }
150 );
151 assert_eq!(
152 room_participants(&room_b, cx_b),
153 RoomParticipants {
154 remote: vec!["user_a".to_string()],
155 pending: vec!["user_c".to_string()]
156 }
157 );
158
159 // User C receives the call, but declines it.
160 let call_c = incoming_call_c.next().await.unwrap().unwrap();
161 assert_eq!(call_c.calling_user.github_login, "user_b");
162 active_call_c.update(cx_c, |call, _| call.decline_incoming().unwrap());
163 assert!(incoming_call_c.next().await.unwrap().is_none());
164
165 deterministic.run_until_parked();
166 assert_eq!(
167 room_participants(&room_a, cx_a),
168 RoomParticipants {
169 remote: vec!["user_b".to_string()],
170 pending: Default::default()
171 }
172 );
173 assert_eq!(
174 room_participants(&room_b, cx_b),
175 RoomParticipants {
176 remote: vec!["user_a".to_string()],
177 pending: Default::default()
178 }
179 );
180
181 // User A shares their screen
182 let display = MacOSDisplay::new();
183 let events_b = active_call_events(cx_b);
184 active_call_a
185 .update(cx_a, |call, cx| {
186 call.room().unwrap().update(cx, |room, cx| {
187 room.set_display_sources(vec![display.clone()]);
188 room.share_screen(cx)
189 })
190 })
191 .await
192 .unwrap();
193
194 deterministic.run_until_parked();
195
196 assert_eq!(events_b.borrow().len(), 1);
197 let event = events_b.borrow().first().unwrap().clone();
198 if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event {
199 assert_eq!(participant_id, client_a.peer_id().unwrap());
200 room_b.read_with(cx_b, |room, _| {
201 assert_eq!(
202 room.remote_participants()[&client_a.user_id().unwrap()]
203 .tracks
204 .len(),
205 1
206 );
207 });
208 } else {
209 panic!("unexpected event")
210 }
211
212 // User A leaves the room.
213 active_call_a.update(cx_a, |call, cx| {
214 call.hang_up(cx).unwrap();
215 assert!(call.room().is_none());
216 });
217 deterministic.run_until_parked();
218 assert_eq!(
219 room_participants(&room_a, cx_a),
220 RoomParticipants {
221 remote: Default::default(),
222 pending: Default::default()
223 }
224 );
225 assert_eq!(
226 room_participants(&room_b, cx_b),
227 RoomParticipants {
228 remote: Default::default(),
229 pending: Default::default()
230 }
231 );
232
233 // User B gets disconnected from the LiveKit server, which causes them
234 // to automatically leave the room.
235 server
236 .test_live_kit_server
237 .disconnect_client(client_b.peer_id().unwrap().to_string())
238 .await;
239 active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
240 assert_eq!(
241 room_participants(&room_a, cx_a),
242 RoomParticipants {
243 remote: Default::default(),
244 pending: Default::default()
245 }
246 );
247 assert_eq!(
248 room_participants(&room_b, cx_b),
249 RoomParticipants {
250 remote: Default::default(),
251 pending: Default::default()
252 }
253 );
254}
255
256#[gpui::test(iterations = 10)]
257async fn test_room_uniqueness(
258 deterministic: Arc<Deterministic>,
259 cx_a: &mut TestAppContext,
260 cx_a2: &mut TestAppContext,
261 cx_b: &mut TestAppContext,
262 cx_b2: &mut TestAppContext,
263 cx_c: &mut TestAppContext,
264) {
265 deterministic.forbid_parking();
266 let mut server = TestServer::start(&deterministic).await;
267 let client_a = server.create_client(cx_a, "user_a").await;
268 let _client_a2 = server.create_client(cx_a2, "user_a").await;
269 let client_b = server.create_client(cx_b, "user_b").await;
270 let _client_b2 = server.create_client(cx_b2, "user_b").await;
271 let client_c = server.create_client(cx_c, "user_c").await;
272 server
273 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
274 .await;
275
276 let active_call_a = cx_a.read(ActiveCall::global);
277 let active_call_a2 = cx_a2.read(ActiveCall::global);
278 let active_call_b = cx_b.read(ActiveCall::global);
279 let active_call_b2 = cx_b2.read(ActiveCall::global);
280 let active_call_c = cx_c.read(ActiveCall::global);
281
282 // Call user B from client A.
283 active_call_a
284 .update(cx_a, |call, cx| {
285 call.invite(client_b.user_id().unwrap(), None, cx)
286 })
287 .await
288 .unwrap();
289
290 // Ensure a new room can't be created given user A just created one.
291 active_call_a2
292 .update(cx_a2, |call, cx| {
293 call.invite(client_c.user_id().unwrap(), None, cx)
294 })
295 .await
296 .unwrap_err();
297 active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
298
299 // User B receives the call from user A.
300 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
301 let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
302 assert_eq!(call_b1.calling_user.github_login, "user_a");
303
304 // Ensure calling users A and B from client C fails.
305 active_call_c
306 .update(cx_c, |call, cx| {
307 call.invite(client_a.user_id().unwrap(), None, cx)
308 })
309 .await
310 .unwrap_err();
311 active_call_c
312 .update(cx_c, |call, cx| {
313 call.invite(client_b.user_id().unwrap(), None, cx)
314 })
315 .await
316 .unwrap_err();
317
318 // Ensure User B can't create a room while they still have an incoming call.
319 active_call_b2
320 .update(cx_b2, |call, cx| {
321 call.invite(client_c.user_id().unwrap(), None, cx)
322 })
323 .await
324 .unwrap_err();
325 active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
326
327 // User B joins the room and calling them after they've joined still fails.
328 active_call_b
329 .update(cx_b, |call, cx| call.accept_incoming(cx))
330 .await
331 .unwrap();
332 active_call_c
333 .update(cx_c, |call, cx| {
334 call.invite(client_b.user_id().unwrap(), None, cx)
335 })
336 .await
337 .unwrap_err();
338
339 // Ensure User B can't create a room while they belong to another room.
340 active_call_b2
341 .update(cx_b2, |call, cx| {
342 call.invite(client_c.user_id().unwrap(), None, cx)
343 })
344 .await
345 .unwrap_err();
346 active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
347
348 // Client C can successfully call client B after client B leaves the room.
349 active_call_b
350 .update(cx_b, |call, cx| call.hang_up(cx))
351 .unwrap();
352 deterministic.run_until_parked();
353 active_call_c
354 .update(cx_c, |call, cx| {
355 call.invite(client_b.user_id().unwrap(), None, cx)
356 })
357 .await
358 .unwrap();
359 deterministic.run_until_parked();
360 let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
361 assert_eq!(call_b2.calling_user.github_login, "user_c");
362}
363
364#[gpui::test(iterations = 10)]
365async fn test_client_disconnecting_from_room(
366 deterministic: Arc<Deterministic>,
367 cx_a: &mut TestAppContext,
368 cx_b: &mut TestAppContext,
369) {
370 deterministic.forbid_parking();
371 let mut server = TestServer::start(&deterministic).await;
372 let client_a = server.create_client(cx_a, "user_a").await;
373 let client_b = server.create_client(cx_b, "user_b").await;
374 server
375 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
376 .await;
377
378 let active_call_a = cx_a.read(ActiveCall::global);
379 let active_call_b = cx_b.read(ActiveCall::global);
380
381 // Call user B from client A.
382 active_call_a
383 .update(cx_a, |call, cx| {
384 call.invite(client_b.user_id().unwrap(), None, cx)
385 })
386 .await
387 .unwrap();
388 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
389
390 // User B receives the call and joins the room.
391 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
392 incoming_call_b.next().await.unwrap().unwrap();
393 active_call_b
394 .update(cx_b, |call, cx| call.accept_incoming(cx))
395 .await
396 .unwrap();
397 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
398 deterministic.run_until_parked();
399 assert_eq!(
400 room_participants(&room_a, cx_a),
401 RoomParticipants {
402 remote: vec!["user_b".to_string()],
403 pending: Default::default()
404 }
405 );
406 assert_eq!(
407 room_participants(&room_b, cx_b),
408 RoomParticipants {
409 remote: vec!["user_a".to_string()],
410 pending: Default::default()
411 }
412 );
413
414 // User A automatically reconnects to the room upon disconnection.
415 server.disconnect_client(client_a.peer_id().unwrap());
416 deterministic.advance_clock(RECEIVE_TIMEOUT);
417 deterministic.run_until_parked();
418 assert_eq!(
419 room_participants(&room_a, cx_a),
420 RoomParticipants {
421 remote: vec!["user_b".to_string()],
422 pending: Default::default()
423 }
424 );
425 assert_eq!(
426 room_participants(&room_b, cx_b),
427 RoomParticipants {
428 remote: vec!["user_a".to_string()],
429 pending: Default::default()
430 }
431 );
432
433 // When user A disconnects, both client A and B clear their room on the active call.
434 server.forbid_connections();
435 server.disconnect_client(client_a.peer_id().unwrap());
436 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
437 active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
438 active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
439 assert_eq!(
440 room_participants(&room_a, cx_a),
441 RoomParticipants {
442 remote: Default::default(),
443 pending: Default::default()
444 }
445 );
446 assert_eq!(
447 room_participants(&room_b, cx_b),
448 RoomParticipants {
449 remote: Default::default(),
450 pending: Default::default()
451 }
452 );
453
454 // Allow user A to reconnect to the server.
455 server.allow_connections();
456 deterministic.advance_clock(RECEIVE_TIMEOUT);
457
458 // Call user B again from client A.
459 active_call_a
460 .update(cx_a, |call, cx| {
461 call.invite(client_b.user_id().unwrap(), None, cx)
462 })
463 .await
464 .unwrap();
465 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
466
467 // User B receives the call and joins the room.
468 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
469 incoming_call_b.next().await.unwrap().unwrap();
470 active_call_b
471 .update(cx_b, |call, cx| call.accept_incoming(cx))
472 .await
473 .unwrap();
474 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
475 deterministic.run_until_parked();
476 assert_eq!(
477 room_participants(&room_a, cx_a),
478 RoomParticipants {
479 remote: vec!["user_b".to_string()],
480 pending: Default::default()
481 }
482 );
483 assert_eq!(
484 room_participants(&room_b, cx_b),
485 RoomParticipants {
486 remote: vec!["user_a".to_string()],
487 pending: Default::default()
488 }
489 );
490
491 // User B gets disconnected from the LiveKit server, which causes it
492 // to automatically leave the room.
493 server
494 .test_live_kit_server
495 .disconnect_client(client_b.user_id().unwrap().to_string())
496 .await;
497 deterministic.run_until_parked();
498 active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
499 active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
500 assert_eq!(
501 room_participants(&room_a, cx_a),
502 RoomParticipants {
503 remote: Default::default(),
504 pending: Default::default()
505 }
506 );
507 assert_eq!(
508 room_participants(&room_b, cx_b),
509 RoomParticipants {
510 remote: Default::default(),
511 pending: Default::default()
512 }
513 );
514}
515
516#[gpui::test(iterations = 10)]
517async fn test_server_restarts(
518 deterministic: Arc<Deterministic>,
519 cx_a: &mut TestAppContext,
520 cx_b: &mut TestAppContext,
521 cx_c: &mut TestAppContext,
522 cx_d: &mut TestAppContext,
523) {
524 deterministic.forbid_parking();
525 let mut server = TestServer::start(&deterministic).await;
526 let client_a = server.create_client(cx_a, "user_a").await;
527 let client_b = server.create_client(cx_b, "user_b").await;
528 let client_c = server.create_client(cx_c, "user_c").await;
529 let client_d = server.create_client(cx_d, "user_d").await;
530 server
531 .make_contacts(&mut [
532 (&client_a, cx_a),
533 (&client_b, cx_b),
534 (&client_c, cx_c),
535 (&client_d, cx_d),
536 ])
537 .await;
538
539 let active_call_a = cx_a.read(ActiveCall::global);
540 let active_call_b = cx_b.read(ActiveCall::global);
541 let active_call_c = cx_c.read(ActiveCall::global);
542 let active_call_d = cx_d.read(ActiveCall::global);
543
544 // User A calls users B, C, and D.
545 active_call_a
546 .update(cx_a, |call, cx| {
547 call.invite(client_b.user_id().unwrap(), None, cx)
548 })
549 .await
550 .unwrap();
551 active_call_a
552 .update(cx_a, |call, cx| {
553 call.invite(client_c.user_id().unwrap(), None, cx)
554 })
555 .await
556 .unwrap();
557 active_call_a
558 .update(cx_a, |call, cx| {
559 call.invite(client_d.user_id().unwrap(), None, cx)
560 })
561 .await
562 .unwrap();
563 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
564
565 // User B receives the call and joins the room.
566 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
567 assert!(incoming_call_b.next().await.unwrap().is_some());
568 active_call_b
569 .update(cx_b, |call, cx| call.accept_incoming(cx))
570 .await
571 .unwrap();
572 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
573
574 // User C receives the call and joins the room.
575 let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
576 assert!(incoming_call_c.next().await.unwrap().is_some());
577 active_call_c
578 .update(cx_c, |call, cx| call.accept_incoming(cx))
579 .await
580 .unwrap();
581 let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
582
583 // User D receives the call but doesn't join the room yet.
584 let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
585 assert!(incoming_call_d.next().await.unwrap().is_some());
586
587 deterministic.run_until_parked();
588 assert_eq!(
589 room_participants(&room_a, cx_a),
590 RoomParticipants {
591 remote: vec!["user_b".to_string(), "user_c".to_string()],
592 pending: vec!["user_d".to_string()]
593 }
594 );
595 assert_eq!(
596 room_participants(&room_b, cx_b),
597 RoomParticipants {
598 remote: vec!["user_a".to_string(), "user_c".to_string()],
599 pending: vec!["user_d".to_string()]
600 }
601 );
602 assert_eq!(
603 room_participants(&room_c, cx_c),
604 RoomParticipants {
605 remote: vec!["user_a".to_string(), "user_b".to_string()],
606 pending: vec!["user_d".to_string()]
607 }
608 );
609
610 // The server is torn down.
611 server.reset().await;
612
613 // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
614 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
615 deterministic.advance_clock(RECEIVE_TIMEOUT);
616 assert_eq!(
617 room_participants(&room_a, cx_a),
618 RoomParticipants {
619 remote: vec!["user_b".to_string(), "user_c".to_string()],
620 pending: vec!["user_d".to_string()]
621 }
622 );
623 assert_eq!(
624 room_participants(&room_b, cx_b),
625 RoomParticipants {
626 remote: vec!["user_a".to_string(), "user_c".to_string()],
627 pending: vec!["user_d".to_string()]
628 }
629 );
630 assert_eq!(
631 room_participants(&room_c, cx_c),
632 RoomParticipants {
633 remote: vec![],
634 pending: vec![]
635 }
636 );
637
638 // User D is notified again of the incoming call and accepts it.
639 assert!(incoming_call_d.next().await.unwrap().is_some());
640 active_call_d
641 .update(cx_d, |call, cx| call.accept_incoming(cx))
642 .await
643 .unwrap();
644 deterministic.run_until_parked();
645 let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
646 assert_eq!(
647 room_participants(&room_a, cx_a),
648 RoomParticipants {
649 remote: vec![
650 "user_b".to_string(),
651 "user_c".to_string(),
652 "user_d".to_string(),
653 ],
654 pending: vec![]
655 }
656 );
657 assert_eq!(
658 room_participants(&room_b, cx_b),
659 RoomParticipants {
660 remote: vec![
661 "user_a".to_string(),
662 "user_c".to_string(),
663 "user_d".to_string(),
664 ],
665 pending: vec![]
666 }
667 );
668 assert_eq!(
669 room_participants(&room_c, cx_c),
670 RoomParticipants {
671 remote: vec![],
672 pending: vec![]
673 }
674 );
675 assert_eq!(
676 room_participants(&room_d, cx_d),
677 RoomParticipants {
678 remote: vec![
679 "user_a".to_string(),
680 "user_b".to_string(),
681 "user_c".to_string(),
682 ],
683 pending: vec![]
684 }
685 );
686
687 // The server finishes restarting, cleaning up stale connections.
688 server.start().await.unwrap();
689 deterministic.advance_clock(CLEANUP_TIMEOUT);
690 assert_eq!(
691 room_participants(&room_a, cx_a),
692 RoomParticipants {
693 remote: vec!["user_b".to_string(), "user_d".to_string()],
694 pending: vec![]
695 }
696 );
697 assert_eq!(
698 room_participants(&room_b, cx_b),
699 RoomParticipants {
700 remote: vec!["user_a".to_string(), "user_d".to_string()],
701 pending: vec![]
702 }
703 );
704 assert_eq!(
705 room_participants(&room_c, cx_c),
706 RoomParticipants {
707 remote: vec![],
708 pending: vec![]
709 }
710 );
711 assert_eq!(
712 room_participants(&room_d, cx_d),
713 RoomParticipants {
714 remote: vec!["user_a".to_string(), "user_b".to_string()],
715 pending: vec![]
716 }
717 );
718
719 // User D hangs up.
720 active_call_d
721 .update(cx_d, |call, cx| call.hang_up(cx))
722 .unwrap();
723 deterministic.run_until_parked();
724 assert_eq!(
725 room_participants(&room_a, cx_a),
726 RoomParticipants {
727 remote: vec!["user_b".to_string()],
728 pending: vec![]
729 }
730 );
731 assert_eq!(
732 room_participants(&room_b, cx_b),
733 RoomParticipants {
734 remote: vec!["user_a".to_string()],
735 pending: vec![]
736 }
737 );
738 assert_eq!(
739 room_participants(&room_c, cx_c),
740 RoomParticipants {
741 remote: vec![],
742 pending: vec![]
743 }
744 );
745 assert_eq!(
746 room_participants(&room_d, cx_d),
747 RoomParticipants {
748 remote: vec![],
749 pending: vec![]
750 }
751 );
752
753 // User B calls user D again.
754 active_call_b
755 .update(cx_b, |call, cx| {
756 call.invite(client_d.user_id().unwrap(), None, cx)
757 })
758 .await
759 .unwrap();
760
761 // User D receives the call but doesn't join the room yet.
762 let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
763 assert!(incoming_call_d.next().await.unwrap().is_some());
764 deterministic.run_until_parked();
765 assert_eq!(
766 room_participants(&room_a, cx_a),
767 RoomParticipants {
768 remote: vec!["user_b".to_string()],
769 pending: vec!["user_d".to_string()]
770 }
771 );
772 assert_eq!(
773 room_participants(&room_b, cx_b),
774 RoomParticipants {
775 remote: vec!["user_a".to_string()],
776 pending: vec!["user_d".to_string()]
777 }
778 );
779
780 // The server is torn down.
781 server.reset().await;
782
783 // Users A and B have troubles reconnecting, so they leave the room.
784 client_a.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
785 client_b.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
786 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
787 deterministic.advance_clock(RECEIVE_TIMEOUT);
788 assert_eq!(
789 room_participants(&room_a, cx_a),
790 RoomParticipants {
791 remote: vec![],
792 pending: vec![]
793 }
794 );
795 assert_eq!(
796 room_participants(&room_b, cx_b),
797 RoomParticipants {
798 remote: vec![],
799 pending: vec![]
800 }
801 );
802
803 // User D is notified again of the incoming call but doesn't accept it.
804 assert!(incoming_call_d.next().await.unwrap().is_some());
805
806 // The server finishes restarting, cleaning up stale connections and canceling the
807 // call to user D because the room has become empty.
808 server.start().await.unwrap();
809 deterministic.advance_clock(CLEANUP_TIMEOUT);
810 assert!(incoming_call_d.next().await.unwrap().is_none());
811}
812
813#[gpui::test(iterations = 10)]
814async fn test_calls_on_multiple_connections(
815 deterministic: Arc<Deterministic>,
816 cx_a: &mut TestAppContext,
817 cx_b1: &mut TestAppContext,
818 cx_b2: &mut TestAppContext,
819) {
820 deterministic.forbid_parking();
821 let mut server = TestServer::start(&deterministic).await;
822 let client_a = server.create_client(cx_a, "user_a").await;
823 let client_b1 = server.create_client(cx_b1, "user_b").await;
824 let client_b2 = server.create_client(cx_b2, "user_b").await;
825 server
826 .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
827 .await;
828
829 let active_call_a = cx_a.read(ActiveCall::global);
830 let active_call_b1 = cx_b1.read(ActiveCall::global);
831 let active_call_b2 = cx_b2.read(ActiveCall::global);
832 let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
833 let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
834 assert!(incoming_call_b1.next().await.unwrap().is_none());
835 assert!(incoming_call_b2.next().await.unwrap().is_none());
836
837 // Call user B from client A, ensuring both clients for user B ring.
838 active_call_a
839 .update(cx_a, |call, cx| {
840 call.invite(client_b1.user_id().unwrap(), None, cx)
841 })
842 .await
843 .unwrap();
844 deterministic.run_until_parked();
845 assert!(incoming_call_b1.next().await.unwrap().is_some());
846 assert!(incoming_call_b2.next().await.unwrap().is_some());
847
848 // User B declines the call on one of the two connections, causing both connections
849 // to stop ringing.
850 active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap());
851 deterministic.run_until_parked();
852 assert!(incoming_call_b1.next().await.unwrap().is_none());
853 assert!(incoming_call_b2.next().await.unwrap().is_none());
854
855 // Call user B again from client A.
856 active_call_a
857 .update(cx_a, |call, cx| {
858 call.invite(client_b1.user_id().unwrap(), None, cx)
859 })
860 .await
861 .unwrap();
862 deterministic.run_until_parked();
863 assert!(incoming_call_b1.next().await.unwrap().is_some());
864 assert!(incoming_call_b2.next().await.unwrap().is_some());
865
866 // User B accepts the call on one of the two connections, causing both connections
867 // to stop ringing.
868 active_call_b2
869 .update(cx_b2, |call, cx| call.accept_incoming(cx))
870 .await
871 .unwrap();
872 deterministic.run_until_parked();
873 assert!(incoming_call_b1.next().await.unwrap().is_none());
874 assert!(incoming_call_b2.next().await.unwrap().is_none());
875
876 // User B disconnects the client that is not on the call. Everything should be fine.
877 client_b1.disconnect(&cx_b1.to_async()).unwrap();
878 deterministic.advance_clock(RECEIVE_TIMEOUT);
879 client_b1
880 .authenticate_and_connect(false, &cx_b1.to_async())
881 .await
882 .unwrap();
883
884 // User B hangs up, and user A calls them again.
885 active_call_b2.update(cx_b2, |call, cx| call.hang_up(cx).unwrap());
886 deterministic.run_until_parked();
887 active_call_a
888 .update(cx_a, |call, cx| {
889 call.invite(client_b1.user_id().unwrap(), None, cx)
890 })
891 .await
892 .unwrap();
893 deterministic.run_until_parked();
894 assert!(incoming_call_b1.next().await.unwrap().is_some());
895 assert!(incoming_call_b2.next().await.unwrap().is_some());
896
897 // User A cancels the call, causing both connections to stop ringing.
898 active_call_a
899 .update(cx_a, |call, cx| {
900 call.cancel_invite(client_b1.user_id().unwrap(), cx)
901 })
902 .await
903 .unwrap();
904 deterministic.run_until_parked();
905 assert!(incoming_call_b1.next().await.unwrap().is_none());
906 assert!(incoming_call_b2.next().await.unwrap().is_none());
907
908 // User A calls user B again.
909 active_call_a
910 .update(cx_a, |call, cx| {
911 call.invite(client_b1.user_id().unwrap(), None, cx)
912 })
913 .await
914 .unwrap();
915 deterministic.run_until_parked();
916 assert!(incoming_call_b1.next().await.unwrap().is_some());
917 assert!(incoming_call_b2.next().await.unwrap().is_some());
918
919 // User A hangs up, causing both connections to stop ringing.
920 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
921 deterministic.run_until_parked();
922 assert!(incoming_call_b1.next().await.unwrap().is_none());
923 assert!(incoming_call_b2.next().await.unwrap().is_none());
924
925 // User A calls user B again.
926 active_call_a
927 .update(cx_a, |call, cx| {
928 call.invite(client_b1.user_id().unwrap(), None, cx)
929 })
930 .await
931 .unwrap();
932 deterministic.run_until_parked();
933 assert!(incoming_call_b1.next().await.unwrap().is_some());
934 assert!(incoming_call_b2.next().await.unwrap().is_some());
935
936 // User A disconnects, causing both connections to stop ringing.
937 server.forbid_connections();
938 server.disconnect_client(client_a.peer_id().unwrap());
939 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
940 assert!(incoming_call_b1.next().await.unwrap().is_none());
941 assert!(incoming_call_b2.next().await.unwrap().is_none());
942
943 // User A reconnects automatically, then calls user B again.
944 server.allow_connections();
945 deterministic.advance_clock(RECEIVE_TIMEOUT);
946 active_call_a
947 .update(cx_a, |call, cx| {
948 call.invite(client_b1.user_id().unwrap(), None, cx)
949 })
950 .await
951 .unwrap();
952 deterministic.run_until_parked();
953 assert!(incoming_call_b1.next().await.unwrap().is_some());
954 assert!(incoming_call_b2.next().await.unwrap().is_some());
955
956 // User B disconnects all clients, causing user A to no longer see a pending call for them.
957 server.forbid_connections();
958 server.disconnect_client(client_b1.peer_id().unwrap());
959 server.disconnect_client(client_b2.peer_id().unwrap());
960 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
961 active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
962}
963
964#[gpui::test(iterations = 10)]
965async fn test_share_project(
966 deterministic: Arc<Deterministic>,
967 cx_a: &mut TestAppContext,
968 cx_b: &mut TestAppContext,
969 cx_c: &mut TestAppContext,
970) {
971 deterministic.forbid_parking();
972 let (_, window_b) = cx_b.add_window(|_| EmptyView);
973 let mut server = TestServer::start(&deterministic).await;
974 let client_a = server.create_client(cx_a, "user_a").await;
975 let client_b = server.create_client(cx_b, "user_b").await;
976 let client_c = server.create_client(cx_c, "user_c").await;
977 server
978 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
979 .await;
980 let active_call_a = cx_a.read(ActiveCall::global);
981 let active_call_b = cx_b.read(ActiveCall::global);
982 let active_call_c = cx_c.read(ActiveCall::global);
983
984 client_a
985 .fs
986 .insert_tree(
987 "/a",
988 json!({
989 ".gitignore": "ignored-dir",
990 "a.txt": "a-contents",
991 "b.txt": "b-contents",
992 "ignored-dir": {
993 "c.txt": "",
994 "d.txt": "",
995 }
996 }),
997 )
998 .await;
999
1000 // Invite client B to collaborate on a project
1001 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1002 active_call_a
1003 .update(cx_a, |call, cx| {
1004 call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1005 })
1006 .await
1007 .unwrap();
1008
1009 // Join that project as client B
1010 let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1011 deterministic.run_until_parked();
1012 let call = incoming_call_b.borrow().clone().unwrap();
1013 assert_eq!(call.calling_user.github_login, "user_a");
1014 let initial_project = call.initial_project.unwrap();
1015 active_call_b
1016 .update(cx_b, |call, cx| call.accept_incoming(cx))
1017 .await
1018 .unwrap();
1019 let client_b_peer_id = client_b.peer_id().unwrap();
1020 let project_b = client_b
1021 .build_remote_project(initial_project.id, cx_b)
1022 .await;
1023 let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1024
1025 deterministic.run_until_parked();
1026 project_a.read_with(cx_a, |project, _| {
1027 let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1028 assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1029 });
1030 project_b.read_with(cx_b, |project, cx| {
1031 let worktree = project.worktrees(cx).next().unwrap().read(cx);
1032 assert_eq!(
1033 worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1034 [
1035 Path::new(".gitignore"),
1036 Path::new("a.txt"),
1037 Path::new("b.txt"),
1038 Path::new("ignored-dir"),
1039 Path::new("ignored-dir/c.txt"),
1040 Path::new("ignored-dir/d.txt"),
1041 ]
1042 );
1043 });
1044
1045 // Open the same file as client B and client A.
1046 let buffer_b = project_b
1047 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1048 .await
1049 .unwrap();
1050 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1051 project_a.read_with(cx_a, |project, cx| {
1052 assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1053 });
1054 let buffer_a = project_a
1055 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1056 .await
1057 .unwrap();
1058
1059 let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
1060
1061 // Client A sees client B's selection
1062 deterministic.run_until_parked();
1063 buffer_a.read_with(cx_a, |buffer, _| {
1064 buffer
1065 .snapshot()
1066 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1067 .count()
1068 == 1
1069 });
1070
1071 // Edit the buffer as client B and see that edit as client A.
1072 editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
1073 deterministic.run_until_parked();
1074 buffer_a.read_with(cx_a, |buffer, _| {
1075 assert_eq!(buffer.text(), "ok, b-contents")
1076 });
1077
1078 // Client B can invite client C on a project shared by client A.
1079 active_call_b
1080 .update(cx_b, |call, cx| {
1081 call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1082 })
1083 .await
1084 .unwrap();
1085
1086 let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1087 deterministic.run_until_parked();
1088 let call = incoming_call_c.borrow().clone().unwrap();
1089 assert_eq!(call.calling_user.github_login, "user_b");
1090 let initial_project = call.initial_project.unwrap();
1091 active_call_c
1092 .update(cx_c, |call, cx| call.accept_incoming(cx))
1093 .await
1094 .unwrap();
1095 let _project_c = client_c
1096 .build_remote_project(initial_project.id, cx_c)
1097 .await;
1098
1099 // Client B closes the editor, and client A sees client B's selections removed.
1100 cx_b.update(move |_| drop(editor_b));
1101 deterministic.run_until_parked();
1102 buffer_a.read_with(cx_a, |buffer, _| {
1103 buffer
1104 .snapshot()
1105 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1106 .count()
1107 == 0
1108 });
1109}
1110
1111#[gpui::test(iterations = 10)]
1112async fn test_unshare_project(
1113 deterministic: Arc<Deterministic>,
1114 cx_a: &mut TestAppContext,
1115 cx_b: &mut TestAppContext,
1116 cx_c: &mut TestAppContext,
1117) {
1118 deterministic.forbid_parking();
1119 let mut server = TestServer::start(&deterministic).await;
1120 let client_a = server.create_client(cx_a, "user_a").await;
1121 let client_b = server.create_client(cx_b, "user_b").await;
1122 let client_c = server.create_client(cx_c, "user_c").await;
1123 server
1124 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1125 .await;
1126
1127 let active_call_a = cx_a.read(ActiveCall::global);
1128 let active_call_b = cx_b.read(ActiveCall::global);
1129
1130 client_a
1131 .fs
1132 .insert_tree(
1133 "/a",
1134 json!({
1135 "a.txt": "a-contents",
1136 "b.txt": "b-contents",
1137 }),
1138 )
1139 .await;
1140
1141 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1142 let project_id = active_call_a
1143 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1144 .await
1145 .unwrap();
1146 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1147 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1148 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1149
1150 project_b
1151 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1152 .await
1153 .unwrap();
1154
1155 // When client B leaves the room, the project becomes read-only.
1156 active_call_b.update(cx_b, |call, cx| call.hang_up(cx).unwrap());
1157 deterministic.run_until_parked();
1158 assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
1159
1160 // Client C opens the project.
1161 let project_c = client_c.build_remote_project(project_id, cx_c).await;
1162
1163 // When client A unshares the project, client C's project becomes read-only.
1164 project_a
1165 .update(cx_a, |project, cx| project.unshare(cx))
1166 .unwrap();
1167 deterministic.run_until_parked();
1168 assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
1169 assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
1170
1171 // Client C can open the project again after client A re-shares.
1172 let project_id = active_call_a
1173 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1174 .await
1175 .unwrap();
1176 let project_c2 = client_c.build_remote_project(project_id, cx_c).await;
1177 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1178 project_c2
1179 .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1180 .await
1181 .unwrap();
1182
1183 // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1184 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
1185 deterministic.run_until_parked();
1186 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1187 project_c2.read_with(cx_c, |project, _| {
1188 assert!(project.is_read_only());
1189 assert!(project.collaborators().is_empty());
1190 });
1191}
1192
1193#[gpui::test(iterations = 10)]
1194async fn test_host_disconnect(
1195 deterministic: Arc<Deterministic>,
1196 cx_a: &mut TestAppContext,
1197 cx_b: &mut TestAppContext,
1198 cx_c: &mut TestAppContext,
1199) {
1200 cx_b.update(editor::init);
1201 deterministic.forbid_parking();
1202 let mut server = TestServer::start(&deterministic).await;
1203 let client_a = server.create_client(cx_a, "user_a").await;
1204 let client_b = server.create_client(cx_b, "user_b").await;
1205 let client_c = server.create_client(cx_c, "user_c").await;
1206 server
1207 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1208 .await;
1209
1210 client_a
1211 .fs
1212 .insert_tree(
1213 "/a",
1214 json!({
1215 "a.txt": "a-contents",
1216 "b.txt": "b-contents",
1217 }),
1218 )
1219 .await;
1220
1221 let active_call_a = cx_a.read(ActiveCall::global);
1222 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1223 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1224 let project_id = active_call_a
1225 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1226 .await
1227 .unwrap();
1228
1229 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1230 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1231
1232 let (_, workspace_b) = cx_b.add_window(|cx| {
1233 Workspace::new(
1234 Default::default(),
1235 0,
1236 project_b.clone(),
1237 |_, _| unimplemented!(),
1238 cx,
1239 )
1240 });
1241 let editor_b = workspace_b
1242 .update(cx_b, |workspace, cx| {
1243 workspace.open_path((worktree_id, "b.txt"), None, true, cx)
1244 })
1245 .await
1246 .unwrap()
1247 .downcast::<Editor>()
1248 .unwrap();
1249 cx_b.read(|cx| {
1250 assert_eq!(
1251 cx.focused_view_id(workspace_b.window_id()),
1252 Some(editor_b.id())
1253 );
1254 });
1255 editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
1256 assert!(cx_b.is_window_edited(workspace_b.window_id()));
1257
1258 // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
1259 server.forbid_connections();
1260 server.disconnect_client(client_a.peer_id().unwrap());
1261 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1262 project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
1263 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1264 project_b.read_with(cx_b, |project, _| project.is_read_only());
1265 assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
1266
1267 // Ensure client B's edited state is reset and that the whole window is blurred.
1268 cx_b.read(|cx| {
1269 assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
1270 });
1271 assert!(!cx_b.is_window_edited(workspace_b.window_id()));
1272
1273 // Ensure client B is not prompted to save edits when closing window after disconnecting.
1274 let can_close = workspace_b
1275 .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
1276 .await
1277 .unwrap();
1278 assert!(can_close);
1279
1280 // Allow client A to reconnect to the server.
1281 server.allow_connections();
1282 deterministic.advance_clock(RECEIVE_TIMEOUT);
1283
1284 // Client B calls client A again after they reconnected.
1285 let active_call_b = cx_b.read(ActiveCall::global);
1286 active_call_b
1287 .update(cx_b, |call, cx| {
1288 call.invite(client_a.user_id().unwrap(), None, cx)
1289 })
1290 .await
1291 .unwrap();
1292 deterministic.run_until_parked();
1293 active_call_a
1294 .update(cx_a, |call, cx| call.accept_incoming(cx))
1295 .await
1296 .unwrap();
1297
1298 active_call_a
1299 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1300 .await
1301 .unwrap();
1302
1303 // Drop client A's connection again. We should still unshare it successfully.
1304 server.disconnect_client(client_a.peer_id().unwrap());
1305 deterministic.advance_clock(RECEIVE_TIMEOUT);
1306 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1307}
1308
1309#[gpui::test(iterations = 10)]
1310async fn test_host_reconnect(
1311 deterministic: Arc<Deterministic>,
1312 cx_a: &mut TestAppContext,
1313 cx_b: &mut TestAppContext,
1314) {
1315 cx_b.update(editor::init);
1316 deterministic.forbid_parking();
1317 let mut server = TestServer::start(&deterministic).await;
1318 let client_a = server.create_client(cx_a, "user_a").await;
1319 let client_b = server.create_client(cx_b, "user_b").await;
1320 server
1321 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1322 .await;
1323
1324 client_a
1325 .fs
1326 .insert_tree(
1327 "/root",
1328 json!({
1329 "dir1": {
1330 "a.txt": "a-contents",
1331 "b.txt": "b-contents",
1332 "subdir1": {
1333 "c.txt": "c-contents",
1334 "d.txt": "d-contents",
1335 "e.txt": "e-contents",
1336 }
1337 },
1338 "dir2": {
1339 "x": "x-contents",
1340 "y": "y-contents",
1341 "z": "z-contents",
1342 },
1343 }),
1344 )
1345 .await;
1346
1347 let active_call_a = cx_a.read(ActiveCall::global);
1348 let (project_a, _) = client_a.build_local_project("/root/dir1", cx_a).await;
1349 let worktree_a1 =
1350 project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1351 let project_id = active_call_a
1352 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1353 .await
1354 .unwrap();
1355
1356 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1357 deterministic.run_until_parked();
1358
1359 let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
1360 assert!(worktree.as_local().unwrap().is_shared());
1361 worktree.id()
1362 });
1363
1364 // Drop client A's connection.
1365 server.forbid_connections();
1366 server.disconnect_client(client_a.peer_id().unwrap());
1367 deterministic.advance_clock(RECEIVE_TIMEOUT);
1368 project_a.read_with(cx_a, |project, _| {
1369 assert!(project.is_shared());
1370 assert_eq!(project.collaborators().len(), 1);
1371 });
1372 project_b.read_with(cx_b, |project, _| {
1373 assert!(!project.is_read_only());
1374 assert_eq!(project.collaborators().len(), 1);
1375 });
1376 worktree_a1.read_with(cx_a, |tree, _| {
1377 assert!(tree.as_local().unwrap().is_shared())
1378 });
1379
1380 // While disconnected, add/remove files and worktrees from client A's project.
1381 client_a
1382 .fs
1383 .insert_tree(
1384 "/root/dir1/subdir2",
1385 json!({
1386 "f.txt": "f-contents",
1387 "g.txt": "g-contents",
1388 "h.txt": "h-contents",
1389 "i.txt": "i-contents",
1390 }),
1391 )
1392 .await;
1393 client_a
1394 .fs
1395 .remove_dir(
1396 "/root/dir1/subdir1".as_ref(),
1397 RemoveOptions {
1398 recursive: true,
1399 ..Default::default()
1400 },
1401 )
1402 .await
1403 .unwrap();
1404 let (worktree_a2, _) = project_a
1405 .update(cx_a, |p, cx| {
1406 p.find_or_create_local_worktree("/root/dir2", true, cx)
1407 })
1408 .await
1409 .unwrap();
1410 worktree_a2
1411 .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
1412 .await;
1413 let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
1414 assert!(tree.as_local().unwrap().is_shared());
1415 tree.id()
1416 });
1417 deterministic.run_until_parked();
1418
1419 // Client A reconnects. Their project is re-shared, and client B re-joins it.
1420 server.allow_connections();
1421 client_a
1422 .authenticate_and_connect(false, &cx_a.to_async())
1423 .await
1424 .unwrap();
1425 deterministic.run_until_parked();
1426 project_a.read_with(cx_a, |project, cx| {
1427 assert!(project.is_shared());
1428 assert_eq!(
1429 worktree_a1
1430 .read(cx)
1431 .snapshot()
1432 .paths()
1433 .map(|p| p.to_str().unwrap())
1434 .collect::<Vec<_>>(),
1435 vec![
1436 "a.txt",
1437 "b.txt",
1438 "subdir1",
1439 "subdir1/c.txt",
1440 "subdir1/d.txt",
1441 "subdir1/e.txt",
1442 "subdir2",
1443 "subdir2/f.txt",
1444 "subdir2/g.txt",
1445 "subdir2/h.txt",
1446 "subdir2/i.txt"
1447 ]
1448 );
1449 assert_eq!(
1450 worktree_a2
1451 .read(cx)
1452 .snapshot()
1453 .paths()
1454 .map(|p| p.to_str().unwrap())
1455 .collect::<Vec<_>>(),
1456 vec!["x", "y", "z"]
1457 );
1458 });
1459 project_b.read_with(cx_b, |project, cx| {
1460 assert!(!project.is_read_only());
1461 assert_eq!(
1462 project
1463 .worktree_for_id(worktree1_id, cx)
1464 .unwrap()
1465 .read(cx)
1466 .snapshot()
1467 .paths()
1468 .map(|p| p.to_str().unwrap())
1469 .collect::<Vec<_>>(),
1470 vec![
1471 "a.txt",
1472 "b.txt",
1473 "subdir1",
1474 "subdir1/c.txt",
1475 "subdir1/d.txt",
1476 "subdir1/e.txt",
1477 "subdir2",
1478 "subdir2/f.txt",
1479 "subdir2/g.txt",
1480 "subdir2/h.txt",
1481 "subdir2/i.txt"
1482 ]
1483 );
1484 assert_eq!(
1485 project
1486 .worktree_for_id(worktree2_id, cx)
1487 .unwrap()
1488 .read(cx)
1489 .snapshot()
1490 .paths()
1491 .map(|p| p.to_str().unwrap())
1492 .collect::<Vec<_>>(),
1493 vec!["x", "y", "z"]
1494 );
1495 });
1496}
1497
1498#[gpui::test(iterations = 10)]
1499async fn test_active_call_events(
1500 deterministic: Arc<Deterministic>,
1501 cx_a: &mut TestAppContext,
1502 cx_b: &mut TestAppContext,
1503) {
1504 deterministic.forbid_parking();
1505 let mut server = TestServer::start(&deterministic).await;
1506 let client_a = server.create_client(cx_a, "user_a").await;
1507 let client_b = server.create_client(cx_b, "user_b").await;
1508 client_a.fs.insert_tree("/a", json!({})).await;
1509 client_b.fs.insert_tree("/b", json!({})).await;
1510
1511 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1512 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1513
1514 server
1515 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1516 .await;
1517 let active_call_a = cx_a.read(ActiveCall::global);
1518 let active_call_b = cx_b.read(ActiveCall::global);
1519
1520 let events_a = active_call_events(cx_a);
1521 let events_b = active_call_events(cx_b);
1522
1523 let project_a_id = active_call_a
1524 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1525 .await
1526 .unwrap();
1527 deterministic.run_until_parked();
1528 assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1529 assert_eq!(
1530 mem::take(&mut *events_b.borrow_mut()),
1531 vec![room::Event::RemoteProjectShared {
1532 owner: Arc::new(User {
1533 id: client_a.user_id().unwrap(),
1534 github_login: "user_a".to_string(),
1535 avatar: None,
1536 }),
1537 project_id: project_a_id,
1538 worktree_root_names: vec!["a".to_string()],
1539 }]
1540 );
1541
1542 let project_b_id = active_call_b
1543 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1544 .await
1545 .unwrap();
1546 deterministic.run_until_parked();
1547 assert_eq!(
1548 mem::take(&mut *events_a.borrow_mut()),
1549 vec![room::Event::RemoteProjectShared {
1550 owner: Arc::new(User {
1551 id: client_b.user_id().unwrap(),
1552 github_login: "user_b".to_string(),
1553 avatar: None,
1554 }),
1555 project_id: project_b_id,
1556 worktree_root_names: vec!["b".to_string()]
1557 }]
1558 );
1559 assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1560
1561 // Sharing a project twice is idempotent.
1562 let project_b_id_2 = active_call_b
1563 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1564 .await
1565 .unwrap();
1566 assert_eq!(project_b_id_2, project_b_id);
1567 deterministic.run_until_parked();
1568 assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1569 assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1570}
1571
1572fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1573 let events = Rc::new(RefCell::new(Vec::new()));
1574 let active_call = cx.read(ActiveCall::global);
1575 cx.update({
1576 let events = events.clone();
1577 |cx| {
1578 cx.subscribe(&active_call, move |_, event, _| {
1579 events.borrow_mut().push(event.clone())
1580 })
1581 .detach()
1582 }
1583 });
1584 events
1585}
1586
1587#[gpui::test(iterations = 10)]
1588async fn test_room_location(
1589 deterministic: Arc<Deterministic>,
1590 cx_a: &mut TestAppContext,
1591 cx_b: &mut TestAppContext,
1592) {
1593 deterministic.forbid_parking();
1594 let mut server = TestServer::start(&deterministic).await;
1595 let client_a = server.create_client(cx_a, "user_a").await;
1596 let client_b = server.create_client(cx_b, "user_b").await;
1597 client_a.fs.insert_tree("/a", json!({})).await;
1598 client_b.fs.insert_tree("/b", json!({})).await;
1599
1600 let active_call_a = cx_a.read(ActiveCall::global);
1601 let active_call_b = cx_b.read(ActiveCall::global);
1602
1603 let a_notified = Rc::new(Cell::new(false));
1604 cx_a.update({
1605 let notified = a_notified.clone();
1606 |cx| {
1607 cx.observe(&active_call_a, move |_, _| notified.set(true))
1608 .detach()
1609 }
1610 });
1611
1612 let b_notified = Rc::new(Cell::new(false));
1613 cx_b.update({
1614 let b_notified = b_notified.clone();
1615 |cx| {
1616 cx.observe(&active_call_b, move |_, _| b_notified.set(true))
1617 .detach()
1618 }
1619 });
1620
1621 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1622 active_call_a
1623 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1624 .await
1625 .unwrap();
1626 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1627
1628 server
1629 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1630 .await;
1631 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1632 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1633 deterministic.run_until_parked();
1634 assert!(a_notified.take());
1635 assert_eq!(
1636 participant_locations(&room_a, cx_a),
1637 vec![("user_b".to_string(), ParticipantLocation::External)]
1638 );
1639 assert!(b_notified.take());
1640 assert_eq!(
1641 participant_locations(&room_b, cx_b),
1642 vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
1643 );
1644
1645 let project_a_id = active_call_a
1646 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1647 .await
1648 .unwrap();
1649 deterministic.run_until_parked();
1650 assert!(a_notified.take());
1651 assert_eq!(
1652 participant_locations(&room_a, cx_a),
1653 vec![("user_b".to_string(), ParticipantLocation::External)]
1654 );
1655 assert!(b_notified.take());
1656 assert_eq!(
1657 participant_locations(&room_b, cx_b),
1658 vec![(
1659 "user_a".to_string(),
1660 ParticipantLocation::SharedProject {
1661 project_id: project_a_id
1662 }
1663 )]
1664 );
1665
1666 let project_b_id = active_call_b
1667 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1668 .await
1669 .unwrap();
1670 deterministic.run_until_parked();
1671 assert!(a_notified.take());
1672 assert_eq!(
1673 participant_locations(&room_a, cx_a),
1674 vec![("user_b".to_string(), ParticipantLocation::External)]
1675 );
1676 assert!(b_notified.take());
1677 assert_eq!(
1678 participant_locations(&room_b, cx_b),
1679 vec![(
1680 "user_a".to_string(),
1681 ParticipantLocation::SharedProject {
1682 project_id: project_a_id
1683 }
1684 )]
1685 );
1686
1687 active_call_b
1688 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1689 .await
1690 .unwrap();
1691 deterministic.run_until_parked();
1692 assert!(a_notified.take());
1693 assert_eq!(
1694 participant_locations(&room_a, cx_a),
1695 vec![(
1696 "user_b".to_string(),
1697 ParticipantLocation::SharedProject {
1698 project_id: project_b_id
1699 }
1700 )]
1701 );
1702 assert!(b_notified.take());
1703 assert_eq!(
1704 participant_locations(&room_b, cx_b),
1705 vec![(
1706 "user_a".to_string(),
1707 ParticipantLocation::SharedProject {
1708 project_id: project_a_id
1709 }
1710 )]
1711 );
1712
1713 active_call_b
1714 .update(cx_b, |call, cx| call.set_location(None, cx))
1715 .await
1716 .unwrap();
1717 deterministic.run_until_parked();
1718 assert!(a_notified.take());
1719 assert_eq!(
1720 participant_locations(&room_a, cx_a),
1721 vec![("user_b".to_string(), ParticipantLocation::External)]
1722 );
1723 assert!(b_notified.take());
1724 assert_eq!(
1725 participant_locations(&room_b, cx_b),
1726 vec![(
1727 "user_a".to_string(),
1728 ParticipantLocation::SharedProject {
1729 project_id: project_a_id
1730 }
1731 )]
1732 );
1733
1734 fn participant_locations(
1735 room: &ModelHandle<Room>,
1736 cx: &TestAppContext,
1737 ) -> Vec<(String, ParticipantLocation)> {
1738 room.read_with(cx, |room, _| {
1739 room.remote_participants()
1740 .values()
1741 .map(|participant| {
1742 (
1743 participant.user.github_login.to_string(),
1744 participant.location,
1745 )
1746 })
1747 .collect()
1748 })
1749 }
1750}
1751
1752#[gpui::test(iterations = 10)]
1753async fn test_propagate_saves_and_fs_changes(
1754 deterministic: Arc<Deterministic>,
1755 cx_a: &mut TestAppContext,
1756 cx_b: &mut TestAppContext,
1757 cx_c: &mut TestAppContext,
1758) {
1759 deterministic.forbid_parking();
1760 let mut server = TestServer::start(&deterministic).await;
1761 let client_a = server.create_client(cx_a, "user_a").await;
1762 let client_b = server.create_client(cx_b, "user_b").await;
1763 let client_c = server.create_client(cx_c, "user_c").await;
1764
1765 server
1766 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1767 .await;
1768 let active_call_a = cx_a.read(ActiveCall::global);
1769
1770 let rust = Arc::new(Language::new(
1771 LanguageConfig {
1772 name: "Rust".into(),
1773 path_suffixes: vec!["rs".to_string()],
1774 ..Default::default()
1775 },
1776 Some(tree_sitter_rust::language()),
1777 ));
1778 let javascript = Arc::new(Language::new(
1779 LanguageConfig {
1780 name: "JavaScript".into(),
1781 path_suffixes: vec!["js".to_string()],
1782 ..Default::default()
1783 },
1784 Some(tree_sitter_rust::language()),
1785 ));
1786 for client in [&client_a, &client_b, &client_c] {
1787 client.language_registry.add(rust.clone());
1788 client.language_registry.add(javascript.clone());
1789 }
1790
1791 client_a
1792 .fs
1793 .insert_tree(
1794 "/a",
1795 json!({
1796 "file1.rs": "",
1797 "file2": ""
1798 }),
1799 )
1800 .await;
1801 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1802 let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
1803 let project_id = active_call_a
1804 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1805 .await
1806 .unwrap();
1807
1808 // Join that worktree as clients B and C.
1809 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1810 let project_c = client_c.build_remote_project(project_id, cx_c).await;
1811 let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
1812 let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
1813
1814 // Open and edit a buffer as both guests B and C.
1815 let buffer_b = project_b
1816 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1817 .await
1818 .unwrap();
1819 let buffer_c = project_c
1820 .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1821 .await
1822 .unwrap();
1823 buffer_b.read_with(cx_b, |buffer, _| {
1824 assert_eq!(&*buffer.language().unwrap().name(), "Rust");
1825 });
1826 buffer_c.read_with(cx_c, |buffer, _| {
1827 assert_eq!(&*buffer.language().unwrap().name(), "Rust");
1828 });
1829 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
1830 buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
1831
1832 // Open and edit that buffer as the host.
1833 let buffer_a = project_a
1834 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1835 .await
1836 .unwrap();
1837
1838 deterministic.run_until_parked();
1839 buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
1840 buffer_a.update(cx_a, |buf, cx| {
1841 buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
1842 });
1843
1844 deterministic.run_until_parked();
1845 buffer_a.read_with(cx_a, |buf, _| {
1846 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1847 });
1848 buffer_b.read_with(cx_b, |buf, _| {
1849 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1850 });
1851 buffer_c.read_with(cx_c, |buf, _| {
1852 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1853 });
1854
1855 // Edit the buffer as the host and concurrently save as guest B.
1856 let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
1857 buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
1858 save_b.await.unwrap();
1859 assert_eq!(
1860 client_a.fs.load("/a/file1.rs".as_ref()).await.unwrap(),
1861 "hi-a, i-am-c, i-am-b, i-am-a"
1862 );
1863
1864 deterministic.run_until_parked();
1865 buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
1866 buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
1867 buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
1868
1869 // Make changes on host's file system, see those changes on guest worktrees.
1870 client_a
1871 .fs
1872 .rename(
1873 "/a/file1.rs".as_ref(),
1874 "/a/file1.js".as_ref(),
1875 Default::default(),
1876 )
1877 .await
1878 .unwrap();
1879 client_a
1880 .fs
1881 .rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
1882 .await
1883 .unwrap();
1884 client_a.fs.insert_file("/a/file4", "4".into()).await;
1885 deterministic.run_until_parked();
1886
1887 worktree_a.read_with(cx_a, |tree, _| {
1888 assert_eq!(
1889 tree.paths()
1890 .map(|p| p.to_string_lossy())
1891 .collect::<Vec<_>>(),
1892 ["file1.js", "file3", "file4"]
1893 )
1894 });
1895 worktree_b.read_with(cx_b, |tree, _| {
1896 assert_eq!(
1897 tree.paths()
1898 .map(|p| p.to_string_lossy())
1899 .collect::<Vec<_>>(),
1900 ["file1.js", "file3", "file4"]
1901 )
1902 });
1903 worktree_c.read_with(cx_c, |tree, _| {
1904 assert_eq!(
1905 tree.paths()
1906 .map(|p| p.to_string_lossy())
1907 .collect::<Vec<_>>(),
1908 ["file1.js", "file3", "file4"]
1909 )
1910 });
1911
1912 // Ensure buffer files are updated as well.
1913 buffer_a.read_with(cx_a, |buffer, _| {
1914 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1915 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1916 });
1917 buffer_b.read_with(cx_b, |buffer, _| {
1918 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1919 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1920 });
1921 buffer_c.read_with(cx_c, |buffer, _| {
1922 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1923 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1924 });
1925}
1926
1927#[gpui::test(iterations = 10)]
1928async fn test_git_diff_base_change(
1929 deterministic: Arc<Deterministic>,
1930 cx_a: &mut TestAppContext,
1931 cx_b: &mut TestAppContext,
1932) {
1933 deterministic.forbid_parking();
1934 let mut server = TestServer::start(&deterministic).await;
1935 let client_a = server.create_client(cx_a, "user_a").await;
1936 let client_b = server.create_client(cx_b, "user_b").await;
1937 server
1938 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1939 .await;
1940 let active_call_a = cx_a.read(ActiveCall::global);
1941
1942 client_a
1943 .fs
1944 .insert_tree(
1945 "/dir",
1946 json!({
1947 ".git": {},
1948 "sub": {
1949 ".git": {},
1950 "b.txt": "
1951 one
1952 two
1953 three
1954 ".unindent(),
1955 },
1956 "a.txt": "
1957 one
1958 two
1959 three
1960 ".unindent(),
1961 }),
1962 )
1963 .await;
1964
1965 let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1966 let project_id = active_call_a
1967 .update(cx_a, |call, cx| {
1968 call.share_project(project_local.clone(), cx)
1969 })
1970 .await
1971 .unwrap();
1972
1973 let project_remote = client_b.build_remote_project(project_id, cx_b).await;
1974
1975 let diff_base = "
1976 one
1977 three
1978 "
1979 .unindent();
1980
1981 let new_diff_base = "
1982 one
1983 two
1984 "
1985 .unindent();
1986
1987 client_a
1988 .fs
1989 .as_fake()
1990 .set_index_for_repo(
1991 Path::new("/dir/.git"),
1992 &[(Path::new("a.txt"), diff_base.clone())],
1993 )
1994 .await;
1995
1996 // Create the buffer
1997 let buffer_local_a = project_local
1998 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1999 .await
2000 .unwrap();
2001
2002 // Wait for it to catch up to the new diff
2003 deterministic.run_until_parked();
2004
2005 // Smoke test diffing
2006 buffer_local_a.read_with(cx_a, |buffer, _| {
2007 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2008 git::diff::assert_hunks(
2009 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2010 &buffer,
2011 &diff_base,
2012 &[(1..2, "", "two\n")],
2013 );
2014 });
2015
2016 // Create remote buffer
2017 let buffer_remote_a = project_remote
2018 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2019 .await
2020 .unwrap();
2021
2022 // Wait remote buffer to catch up to the new diff
2023 deterministic.run_until_parked();
2024
2025 // Smoke test diffing
2026 buffer_remote_a.read_with(cx_b, |buffer, _| {
2027 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2028 git::diff::assert_hunks(
2029 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2030 &buffer,
2031 &diff_base,
2032 &[(1..2, "", "two\n")],
2033 );
2034 });
2035
2036 client_a
2037 .fs
2038 .as_fake()
2039 .set_index_for_repo(
2040 Path::new("/dir/.git"),
2041 &[(Path::new("a.txt"), new_diff_base.clone())],
2042 )
2043 .await;
2044
2045 // Wait for buffer_local_a to receive it
2046 deterministic.run_until_parked();
2047
2048 // Smoke test new diffing
2049 buffer_local_a.read_with(cx_a, |buffer, _| {
2050 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2051
2052 git::diff::assert_hunks(
2053 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2054 &buffer,
2055 &diff_base,
2056 &[(2..3, "", "three\n")],
2057 );
2058 });
2059
2060 // Smoke test B
2061 buffer_remote_a.read_with(cx_b, |buffer, _| {
2062 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2063 git::diff::assert_hunks(
2064 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2065 &buffer,
2066 &diff_base,
2067 &[(2..3, "", "three\n")],
2068 );
2069 });
2070
2071 //Nested git dir
2072
2073 let diff_base = "
2074 one
2075 three
2076 "
2077 .unindent();
2078
2079 let new_diff_base = "
2080 one
2081 two
2082 "
2083 .unindent();
2084
2085 client_a
2086 .fs
2087 .as_fake()
2088 .set_index_for_repo(
2089 Path::new("/dir/sub/.git"),
2090 &[(Path::new("b.txt"), diff_base.clone())],
2091 )
2092 .await;
2093
2094 // Create the buffer
2095 let buffer_local_b = project_local
2096 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2097 .await
2098 .unwrap();
2099
2100 // Wait for it to catch up to the new diff
2101 deterministic.run_until_parked();
2102
2103 // Smoke test diffing
2104 buffer_local_b.read_with(cx_a, |buffer, _| {
2105 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2106 git::diff::assert_hunks(
2107 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2108 &buffer,
2109 &diff_base,
2110 &[(1..2, "", "two\n")],
2111 );
2112 });
2113
2114 // Create remote buffer
2115 let buffer_remote_b = project_remote
2116 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2117 .await
2118 .unwrap();
2119
2120 // Wait remote buffer to catch up to the new diff
2121 deterministic.run_until_parked();
2122
2123 // Smoke test diffing
2124 buffer_remote_b.read_with(cx_b, |buffer, _| {
2125 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2126 git::diff::assert_hunks(
2127 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2128 &buffer,
2129 &diff_base,
2130 &[(1..2, "", "two\n")],
2131 );
2132 });
2133
2134 client_a
2135 .fs
2136 .as_fake()
2137 .set_index_for_repo(
2138 Path::new("/dir/sub/.git"),
2139 &[(Path::new("b.txt"), new_diff_base.clone())],
2140 )
2141 .await;
2142
2143 // Wait for buffer_local_b to receive it
2144 deterministic.run_until_parked();
2145
2146 // Smoke test new diffing
2147 buffer_local_b.read_with(cx_a, |buffer, _| {
2148 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2149 println!("{:?}", buffer.as_rope().to_string());
2150 println!("{:?}", buffer.diff_base());
2151 println!(
2152 "{:?}",
2153 buffer
2154 .snapshot()
2155 .git_diff_hunks_in_range(0..4, false)
2156 .collect::<Vec<_>>()
2157 );
2158
2159 git::diff::assert_hunks(
2160 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2161 &buffer,
2162 &diff_base,
2163 &[(2..3, "", "three\n")],
2164 );
2165 });
2166
2167 // Smoke test B
2168 buffer_remote_b.read_with(cx_b, |buffer, _| {
2169 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2170 git::diff::assert_hunks(
2171 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2172 &buffer,
2173 &diff_base,
2174 &[(2..3, "", "three\n")],
2175 );
2176 });
2177}
2178
2179#[gpui::test(iterations = 10)]
2180async fn test_fs_operations(
2181 deterministic: Arc<Deterministic>,
2182 cx_a: &mut TestAppContext,
2183 cx_b: &mut TestAppContext,
2184) {
2185 deterministic.forbid_parking();
2186 let mut server = TestServer::start(&deterministic).await;
2187 let client_a = server.create_client(cx_a, "user_a").await;
2188 let client_b = server.create_client(cx_b, "user_b").await;
2189 server
2190 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2191 .await;
2192 let active_call_a = cx_a.read(ActiveCall::global);
2193
2194 client_a
2195 .fs
2196 .insert_tree(
2197 "/dir",
2198 json!({
2199 "a.txt": "a-contents",
2200 "b.txt": "b-contents",
2201 }),
2202 )
2203 .await;
2204 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2205 let project_id = active_call_a
2206 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2207 .await
2208 .unwrap();
2209 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2210
2211 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
2212 let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
2213
2214 let entry = project_b
2215 .update(cx_b, |project, cx| {
2216 project
2217 .create_entry((worktree_id, "c.txt"), false, cx)
2218 .unwrap()
2219 })
2220 .await
2221 .unwrap();
2222 worktree_a.read_with(cx_a, |worktree, _| {
2223 assert_eq!(
2224 worktree
2225 .paths()
2226 .map(|p| p.to_string_lossy())
2227 .collect::<Vec<_>>(),
2228 ["a.txt", "b.txt", "c.txt"]
2229 );
2230 });
2231 worktree_b.read_with(cx_b, |worktree, _| {
2232 assert_eq!(
2233 worktree
2234 .paths()
2235 .map(|p| p.to_string_lossy())
2236 .collect::<Vec<_>>(),
2237 ["a.txt", "b.txt", "c.txt"]
2238 );
2239 });
2240
2241 project_b
2242 .update(cx_b, |project, cx| {
2243 project.rename_entry(entry.id, Path::new("d.txt"), cx)
2244 })
2245 .unwrap()
2246 .await
2247 .unwrap();
2248 worktree_a.read_with(cx_a, |worktree, _| {
2249 assert_eq!(
2250 worktree
2251 .paths()
2252 .map(|p| p.to_string_lossy())
2253 .collect::<Vec<_>>(),
2254 ["a.txt", "b.txt", "d.txt"]
2255 );
2256 });
2257 worktree_b.read_with(cx_b, |worktree, _| {
2258 assert_eq!(
2259 worktree
2260 .paths()
2261 .map(|p| p.to_string_lossy())
2262 .collect::<Vec<_>>(),
2263 ["a.txt", "b.txt", "d.txt"]
2264 );
2265 });
2266
2267 let dir_entry = project_b
2268 .update(cx_b, |project, cx| {
2269 project
2270 .create_entry((worktree_id, "DIR"), true, cx)
2271 .unwrap()
2272 })
2273 .await
2274 .unwrap();
2275 worktree_a.read_with(cx_a, |worktree, _| {
2276 assert_eq!(
2277 worktree
2278 .paths()
2279 .map(|p| p.to_string_lossy())
2280 .collect::<Vec<_>>(),
2281 ["DIR", "a.txt", "b.txt", "d.txt"]
2282 );
2283 });
2284 worktree_b.read_with(cx_b, |worktree, _| {
2285 assert_eq!(
2286 worktree
2287 .paths()
2288 .map(|p| p.to_string_lossy())
2289 .collect::<Vec<_>>(),
2290 ["DIR", "a.txt", "b.txt", "d.txt"]
2291 );
2292 });
2293
2294 project_b
2295 .update(cx_b, |project, cx| {
2296 project
2297 .create_entry((worktree_id, "DIR/e.txt"), false, cx)
2298 .unwrap()
2299 })
2300 .await
2301 .unwrap();
2302 project_b
2303 .update(cx_b, |project, cx| {
2304 project
2305 .create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
2306 .unwrap()
2307 })
2308 .await
2309 .unwrap();
2310 project_b
2311 .update(cx_b, |project, cx| {
2312 project
2313 .create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
2314 .unwrap()
2315 })
2316 .await
2317 .unwrap();
2318 worktree_a.read_with(cx_a, |worktree, _| {
2319 assert_eq!(
2320 worktree
2321 .paths()
2322 .map(|p| p.to_string_lossy())
2323 .collect::<Vec<_>>(),
2324 [
2325 "DIR",
2326 "DIR/SUBDIR",
2327 "DIR/SUBDIR/f.txt",
2328 "DIR/e.txt",
2329 "a.txt",
2330 "b.txt",
2331 "d.txt"
2332 ]
2333 );
2334 });
2335 worktree_b.read_with(cx_b, |worktree, _| {
2336 assert_eq!(
2337 worktree
2338 .paths()
2339 .map(|p| p.to_string_lossy())
2340 .collect::<Vec<_>>(),
2341 [
2342 "DIR",
2343 "DIR/SUBDIR",
2344 "DIR/SUBDIR/f.txt",
2345 "DIR/e.txt",
2346 "a.txt",
2347 "b.txt",
2348 "d.txt"
2349 ]
2350 );
2351 });
2352
2353 project_b
2354 .update(cx_b, |project, cx| {
2355 project
2356 .copy_entry(entry.id, Path::new("f.txt"), cx)
2357 .unwrap()
2358 })
2359 .await
2360 .unwrap();
2361 worktree_a.read_with(cx_a, |worktree, _| {
2362 assert_eq!(
2363 worktree
2364 .paths()
2365 .map(|p| p.to_string_lossy())
2366 .collect::<Vec<_>>(),
2367 [
2368 "DIR",
2369 "DIR/SUBDIR",
2370 "DIR/SUBDIR/f.txt",
2371 "DIR/e.txt",
2372 "a.txt",
2373 "b.txt",
2374 "d.txt",
2375 "f.txt"
2376 ]
2377 );
2378 });
2379 worktree_b.read_with(cx_b, |worktree, _| {
2380 assert_eq!(
2381 worktree
2382 .paths()
2383 .map(|p| p.to_string_lossy())
2384 .collect::<Vec<_>>(),
2385 [
2386 "DIR",
2387 "DIR/SUBDIR",
2388 "DIR/SUBDIR/f.txt",
2389 "DIR/e.txt",
2390 "a.txt",
2391 "b.txt",
2392 "d.txt",
2393 "f.txt"
2394 ]
2395 );
2396 });
2397
2398 project_b
2399 .update(cx_b, |project, cx| {
2400 project.delete_entry(dir_entry.id, cx).unwrap()
2401 })
2402 .await
2403 .unwrap();
2404 worktree_a.read_with(cx_a, |worktree, _| {
2405 assert_eq!(
2406 worktree
2407 .paths()
2408 .map(|p| p.to_string_lossy())
2409 .collect::<Vec<_>>(),
2410 ["a.txt", "b.txt", "d.txt", "f.txt"]
2411 );
2412 });
2413 worktree_b.read_with(cx_b, |worktree, _| {
2414 assert_eq!(
2415 worktree
2416 .paths()
2417 .map(|p| p.to_string_lossy())
2418 .collect::<Vec<_>>(),
2419 ["a.txt", "b.txt", "d.txt", "f.txt"]
2420 );
2421 });
2422
2423 project_b
2424 .update(cx_b, |project, cx| {
2425 project.delete_entry(entry.id, cx).unwrap()
2426 })
2427 .await
2428 .unwrap();
2429 worktree_a.read_with(cx_a, |worktree, _| {
2430 assert_eq!(
2431 worktree
2432 .paths()
2433 .map(|p| p.to_string_lossy())
2434 .collect::<Vec<_>>(),
2435 ["a.txt", "b.txt", "f.txt"]
2436 );
2437 });
2438 worktree_b.read_with(cx_b, |worktree, _| {
2439 assert_eq!(
2440 worktree
2441 .paths()
2442 .map(|p| p.to_string_lossy())
2443 .collect::<Vec<_>>(),
2444 ["a.txt", "b.txt", "f.txt"]
2445 );
2446 });
2447}
2448
2449#[gpui::test(iterations = 10)]
2450async fn test_buffer_conflict_after_save(
2451 deterministic: Arc<Deterministic>,
2452 cx_a: &mut TestAppContext,
2453 cx_b: &mut TestAppContext,
2454) {
2455 deterministic.forbid_parking();
2456 let mut server = TestServer::start(&deterministic).await;
2457 let client_a = server.create_client(cx_a, "user_a").await;
2458 let client_b = server.create_client(cx_b, "user_b").await;
2459 server
2460 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2461 .await;
2462 let active_call_a = cx_a.read(ActiveCall::global);
2463
2464 client_a
2465 .fs
2466 .insert_tree(
2467 "/dir",
2468 json!({
2469 "a.txt": "a-contents",
2470 }),
2471 )
2472 .await;
2473 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2474 let project_id = active_call_a
2475 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2476 .await
2477 .unwrap();
2478 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2479
2480 // Open a buffer as client B
2481 let buffer_b = project_b
2482 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2483 .await
2484 .unwrap();
2485
2486 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
2487 buffer_b.read_with(cx_b, |buf, _| {
2488 assert!(buf.is_dirty());
2489 assert!(!buf.has_conflict());
2490 });
2491
2492 buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap();
2493 cx_a.foreground().forbid_parking();
2494 buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
2495 buffer_b.read_with(cx_b, |buf, _| {
2496 assert!(!buf.has_conflict());
2497 });
2498
2499 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
2500 buffer_b.read_with(cx_b, |buf, _| {
2501 assert!(buf.is_dirty());
2502 assert!(!buf.has_conflict());
2503 });
2504}
2505
2506#[gpui::test(iterations = 10)]
2507async fn test_buffer_reloading(
2508 deterministic: Arc<Deterministic>,
2509 cx_a: &mut TestAppContext,
2510 cx_b: &mut TestAppContext,
2511) {
2512 deterministic.forbid_parking();
2513 let mut server = TestServer::start(&deterministic).await;
2514 let client_a = server.create_client(cx_a, "user_a").await;
2515 let client_b = server.create_client(cx_b, "user_b").await;
2516 server
2517 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2518 .await;
2519 let active_call_a = cx_a.read(ActiveCall::global);
2520
2521 client_a
2522 .fs
2523 .insert_tree(
2524 "/dir",
2525 json!({
2526 "a.txt": "a\nb\nc",
2527 }),
2528 )
2529 .await;
2530 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2531 let project_id = active_call_a
2532 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2533 .await
2534 .unwrap();
2535 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2536
2537 // Open a buffer as client B
2538 let buffer_b = project_b
2539 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2540 .await
2541 .unwrap();
2542 buffer_b.read_with(cx_b, |buf, _| {
2543 assert!(!buf.is_dirty());
2544 assert!(!buf.has_conflict());
2545 assert_eq!(buf.line_ending(), LineEnding::Unix);
2546 });
2547
2548 let new_contents = Rope::from("d\ne\nf");
2549 client_a
2550 .fs
2551 .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
2552 .await
2553 .unwrap();
2554 cx_a.foreground().run_until_parked();
2555 buffer_b.read_with(cx_b, |buf, _| {
2556 assert_eq!(buf.text(), new_contents.to_string());
2557 assert!(!buf.is_dirty());
2558 assert!(!buf.has_conflict());
2559 assert_eq!(buf.line_ending(), LineEnding::Windows);
2560 });
2561}
2562
2563#[gpui::test(iterations = 10)]
2564async fn test_editing_while_guest_opens_buffer(
2565 deterministic: Arc<Deterministic>,
2566 cx_a: &mut TestAppContext,
2567 cx_b: &mut TestAppContext,
2568) {
2569 deterministic.forbid_parking();
2570 let mut server = TestServer::start(&deterministic).await;
2571 let client_a = server.create_client(cx_a, "user_a").await;
2572 let client_b = server.create_client(cx_b, "user_b").await;
2573 server
2574 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2575 .await;
2576 let active_call_a = cx_a.read(ActiveCall::global);
2577
2578 client_a
2579 .fs
2580 .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
2581 .await;
2582 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2583 let project_id = active_call_a
2584 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2585 .await
2586 .unwrap();
2587 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2588
2589 // Open a buffer as client A
2590 let buffer_a = project_a
2591 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2592 .await
2593 .unwrap();
2594
2595 // Start opening the same buffer as client B
2596 let buffer_b = cx_b
2597 .background()
2598 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
2599
2600 // Edit the buffer as client A while client B is still opening it.
2601 cx_b.background().simulate_random_delay().await;
2602 buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
2603 cx_b.background().simulate_random_delay().await;
2604 buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
2605
2606 let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
2607 let buffer_b = buffer_b.await.unwrap();
2608 cx_a.foreground().run_until_parked();
2609 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
2610}
2611
2612#[gpui::test(iterations = 10)]
2613async fn test_leaving_worktree_while_opening_buffer(
2614 deterministic: Arc<Deterministic>,
2615 cx_a: &mut TestAppContext,
2616 cx_b: &mut TestAppContext,
2617) {
2618 deterministic.forbid_parking();
2619 let mut server = TestServer::start(&deterministic).await;
2620 let client_a = server.create_client(cx_a, "user_a").await;
2621 let client_b = server.create_client(cx_b, "user_b").await;
2622 server
2623 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2624 .await;
2625 let active_call_a = cx_a.read(ActiveCall::global);
2626
2627 client_a
2628 .fs
2629 .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
2630 .await;
2631 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2632 let project_id = active_call_a
2633 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2634 .await
2635 .unwrap();
2636 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2637
2638 // See that a guest has joined as client A.
2639 cx_a.foreground().run_until_parked();
2640 project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
2641
2642 // Begin opening a buffer as client B, but leave the project before the open completes.
2643 let buffer_b = cx_b
2644 .background()
2645 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
2646 cx_b.update(|_| drop(project_b));
2647 drop(buffer_b);
2648
2649 // See that the guest has left.
2650 cx_a.foreground().run_until_parked();
2651 project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
2652}
2653
2654#[gpui::test(iterations = 10)]
2655async fn test_canceling_buffer_opening(
2656 deterministic: Arc<Deterministic>,
2657 cx_a: &mut TestAppContext,
2658 cx_b: &mut TestAppContext,
2659) {
2660 deterministic.forbid_parking();
2661
2662 let mut server = TestServer::start(&deterministic).await;
2663 let client_a = server.create_client(cx_a, "user_a").await;
2664 let client_b = server.create_client(cx_b, "user_b").await;
2665 server
2666 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2667 .await;
2668 let active_call_a = cx_a.read(ActiveCall::global);
2669
2670 client_a
2671 .fs
2672 .insert_tree(
2673 "/dir",
2674 json!({
2675 "a.txt": "abc",
2676 }),
2677 )
2678 .await;
2679 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2680 let project_id = active_call_a
2681 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2682 .await
2683 .unwrap();
2684 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2685
2686 let buffer_a = project_a
2687 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2688 .await
2689 .unwrap();
2690
2691 // Open a buffer as client B but cancel after a random amount of time.
2692 let buffer_b = project_b.update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx));
2693 deterministic.simulate_random_delay().await;
2694 drop(buffer_b);
2695
2696 // Try opening the same buffer again as client B, and ensure we can
2697 // still do it despite the cancellation above.
2698 let buffer_b = project_b
2699 .update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx))
2700 .await
2701 .unwrap();
2702 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
2703}
2704
2705#[gpui::test(iterations = 10)]
2706async fn test_leaving_project(
2707 deterministic: Arc<Deterministic>,
2708 cx_a: &mut TestAppContext,
2709 cx_b: &mut TestAppContext,
2710 cx_c: &mut TestAppContext,
2711) {
2712 deterministic.forbid_parking();
2713 let mut server = TestServer::start(&deterministic).await;
2714 let client_a = server.create_client(cx_a, "user_a").await;
2715 let client_b = server.create_client(cx_b, "user_b").await;
2716 let client_c = server.create_client(cx_c, "user_c").await;
2717 server
2718 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2719 .await;
2720 let active_call_a = cx_a.read(ActiveCall::global);
2721
2722 client_a
2723 .fs
2724 .insert_tree(
2725 "/a",
2726 json!({
2727 "a.txt": "a-contents",
2728 "b.txt": "b-contents",
2729 }),
2730 )
2731 .await;
2732 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2733 let project_id = active_call_a
2734 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2735 .await
2736 .unwrap();
2737 let project_b1 = client_b.build_remote_project(project_id, cx_b).await;
2738 let project_c = client_c.build_remote_project(project_id, cx_c).await;
2739
2740 // Client A sees that a guest has joined.
2741 deterministic.run_until_parked();
2742 project_a.read_with(cx_a, |project, _| {
2743 assert_eq!(project.collaborators().len(), 2);
2744 });
2745 project_b1.read_with(cx_b, |project, _| {
2746 assert_eq!(project.collaborators().len(), 2);
2747 });
2748 project_c.read_with(cx_c, |project, _| {
2749 assert_eq!(project.collaborators().len(), 2);
2750 });
2751
2752 // Client B opens a buffer.
2753 let buffer_b1 = project_b1
2754 .update(cx_b, |project, cx| {
2755 let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
2756 project.open_buffer((worktree_id, "a.txt"), cx)
2757 })
2758 .await
2759 .unwrap();
2760 buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
2761
2762 // Drop client B's project and ensure client A and client C observe client B leaving.
2763 cx_b.update(|_| drop(project_b1));
2764 deterministic.run_until_parked();
2765 project_a.read_with(cx_a, |project, _| {
2766 assert_eq!(project.collaborators().len(), 1);
2767 });
2768 project_c.read_with(cx_c, |project, _| {
2769 assert_eq!(project.collaborators().len(), 1);
2770 });
2771
2772 // Client B re-joins the project and can open buffers as before.
2773 let project_b2 = client_b.build_remote_project(project_id, cx_b).await;
2774 deterministic.run_until_parked();
2775 project_a.read_with(cx_a, |project, _| {
2776 assert_eq!(project.collaborators().len(), 2);
2777 });
2778 project_b2.read_with(cx_b, |project, _| {
2779 assert_eq!(project.collaborators().len(), 2);
2780 });
2781 project_c.read_with(cx_c, |project, _| {
2782 assert_eq!(project.collaborators().len(), 2);
2783 });
2784
2785 let buffer_b2 = project_b2
2786 .update(cx_b, |project, cx| {
2787 let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
2788 project.open_buffer((worktree_id, "a.txt"), cx)
2789 })
2790 .await
2791 .unwrap();
2792 buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
2793
2794 // Drop client B's connection and ensure client A and client C observe client B leaving.
2795 client_b.disconnect(&cx_b.to_async()).unwrap();
2796 deterministic.run_until_parked();
2797 project_a.read_with(cx_a, |project, _| {
2798 assert_eq!(project.collaborators().len(), 1);
2799 });
2800 project_b2.read_with(cx_b, |project, _| {
2801 assert!(project.is_read_only());
2802 });
2803 project_c.read_with(cx_c, |project, _| {
2804 assert_eq!(project.collaborators().len(), 1);
2805 });
2806
2807 // Client B can't join the project, unless they re-join the room.
2808 cx_b.spawn(|cx| {
2809 Project::remote(
2810 project_id,
2811 client_b.client.clone(),
2812 client_b.user_store.clone(),
2813 client_b.language_registry.clone(),
2814 FakeFs::new(cx.background()),
2815 cx,
2816 )
2817 })
2818 .await
2819 .unwrap_err();
2820
2821 // Simulate connection loss for client C and ensure client A observes client C leaving the project.
2822 client_c.wait_for_current_user(cx_c).await;
2823 server.disconnect_client(client_c.peer_id().unwrap());
2824 deterministic.advance_clock(RECEIVE_TIMEOUT);
2825 deterministic.run_until_parked();
2826 project_a.read_with(cx_a, |project, _| {
2827 assert_eq!(project.collaborators().len(), 0);
2828 });
2829 project_b2.read_with(cx_b, |project, _| {
2830 assert!(project.is_read_only());
2831 });
2832 project_c.read_with(cx_c, |project, _| {
2833 assert!(project.is_read_only());
2834 });
2835}
2836
2837#[gpui::test(iterations = 10)]
2838async fn test_collaborating_with_diagnostics(
2839 deterministic: Arc<Deterministic>,
2840 cx_a: &mut TestAppContext,
2841 cx_b: &mut TestAppContext,
2842 cx_c: &mut TestAppContext,
2843) {
2844 deterministic.forbid_parking();
2845 let mut server = TestServer::start(&deterministic).await;
2846 let client_a = server.create_client(cx_a, "user_a").await;
2847 let client_b = server.create_client(cx_b, "user_b").await;
2848 let client_c = server.create_client(cx_c, "user_c").await;
2849 server
2850 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2851 .await;
2852 let active_call_a = cx_a.read(ActiveCall::global);
2853
2854 // Set up a fake language server.
2855 let mut language = Language::new(
2856 LanguageConfig {
2857 name: "Rust".into(),
2858 path_suffixes: vec!["rs".to_string()],
2859 ..Default::default()
2860 },
2861 Some(tree_sitter_rust::language()),
2862 );
2863 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2864 client_a.language_registry.add(Arc::new(language));
2865
2866 // Share a project as client A
2867 client_a
2868 .fs
2869 .insert_tree(
2870 "/a",
2871 json!({
2872 "a.rs": "let one = two",
2873 "other.rs": "",
2874 }),
2875 )
2876 .await;
2877 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2878
2879 // Cause the language server to start.
2880 let _buffer = project_a
2881 .update(cx_a, |project, cx| {
2882 project.open_buffer(
2883 ProjectPath {
2884 worktree_id,
2885 path: Path::new("other.rs").into(),
2886 },
2887 cx,
2888 )
2889 })
2890 .await
2891 .unwrap();
2892
2893 // Simulate a language server reporting errors for a file.
2894 let mut fake_language_server = fake_language_servers.next().await.unwrap();
2895 fake_language_server
2896 .receive_notification::<lsp::notification::DidOpenTextDocument>()
2897 .await;
2898 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2899 lsp::PublishDiagnosticsParams {
2900 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2901 version: None,
2902 diagnostics: vec![lsp::Diagnostic {
2903 severity: Some(lsp::DiagnosticSeverity::WARNING),
2904 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2905 message: "message 0".to_string(),
2906 ..Default::default()
2907 }],
2908 },
2909 );
2910
2911 // Client A shares the project and, simultaneously, the language server
2912 // publishes a diagnostic. This is done to ensure that the server always
2913 // observes the latest diagnostics for a worktree.
2914 let project_id = active_call_a
2915 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2916 .await
2917 .unwrap();
2918 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2919 lsp::PublishDiagnosticsParams {
2920 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2921 version: None,
2922 diagnostics: vec![lsp::Diagnostic {
2923 severity: Some(lsp::DiagnosticSeverity::ERROR),
2924 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2925 message: "message 1".to_string(),
2926 ..Default::default()
2927 }],
2928 },
2929 );
2930
2931 // Join the worktree as client B.
2932 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2933
2934 // Wait for server to see the diagnostics update.
2935 deterministic.run_until_parked();
2936
2937 // Ensure client B observes the new diagnostics.
2938 project_b.read_with(cx_b, |project, cx| {
2939 assert_eq!(
2940 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2941 &[(
2942 ProjectPath {
2943 worktree_id,
2944 path: Arc::from(Path::new("a.rs")),
2945 },
2946 DiagnosticSummary {
2947 error_count: 1,
2948 warning_count: 0,
2949 ..Default::default()
2950 },
2951 )]
2952 )
2953 });
2954
2955 // Join project as client C and observe the diagnostics.
2956 let project_c = client_c.build_remote_project(project_id, cx_c).await;
2957 let project_c_diagnostic_summaries =
2958 Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
2959 project.diagnostic_summaries(cx).collect::<Vec<_>>()
2960 })));
2961 project_c.update(cx_c, |_, cx| {
2962 let summaries = project_c_diagnostic_summaries.clone();
2963 cx.subscribe(&project_c, {
2964 move |p, _, event, cx| {
2965 if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
2966 *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
2967 }
2968 }
2969 })
2970 .detach();
2971 });
2972
2973 deterministic.run_until_parked();
2974 assert_eq!(
2975 project_c_diagnostic_summaries.borrow().as_slice(),
2976 &[(
2977 ProjectPath {
2978 worktree_id,
2979 path: Arc::from(Path::new("a.rs")),
2980 },
2981 DiagnosticSummary {
2982 error_count: 1,
2983 warning_count: 0,
2984 ..Default::default()
2985 },
2986 )]
2987 );
2988
2989 // Simulate a language server reporting more errors for a file.
2990 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2991 lsp::PublishDiagnosticsParams {
2992 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2993 version: None,
2994 diagnostics: vec![
2995 lsp::Diagnostic {
2996 severity: Some(lsp::DiagnosticSeverity::ERROR),
2997 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2998 message: "message 1".to_string(),
2999 ..Default::default()
3000 },
3001 lsp::Diagnostic {
3002 severity: Some(lsp::DiagnosticSeverity::WARNING),
3003 range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
3004 message: "message 2".to_string(),
3005 ..Default::default()
3006 },
3007 ],
3008 },
3009 );
3010
3011 // Clients B and C get the updated summaries
3012 deterministic.run_until_parked();
3013 project_b.read_with(cx_b, |project, cx| {
3014 assert_eq!(
3015 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
3016 [(
3017 ProjectPath {
3018 worktree_id,
3019 path: Arc::from(Path::new("a.rs")),
3020 },
3021 DiagnosticSummary {
3022 error_count: 1,
3023 warning_count: 1,
3024 ..Default::default()
3025 },
3026 )]
3027 );
3028 });
3029 project_c.read_with(cx_c, |project, cx| {
3030 assert_eq!(
3031 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
3032 [(
3033 ProjectPath {
3034 worktree_id,
3035 path: Arc::from(Path::new("a.rs")),
3036 },
3037 DiagnosticSummary {
3038 error_count: 1,
3039 warning_count: 1,
3040 ..Default::default()
3041 },
3042 )]
3043 );
3044 });
3045
3046 // Open the file with the errors on client B. They should be present.
3047 let buffer_b = cx_b
3048 .background()
3049 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3050 .await
3051 .unwrap();
3052
3053 buffer_b.read_with(cx_b, |buffer, _| {
3054 assert_eq!(
3055 buffer
3056 .snapshot()
3057 .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
3058 .collect::<Vec<_>>(),
3059 &[
3060 DiagnosticEntry {
3061 range: Point::new(0, 4)..Point::new(0, 7),
3062 diagnostic: Diagnostic {
3063 group_id: 2,
3064 message: "message 1".to_string(),
3065 severity: lsp::DiagnosticSeverity::ERROR,
3066 is_primary: true,
3067 ..Default::default()
3068 }
3069 },
3070 DiagnosticEntry {
3071 range: Point::new(0, 10)..Point::new(0, 13),
3072 diagnostic: Diagnostic {
3073 group_id: 3,
3074 severity: lsp::DiagnosticSeverity::WARNING,
3075 message: "message 2".to_string(),
3076 is_primary: true,
3077 ..Default::default()
3078 }
3079 }
3080 ]
3081 );
3082 });
3083
3084 // Simulate a language server reporting no errors for a file.
3085 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3086 lsp::PublishDiagnosticsParams {
3087 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
3088 version: None,
3089 diagnostics: vec![],
3090 },
3091 );
3092 deterministic.run_until_parked();
3093 project_a.read_with(cx_a, |project, cx| {
3094 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3095 });
3096 project_b.read_with(cx_b, |project, cx| {
3097 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3098 });
3099 project_c.read_with(cx_c, |project, cx| {
3100 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3101 });
3102}
3103
3104#[gpui::test(iterations = 10)]
3105async fn test_collaborating_with_completion(
3106 deterministic: Arc<Deterministic>,
3107 cx_a: &mut TestAppContext,
3108 cx_b: &mut TestAppContext,
3109) {
3110 deterministic.forbid_parking();
3111 let mut server = TestServer::start(&deterministic).await;
3112 let client_a = server.create_client(cx_a, "user_a").await;
3113 let client_b = server.create_client(cx_b, "user_b").await;
3114 server
3115 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3116 .await;
3117 let active_call_a = cx_a.read(ActiveCall::global);
3118
3119 // Set up a fake language server.
3120 let mut language = Language::new(
3121 LanguageConfig {
3122 name: "Rust".into(),
3123 path_suffixes: vec!["rs".to_string()],
3124 ..Default::default()
3125 },
3126 Some(tree_sitter_rust::language()),
3127 );
3128 let mut fake_language_servers = language
3129 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3130 capabilities: lsp::ServerCapabilities {
3131 completion_provider: Some(lsp::CompletionOptions {
3132 trigger_characters: Some(vec![".".to_string()]),
3133 ..Default::default()
3134 }),
3135 ..Default::default()
3136 },
3137 ..Default::default()
3138 }))
3139 .await;
3140 client_a.language_registry.add(Arc::new(language));
3141
3142 client_a
3143 .fs
3144 .insert_tree(
3145 "/a",
3146 json!({
3147 "main.rs": "fn main() { a }",
3148 "other.rs": "",
3149 }),
3150 )
3151 .await;
3152 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3153 let project_id = active_call_a
3154 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3155 .await
3156 .unwrap();
3157 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3158
3159 // Open a file in an editor as the guest.
3160 let buffer_b = project_b
3161 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3162 .await
3163 .unwrap();
3164 let (_, window_b) = cx_b.add_window(|_| EmptyView);
3165 let editor_b = cx_b.add_view(&window_b, |cx| {
3166 Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
3167 });
3168
3169 let fake_language_server = fake_language_servers.next().await.unwrap();
3170 cx_a.foreground().run_until_parked();
3171 buffer_b.read_with(cx_b, |buffer, _| {
3172 assert!(!buffer.completion_triggers().is_empty())
3173 });
3174
3175 // Type a completion trigger character as the guest.
3176 editor_b.update(cx_b, |editor, cx| {
3177 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
3178 editor.handle_input(".", cx);
3179 cx.focus(&editor_b);
3180 });
3181
3182 // Receive a completion request as the host's language server.
3183 // Return some completions from the host's language server.
3184 cx_a.foreground().start_waiting();
3185 fake_language_server
3186 .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
3187 assert_eq!(
3188 params.text_document_position.text_document.uri,
3189 lsp::Url::from_file_path("/a/main.rs").unwrap(),
3190 );
3191 assert_eq!(
3192 params.text_document_position.position,
3193 lsp::Position::new(0, 14),
3194 );
3195
3196 Ok(Some(lsp::CompletionResponse::Array(vec![
3197 lsp::CompletionItem {
3198 label: "first_method(…)".into(),
3199 detail: Some("fn(&mut self, B) -> C".into()),
3200 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3201 new_text: "first_method($1)".to_string(),
3202 range: lsp::Range::new(
3203 lsp::Position::new(0, 14),
3204 lsp::Position::new(0, 14),
3205 ),
3206 })),
3207 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3208 ..Default::default()
3209 },
3210 lsp::CompletionItem {
3211 label: "second_method(…)".into(),
3212 detail: Some("fn(&mut self, C) -> D<E>".into()),
3213 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3214 new_text: "second_method()".to_string(),
3215 range: lsp::Range::new(
3216 lsp::Position::new(0, 14),
3217 lsp::Position::new(0, 14),
3218 ),
3219 })),
3220 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3221 ..Default::default()
3222 },
3223 ])))
3224 })
3225 .next()
3226 .await
3227 .unwrap();
3228 cx_a.foreground().finish_waiting();
3229
3230 // Open the buffer on the host.
3231 let buffer_a = project_a
3232 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3233 .await
3234 .unwrap();
3235 cx_a.foreground().run_until_parked();
3236 buffer_a.read_with(cx_a, |buffer, _| {
3237 assert_eq!(buffer.text(), "fn main() { a. }")
3238 });
3239
3240 // Confirm a completion on the guest.
3241 editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
3242 editor_b.update(cx_b, |editor, cx| {
3243 editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
3244 assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
3245 });
3246
3247 // Return a resolved completion from the host's language server.
3248 // The resolved completion has an additional text edit.
3249 fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
3250 |params, _| async move {
3251 assert_eq!(params.label, "first_method(…)");
3252 Ok(lsp::CompletionItem {
3253 label: "first_method(…)".into(),
3254 detail: Some("fn(&mut self, B) -> C".into()),
3255 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3256 new_text: "first_method($1)".to_string(),
3257 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
3258 })),
3259 additional_text_edits: Some(vec![lsp::TextEdit {
3260 new_text: "use d::SomeTrait;\n".to_string(),
3261 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
3262 }]),
3263 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3264 ..Default::default()
3265 })
3266 },
3267 );
3268
3269 // The additional edit is applied.
3270 cx_a.foreground().run_until_parked();
3271 buffer_a.read_with(cx_a, |buffer, _| {
3272 assert_eq!(
3273 buffer.text(),
3274 "use d::SomeTrait;\nfn main() { a.first_method() }"
3275 );
3276 });
3277 buffer_b.read_with(cx_b, |buffer, _| {
3278 assert_eq!(
3279 buffer.text(),
3280 "use d::SomeTrait;\nfn main() { a.first_method() }"
3281 );
3282 });
3283}
3284
3285#[gpui::test(iterations = 10)]
3286async fn test_reloading_buffer_manually(
3287 deterministic: Arc<Deterministic>,
3288 cx_a: &mut TestAppContext,
3289 cx_b: &mut TestAppContext,
3290) {
3291 deterministic.forbid_parking();
3292 let mut server = TestServer::start(&deterministic).await;
3293 let client_a = server.create_client(cx_a, "user_a").await;
3294 let client_b = server.create_client(cx_b, "user_b").await;
3295 server
3296 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3297 .await;
3298 let active_call_a = cx_a.read(ActiveCall::global);
3299
3300 client_a
3301 .fs
3302 .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
3303 .await;
3304 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3305 let buffer_a = project_a
3306 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
3307 .await
3308 .unwrap();
3309 let project_id = active_call_a
3310 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3311 .await
3312 .unwrap();
3313
3314 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3315
3316 let buffer_b = cx_b
3317 .background()
3318 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3319 .await
3320 .unwrap();
3321 buffer_b.update(cx_b, |buffer, cx| {
3322 buffer.edit([(4..7, "six")], None, cx);
3323 buffer.edit([(10..11, "6")], None, cx);
3324 assert_eq!(buffer.text(), "let six = 6;");
3325 assert!(buffer.is_dirty());
3326 assert!(!buffer.has_conflict());
3327 });
3328 cx_a.foreground().run_until_parked();
3329 buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
3330
3331 client_a
3332 .fs
3333 .save(
3334 "/a/a.rs".as_ref(),
3335 &Rope::from("let seven = 7;"),
3336 LineEnding::Unix,
3337 )
3338 .await
3339 .unwrap();
3340 cx_a.foreground().run_until_parked();
3341 buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
3342 buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
3343
3344 project_b
3345 .update(cx_b, |project, cx| {
3346 project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
3347 })
3348 .await
3349 .unwrap();
3350 buffer_a.read_with(cx_a, |buffer, _| {
3351 assert_eq!(buffer.text(), "let seven = 7;");
3352 assert!(!buffer.is_dirty());
3353 assert!(!buffer.has_conflict());
3354 });
3355 buffer_b.read_with(cx_b, |buffer, _| {
3356 assert_eq!(buffer.text(), "let seven = 7;");
3357 assert!(!buffer.is_dirty());
3358 assert!(!buffer.has_conflict());
3359 });
3360
3361 buffer_a.update(cx_a, |buffer, cx| {
3362 // Undoing on the host is a no-op when the reload was initiated by the guest.
3363 buffer.undo(cx);
3364 assert_eq!(buffer.text(), "let seven = 7;");
3365 assert!(!buffer.is_dirty());
3366 assert!(!buffer.has_conflict());
3367 });
3368 buffer_b.update(cx_b, |buffer, cx| {
3369 // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
3370 buffer.undo(cx);
3371 assert_eq!(buffer.text(), "let six = 6;");
3372 assert!(buffer.is_dirty());
3373 assert!(!buffer.has_conflict());
3374 });
3375}
3376
3377#[gpui::test(iterations = 10)]
3378async fn test_formatting_buffer(
3379 deterministic: Arc<Deterministic>,
3380 cx_a: &mut TestAppContext,
3381 cx_b: &mut TestAppContext,
3382) {
3383 use project::FormatTrigger;
3384
3385 let mut server = TestServer::start(&deterministic).await;
3386 let client_a = server.create_client(cx_a, "user_a").await;
3387 let client_b = server.create_client(cx_b, "user_b").await;
3388 server
3389 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3390 .await;
3391 let active_call_a = cx_a.read(ActiveCall::global);
3392
3393 // Set up a fake language server.
3394 let mut language = Language::new(
3395 LanguageConfig {
3396 name: "Rust".into(),
3397 path_suffixes: vec!["rs".to_string()],
3398 ..Default::default()
3399 },
3400 Some(tree_sitter_rust::language()),
3401 );
3402 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3403 client_a.language_registry.add(Arc::new(language));
3404
3405 // Here we insert a fake tree with a directory that exists on disk. This is needed
3406 // because later we'll invoke a command, which requires passing a working directory
3407 // that points to a valid location on disk.
3408 let directory = env::current_dir().unwrap();
3409 client_a
3410 .fs
3411 .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
3412 .await;
3413 let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
3414 let project_id = active_call_a
3415 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3416 .await
3417 .unwrap();
3418 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3419
3420 let buffer_b = cx_b
3421 .background()
3422 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3423 .await
3424 .unwrap();
3425
3426 let fake_language_server = fake_language_servers.next().await.unwrap();
3427 fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
3428 Ok(Some(vec![
3429 lsp::TextEdit {
3430 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
3431 new_text: "h".to_string(),
3432 },
3433 lsp::TextEdit {
3434 range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
3435 new_text: "y".to_string(),
3436 },
3437 ]))
3438 });
3439
3440 project_b
3441 .update(cx_b, |project, cx| {
3442 project.format(
3443 HashSet::from_iter([buffer_b.clone()]),
3444 true,
3445 FormatTrigger::Save,
3446 cx,
3447 )
3448 })
3449 .await
3450 .unwrap();
3451 assert_eq!(
3452 buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
3453 "let honey = \"two\""
3454 );
3455
3456 // Ensure buffer can be formatted using an external command. Notice how the
3457 // host's configuration is honored as opposed to using the guest's settings.
3458 cx_a.update(|cx| {
3459 cx.update_global(|settings: &mut Settings, _| {
3460 settings.editor_defaults.formatter = Some(Formatter::External {
3461 command: "awk".to_string(),
3462 arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
3463 });
3464 });
3465 });
3466 project_b
3467 .update(cx_b, |project, cx| {
3468 project.format(
3469 HashSet::from_iter([buffer_b.clone()]),
3470 true,
3471 FormatTrigger::Save,
3472 cx,
3473 )
3474 })
3475 .await
3476 .unwrap();
3477 assert_eq!(
3478 buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
3479 format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
3480 );
3481}
3482
3483#[gpui::test(iterations = 10)]
3484async fn test_definition(
3485 deterministic: Arc<Deterministic>,
3486 cx_a: &mut TestAppContext,
3487 cx_b: &mut TestAppContext,
3488) {
3489 deterministic.forbid_parking();
3490 let mut server = TestServer::start(&deterministic).await;
3491 let client_a = server.create_client(cx_a, "user_a").await;
3492 let client_b = server.create_client(cx_b, "user_b").await;
3493 server
3494 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3495 .await;
3496 let active_call_a = cx_a.read(ActiveCall::global);
3497
3498 // Set up a fake language server.
3499 let mut language = Language::new(
3500 LanguageConfig {
3501 name: "Rust".into(),
3502 path_suffixes: vec!["rs".to_string()],
3503 ..Default::default()
3504 },
3505 Some(tree_sitter_rust::language()),
3506 );
3507 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3508 client_a.language_registry.add(Arc::new(language));
3509
3510 client_a
3511 .fs
3512 .insert_tree(
3513 "/root",
3514 json!({
3515 "dir-1": {
3516 "a.rs": "const ONE: usize = b::TWO + b::THREE;",
3517 },
3518 "dir-2": {
3519 "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
3520 "c.rs": "type T2 = usize;",
3521 }
3522 }),
3523 )
3524 .await;
3525 let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
3526 let project_id = active_call_a
3527 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3528 .await
3529 .unwrap();
3530 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3531
3532 // Open the file on client B.
3533 let buffer_b = cx_b
3534 .background()
3535 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3536 .await
3537 .unwrap();
3538
3539 // Request the definition of a symbol as the guest.
3540 let fake_language_server = fake_language_servers.next().await.unwrap();
3541 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
3542 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3543 lsp::Location::new(
3544 lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
3545 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3546 ),
3547 )))
3548 });
3549
3550 let definitions_1 = project_b
3551 .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
3552 .await
3553 .unwrap();
3554 cx_b.read(|cx| {
3555 assert_eq!(definitions_1.len(), 1);
3556 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3557 let target_buffer = definitions_1[0].target.buffer.read(cx);
3558 assert_eq!(
3559 target_buffer.text(),
3560 "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
3561 );
3562 assert_eq!(
3563 definitions_1[0].target.range.to_point(target_buffer),
3564 Point::new(0, 6)..Point::new(0, 9)
3565 );
3566 });
3567
3568 // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
3569 // the previous call to `definition`.
3570 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
3571 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3572 lsp::Location::new(
3573 lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
3574 lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
3575 ),
3576 )))
3577 });
3578
3579 let definitions_2 = project_b
3580 .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
3581 .await
3582 .unwrap();
3583 cx_b.read(|cx| {
3584 assert_eq!(definitions_2.len(), 1);
3585 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3586 let target_buffer = definitions_2[0].target.buffer.read(cx);
3587 assert_eq!(
3588 target_buffer.text(),
3589 "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
3590 );
3591 assert_eq!(
3592 definitions_2[0].target.range.to_point(target_buffer),
3593 Point::new(1, 6)..Point::new(1, 11)
3594 );
3595 });
3596 assert_eq!(
3597 definitions_1[0].target.buffer,
3598 definitions_2[0].target.buffer
3599 );
3600
3601 fake_language_server.handle_request::<lsp::request::GotoTypeDefinition, _, _>(
3602 |req, _| async move {
3603 assert_eq!(
3604 req.text_document_position_params.position,
3605 lsp::Position::new(0, 7)
3606 );
3607 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3608 lsp::Location::new(
3609 lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
3610 lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
3611 ),
3612 )))
3613 },
3614 );
3615
3616 let type_definitions = project_b
3617 .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
3618 .await
3619 .unwrap();
3620 cx_b.read(|cx| {
3621 assert_eq!(type_definitions.len(), 1);
3622 let target_buffer = type_definitions[0].target.buffer.read(cx);
3623 assert_eq!(target_buffer.text(), "type T2 = usize;");
3624 assert_eq!(
3625 type_definitions[0].target.range.to_point(target_buffer),
3626 Point::new(0, 5)..Point::new(0, 7)
3627 );
3628 });
3629}
3630
3631#[gpui::test(iterations = 10)]
3632async fn test_references(
3633 deterministic: Arc<Deterministic>,
3634 cx_a: &mut TestAppContext,
3635 cx_b: &mut TestAppContext,
3636) {
3637 deterministic.forbid_parking();
3638 let mut server = TestServer::start(&deterministic).await;
3639 let client_a = server.create_client(cx_a, "user_a").await;
3640 let client_b = server.create_client(cx_b, "user_b").await;
3641 server
3642 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3643 .await;
3644 let active_call_a = cx_a.read(ActiveCall::global);
3645
3646 // Set up a fake language server.
3647 let mut language = Language::new(
3648 LanguageConfig {
3649 name: "Rust".into(),
3650 path_suffixes: vec!["rs".to_string()],
3651 ..Default::default()
3652 },
3653 Some(tree_sitter_rust::language()),
3654 );
3655 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3656 client_a.language_registry.add(Arc::new(language));
3657
3658 client_a
3659 .fs
3660 .insert_tree(
3661 "/root",
3662 json!({
3663 "dir-1": {
3664 "one.rs": "const ONE: usize = 1;",
3665 "two.rs": "const TWO: usize = one::ONE + one::ONE;",
3666 },
3667 "dir-2": {
3668 "three.rs": "const THREE: usize = two::TWO + one::ONE;",
3669 }
3670 }),
3671 )
3672 .await;
3673 let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
3674 let project_id = active_call_a
3675 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3676 .await
3677 .unwrap();
3678 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3679
3680 // Open the file on client B.
3681 let buffer_b = cx_b
3682 .background()
3683 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
3684 .await
3685 .unwrap();
3686
3687 // Request references to a symbol as the guest.
3688 let fake_language_server = fake_language_servers.next().await.unwrap();
3689 fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
3690 assert_eq!(
3691 params.text_document_position.text_document.uri.as_str(),
3692 "file:///root/dir-1/one.rs"
3693 );
3694 Ok(Some(vec![
3695 lsp::Location {
3696 uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
3697 range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
3698 },
3699 lsp::Location {
3700 uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
3701 range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
3702 },
3703 lsp::Location {
3704 uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
3705 range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
3706 },
3707 ]))
3708 });
3709
3710 let references = project_b
3711 .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
3712 .await
3713 .unwrap();
3714 cx_b.read(|cx| {
3715 assert_eq!(references.len(), 3);
3716 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3717
3718 let two_buffer = references[0].buffer.read(cx);
3719 let three_buffer = references[2].buffer.read(cx);
3720 assert_eq!(
3721 two_buffer.file().unwrap().path().as_ref(),
3722 Path::new("two.rs")
3723 );
3724 assert_eq!(references[1].buffer, references[0].buffer);
3725 assert_eq!(
3726 three_buffer.file().unwrap().full_path(cx),
3727 Path::new("/root/dir-2/three.rs")
3728 );
3729
3730 assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
3731 assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
3732 assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
3733 });
3734}
3735
3736#[gpui::test(iterations = 10)]
3737async fn test_project_search(
3738 deterministic: Arc<Deterministic>,
3739 cx_a: &mut TestAppContext,
3740 cx_b: &mut TestAppContext,
3741) {
3742 deterministic.forbid_parking();
3743 let mut server = TestServer::start(&deterministic).await;
3744 let client_a = server.create_client(cx_a, "user_a").await;
3745 let client_b = server.create_client(cx_b, "user_b").await;
3746 server
3747 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3748 .await;
3749 let active_call_a = cx_a.read(ActiveCall::global);
3750
3751 client_a
3752 .fs
3753 .insert_tree(
3754 "/root",
3755 json!({
3756 "dir-1": {
3757 "a": "hello world",
3758 "b": "goodnight moon",
3759 "c": "a world of goo",
3760 "d": "world champion of clown world",
3761 },
3762 "dir-2": {
3763 "e": "disney world is fun",
3764 }
3765 }),
3766 )
3767 .await;
3768 let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
3769 let (worktree_2, _) = project_a
3770 .update(cx_a, |p, cx| {
3771 p.find_or_create_local_worktree("/root/dir-2", true, cx)
3772 })
3773 .await
3774 .unwrap();
3775 worktree_2
3776 .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
3777 .await;
3778 let project_id = active_call_a
3779 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3780 .await
3781 .unwrap();
3782
3783 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3784
3785 // Perform a search as the guest.
3786 let results = project_b
3787 .update(cx_b, |project, cx| {
3788 project.search(SearchQuery::text("world", false, false), cx)
3789 })
3790 .await
3791 .unwrap();
3792
3793 let mut ranges_by_path = results
3794 .into_iter()
3795 .map(|(buffer, ranges)| {
3796 buffer.read_with(cx_b, |buffer, cx| {
3797 let path = buffer.file().unwrap().full_path(cx);
3798 let offset_ranges = ranges
3799 .into_iter()
3800 .map(|range| range.to_offset(buffer))
3801 .collect::<Vec<_>>();
3802 (path, offset_ranges)
3803 })
3804 })
3805 .collect::<Vec<_>>();
3806 ranges_by_path.sort_by_key(|(path, _)| path.clone());
3807
3808 assert_eq!(
3809 ranges_by_path,
3810 &[
3811 (PathBuf::from("dir-1/a"), vec![6..11]),
3812 (PathBuf::from("dir-1/c"), vec![2..7]),
3813 (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
3814 (PathBuf::from("dir-2/e"), vec![7..12]),
3815 ]
3816 );
3817}
3818
3819#[gpui::test(iterations = 10)]
3820async fn test_document_highlights(
3821 deterministic: Arc<Deterministic>,
3822 cx_a: &mut TestAppContext,
3823 cx_b: &mut TestAppContext,
3824) {
3825 deterministic.forbid_parking();
3826 let mut server = TestServer::start(&deterministic).await;
3827 let client_a = server.create_client(cx_a, "user_a").await;
3828 let client_b = server.create_client(cx_b, "user_b").await;
3829 server
3830 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3831 .await;
3832 let active_call_a = cx_a.read(ActiveCall::global);
3833
3834 client_a
3835 .fs
3836 .insert_tree(
3837 "/root-1",
3838 json!({
3839 "main.rs": "fn double(number: i32) -> i32 { number + number }",
3840 }),
3841 )
3842 .await;
3843
3844 // Set up a fake language server.
3845 let mut language = Language::new(
3846 LanguageConfig {
3847 name: "Rust".into(),
3848 path_suffixes: vec!["rs".to_string()],
3849 ..Default::default()
3850 },
3851 Some(tree_sitter_rust::language()),
3852 );
3853 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3854 client_a.language_registry.add(Arc::new(language));
3855
3856 let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3857 let project_id = active_call_a
3858 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3859 .await
3860 .unwrap();
3861 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3862
3863 // Open the file on client B.
3864 let buffer_b = cx_b
3865 .background()
3866 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3867 .await
3868 .unwrap();
3869
3870 // Request document highlights as the guest.
3871 let fake_language_server = fake_language_servers.next().await.unwrap();
3872 fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
3873 |params, _| async move {
3874 assert_eq!(
3875 params
3876 .text_document_position_params
3877 .text_document
3878 .uri
3879 .as_str(),
3880 "file:///root-1/main.rs"
3881 );
3882 assert_eq!(
3883 params.text_document_position_params.position,
3884 lsp::Position::new(0, 34)
3885 );
3886 Ok(Some(vec![
3887 lsp::DocumentHighlight {
3888 kind: Some(lsp::DocumentHighlightKind::WRITE),
3889 range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
3890 },
3891 lsp::DocumentHighlight {
3892 kind: Some(lsp::DocumentHighlightKind::READ),
3893 range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
3894 },
3895 lsp::DocumentHighlight {
3896 kind: Some(lsp::DocumentHighlightKind::READ),
3897 range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
3898 },
3899 ]))
3900 },
3901 );
3902
3903 let highlights = project_b
3904 .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
3905 .await
3906 .unwrap();
3907 buffer_b.read_with(cx_b, |buffer, _| {
3908 let snapshot = buffer.snapshot();
3909
3910 let highlights = highlights
3911 .into_iter()
3912 .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
3913 .collect::<Vec<_>>();
3914 assert_eq!(
3915 highlights,
3916 &[
3917 (lsp::DocumentHighlightKind::WRITE, 10..16),
3918 (lsp::DocumentHighlightKind::READ, 32..38),
3919 (lsp::DocumentHighlightKind::READ, 41..47)
3920 ]
3921 )
3922 });
3923}
3924
3925#[gpui::test(iterations = 10)]
3926async fn test_lsp_hover(
3927 deterministic: Arc<Deterministic>,
3928 cx_a: &mut TestAppContext,
3929 cx_b: &mut TestAppContext,
3930) {
3931 deterministic.forbid_parking();
3932 let mut server = TestServer::start(&deterministic).await;
3933 let client_a = server.create_client(cx_a, "user_a").await;
3934 let client_b = server.create_client(cx_b, "user_b").await;
3935 server
3936 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3937 .await;
3938 let active_call_a = cx_a.read(ActiveCall::global);
3939
3940 client_a
3941 .fs
3942 .insert_tree(
3943 "/root-1",
3944 json!({
3945 "main.rs": "use std::collections::HashMap;",
3946 }),
3947 )
3948 .await;
3949
3950 // Set up a fake language server.
3951 let mut language = Language::new(
3952 LanguageConfig {
3953 name: "Rust".into(),
3954 path_suffixes: vec!["rs".to_string()],
3955 ..Default::default()
3956 },
3957 Some(tree_sitter_rust::language()),
3958 );
3959 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3960 client_a.language_registry.add(Arc::new(language));
3961
3962 let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3963 let project_id = active_call_a
3964 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3965 .await
3966 .unwrap();
3967 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3968
3969 // Open the file as the guest
3970 let buffer_b = cx_b
3971 .background()
3972 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3973 .await
3974 .unwrap();
3975
3976 // Request hover information as the guest.
3977 let fake_language_server = fake_language_servers.next().await.unwrap();
3978 fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
3979 |params, _| async move {
3980 assert_eq!(
3981 params
3982 .text_document_position_params
3983 .text_document
3984 .uri
3985 .as_str(),
3986 "file:///root-1/main.rs"
3987 );
3988 assert_eq!(
3989 params.text_document_position_params.position,
3990 lsp::Position::new(0, 22)
3991 );
3992 Ok(Some(lsp::Hover {
3993 contents: lsp::HoverContents::Array(vec![
3994 lsp::MarkedString::String("Test hover content.".to_string()),
3995 lsp::MarkedString::LanguageString(lsp::LanguageString {
3996 language: "Rust".to_string(),
3997 value: "let foo = 42;".to_string(),
3998 }),
3999 ]),
4000 range: Some(lsp::Range::new(
4001 lsp::Position::new(0, 22),
4002 lsp::Position::new(0, 29),
4003 )),
4004 }))
4005 },
4006 );
4007
4008 let hover_info = project_b
4009 .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
4010 .await
4011 .unwrap()
4012 .unwrap();
4013 buffer_b.read_with(cx_b, |buffer, _| {
4014 let snapshot = buffer.snapshot();
4015 assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
4016 assert_eq!(
4017 hover_info.contents,
4018 vec![
4019 project::HoverBlock {
4020 text: "Test hover content.".to_string(),
4021 language: None,
4022 },
4023 project::HoverBlock {
4024 text: "let foo = 42;".to_string(),
4025 language: Some("Rust".to_string()),
4026 }
4027 ]
4028 );
4029 });
4030}
4031
4032#[gpui::test(iterations = 10)]
4033async fn test_project_symbols(
4034 deterministic: Arc<Deterministic>,
4035 cx_a: &mut TestAppContext,
4036 cx_b: &mut TestAppContext,
4037) {
4038 deterministic.forbid_parking();
4039 let mut server = TestServer::start(&deterministic).await;
4040 let client_a = server.create_client(cx_a, "user_a").await;
4041 let client_b = server.create_client(cx_b, "user_b").await;
4042 server
4043 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4044 .await;
4045 let active_call_a = cx_a.read(ActiveCall::global);
4046
4047 // Set up a fake language server.
4048 let mut language = Language::new(
4049 LanguageConfig {
4050 name: "Rust".into(),
4051 path_suffixes: vec!["rs".to_string()],
4052 ..Default::default()
4053 },
4054 Some(tree_sitter_rust::language()),
4055 );
4056 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4057 client_a.language_registry.add(Arc::new(language));
4058
4059 client_a
4060 .fs
4061 .insert_tree(
4062 "/code",
4063 json!({
4064 "crate-1": {
4065 "one.rs": "const ONE: usize = 1;",
4066 },
4067 "crate-2": {
4068 "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
4069 },
4070 "private": {
4071 "passwords.txt": "the-password",
4072 }
4073 }),
4074 )
4075 .await;
4076 let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
4077 let project_id = active_call_a
4078 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4079 .await
4080 .unwrap();
4081 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4082
4083 // Cause the language server to start.
4084 let _buffer = cx_b
4085 .background()
4086 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
4087 .await
4088 .unwrap();
4089
4090 let fake_language_server = fake_language_servers.next().await.unwrap();
4091 fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
4092 #[allow(deprecated)]
4093 Ok(Some(vec![lsp::SymbolInformation {
4094 name: "TWO".into(),
4095 location: lsp::Location {
4096 uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
4097 range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4098 },
4099 kind: lsp::SymbolKind::CONSTANT,
4100 tags: None,
4101 container_name: None,
4102 deprecated: None,
4103 }]))
4104 });
4105
4106 // Request the definition of a symbol as the guest.
4107 let symbols = project_b
4108 .update(cx_b, |p, cx| p.symbols("two", cx))
4109 .await
4110 .unwrap();
4111 assert_eq!(symbols.len(), 1);
4112 assert_eq!(symbols[0].name, "TWO");
4113
4114 // Open one of the returned symbols.
4115 let buffer_b_2 = project_b
4116 .update(cx_b, |project, cx| {
4117 project.open_buffer_for_symbol(&symbols[0], cx)
4118 })
4119 .await
4120 .unwrap();
4121 buffer_b_2.read_with(cx_b, |buffer, _| {
4122 assert_eq!(
4123 buffer.file().unwrap().path().as_ref(),
4124 Path::new("../crate-2/two.rs")
4125 );
4126 });
4127
4128 // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
4129 let mut fake_symbol = symbols[0].clone();
4130 fake_symbol.path.path = Path::new("/code/secrets").into();
4131 let error = project_b
4132 .update(cx_b, |project, cx| {
4133 project.open_buffer_for_symbol(&fake_symbol, cx)
4134 })
4135 .await
4136 .unwrap_err();
4137 assert!(error.to_string().contains("invalid symbol signature"));
4138}
4139
4140#[gpui::test(iterations = 10)]
4141async fn test_open_buffer_while_getting_definition_pointing_to_it(
4142 deterministic: Arc<Deterministic>,
4143 cx_a: &mut TestAppContext,
4144 cx_b: &mut TestAppContext,
4145 mut rng: StdRng,
4146) {
4147 deterministic.forbid_parking();
4148 let mut server = TestServer::start(&deterministic).await;
4149 let client_a = server.create_client(cx_a, "user_a").await;
4150 let client_b = server.create_client(cx_b, "user_b").await;
4151 server
4152 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4153 .await;
4154 let active_call_a = cx_a.read(ActiveCall::global);
4155
4156 // Set up a fake language server.
4157 let mut language = Language::new(
4158 LanguageConfig {
4159 name: "Rust".into(),
4160 path_suffixes: vec!["rs".to_string()],
4161 ..Default::default()
4162 },
4163 Some(tree_sitter_rust::language()),
4164 );
4165 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4166 client_a.language_registry.add(Arc::new(language));
4167
4168 client_a
4169 .fs
4170 .insert_tree(
4171 "/root",
4172 json!({
4173 "a.rs": "const ONE: usize = b::TWO;",
4174 "b.rs": "const TWO: usize = 2",
4175 }),
4176 )
4177 .await;
4178 let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
4179 let project_id = active_call_a
4180 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4181 .await
4182 .unwrap();
4183 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4184
4185 let buffer_b1 = cx_b
4186 .background()
4187 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
4188 .await
4189 .unwrap();
4190
4191 let fake_language_server = fake_language_servers.next().await.unwrap();
4192 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
4193 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4194 lsp::Location::new(
4195 lsp::Url::from_file_path("/root/b.rs").unwrap(),
4196 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4197 ),
4198 )))
4199 });
4200
4201 let definitions;
4202 let buffer_b2;
4203 if rng.gen() {
4204 definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
4205 buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
4206 } else {
4207 buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
4208 definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
4209 }
4210
4211 let buffer_b2 = buffer_b2.await.unwrap();
4212 let definitions = definitions.await.unwrap();
4213 assert_eq!(definitions.len(), 1);
4214 assert_eq!(definitions[0].target.buffer, buffer_b2);
4215}
4216
4217#[gpui::test(iterations = 10)]
4218async fn test_collaborating_with_code_actions(
4219 deterministic: Arc<Deterministic>,
4220 cx_a: &mut TestAppContext,
4221 cx_b: &mut TestAppContext,
4222) {
4223 deterministic.forbid_parking();
4224 cx_b.update(editor::init);
4225 let mut server = TestServer::start(&deterministic).await;
4226 let client_a = server.create_client(cx_a, "user_a").await;
4227 let client_b = server.create_client(cx_b, "user_b").await;
4228 server
4229 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4230 .await;
4231 let active_call_a = cx_a.read(ActiveCall::global);
4232
4233 // Set up a fake language server.
4234 let mut language = Language::new(
4235 LanguageConfig {
4236 name: "Rust".into(),
4237 path_suffixes: vec!["rs".to_string()],
4238 ..Default::default()
4239 },
4240 Some(tree_sitter_rust::language()),
4241 );
4242 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4243 client_a.language_registry.add(Arc::new(language));
4244
4245 client_a
4246 .fs
4247 .insert_tree(
4248 "/a",
4249 json!({
4250 "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
4251 "other.rs": "pub fn foo() -> usize { 4 }",
4252 }),
4253 )
4254 .await;
4255 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4256 let project_id = active_call_a
4257 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4258 .await
4259 .unwrap();
4260
4261 // Join the project as client B.
4262 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4263 let (_window_b, workspace_b) = cx_b.add_window(|cx| {
4264 Workspace::new(
4265 Default::default(),
4266 0,
4267 project_b.clone(),
4268 |_, _| unimplemented!(),
4269 cx,
4270 )
4271 });
4272 let editor_b = workspace_b
4273 .update(cx_b, |workspace, cx| {
4274 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
4275 })
4276 .await
4277 .unwrap()
4278 .downcast::<Editor>()
4279 .unwrap();
4280
4281 let mut fake_language_server = fake_language_servers.next().await.unwrap();
4282 fake_language_server
4283 .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
4284 assert_eq!(
4285 params.text_document.uri,
4286 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4287 );
4288 assert_eq!(params.range.start, lsp::Position::new(0, 0));
4289 assert_eq!(params.range.end, lsp::Position::new(0, 0));
4290 Ok(None)
4291 })
4292 .next()
4293 .await;
4294
4295 // Move cursor to a location that contains code actions.
4296 editor_b.update(cx_b, |editor, cx| {
4297 editor.change_selections(None, cx, |s| {
4298 s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
4299 });
4300 cx.focus(&editor_b);
4301 });
4302
4303 fake_language_server
4304 .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
4305 assert_eq!(
4306 params.text_document.uri,
4307 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4308 );
4309 assert_eq!(params.range.start, lsp::Position::new(1, 31));
4310 assert_eq!(params.range.end, lsp::Position::new(1, 31));
4311
4312 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4313 lsp::CodeAction {
4314 title: "Inline into all callers".to_string(),
4315 edit: Some(lsp::WorkspaceEdit {
4316 changes: Some(
4317 [
4318 (
4319 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4320 vec![lsp::TextEdit::new(
4321 lsp::Range::new(
4322 lsp::Position::new(1, 22),
4323 lsp::Position::new(1, 34),
4324 ),
4325 "4".to_string(),
4326 )],
4327 ),
4328 (
4329 lsp::Url::from_file_path("/a/other.rs").unwrap(),
4330 vec![lsp::TextEdit::new(
4331 lsp::Range::new(
4332 lsp::Position::new(0, 0),
4333 lsp::Position::new(0, 27),
4334 ),
4335 "".to_string(),
4336 )],
4337 ),
4338 ]
4339 .into_iter()
4340 .collect(),
4341 ),
4342 ..Default::default()
4343 }),
4344 data: Some(json!({
4345 "codeActionParams": {
4346 "range": {
4347 "start": {"line": 1, "column": 31},
4348 "end": {"line": 1, "column": 31},
4349 }
4350 }
4351 })),
4352 ..Default::default()
4353 },
4354 )]))
4355 })
4356 .next()
4357 .await;
4358
4359 // Toggle code actions and wait for them to display.
4360 editor_b.update(cx_b, |editor, cx| {
4361 editor.toggle_code_actions(
4362 &ToggleCodeActions {
4363 deployed_from_indicator: false,
4364 },
4365 cx,
4366 );
4367 });
4368 cx_a.foreground().run_until_parked();
4369 editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
4370
4371 fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
4372
4373 // Confirming the code action will trigger a resolve request.
4374 let confirm_action = workspace_b
4375 .update(cx_b, |workspace, cx| {
4376 Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
4377 })
4378 .unwrap();
4379 fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
4380 |_, _| async move {
4381 Ok(lsp::CodeAction {
4382 title: "Inline into all callers".to_string(),
4383 edit: Some(lsp::WorkspaceEdit {
4384 changes: Some(
4385 [
4386 (
4387 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4388 vec![lsp::TextEdit::new(
4389 lsp::Range::new(
4390 lsp::Position::new(1, 22),
4391 lsp::Position::new(1, 34),
4392 ),
4393 "4".to_string(),
4394 )],
4395 ),
4396 (
4397 lsp::Url::from_file_path("/a/other.rs").unwrap(),
4398 vec![lsp::TextEdit::new(
4399 lsp::Range::new(
4400 lsp::Position::new(0, 0),
4401 lsp::Position::new(0, 27),
4402 ),
4403 "".to_string(),
4404 )],
4405 ),
4406 ]
4407 .into_iter()
4408 .collect(),
4409 ),
4410 ..Default::default()
4411 }),
4412 ..Default::default()
4413 })
4414 },
4415 );
4416
4417 // After the action is confirmed, an editor containing both modified files is opened.
4418 confirm_action.await.unwrap();
4419 let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
4420 workspace
4421 .active_item(cx)
4422 .unwrap()
4423 .downcast::<Editor>()
4424 .unwrap()
4425 });
4426 code_action_editor.update(cx_b, |editor, cx| {
4427 assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
4428 editor.undo(&Undo, cx);
4429 assert_eq!(
4430 editor.text(cx),
4431 "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
4432 );
4433 editor.redo(&Redo, cx);
4434 assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
4435 });
4436}
4437
4438#[gpui::test(iterations = 10)]
4439async fn test_collaborating_with_renames(
4440 deterministic: Arc<Deterministic>,
4441 cx_a: &mut TestAppContext,
4442 cx_b: &mut TestAppContext,
4443) {
4444 deterministic.forbid_parking();
4445 cx_b.update(editor::init);
4446 let mut server = TestServer::start(&deterministic).await;
4447 let client_a = server.create_client(cx_a, "user_a").await;
4448 let client_b = server.create_client(cx_b, "user_b").await;
4449 server
4450 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4451 .await;
4452 let active_call_a = cx_a.read(ActiveCall::global);
4453
4454 // Set up a fake language server.
4455 let mut language = Language::new(
4456 LanguageConfig {
4457 name: "Rust".into(),
4458 path_suffixes: vec!["rs".to_string()],
4459 ..Default::default()
4460 },
4461 Some(tree_sitter_rust::language()),
4462 );
4463 let mut fake_language_servers = language
4464 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4465 capabilities: lsp::ServerCapabilities {
4466 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
4467 prepare_provider: Some(true),
4468 work_done_progress_options: Default::default(),
4469 })),
4470 ..Default::default()
4471 },
4472 ..Default::default()
4473 }))
4474 .await;
4475 client_a.language_registry.add(Arc::new(language));
4476
4477 client_a
4478 .fs
4479 .insert_tree(
4480 "/dir",
4481 json!({
4482 "one.rs": "const ONE: usize = 1;",
4483 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
4484 }),
4485 )
4486 .await;
4487 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
4488 let project_id = active_call_a
4489 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4490 .await
4491 .unwrap();
4492 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4493
4494 let (_window_b, workspace_b) = cx_b.add_window(|cx| {
4495 Workspace::new(
4496 Default::default(),
4497 0,
4498 project_b.clone(),
4499 |_, _| unimplemented!(),
4500 cx,
4501 )
4502 });
4503 let editor_b = workspace_b
4504 .update(cx_b, |workspace, cx| {
4505 workspace.open_path((worktree_id, "one.rs"), None, true, cx)
4506 })
4507 .await
4508 .unwrap()
4509 .downcast::<Editor>()
4510 .unwrap();
4511 let fake_language_server = fake_language_servers.next().await.unwrap();
4512
4513 // Move cursor to a location that can be renamed.
4514 let prepare_rename = editor_b.update(cx_b, |editor, cx| {
4515 editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
4516 editor.rename(&Rename, cx).unwrap()
4517 });
4518
4519 fake_language_server
4520 .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
4521 assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
4522 assert_eq!(params.position, lsp::Position::new(0, 7));
4523 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4524 lsp::Position::new(0, 6),
4525 lsp::Position::new(0, 9),
4526 ))))
4527 })
4528 .next()
4529 .await
4530 .unwrap();
4531 prepare_rename.await.unwrap();
4532 editor_b.update(cx_b, |editor, cx| {
4533 let rename = editor.pending_rename().unwrap();
4534 let buffer = editor.buffer().read(cx).snapshot(cx);
4535 assert_eq!(
4536 rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
4537 6..9
4538 );
4539 rename.editor.update(cx, |rename_editor, cx| {
4540 rename_editor.buffer().update(cx, |rename_buffer, cx| {
4541 rename_buffer.edit([(0..3, "THREE")], None, cx);
4542 });
4543 });
4544 });
4545
4546 let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
4547 Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
4548 });
4549 fake_language_server
4550 .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
4551 assert_eq!(
4552 params.text_document_position.text_document.uri.as_str(),
4553 "file:///dir/one.rs"
4554 );
4555 assert_eq!(
4556 params.text_document_position.position,
4557 lsp::Position::new(0, 6)
4558 );
4559 assert_eq!(params.new_name, "THREE");
4560 Ok(Some(lsp::WorkspaceEdit {
4561 changes: Some(
4562 [
4563 (
4564 lsp::Url::from_file_path("/dir/one.rs").unwrap(),
4565 vec![lsp::TextEdit::new(
4566 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4567 "THREE".to_string(),
4568 )],
4569 ),
4570 (
4571 lsp::Url::from_file_path("/dir/two.rs").unwrap(),
4572 vec![
4573 lsp::TextEdit::new(
4574 lsp::Range::new(
4575 lsp::Position::new(0, 24),
4576 lsp::Position::new(0, 27),
4577 ),
4578 "THREE".to_string(),
4579 ),
4580 lsp::TextEdit::new(
4581 lsp::Range::new(
4582 lsp::Position::new(0, 35),
4583 lsp::Position::new(0, 38),
4584 ),
4585 "THREE".to_string(),
4586 ),
4587 ],
4588 ),
4589 ]
4590 .into_iter()
4591 .collect(),
4592 ),
4593 ..Default::default()
4594 }))
4595 })
4596 .next()
4597 .await
4598 .unwrap();
4599 confirm_rename.await.unwrap();
4600
4601 let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
4602 workspace
4603 .active_item(cx)
4604 .unwrap()
4605 .downcast::<Editor>()
4606 .unwrap()
4607 });
4608 rename_editor.update(cx_b, |editor, cx| {
4609 assert_eq!(
4610 editor.text(cx),
4611 "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
4612 );
4613 editor.undo(&Undo, cx);
4614 assert_eq!(
4615 editor.text(cx),
4616 "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
4617 );
4618 editor.redo(&Redo, cx);
4619 assert_eq!(
4620 editor.text(cx),
4621 "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
4622 );
4623 });
4624
4625 // Ensure temporary rename edits cannot be undone/redone.
4626 editor_b.update(cx_b, |editor, cx| {
4627 editor.undo(&Undo, cx);
4628 assert_eq!(editor.text(cx), "const ONE: usize = 1;");
4629 editor.undo(&Undo, cx);
4630 assert_eq!(editor.text(cx), "const ONE: usize = 1;");
4631 editor.redo(&Redo, cx);
4632 assert_eq!(editor.text(cx), "const THREE: usize = 1;");
4633 })
4634}
4635
4636#[gpui::test(iterations = 10)]
4637async fn test_language_server_statuses(
4638 deterministic: Arc<Deterministic>,
4639 cx_a: &mut TestAppContext,
4640 cx_b: &mut TestAppContext,
4641) {
4642 deterministic.forbid_parking();
4643
4644 cx_b.update(editor::init);
4645 let mut server = TestServer::start(&deterministic).await;
4646 let client_a = server.create_client(cx_a, "user_a").await;
4647 let client_b = server.create_client(cx_b, "user_b").await;
4648 server
4649 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4650 .await;
4651 let active_call_a = cx_a.read(ActiveCall::global);
4652
4653 // Set up a fake language server.
4654 let mut language = Language::new(
4655 LanguageConfig {
4656 name: "Rust".into(),
4657 path_suffixes: vec!["rs".to_string()],
4658 ..Default::default()
4659 },
4660 Some(tree_sitter_rust::language()),
4661 );
4662 let mut fake_language_servers = language
4663 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4664 name: "the-language-server",
4665 ..Default::default()
4666 }))
4667 .await;
4668 client_a.language_registry.add(Arc::new(language));
4669
4670 client_a
4671 .fs
4672 .insert_tree(
4673 "/dir",
4674 json!({
4675 "main.rs": "const ONE: usize = 1;",
4676 }),
4677 )
4678 .await;
4679 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
4680
4681 let _buffer_a = project_a
4682 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
4683 .await
4684 .unwrap();
4685
4686 let fake_language_server = fake_language_servers.next().await.unwrap();
4687 fake_language_server.start_progress("the-token").await;
4688 fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4689 token: lsp::NumberOrString::String("the-token".to_string()),
4690 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
4691 lsp::WorkDoneProgressReport {
4692 message: Some("the-message".to_string()),
4693 ..Default::default()
4694 },
4695 )),
4696 });
4697 deterministic.run_until_parked();
4698 project_a.read_with(cx_a, |project, _| {
4699 let status = project.language_server_statuses().next().unwrap();
4700 assert_eq!(status.name, "the-language-server");
4701 assert_eq!(status.pending_work.len(), 1);
4702 assert_eq!(
4703 status.pending_work["the-token"].message.as_ref().unwrap(),
4704 "the-message"
4705 );
4706 });
4707
4708 let project_id = active_call_a
4709 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4710 .await
4711 .unwrap();
4712 deterministic.run_until_parked();
4713 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4714 project_b.read_with(cx_b, |project, _| {
4715 let status = project.language_server_statuses().next().unwrap();
4716 assert_eq!(status.name, "the-language-server");
4717 });
4718
4719 fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4720 token: lsp::NumberOrString::String("the-token".to_string()),
4721 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
4722 lsp::WorkDoneProgressReport {
4723 message: Some("the-message-2".to_string()),
4724 ..Default::default()
4725 },
4726 )),
4727 });
4728 deterministic.run_until_parked();
4729 project_a.read_with(cx_a, |project, _| {
4730 let status = project.language_server_statuses().next().unwrap();
4731 assert_eq!(status.name, "the-language-server");
4732 assert_eq!(status.pending_work.len(), 1);
4733 assert_eq!(
4734 status.pending_work["the-token"].message.as_ref().unwrap(),
4735 "the-message-2"
4736 );
4737 });
4738 project_b.read_with(cx_b, |project, _| {
4739 let status = project.language_server_statuses().next().unwrap();
4740 assert_eq!(status.name, "the-language-server");
4741 assert_eq!(status.pending_work.len(), 1);
4742 assert_eq!(
4743 status.pending_work["the-token"].message.as_ref().unwrap(),
4744 "the-message-2"
4745 );
4746 });
4747}
4748
4749#[gpui::test(iterations = 10)]
4750async fn test_contacts(
4751 deterministic: Arc<Deterministic>,
4752 cx_a: &mut TestAppContext,
4753 cx_b: &mut TestAppContext,
4754 cx_c: &mut TestAppContext,
4755 cx_d: &mut TestAppContext,
4756) {
4757 deterministic.forbid_parking();
4758 let mut server = TestServer::start(&deterministic).await;
4759 let client_a = server.create_client(cx_a, "user_a").await;
4760 let client_b = server.create_client(cx_b, "user_b").await;
4761 let client_c = server.create_client(cx_c, "user_c").await;
4762 let client_d = server.create_client(cx_d, "user_d").await;
4763 server
4764 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4765 .await;
4766 let active_call_a = cx_a.read(ActiveCall::global);
4767 let active_call_b = cx_b.read(ActiveCall::global);
4768 let active_call_c = cx_c.read(ActiveCall::global);
4769 let _active_call_d = cx_d.read(ActiveCall::global);
4770
4771 deterministic.run_until_parked();
4772 assert_eq!(
4773 contacts(&client_a, cx_a),
4774 [
4775 ("user_b".to_string(), "online", "free"),
4776 ("user_c".to_string(), "online", "free")
4777 ]
4778 );
4779 assert_eq!(
4780 contacts(&client_b, cx_b),
4781 [
4782 ("user_a".to_string(), "online", "free"),
4783 ("user_c".to_string(), "online", "free")
4784 ]
4785 );
4786 assert_eq!(
4787 contacts(&client_c, cx_c),
4788 [
4789 ("user_a".to_string(), "online", "free"),
4790 ("user_b".to_string(), "online", "free")
4791 ]
4792 );
4793 assert_eq!(contacts(&client_d, cx_d), []);
4794
4795 server.disconnect_client(client_c.peer_id().unwrap());
4796 server.forbid_connections();
4797 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
4798 assert_eq!(
4799 contacts(&client_a, cx_a),
4800 [
4801 ("user_b".to_string(), "online", "free"),
4802 ("user_c".to_string(), "offline", "free")
4803 ]
4804 );
4805 assert_eq!(
4806 contacts(&client_b, cx_b),
4807 [
4808 ("user_a".to_string(), "online", "free"),
4809 ("user_c".to_string(), "offline", "free")
4810 ]
4811 );
4812 assert_eq!(contacts(&client_c, cx_c), []);
4813 assert_eq!(contacts(&client_d, cx_d), []);
4814
4815 server.allow_connections();
4816 client_c
4817 .authenticate_and_connect(false, &cx_c.to_async())
4818 .await
4819 .unwrap();
4820
4821 deterministic.run_until_parked();
4822 assert_eq!(
4823 contacts(&client_a, cx_a),
4824 [
4825 ("user_b".to_string(), "online", "free"),
4826 ("user_c".to_string(), "online", "free")
4827 ]
4828 );
4829 assert_eq!(
4830 contacts(&client_b, cx_b),
4831 [
4832 ("user_a".to_string(), "online", "free"),
4833 ("user_c".to_string(), "online", "free")
4834 ]
4835 );
4836 assert_eq!(
4837 contacts(&client_c, cx_c),
4838 [
4839 ("user_a".to_string(), "online", "free"),
4840 ("user_b".to_string(), "online", "free")
4841 ]
4842 );
4843 assert_eq!(contacts(&client_d, cx_d), []);
4844
4845 active_call_a
4846 .update(cx_a, |call, cx| {
4847 call.invite(client_b.user_id().unwrap(), None, cx)
4848 })
4849 .await
4850 .unwrap();
4851 deterministic.run_until_parked();
4852 assert_eq!(
4853 contacts(&client_a, cx_a),
4854 [
4855 ("user_b".to_string(), "online", "busy"),
4856 ("user_c".to_string(), "online", "free")
4857 ]
4858 );
4859 assert_eq!(
4860 contacts(&client_b, cx_b),
4861 [
4862 ("user_a".to_string(), "online", "busy"),
4863 ("user_c".to_string(), "online", "free")
4864 ]
4865 );
4866 assert_eq!(
4867 contacts(&client_c, cx_c),
4868 [
4869 ("user_a".to_string(), "online", "busy"),
4870 ("user_b".to_string(), "online", "busy")
4871 ]
4872 );
4873 assert_eq!(contacts(&client_d, cx_d), []);
4874
4875 // Client B and client D become contacts while client B is being called.
4876 server
4877 .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
4878 .await;
4879 deterministic.run_until_parked();
4880 assert_eq!(
4881 contacts(&client_a, cx_a),
4882 [
4883 ("user_b".to_string(), "online", "busy"),
4884 ("user_c".to_string(), "online", "free")
4885 ]
4886 );
4887 assert_eq!(
4888 contacts(&client_b, cx_b),
4889 [
4890 ("user_a".to_string(), "online", "busy"),
4891 ("user_c".to_string(), "online", "free"),
4892 ("user_d".to_string(), "online", "free"),
4893 ]
4894 );
4895 assert_eq!(
4896 contacts(&client_c, cx_c),
4897 [
4898 ("user_a".to_string(), "online", "busy"),
4899 ("user_b".to_string(), "online", "busy")
4900 ]
4901 );
4902 assert_eq!(
4903 contacts(&client_d, cx_d),
4904 [("user_b".to_string(), "online", "busy")]
4905 );
4906
4907 active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap());
4908 deterministic.run_until_parked();
4909 assert_eq!(
4910 contacts(&client_a, cx_a),
4911 [
4912 ("user_b".to_string(), "online", "free"),
4913 ("user_c".to_string(), "online", "free")
4914 ]
4915 );
4916 assert_eq!(
4917 contacts(&client_b, cx_b),
4918 [
4919 ("user_a".to_string(), "online", "free"),
4920 ("user_c".to_string(), "online", "free"),
4921 ("user_d".to_string(), "online", "free")
4922 ]
4923 );
4924 assert_eq!(
4925 contacts(&client_c, cx_c),
4926 [
4927 ("user_a".to_string(), "online", "free"),
4928 ("user_b".to_string(), "online", "free")
4929 ]
4930 );
4931 assert_eq!(
4932 contacts(&client_d, cx_d),
4933 [("user_b".to_string(), "online", "free")]
4934 );
4935
4936 active_call_c
4937 .update(cx_c, |call, cx| {
4938 call.invite(client_a.user_id().unwrap(), None, cx)
4939 })
4940 .await
4941 .unwrap();
4942 deterministic.run_until_parked();
4943 assert_eq!(
4944 contacts(&client_a, cx_a),
4945 [
4946 ("user_b".to_string(), "online", "free"),
4947 ("user_c".to_string(), "online", "busy")
4948 ]
4949 );
4950 assert_eq!(
4951 contacts(&client_b, cx_b),
4952 [
4953 ("user_a".to_string(), "online", "busy"),
4954 ("user_c".to_string(), "online", "busy"),
4955 ("user_d".to_string(), "online", "free")
4956 ]
4957 );
4958 assert_eq!(
4959 contacts(&client_c, cx_c),
4960 [
4961 ("user_a".to_string(), "online", "busy"),
4962 ("user_b".to_string(), "online", "free")
4963 ]
4964 );
4965 assert_eq!(
4966 contacts(&client_d, cx_d),
4967 [("user_b".to_string(), "online", "free")]
4968 );
4969
4970 active_call_a
4971 .update(cx_a, |call, cx| call.accept_incoming(cx))
4972 .await
4973 .unwrap();
4974 deterministic.run_until_parked();
4975 assert_eq!(
4976 contacts(&client_a, cx_a),
4977 [
4978 ("user_b".to_string(), "online", "free"),
4979 ("user_c".to_string(), "online", "busy")
4980 ]
4981 );
4982 assert_eq!(
4983 contacts(&client_b, cx_b),
4984 [
4985 ("user_a".to_string(), "online", "busy"),
4986 ("user_c".to_string(), "online", "busy"),
4987 ("user_d".to_string(), "online", "free")
4988 ]
4989 );
4990 assert_eq!(
4991 contacts(&client_c, cx_c),
4992 [
4993 ("user_a".to_string(), "online", "busy"),
4994 ("user_b".to_string(), "online", "free")
4995 ]
4996 );
4997 assert_eq!(
4998 contacts(&client_d, cx_d),
4999 [("user_b".to_string(), "online", "free")]
5000 );
5001
5002 active_call_a
5003 .update(cx_a, |call, cx| {
5004 call.invite(client_b.user_id().unwrap(), None, cx)
5005 })
5006 .await
5007 .unwrap();
5008 deterministic.run_until_parked();
5009 assert_eq!(
5010 contacts(&client_a, cx_a),
5011 [
5012 ("user_b".to_string(), "online", "busy"),
5013 ("user_c".to_string(), "online", "busy")
5014 ]
5015 );
5016 assert_eq!(
5017 contacts(&client_b, cx_b),
5018 [
5019 ("user_a".to_string(), "online", "busy"),
5020 ("user_c".to_string(), "online", "busy"),
5021 ("user_d".to_string(), "online", "free")
5022 ]
5023 );
5024 assert_eq!(
5025 contacts(&client_c, cx_c),
5026 [
5027 ("user_a".to_string(), "online", "busy"),
5028 ("user_b".to_string(), "online", "busy")
5029 ]
5030 );
5031 assert_eq!(
5032 contacts(&client_d, cx_d),
5033 [("user_b".to_string(), "online", "busy")]
5034 );
5035
5036 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
5037 deterministic.run_until_parked();
5038 assert_eq!(
5039 contacts(&client_a, cx_a),
5040 [
5041 ("user_b".to_string(), "online", "free"),
5042 ("user_c".to_string(), "online", "free")
5043 ]
5044 );
5045 assert_eq!(
5046 contacts(&client_b, cx_b),
5047 [
5048 ("user_a".to_string(), "online", "free"),
5049 ("user_c".to_string(), "online", "free"),
5050 ("user_d".to_string(), "online", "free")
5051 ]
5052 );
5053 assert_eq!(
5054 contacts(&client_c, cx_c),
5055 [
5056 ("user_a".to_string(), "online", "free"),
5057 ("user_b".to_string(), "online", "free")
5058 ]
5059 );
5060 assert_eq!(
5061 contacts(&client_d, cx_d),
5062 [("user_b".to_string(), "online", "free")]
5063 );
5064
5065 active_call_a
5066 .update(cx_a, |call, cx| {
5067 call.invite(client_b.user_id().unwrap(), None, cx)
5068 })
5069 .await
5070 .unwrap();
5071 deterministic.run_until_parked();
5072 assert_eq!(
5073 contacts(&client_a, cx_a),
5074 [
5075 ("user_b".to_string(), "online", "busy"),
5076 ("user_c".to_string(), "online", "free")
5077 ]
5078 );
5079 assert_eq!(
5080 contacts(&client_b, cx_b),
5081 [
5082 ("user_a".to_string(), "online", "busy"),
5083 ("user_c".to_string(), "online", "free"),
5084 ("user_d".to_string(), "online", "free")
5085 ]
5086 );
5087 assert_eq!(
5088 contacts(&client_c, cx_c),
5089 [
5090 ("user_a".to_string(), "online", "busy"),
5091 ("user_b".to_string(), "online", "busy")
5092 ]
5093 );
5094 assert_eq!(
5095 contacts(&client_d, cx_d),
5096 [("user_b".to_string(), "online", "busy")]
5097 );
5098
5099 server.forbid_connections();
5100 server.disconnect_client(client_a.peer_id().unwrap());
5101 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5102 assert_eq!(contacts(&client_a, cx_a), []);
5103 assert_eq!(
5104 contacts(&client_b, cx_b),
5105 [
5106 ("user_a".to_string(), "offline", "free"),
5107 ("user_c".to_string(), "online", "free"),
5108 ("user_d".to_string(), "online", "free")
5109 ]
5110 );
5111 assert_eq!(
5112 contacts(&client_c, cx_c),
5113 [
5114 ("user_a".to_string(), "offline", "free"),
5115 ("user_b".to_string(), "online", "free")
5116 ]
5117 );
5118 assert_eq!(
5119 contacts(&client_d, cx_d),
5120 [("user_b".to_string(), "online", "free")]
5121 );
5122
5123 fn contacts(
5124 client: &TestClient,
5125 cx: &TestAppContext,
5126 ) -> Vec<(String, &'static str, &'static str)> {
5127 client.user_store.read_with(cx, |store, _| {
5128 store
5129 .contacts()
5130 .iter()
5131 .map(|contact| {
5132 (
5133 contact.user.github_login.clone(),
5134 if contact.online { "online" } else { "offline" },
5135 if contact.busy { "busy" } else { "free" },
5136 )
5137 })
5138 .collect()
5139 })
5140 }
5141}
5142
5143#[gpui::test(iterations = 10)]
5144async fn test_contact_requests(
5145 deterministic: Arc<Deterministic>,
5146 cx_a: &mut TestAppContext,
5147 cx_a2: &mut TestAppContext,
5148 cx_b: &mut TestAppContext,
5149 cx_b2: &mut TestAppContext,
5150 cx_c: &mut TestAppContext,
5151 cx_c2: &mut TestAppContext,
5152) {
5153 deterministic.forbid_parking();
5154
5155 // Connect to a server as 3 clients.
5156 let mut server = TestServer::start(&deterministic).await;
5157 let client_a = server.create_client(cx_a, "user_a").await;
5158 let client_a2 = server.create_client(cx_a2, "user_a").await;
5159 let client_b = server.create_client(cx_b, "user_b").await;
5160 let client_b2 = server.create_client(cx_b2, "user_b").await;
5161 let client_c = server.create_client(cx_c, "user_c").await;
5162 let client_c2 = server.create_client(cx_c2, "user_c").await;
5163
5164 assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
5165 assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
5166 assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
5167
5168 // User A and User C request that user B become their contact.
5169 client_a
5170 .user_store
5171 .update(cx_a, |store, cx| {
5172 store.request_contact(client_b.user_id().unwrap(), cx)
5173 })
5174 .await
5175 .unwrap();
5176 client_c
5177 .user_store
5178 .update(cx_c, |store, cx| {
5179 store.request_contact(client_b.user_id().unwrap(), cx)
5180 })
5181 .await
5182 .unwrap();
5183 deterministic.run_until_parked();
5184
5185 // All users see the pending request appear in all their clients.
5186 assert_eq!(
5187 client_a.summarize_contacts(cx_a).outgoing_requests,
5188 &["user_b"]
5189 );
5190 assert_eq!(
5191 client_a2.summarize_contacts(cx_a2).outgoing_requests,
5192 &["user_b"]
5193 );
5194 assert_eq!(
5195 client_b.summarize_contacts(cx_b).incoming_requests,
5196 &["user_a", "user_c"]
5197 );
5198 assert_eq!(
5199 client_b2.summarize_contacts(cx_b2).incoming_requests,
5200 &["user_a", "user_c"]
5201 );
5202 assert_eq!(
5203 client_c.summarize_contacts(cx_c).outgoing_requests,
5204 &["user_b"]
5205 );
5206 assert_eq!(
5207 client_c2.summarize_contacts(cx_c2).outgoing_requests,
5208 &["user_b"]
5209 );
5210
5211 // Contact requests are present upon connecting (tested here via disconnect/reconnect)
5212 disconnect_and_reconnect(&client_a, cx_a).await;
5213 disconnect_and_reconnect(&client_b, cx_b).await;
5214 disconnect_and_reconnect(&client_c, cx_c).await;
5215 deterministic.run_until_parked();
5216 assert_eq!(
5217 client_a.summarize_contacts(cx_a).outgoing_requests,
5218 &["user_b"]
5219 );
5220 assert_eq!(
5221 client_b.summarize_contacts(cx_b).incoming_requests,
5222 &["user_a", "user_c"]
5223 );
5224 assert_eq!(
5225 client_c.summarize_contacts(cx_c).outgoing_requests,
5226 &["user_b"]
5227 );
5228
5229 // User B accepts the request from user A.
5230 client_b
5231 .user_store
5232 .update(cx_b, |store, cx| {
5233 store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
5234 })
5235 .await
5236 .unwrap();
5237
5238 deterministic.run_until_parked();
5239
5240 // User B sees user A as their contact now in all client, and the incoming request from them is removed.
5241 let contacts_b = client_b.summarize_contacts(cx_b);
5242 assert_eq!(contacts_b.current, &["user_a"]);
5243 assert_eq!(contacts_b.incoming_requests, &["user_c"]);
5244 let contacts_b2 = client_b2.summarize_contacts(cx_b2);
5245 assert_eq!(contacts_b2.current, &["user_a"]);
5246 assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
5247
5248 // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
5249 let contacts_a = client_a.summarize_contacts(cx_a);
5250 assert_eq!(contacts_a.current, &["user_b"]);
5251 assert!(contacts_a.outgoing_requests.is_empty());
5252 let contacts_a2 = client_a2.summarize_contacts(cx_a2);
5253 assert_eq!(contacts_a2.current, &["user_b"]);
5254 assert!(contacts_a2.outgoing_requests.is_empty());
5255
5256 // Contacts are present upon connecting (tested here via disconnect/reconnect)
5257 disconnect_and_reconnect(&client_a, cx_a).await;
5258 disconnect_and_reconnect(&client_b, cx_b).await;
5259 disconnect_and_reconnect(&client_c, cx_c).await;
5260 deterministic.run_until_parked();
5261 assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
5262 assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
5263 assert_eq!(
5264 client_b.summarize_contacts(cx_b).incoming_requests,
5265 &["user_c"]
5266 );
5267 assert!(client_c.summarize_contacts(cx_c).current.is_empty());
5268 assert_eq!(
5269 client_c.summarize_contacts(cx_c).outgoing_requests,
5270 &["user_b"]
5271 );
5272
5273 // User B rejects the request from user C.
5274 client_b
5275 .user_store
5276 .update(cx_b, |store, cx| {
5277 store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
5278 })
5279 .await
5280 .unwrap();
5281
5282 deterministic.run_until_parked();
5283
5284 // User B doesn't see user C as their contact, and the incoming request from them is removed.
5285 let contacts_b = client_b.summarize_contacts(cx_b);
5286 assert_eq!(contacts_b.current, &["user_a"]);
5287 assert!(contacts_b.incoming_requests.is_empty());
5288 let contacts_b2 = client_b2.summarize_contacts(cx_b2);
5289 assert_eq!(contacts_b2.current, &["user_a"]);
5290 assert!(contacts_b2.incoming_requests.is_empty());
5291
5292 // User C doesn't see user B as their contact, and the outgoing request to them is removed.
5293 let contacts_c = client_c.summarize_contacts(cx_c);
5294 assert!(contacts_c.current.is_empty());
5295 assert!(contacts_c.outgoing_requests.is_empty());
5296 let contacts_c2 = client_c2.summarize_contacts(cx_c2);
5297 assert!(contacts_c2.current.is_empty());
5298 assert!(contacts_c2.outgoing_requests.is_empty());
5299
5300 // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
5301 disconnect_and_reconnect(&client_a, cx_a).await;
5302 disconnect_and_reconnect(&client_b, cx_b).await;
5303 disconnect_and_reconnect(&client_c, cx_c).await;
5304 deterministic.run_until_parked();
5305 assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
5306 assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
5307 assert!(client_b
5308 .summarize_contacts(cx_b)
5309 .incoming_requests
5310 .is_empty());
5311 assert!(client_c.summarize_contacts(cx_c).current.is_empty());
5312 assert!(client_c
5313 .summarize_contacts(cx_c)
5314 .outgoing_requests
5315 .is_empty());
5316
5317 async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
5318 client.disconnect(&cx.to_async()).unwrap();
5319 client.clear_contacts(cx).await;
5320 client
5321 .authenticate_and_connect(false, &cx.to_async())
5322 .await
5323 .unwrap();
5324 }
5325}
5326
5327#[gpui::test(iterations = 10)]
5328async fn test_following(
5329 deterministic: Arc<Deterministic>,
5330 cx_a: &mut TestAppContext,
5331 cx_b: &mut TestAppContext,
5332) {
5333 deterministic.forbid_parking();
5334 cx_a.update(editor::init);
5335 cx_b.update(editor::init);
5336
5337 let mut server = TestServer::start(&deterministic).await;
5338 let client_a = server.create_client(cx_a, "user_a").await;
5339 let client_b = server.create_client(cx_b, "user_b").await;
5340 server
5341 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5342 .await;
5343 let active_call_a = cx_a.read(ActiveCall::global);
5344 let active_call_b = cx_b.read(ActiveCall::global);
5345
5346 client_a
5347 .fs
5348 .insert_tree(
5349 "/a",
5350 json!({
5351 "1.txt": "one\none\none",
5352 "2.txt": "two\ntwo\ntwo",
5353 "3.txt": "three\nthree\nthree",
5354 }),
5355 )
5356 .await;
5357 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5358 active_call_a
5359 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5360 .await
5361 .unwrap();
5362
5363 let project_id = active_call_a
5364 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5365 .await
5366 .unwrap();
5367 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5368 active_call_b
5369 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5370 .await
5371 .unwrap();
5372
5373 // Client A opens some editors.
5374 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5375 let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5376 let editor_a1 = workspace_a
5377 .update(cx_a, |workspace, cx| {
5378 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5379 })
5380 .await
5381 .unwrap()
5382 .downcast::<Editor>()
5383 .unwrap();
5384 let editor_a2 = workspace_a
5385 .update(cx_a, |workspace, cx| {
5386 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5387 })
5388 .await
5389 .unwrap()
5390 .downcast::<Editor>()
5391 .unwrap();
5392
5393 // Client B opens an editor.
5394 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5395 let editor_b1 = workspace_b
5396 .update(cx_b, |workspace, cx| {
5397 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5398 })
5399 .await
5400 .unwrap()
5401 .downcast::<Editor>()
5402 .unwrap();
5403
5404 let client_a_id = project_b.read_with(cx_b, |project, _| {
5405 project.collaborators().values().next().unwrap().peer_id
5406 });
5407 let client_b_id = project_a.read_with(cx_a, |project, _| {
5408 project.collaborators().values().next().unwrap().peer_id
5409 });
5410
5411 // When client B starts following client A, all visible view states are replicated to client B.
5412 editor_a1.update(cx_a, |editor, cx| {
5413 editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
5414 });
5415 editor_a2.update(cx_a, |editor, cx| {
5416 editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
5417 });
5418 workspace_b
5419 .update(cx_b, |workspace, cx| {
5420 workspace
5421 .toggle_follow(&ToggleFollow(client_a_id), cx)
5422 .unwrap()
5423 })
5424 .await
5425 .unwrap();
5426
5427 let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
5428 workspace
5429 .active_item(cx)
5430 .unwrap()
5431 .downcast::<Editor>()
5432 .unwrap()
5433 });
5434 assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
5435 assert_eq!(
5436 editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
5437 Some((worktree_id, "2.txt").into())
5438 );
5439 assert_eq!(
5440 editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
5441 vec![2..3]
5442 );
5443 assert_eq!(
5444 editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
5445 vec![0..1]
5446 );
5447
5448 // When client A activates a different editor, client B does so as well.
5449 workspace_a.update(cx_a, |workspace, cx| {
5450 workspace.activate_item(&editor_a1, cx)
5451 });
5452 deterministic.run_until_parked();
5453 workspace_b.read_with(cx_b, |workspace, cx| {
5454 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5455 });
5456
5457 // When client A opens a multibuffer, client B does so as well.
5458 let multibuffer_a = cx_a.add_model(|cx| {
5459 let buffer_a1 = project_a.update(cx, |project, cx| {
5460 project
5461 .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
5462 .unwrap()
5463 });
5464 let buffer_a2 = project_a.update(cx, |project, cx| {
5465 project
5466 .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
5467 .unwrap()
5468 });
5469 let mut result = MultiBuffer::new(0);
5470 result.push_excerpts(
5471 buffer_a1,
5472 [ExcerptRange {
5473 context: 0..3,
5474 primary: None,
5475 }],
5476 cx,
5477 );
5478 result.push_excerpts(
5479 buffer_a2,
5480 [ExcerptRange {
5481 context: 4..7,
5482 primary: None,
5483 }],
5484 cx,
5485 );
5486 result
5487 });
5488 let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
5489 let editor =
5490 cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
5491 workspace.add_item(Box::new(editor.clone()), cx);
5492 editor
5493 });
5494 deterministic.run_until_parked();
5495 let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
5496 workspace
5497 .active_item(cx)
5498 .unwrap()
5499 .downcast::<Editor>()
5500 .unwrap()
5501 });
5502 assert_eq!(
5503 multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
5504 multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
5505 );
5506
5507 // When client A navigates back and forth, client B does so as well.
5508 workspace_a
5509 .update(cx_a, |workspace, cx| {
5510 workspace::Pane::go_back(workspace, None, cx)
5511 })
5512 .await;
5513 deterministic.run_until_parked();
5514 workspace_b.read_with(cx_b, |workspace, cx| {
5515 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5516 });
5517
5518 workspace_a
5519 .update(cx_a, |workspace, cx| {
5520 workspace::Pane::go_back(workspace, None, cx)
5521 })
5522 .await;
5523 deterministic.run_until_parked();
5524 workspace_b.read_with(cx_b, |workspace, cx| {
5525 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
5526 });
5527
5528 workspace_a
5529 .update(cx_a, |workspace, cx| {
5530 workspace::Pane::go_forward(workspace, None, cx)
5531 })
5532 .await;
5533 deterministic.run_until_parked();
5534 workspace_b.read_with(cx_b, |workspace, cx| {
5535 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5536 });
5537
5538 // Changes to client A's editor are reflected on client B.
5539 editor_a1.update(cx_a, |editor, cx| {
5540 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
5541 });
5542 deterministic.run_until_parked();
5543 editor_b1.read_with(cx_b, |editor, cx| {
5544 assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
5545 });
5546
5547 editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
5548 deterministic.run_until_parked();
5549 editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
5550
5551 editor_a1.update(cx_a, |editor, cx| {
5552 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
5553 editor.set_scroll_position(vec2f(0., 100.), cx);
5554 });
5555 deterministic.run_until_parked();
5556 editor_b1.read_with(cx_b, |editor, cx| {
5557 assert_eq!(editor.selections.ranges(cx), &[3..3]);
5558 });
5559
5560 // After unfollowing, client B stops receiving updates from client A.
5561 workspace_b.update(cx_b, |workspace, cx| {
5562 workspace.unfollow(&workspace.active_pane().clone(), cx)
5563 });
5564 workspace_a.update(cx_a, |workspace, cx| {
5565 workspace.activate_item(&editor_a2, cx)
5566 });
5567 deterministic.run_until_parked();
5568 assert_eq!(
5569 workspace_b.read_with(cx_b, |workspace, cx| workspace
5570 .active_item(cx)
5571 .unwrap()
5572 .id()),
5573 editor_b1.id()
5574 );
5575
5576 // Client A starts following client B.
5577 workspace_a
5578 .update(cx_a, |workspace, cx| {
5579 workspace
5580 .toggle_follow(&ToggleFollow(client_b_id), cx)
5581 .unwrap()
5582 })
5583 .await
5584 .unwrap();
5585 assert_eq!(
5586 workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5587 Some(client_b_id)
5588 );
5589 assert_eq!(
5590 workspace_a.read_with(cx_a, |workspace, cx| workspace
5591 .active_item(cx)
5592 .unwrap()
5593 .id()),
5594 editor_a1.id()
5595 );
5596
5597 // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
5598 let display = MacOSDisplay::new();
5599 active_call_b
5600 .update(cx_b, |call, cx| call.set_location(None, cx))
5601 .await
5602 .unwrap();
5603 active_call_b
5604 .update(cx_b, |call, cx| {
5605 call.room().unwrap().update(cx, |room, cx| {
5606 room.set_display_sources(vec![display.clone()]);
5607 room.share_screen(cx)
5608 })
5609 })
5610 .await
5611 .unwrap();
5612 deterministic.run_until_parked();
5613 let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
5614 workspace
5615 .active_item(cx)
5616 .unwrap()
5617 .downcast::<SharedScreen>()
5618 .unwrap()
5619 });
5620
5621 // Client B activates Zed again, which causes the previous editor to become focused again.
5622 active_call_b
5623 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5624 .await
5625 .unwrap();
5626 deterministic.run_until_parked();
5627 workspace_a.read_with(cx_a, |workspace, cx| {
5628 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
5629 });
5630
5631 // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
5632 workspace_b.update(cx_b, |workspace, cx| {
5633 workspace.activate_item(&multibuffer_editor_b, cx)
5634 });
5635 deterministic.run_until_parked();
5636 workspace_a.read_with(cx_a, |workspace, cx| {
5637 assert_eq!(
5638 workspace.active_item(cx).unwrap().id(),
5639 multibuffer_editor_a.id()
5640 )
5641 });
5642
5643 // Client B activates an external window again, and the previously-opened screen-sharing item
5644 // gets activated.
5645 active_call_b
5646 .update(cx_b, |call, cx| call.set_location(None, cx))
5647 .await
5648 .unwrap();
5649 deterministic.run_until_parked();
5650 assert_eq!(
5651 workspace_a.read_with(cx_a, |workspace, cx| workspace
5652 .active_item(cx)
5653 .unwrap()
5654 .id()),
5655 shared_screen.id()
5656 );
5657
5658 // Following interrupts when client B disconnects.
5659 client_b.disconnect(&cx_b.to_async()).unwrap();
5660 deterministic.run_until_parked();
5661 assert_eq!(
5662 workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5663 None
5664 );
5665}
5666
5667#[gpui::test]
5668async fn test_following_tab_order(
5669 deterministic: Arc<Deterministic>,
5670 cx_a: &mut TestAppContext,
5671 cx_b: &mut TestAppContext,
5672) {
5673 cx_a.update(editor::init);
5674 cx_b.update(editor::init);
5675
5676 let mut server = TestServer::start(&deterministic).await;
5677 let client_a = server.create_client(cx_a, "user_a").await;
5678 let client_b = server.create_client(cx_b, "user_b").await;
5679 server
5680 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5681 .await;
5682 let active_call_a = cx_a.read(ActiveCall::global);
5683 let active_call_b = cx_b.read(ActiveCall::global);
5684
5685 client_a
5686 .fs
5687 .insert_tree(
5688 "/a",
5689 json!({
5690 "1.txt": "one",
5691 "2.txt": "two",
5692 "3.txt": "three",
5693 }),
5694 )
5695 .await;
5696 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5697 active_call_a
5698 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5699 .await
5700 .unwrap();
5701
5702 let project_id = active_call_a
5703 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5704 .await
5705 .unwrap();
5706 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5707 active_call_b
5708 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5709 .await
5710 .unwrap();
5711
5712 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5713 let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5714
5715 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5716 let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5717
5718 let client_b_id = project_a.read_with(cx_a, |project, _| {
5719 project.collaborators().values().next().unwrap().peer_id
5720 });
5721
5722 //Open 1, 3 in that order on client A
5723 workspace_a
5724 .update(cx_a, |workspace, cx| {
5725 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5726 })
5727 .await
5728 .unwrap();
5729 workspace_a
5730 .update(cx_a, |workspace, cx| {
5731 workspace.open_path((worktree_id, "3.txt"), None, true, cx)
5732 })
5733 .await
5734 .unwrap();
5735
5736 let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
5737 pane.update(cx, |pane, cx| {
5738 pane.items()
5739 .map(|item| {
5740 item.project_path(cx)
5741 .unwrap()
5742 .path
5743 .to_str()
5744 .unwrap()
5745 .to_owned()
5746 })
5747 .collect::<Vec<_>>()
5748 })
5749 };
5750
5751 //Verify that the tabs opened in the order we expect
5752 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
5753
5754 //Follow client B as client A
5755 workspace_a
5756 .update(cx_a, |workspace, cx| {
5757 workspace
5758 .toggle_follow(&ToggleFollow(client_b_id), cx)
5759 .unwrap()
5760 })
5761 .await
5762 .unwrap();
5763
5764 //Open just 2 on client B
5765 workspace_b
5766 .update(cx_b, |workspace, cx| {
5767 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5768 })
5769 .await
5770 .unwrap();
5771 deterministic.run_until_parked();
5772
5773 // Verify that newly opened followed file is at the end
5774 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
5775
5776 //Open just 1 on client B
5777 workspace_b
5778 .update(cx_b, |workspace, cx| {
5779 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5780 })
5781 .await
5782 .unwrap();
5783 assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
5784 deterministic.run_until_parked();
5785
5786 // Verify that following into 1 did not reorder
5787 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
5788}
5789
5790#[gpui::test(iterations = 10)]
5791async fn test_peers_following_each_other(
5792 deterministic: Arc<Deterministic>,
5793 cx_a: &mut TestAppContext,
5794 cx_b: &mut TestAppContext,
5795) {
5796 deterministic.forbid_parking();
5797 cx_a.update(editor::init);
5798 cx_b.update(editor::init);
5799
5800 let mut server = TestServer::start(&deterministic).await;
5801 let client_a = server.create_client(cx_a, "user_a").await;
5802 let client_b = server.create_client(cx_b, "user_b").await;
5803 server
5804 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5805 .await;
5806 let active_call_a = cx_a.read(ActiveCall::global);
5807 let active_call_b = cx_b.read(ActiveCall::global);
5808
5809 // Client A shares a project.
5810 client_a
5811 .fs
5812 .insert_tree(
5813 "/a",
5814 json!({
5815 "1.txt": "one",
5816 "2.txt": "two",
5817 "3.txt": "three",
5818 "4.txt": "four",
5819 }),
5820 )
5821 .await;
5822 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5823 active_call_a
5824 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5825 .await
5826 .unwrap();
5827 let project_id = active_call_a
5828 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5829 .await
5830 .unwrap();
5831
5832 // Client B joins the project.
5833 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5834 active_call_b
5835 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5836 .await
5837 .unwrap();
5838
5839 // Client A opens some editors.
5840 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5841 let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5842 let _editor_a1 = workspace_a
5843 .update(cx_a, |workspace, cx| {
5844 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5845 })
5846 .await
5847 .unwrap()
5848 .downcast::<Editor>()
5849 .unwrap();
5850
5851 // Client B opens an editor.
5852 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5853 let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5854 let _editor_b1 = workspace_b
5855 .update(cx_b, |workspace, cx| {
5856 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5857 })
5858 .await
5859 .unwrap()
5860 .downcast::<Editor>()
5861 .unwrap();
5862
5863 // Clients A and B follow each other in split panes
5864 workspace_a.update(cx_a, |workspace, cx| {
5865 workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5866 let pane_a1 = pane_a1.clone();
5867 cx.defer(move |workspace, _| {
5868 assert_ne!(*workspace.active_pane(), pane_a1);
5869 });
5870 });
5871 workspace_a
5872 .update(cx_a, |workspace, cx| {
5873 let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
5874 workspace
5875 .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5876 .unwrap()
5877 })
5878 .await
5879 .unwrap();
5880 workspace_b.update(cx_b, |workspace, cx| {
5881 workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5882 let pane_b1 = pane_b1.clone();
5883 cx.defer(move |workspace, _| {
5884 assert_ne!(*workspace.active_pane(), pane_b1);
5885 });
5886 });
5887 workspace_b
5888 .update(cx_b, |workspace, cx| {
5889 let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
5890 workspace
5891 .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5892 .unwrap()
5893 })
5894 .await
5895 .unwrap();
5896
5897 workspace_a.update(cx_a, |workspace, cx| {
5898 workspace.activate_next_pane(cx);
5899 });
5900 // Wait for focus effects to be fully flushed
5901 workspace_a.update(cx_a, |workspace, _| {
5902 assert_eq!(*workspace.active_pane(), pane_a1);
5903 });
5904
5905 workspace_a
5906 .update(cx_a, |workspace, cx| {
5907 workspace.open_path((worktree_id, "3.txt"), None, true, cx)
5908 })
5909 .await
5910 .unwrap();
5911 workspace_b.update(cx_b, |workspace, cx| {
5912 workspace.activate_next_pane(cx);
5913 });
5914
5915 workspace_b
5916 .update(cx_b, |workspace, cx| {
5917 assert_eq!(*workspace.active_pane(), pane_b1);
5918 workspace.open_path((worktree_id, "4.txt"), None, true, cx)
5919 })
5920 .await
5921 .unwrap();
5922 cx_a.foreground().run_until_parked();
5923
5924 // Ensure leader updates don't change the active pane of followers
5925 workspace_a.read_with(cx_a, |workspace, _| {
5926 assert_eq!(*workspace.active_pane(), pane_a1);
5927 });
5928 workspace_b.read_with(cx_b, |workspace, _| {
5929 assert_eq!(*workspace.active_pane(), pane_b1);
5930 });
5931
5932 // Ensure peers following each other doesn't cause an infinite loop.
5933 assert_eq!(
5934 workspace_a.read_with(cx_a, |workspace, cx| workspace
5935 .active_item(cx)
5936 .unwrap()
5937 .project_path(cx)),
5938 Some((worktree_id, "3.txt").into())
5939 );
5940 workspace_a.update(cx_a, |workspace, cx| {
5941 assert_eq!(
5942 workspace.active_item(cx).unwrap().project_path(cx),
5943 Some((worktree_id, "3.txt").into())
5944 );
5945 workspace.activate_next_pane(cx);
5946 });
5947
5948 workspace_a.update(cx_a, |workspace, cx| {
5949 assert_eq!(
5950 workspace.active_item(cx).unwrap().project_path(cx),
5951 Some((worktree_id, "4.txt").into())
5952 );
5953 });
5954
5955 workspace_b.update(cx_b, |workspace, cx| {
5956 assert_eq!(
5957 workspace.active_item(cx).unwrap().project_path(cx),
5958 Some((worktree_id, "4.txt").into())
5959 );
5960 workspace.activate_next_pane(cx);
5961 });
5962
5963 workspace_b.update(cx_b, |workspace, cx| {
5964 assert_eq!(
5965 workspace.active_item(cx).unwrap().project_path(cx),
5966 Some((worktree_id, "3.txt").into())
5967 );
5968 });
5969}
5970
5971#[gpui::test(iterations = 10)]
5972async fn test_auto_unfollowing(
5973 deterministic: Arc<Deterministic>,
5974 cx_a: &mut TestAppContext,
5975 cx_b: &mut TestAppContext,
5976) {
5977 deterministic.forbid_parking();
5978 cx_a.update(editor::init);
5979 cx_b.update(editor::init);
5980
5981 // 2 clients connect to a server.
5982 let mut server = TestServer::start(&deterministic).await;
5983 let client_a = server.create_client(cx_a, "user_a").await;
5984 let client_b = server.create_client(cx_b, "user_b").await;
5985 server
5986 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5987 .await;
5988 let active_call_a = cx_a.read(ActiveCall::global);
5989 let active_call_b = cx_b.read(ActiveCall::global);
5990
5991 // Client A shares a project.
5992 client_a
5993 .fs
5994 .insert_tree(
5995 "/a",
5996 json!({
5997 "1.txt": "one",
5998 "2.txt": "two",
5999 "3.txt": "three",
6000 }),
6001 )
6002 .await;
6003 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
6004 active_call_a
6005 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
6006 .await
6007 .unwrap();
6008
6009 let project_id = active_call_a
6010 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6011 .await
6012 .unwrap();
6013 let project_b = client_b.build_remote_project(project_id, cx_b).await;
6014 active_call_b
6015 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
6016 .await
6017 .unwrap();
6018
6019 // Client A opens some editors.
6020 let workspace_a = client_a.build_workspace(&project_a, cx_a);
6021 let _editor_a1 = workspace_a
6022 .update(cx_a, |workspace, cx| {
6023 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
6024 })
6025 .await
6026 .unwrap()
6027 .downcast::<Editor>()
6028 .unwrap();
6029
6030 // Client B starts following client A.
6031 let workspace_b = client_b.build_workspace(&project_b, cx_b);
6032 let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
6033 let leader_id = project_b.read_with(cx_b, |project, _| {
6034 project.collaborators().values().next().unwrap().peer_id
6035 });
6036 workspace_b
6037 .update(cx_b, |workspace, cx| {
6038 workspace
6039 .toggle_follow(&ToggleFollow(leader_id), cx)
6040 .unwrap()
6041 })
6042 .await
6043 .unwrap();
6044 assert_eq!(
6045 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6046 Some(leader_id)
6047 );
6048 let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
6049 workspace
6050 .active_item(cx)
6051 .unwrap()
6052 .downcast::<Editor>()
6053 .unwrap()
6054 });
6055
6056 // When client B moves, it automatically stops following client A.
6057 editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
6058 assert_eq!(
6059 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6060 None
6061 );
6062
6063 workspace_b
6064 .update(cx_b, |workspace, cx| {
6065 workspace
6066 .toggle_follow(&ToggleFollow(leader_id), cx)
6067 .unwrap()
6068 })
6069 .await
6070 .unwrap();
6071 assert_eq!(
6072 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6073 Some(leader_id)
6074 );
6075
6076 // When client B edits, it automatically stops following client A.
6077 editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
6078 assert_eq!(
6079 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6080 None
6081 );
6082
6083 workspace_b
6084 .update(cx_b, |workspace, cx| {
6085 workspace
6086 .toggle_follow(&ToggleFollow(leader_id), cx)
6087 .unwrap()
6088 })
6089 .await
6090 .unwrap();
6091 assert_eq!(
6092 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6093 Some(leader_id)
6094 );
6095
6096 // When client B scrolls, it automatically stops following client A.
6097 editor_b2.update(cx_b, |editor, cx| {
6098 editor.set_scroll_position(vec2f(0., 3.), cx)
6099 });
6100 assert_eq!(
6101 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6102 None
6103 );
6104
6105 workspace_b
6106 .update(cx_b, |workspace, cx| {
6107 workspace
6108 .toggle_follow(&ToggleFollow(leader_id), cx)
6109 .unwrap()
6110 })
6111 .await
6112 .unwrap();
6113 assert_eq!(
6114 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6115 Some(leader_id)
6116 );
6117
6118 // When client B activates a different pane, it continues following client A in the original pane.
6119 workspace_b.update(cx_b, |workspace, cx| {
6120 workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
6121 });
6122 assert_eq!(
6123 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6124 Some(leader_id)
6125 );
6126
6127 workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
6128 assert_eq!(
6129 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6130 Some(leader_id)
6131 );
6132
6133 // When client B activates a different item in the original pane, it automatically stops following client A.
6134 workspace_b
6135 .update(cx_b, |workspace, cx| {
6136 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
6137 })
6138 .await
6139 .unwrap();
6140 assert_eq!(
6141 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6142 None
6143 );
6144}
6145
6146#[gpui::test(iterations = 10)]
6147async fn test_peers_simultaneously_following_each_other(
6148 deterministic: Arc<Deterministic>,
6149 cx_a: &mut TestAppContext,
6150 cx_b: &mut TestAppContext,
6151) {
6152 deterministic.forbid_parking();
6153 cx_a.update(editor::init);
6154 cx_b.update(editor::init);
6155
6156 let mut server = TestServer::start(&deterministic).await;
6157 let client_a = server.create_client(cx_a, "user_a").await;
6158 let client_b = server.create_client(cx_b, "user_b").await;
6159 server
6160 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6161 .await;
6162 let active_call_a = cx_a.read(ActiveCall::global);
6163
6164 client_a.fs.insert_tree("/a", json!({})).await;
6165 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6166 let workspace_a = client_a.build_workspace(&project_a, cx_a);
6167 let project_id = active_call_a
6168 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6169 .await
6170 .unwrap();
6171
6172 let project_b = client_b.build_remote_project(project_id, cx_b).await;
6173 let workspace_b = client_b.build_workspace(&project_b, cx_b);
6174
6175 deterministic.run_until_parked();
6176 let client_a_id = project_b.read_with(cx_b, |project, _| {
6177 project.collaborators().values().next().unwrap().peer_id
6178 });
6179 let client_b_id = project_a.read_with(cx_a, |project, _| {
6180 project.collaborators().values().next().unwrap().peer_id
6181 });
6182
6183 let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
6184 workspace
6185 .toggle_follow(&ToggleFollow(client_b_id), cx)
6186 .unwrap()
6187 });
6188 let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
6189 workspace
6190 .toggle_follow(&ToggleFollow(client_a_id), cx)
6191 .unwrap()
6192 });
6193
6194 futures::try_join!(a_follow_b, b_follow_a).unwrap();
6195 workspace_a.read_with(cx_a, |workspace, _| {
6196 assert_eq!(
6197 workspace.leader_for_pane(workspace.active_pane()),
6198 Some(client_b_id)
6199 );
6200 });
6201 workspace_b.read_with(cx_b, |workspace, _| {
6202 assert_eq!(
6203 workspace.leader_for_pane(workspace.active_pane()),
6204 Some(client_a_id)
6205 );
6206 });
6207}
6208
6209#[gpui::test(iterations = 100)]
6210async fn test_random_collaboration(
6211 cx: &mut TestAppContext,
6212 deterministic: Arc<Deterministic>,
6213 rng: StdRng,
6214) {
6215 deterministic.forbid_parking();
6216 let rng = Arc::new(Mutex::new(rng));
6217
6218 let max_peers = env::var("MAX_PEERS")
6219 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
6220 .unwrap_or(5);
6221
6222 let max_operations = env::var("OPERATIONS")
6223 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
6224 .unwrap_or(10);
6225
6226 let mut server = TestServer::start(&deterministic).await;
6227 let db = server.app_state.db.clone();
6228
6229 let mut available_guests = Vec::new();
6230 for ix in 0..max_peers {
6231 let username = format!("guest-{}", ix + 1);
6232 let user_id = db
6233 .create_user(
6234 &format!("{username}@example.com"),
6235 false,
6236 NewUserParams {
6237 github_login: username.clone(),
6238 github_user_id: (ix + 1) as i32,
6239 invite_count: 0,
6240 },
6241 )
6242 .await
6243 .unwrap()
6244 .user_id;
6245 available_guests.push((user_id, username));
6246 }
6247
6248 for (ix, (user_id_a, _)) in available_guests.iter().enumerate() {
6249 for (user_id_b, _) in &available_guests[ix + 1..] {
6250 server
6251 .app_state
6252 .db
6253 .send_contact_request(*user_id_a, *user_id_b)
6254 .await
6255 .unwrap();
6256 server
6257 .app_state
6258 .db
6259 .respond_to_contact_request(*user_id_b, *user_id_a, true)
6260 .await
6261 .unwrap();
6262 }
6263 }
6264
6265 let mut clients = Vec::new();
6266 let mut user_ids = Vec::new();
6267 let mut op_start_signals = Vec::new();
6268 let mut next_entity_id = 100000;
6269
6270 let mut operations = 0;
6271 while operations < max_operations {
6272 let distribution = rng.lock().gen_range(0..100);
6273 match distribution {
6274 0..=19 if !available_guests.is_empty() => {
6275 let guest_ix = rng.lock().gen_range(0..available_guests.len());
6276 let (_, guest_username) = available_guests.remove(guest_ix);
6277 log::info!("Adding new connection for {}", guest_username);
6278 next_entity_id += 100000;
6279 let mut guest_cx = TestAppContext::new(
6280 cx.foreground_platform(),
6281 cx.platform(),
6282 deterministic.build_foreground(next_entity_id),
6283 deterministic.build_background(),
6284 cx.font_cache(),
6285 cx.leak_detector(),
6286 next_entity_id,
6287 cx.function_name.clone(),
6288 );
6289
6290 let op_start_signal = futures::channel::mpsc::unbounded();
6291 let guest = server.create_client(&mut guest_cx, &guest_username).await;
6292 user_ids.push(guest.current_user_id(&guest_cx));
6293 op_start_signals.push(op_start_signal.0);
6294 clients.push(guest_cx.foreground().spawn(guest.simulate(
6295 guest_username.clone(),
6296 op_start_signal.1,
6297 rng.clone(),
6298 guest_cx,
6299 )));
6300
6301 log::info!("Added connection for {}", guest_username);
6302 operations += 1;
6303 }
6304 20..=24 if clients.len() > 1 => {
6305 let guest_ix = rng.lock().gen_range(1..clients.len());
6306 log::info!(
6307 "Simulating full disconnection of guest {}",
6308 user_ids[guest_ix]
6309 );
6310 let removed_guest_id = user_ids.remove(guest_ix);
6311 let user_connection_ids = server
6312 .connection_pool
6313 .lock()
6314 .user_connection_ids(removed_guest_id)
6315 .collect::<Vec<_>>();
6316 assert_eq!(user_connection_ids.len(), 1);
6317 let removed_peer_id = user_connection_ids[0].into();
6318 let guest = clients.remove(guest_ix);
6319 op_start_signals.remove(guest_ix);
6320 server.forbid_connections();
6321 server.disconnect_client(removed_peer_id);
6322 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6323 deterministic.start_waiting();
6324 log::info!("Waiting for guest {} to exit...", removed_guest_id);
6325 let (guest, mut guest_cx) = guest.await;
6326 deterministic.finish_waiting();
6327 server.allow_connections();
6328
6329 for project in &guest.remote_projects {
6330 project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
6331 }
6332 for user_id in &user_ids {
6333 let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
6334 let pool = server.connection_pool.lock();
6335 for contact in contacts {
6336 if let db::Contact::Accepted { user_id, .. } = contact {
6337 if pool.is_user_online(user_id) {
6338 assert_ne!(
6339 user_id, removed_guest_id,
6340 "removed guest is still a contact of another peer"
6341 );
6342 }
6343 }
6344 }
6345 }
6346
6347 log::info!("{} removed", guest.username);
6348 available_guests.push((removed_guest_id, guest.username.clone()));
6349 guest_cx.update(|cx| {
6350 cx.clear_globals();
6351 drop(guest);
6352 });
6353
6354 operations += 1;
6355 }
6356 25..=29 if clients.len() > 1 => {
6357 let guest_ix = rng.lock().gen_range(1..clients.len());
6358 let user_id = user_ids[guest_ix];
6359 log::info!("Simulating temporary disconnection of guest {}", user_id);
6360 let user_connection_ids = server
6361 .connection_pool
6362 .lock()
6363 .user_connection_ids(user_id)
6364 .collect::<Vec<_>>();
6365 assert_eq!(user_connection_ids.len(), 1);
6366 let peer_id = user_connection_ids[0].into();
6367 server.disconnect_client(peer_id);
6368 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6369 operations += 1;
6370 }
6371 30..=34 => {
6372 log::info!("Simulating server restart");
6373 server.reset().await;
6374 deterministic.advance_clock(RECEIVE_TIMEOUT);
6375 server.start().await.unwrap();
6376 deterministic.advance_clock(CLEANUP_TIMEOUT);
6377 let environment = &server.app_state.config.zed_environment;
6378 let stale_room_ids = server
6379 .app_state
6380 .db
6381 .stale_room_ids(environment, server.id())
6382 .await
6383 .unwrap();
6384 assert_eq!(stale_room_ids, vec![]);
6385 }
6386 _ if !op_start_signals.is_empty() => {
6387 while operations < max_operations && rng.lock().gen_bool(0.7) {
6388 op_start_signals
6389 .choose(&mut *rng.lock())
6390 .unwrap()
6391 .unbounded_send(())
6392 .unwrap();
6393 operations += 1;
6394 }
6395
6396 if rng.lock().gen_bool(0.8) {
6397 deterministic.run_until_parked();
6398 }
6399 }
6400 _ => {}
6401 }
6402 }
6403
6404 drop(op_start_signals);
6405 deterministic.start_waiting();
6406 let clients = futures::future::join_all(clients).await;
6407 deterministic.finish_waiting();
6408 deterministic.run_until_parked();
6409
6410 for (guest_client, guest_cx) in &clients {
6411 for guest_project in &guest_client.remote_projects {
6412 guest_project.read_with(guest_cx, |guest_project, cx| {
6413 let host_project = clients.iter().find_map(|(client, cx)| {
6414 let project = client.local_projects.iter().find(|host_project| {
6415 host_project.read_with(cx, |host_project, _| {
6416 host_project.remote_id() == guest_project.remote_id()
6417 })
6418 })?;
6419 Some((project, cx))
6420 });
6421
6422 if !guest_project.is_read_only() {
6423 if let Some((host_project, host_cx)) = host_project {
6424 let host_worktree_snapshots =
6425 host_project.read_with(host_cx, |host_project, cx| {
6426 host_project
6427 .worktrees(cx)
6428 .map(|worktree| {
6429 let worktree = worktree.read(cx);
6430 (worktree.id(), worktree.snapshot())
6431 })
6432 .collect::<BTreeMap<_, _>>()
6433 });
6434 let guest_worktree_snapshots = guest_project
6435 .worktrees(cx)
6436 .map(|worktree| {
6437 let worktree = worktree.read(cx);
6438 (worktree.id(), worktree.snapshot())
6439 })
6440 .collect::<BTreeMap<_, _>>();
6441
6442 assert_eq!(
6443 guest_worktree_snapshots.keys().collect::<Vec<_>>(),
6444 host_worktree_snapshots.keys().collect::<Vec<_>>(),
6445 "{} has different worktrees than the host",
6446 guest_client.username
6447 );
6448
6449 for (id, host_snapshot) in &host_worktree_snapshots {
6450 let guest_snapshot = &guest_worktree_snapshots[id];
6451 assert_eq!(
6452 guest_snapshot.root_name(),
6453 host_snapshot.root_name(),
6454 "{} has different root name than the host for worktree {}",
6455 guest_client.username,
6456 id
6457 );
6458 assert_eq!(
6459 guest_snapshot.abs_path(),
6460 host_snapshot.abs_path(),
6461 "{} has different abs path than the host for worktree {}",
6462 guest_client.username,
6463 id
6464 );
6465 assert_eq!(
6466 guest_snapshot.entries(false).collect::<Vec<_>>(),
6467 host_snapshot.entries(false).collect::<Vec<_>>(),
6468 "{} has different snapshot than the host for worktree {}",
6469 guest_client.username,
6470 id
6471 );
6472 assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
6473 }
6474 }
6475 }
6476
6477 guest_project.check_invariants(cx);
6478 });
6479 }
6480
6481 for (guest_project, guest_buffers) in &guest_client.buffers {
6482 let project_id = if guest_project.read_with(guest_cx, |project, _| {
6483 project.is_local() || project.is_read_only()
6484 }) {
6485 continue;
6486 } else {
6487 guest_project
6488 .read_with(guest_cx, |project, _| project.remote_id())
6489 .unwrap()
6490 };
6491
6492 let host_project = clients.iter().find_map(|(client, cx)| {
6493 let project = client.local_projects.iter().find(|host_project| {
6494 host_project.read_with(cx, |host_project, _| {
6495 host_project.remote_id() == Some(project_id)
6496 })
6497 })?;
6498 Some((project, cx))
6499 });
6500
6501 let (host_project, host_cx) = if let Some((host_project, host_cx)) = host_project {
6502 (host_project, host_cx)
6503 } else {
6504 continue;
6505 };
6506
6507 for guest_buffer in guest_buffers {
6508 let buffer_id = guest_buffer.read_with(guest_cx, |buffer, _| buffer.remote_id());
6509 let host_buffer = host_project.read_with(host_cx, |project, cx| {
6510 project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
6511 panic!(
6512 "host does not have buffer for guest:{}, peer:{:?}, id:{}",
6513 guest_client.username,
6514 guest_client.peer_id(),
6515 buffer_id
6516 )
6517 })
6518 });
6519 let path = host_buffer
6520 .read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
6521
6522 assert_eq!(
6523 guest_buffer.read_with(guest_cx, |buffer, _| buffer.deferred_ops_len()),
6524 0,
6525 "{}, buffer {}, path {:?} has deferred operations",
6526 guest_client.username,
6527 buffer_id,
6528 path,
6529 );
6530 assert_eq!(
6531 guest_buffer.read_with(guest_cx, |buffer, _| buffer.text()),
6532 host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
6533 "{}, buffer {}, path {:?}, differs from the host's buffer",
6534 guest_client.username,
6535 buffer_id,
6536 path
6537 );
6538 }
6539 }
6540 }
6541
6542 for (client, mut cx) in clients {
6543 cx.update(|cx| {
6544 cx.clear_globals();
6545 drop(client);
6546 });
6547 }
6548}
6549
6550struct TestServer {
6551 app_state: Arc<AppState>,
6552 server: Arc<Server>,
6553 connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
6554 forbid_connections: Arc<AtomicBool>,
6555 _test_db: TestDb,
6556 test_live_kit_server: Arc<live_kit_client::TestServer>,
6557}
6558
6559impl TestServer {
6560 async fn start(deterministic: &Arc<Deterministic>) -> Self {
6561 static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
6562
6563 let use_postgres = env::var("USE_POSTGRES").ok();
6564 let use_postgres = use_postgres.as_deref();
6565 let test_db = if use_postgres == Some("true") || use_postgres == Some("1") {
6566 TestDb::postgres(deterministic.build_background())
6567 } else {
6568 TestDb::sqlite(deterministic.build_background())
6569 };
6570 let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
6571 let live_kit_server = live_kit_client::TestServer::create(
6572 format!("http://livekit.{}.test", live_kit_server_id),
6573 format!("devkey-{}", live_kit_server_id),
6574 format!("secret-{}", live_kit_server_id),
6575 deterministic.build_background(),
6576 )
6577 .unwrap();
6578 let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
6579 let epoch = app_state
6580 .db
6581 .create_server(&app_state.config.zed_environment)
6582 .await
6583 .unwrap();
6584 let server = Server::new(
6585 epoch,
6586 app_state.clone(),
6587 Executor::Deterministic(deterministic.build_background()),
6588 );
6589 server.start().await.unwrap();
6590 // Advance clock to ensure the server's cleanup task is finished.
6591 deterministic.advance_clock(CLEANUP_TIMEOUT);
6592 Self {
6593 app_state,
6594 server,
6595 connection_killers: Default::default(),
6596 forbid_connections: Default::default(),
6597 _test_db: test_db,
6598 test_live_kit_server: live_kit_server,
6599 }
6600 }
6601
6602 async fn reset(&self) {
6603 self.app_state.db.reset();
6604 let epoch = self
6605 .app_state
6606 .db
6607 .create_server(&self.app_state.config.zed_environment)
6608 .await
6609 .unwrap();
6610 self.server.reset(epoch);
6611 }
6612
6613 async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
6614 cx.update(|cx| {
6615 cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf()));
6616
6617 let mut settings = Settings::test(cx);
6618 settings.projects_online_by_default = false;
6619 cx.set_global(settings);
6620 });
6621
6622 let http = FakeHttpClient::with_404_response();
6623 let user_id = if let Ok(Some(user)) = self
6624 .app_state
6625 .db
6626 .get_user_by_github_account(name, None)
6627 .await
6628 {
6629 user.id
6630 } else {
6631 self.app_state
6632 .db
6633 .create_user(
6634 &format!("{name}@example.com"),
6635 false,
6636 NewUserParams {
6637 github_login: name.into(),
6638 github_user_id: 0,
6639 invite_count: 0,
6640 },
6641 )
6642 .await
6643 .expect("creating user failed")
6644 .user_id
6645 };
6646 let client_name = name.to_string();
6647 let mut client = cx.read(|cx| Client::new(http.clone(), cx));
6648 let server = self.server.clone();
6649 let db = self.app_state.db.clone();
6650 let connection_killers = self.connection_killers.clone();
6651 let forbid_connections = self.forbid_connections.clone();
6652
6653 Arc::get_mut(&mut client)
6654 .unwrap()
6655 .set_id(user_id.0 as usize)
6656 .override_authenticate(move |cx| {
6657 cx.spawn(|_| async move {
6658 let access_token = "the-token".to_string();
6659 Ok(Credentials {
6660 user_id: user_id.0 as u64,
6661 access_token,
6662 })
6663 })
6664 })
6665 .override_establish_connection(move |credentials, cx| {
6666 assert_eq!(credentials.user_id, user_id.0 as u64);
6667 assert_eq!(credentials.access_token, "the-token");
6668
6669 let server = server.clone();
6670 let db = db.clone();
6671 let connection_killers = connection_killers.clone();
6672 let forbid_connections = forbid_connections.clone();
6673 let client_name = client_name.clone();
6674 cx.spawn(move |cx| async move {
6675 if forbid_connections.load(SeqCst) {
6676 Err(EstablishConnectionError::other(anyhow!(
6677 "server is forbidding connections"
6678 )))
6679 } else {
6680 let (client_conn, server_conn, killed) =
6681 Connection::in_memory(cx.background());
6682 let (connection_id_tx, connection_id_rx) = oneshot::channel();
6683 let user = db
6684 .get_user_by_id(user_id)
6685 .await
6686 .expect("retrieving user failed")
6687 .unwrap();
6688 cx.background()
6689 .spawn(server.handle_connection(
6690 server_conn,
6691 client_name,
6692 user,
6693 Some(connection_id_tx),
6694 Executor::Deterministic(cx.background()),
6695 ))
6696 .detach();
6697 let connection_id = connection_id_rx.await.unwrap();
6698 connection_killers
6699 .lock()
6700 .insert(connection_id.into(), killed);
6701 Ok(client_conn)
6702 }
6703 })
6704 });
6705
6706 let fs = FakeFs::new(cx.background());
6707 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
6708 let app_state = Arc::new(workspace::AppState {
6709 client: client.clone(),
6710 user_store: user_store.clone(),
6711 languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
6712 themes: ThemeRegistry::new((), cx.font_cache()),
6713 fs: fs.clone(),
6714 build_window_options: Default::default,
6715 initialize_workspace: |_, _, _| unimplemented!(),
6716 dock_default_item_factory: |_, _| unimplemented!(),
6717 });
6718
6719 Project::init(&client);
6720 cx.update(|cx| {
6721 workspace::init(app_state.clone(), cx);
6722 call::init(client.clone(), user_store.clone(), cx);
6723 });
6724
6725 client
6726 .authenticate_and_connect(false, &cx.to_async())
6727 .await
6728 .unwrap();
6729
6730 let client = TestClient {
6731 client,
6732 username: name.to_string(),
6733 local_projects: Default::default(),
6734 remote_projects: Default::default(),
6735 next_root_dir_id: 0,
6736 user_store,
6737 fs,
6738 language_registry: Arc::new(LanguageRegistry::test()),
6739 buffers: Default::default(),
6740 };
6741 client.wait_for_current_user(cx).await;
6742 client
6743 }
6744
6745 fn disconnect_client(&self, peer_id: PeerId) {
6746 self.connection_killers
6747 .lock()
6748 .remove(&peer_id)
6749 .unwrap()
6750 .store(true, SeqCst);
6751 }
6752
6753 fn forbid_connections(&self) {
6754 self.forbid_connections.store(true, SeqCst);
6755 }
6756
6757 fn allow_connections(&self) {
6758 self.forbid_connections.store(false, SeqCst);
6759 }
6760
6761 async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6762 for ix in 1..clients.len() {
6763 let (left, right) = clients.split_at_mut(ix);
6764 let (client_a, cx_a) = left.last_mut().unwrap();
6765 for (client_b, cx_b) in right {
6766 client_a
6767 .user_store
6768 .update(*cx_a, |store, cx| {
6769 store.request_contact(client_b.user_id().unwrap(), cx)
6770 })
6771 .await
6772 .unwrap();
6773 cx_a.foreground().run_until_parked();
6774 client_b
6775 .user_store
6776 .update(*cx_b, |store, cx| {
6777 store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6778 })
6779 .await
6780 .unwrap();
6781 }
6782 }
6783 }
6784
6785 async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6786 self.make_contacts(clients).await;
6787
6788 let (left, right) = clients.split_at_mut(1);
6789 let (_client_a, cx_a) = &mut left[0];
6790 let active_call_a = cx_a.read(ActiveCall::global);
6791
6792 for (client_b, cx_b) in right {
6793 let user_id_b = client_b.current_user_id(*cx_b).to_proto();
6794 active_call_a
6795 .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
6796 .await
6797 .unwrap();
6798
6799 cx_b.foreground().run_until_parked();
6800 let active_call_b = cx_b.read(ActiveCall::global);
6801 active_call_b
6802 .update(*cx_b, |call, cx| call.accept_incoming(cx))
6803 .await
6804 .unwrap();
6805 }
6806 }
6807
6808 async fn build_app_state(
6809 test_db: &TestDb,
6810 fake_server: &live_kit_client::TestServer,
6811 ) -> Arc<AppState> {
6812 Arc::new(AppState {
6813 db: test_db.db().clone(),
6814 live_kit_client: Some(Arc::new(fake_server.create_api_client())),
6815 config: Default::default(),
6816 })
6817 }
6818}
6819
6820impl Deref for TestServer {
6821 type Target = Server;
6822
6823 fn deref(&self) -> &Self::Target {
6824 &self.server
6825 }
6826}
6827
6828impl Drop for TestServer {
6829 fn drop(&mut self) {
6830 self.server.teardown();
6831 self.test_live_kit_server.teardown().unwrap();
6832 }
6833}
6834
6835struct TestClient {
6836 client: Arc<Client>,
6837 username: String,
6838 local_projects: Vec<ModelHandle<Project>>,
6839 remote_projects: Vec<ModelHandle<Project>>,
6840 next_root_dir_id: usize,
6841 pub user_store: ModelHandle<UserStore>,
6842 language_registry: Arc<LanguageRegistry>,
6843 fs: Arc<FakeFs>,
6844 buffers: HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>,
6845}
6846
6847impl Deref for TestClient {
6848 type Target = Arc<Client>;
6849
6850 fn deref(&self) -> &Self::Target {
6851 &self.client
6852 }
6853}
6854
6855struct ContactsSummary {
6856 pub current: Vec<String>,
6857 pub outgoing_requests: Vec<String>,
6858 pub incoming_requests: Vec<String>,
6859}
6860
6861impl TestClient {
6862 pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
6863 UserId::from_proto(
6864 self.user_store
6865 .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
6866 )
6867 }
6868
6869 async fn wait_for_current_user(&self, cx: &TestAppContext) {
6870 let mut authed_user = self
6871 .user_store
6872 .read_with(cx, |user_store, _| user_store.watch_current_user());
6873 while authed_user.next().await.unwrap().is_none() {}
6874 }
6875
6876 async fn clear_contacts(&self, cx: &mut TestAppContext) {
6877 self.user_store
6878 .update(cx, |store, _| store.clear_contacts())
6879 .await;
6880 }
6881
6882 fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
6883 self.user_store.read_with(cx, |store, _| ContactsSummary {
6884 current: store
6885 .contacts()
6886 .iter()
6887 .map(|contact| contact.user.github_login.clone())
6888 .collect(),
6889 outgoing_requests: store
6890 .outgoing_contact_requests()
6891 .iter()
6892 .map(|user| user.github_login.clone())
6893 .collect(),
6894 incoming_requests: store
6895 .incoming_contact_requests()
6896 .iter()
6897 .map(|user| user.github_login.clone())
6898 .collect(),
6899 })
6900 }
6901
6902 async fn build_local_project(
6903 &self,
6904 root_path: impl AsRef<Path>,
6905 cx: &mut TestAppContext,
6906 ) -> (ModelHandle<Project>, WorktreeId) {
6907 let project = cx.update(|cx| {
6908 Project::local(
6909 self.client.clone(),
6910 self.user_store.clone(),
6911 self.language_registry.clone(),
6912 self.fs.clone(),
6913 cx,
6914 )
6915 });
6916 let (worktree, _) = project
6917 .update(cx, |p, cx| {
6918 p.find_or_create_local_worktree(root_path, true, cx)
6919 })
6920 .await
6921 .unwrap();
6922 worktree
6923 .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
6924 .await;
6925 (project, worktree.read_with(cx, |tree, _| tree.id()))
6926 }
6927
6928 async fn build_remote_project(
6929 &self,
6930 host_project_id: u64,
6931 guest_cx: &mut TestAppContext,
6932 ) -> ModelHandle<Project> {
6933 let project_b = guest_cx.spawn(|cx| {
6934 Project::remote(
6935 host_project_id,
6936 self.client.clone(),
6937 self.user_store.clone(),
6938 self.language_registry.clone(),
6939 FakeFs::new(cx.background()),
6940 cx,
6941 )
6942 });
6943 project_b.await.unwrap()
6944 }
6945
6946 fn build_workspace(
6947 &self,
6948 project: &ModelHandle<Project>,
6949 cx: &mut TestAppContext,
6950 ) -> ViewHandle<Workspace> {
6951 let (_, root_view) = cx.add_window(|_| EmptyView);
6952 cx.add_view(&root_view, |cx| {
6953 Workspace::new(
6954 Default::default(),
6955 0,
6956 project.clone(),
6957 |_, _| unimplemented!(),
6958 cx,
6959 )
6960 })
6961 }
6962
6963 pub async fn simulate(
6964 mut self,
6965 username: String,
6966 mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6967 rng: Arc<Mutex<StdRng>>,
6968 mut cx: TestAppContext,
6969 ) -> (Self, TestAppContext) {
6970 async fn tick(
6971 client: &mut TestClient,
6972 username: &str,
6973 rng: Arc<Mutex<StdRng>>,
6974 cx: &mut TestAppContext,
6975 ) -> anyhow::Result<()> {
6976 let active_call = cx.read(ActiveCall::global);
6977 if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
6978 if rng.lock().gen() {
6979 log::info!("{}: accepting incoming call", username);
6980 active_call
6981 .update(cx, |call, cx| call.accept_incoming(cx))
6982 .await?;
6983 } else {
6984 log::info!("{}: declining incoming call", username);
6985 active_call.update(cx, |call, _| call.decline_incoming())?;
6986 }
6987 } else {
6988 let available_contacts = client.user_store.read_with(cx, |user_store, _| {
6989 user_store
6990 .contacts()
6991 .iter()
6992 .filter(|contact| contact.online && !contact.busy)
6993 .cloned()
6994 .collect::<Vec<_>>()
6995 });
6996
6997 let distribution = rng.lock().gen_range(0..100);
6998 match distribution {
6999 0..=29 if !available_contacts.is_empty() => {
7000 let contact = available_contacts.choose(&mut *rng.lock()).unwrap();
7001 log::info!("{}: inviting {}", username, contact.user.github_login);
7002 active_call
7003 .update(cx, |call, cx| call.invite(contact.user.id, None, cx))
7004 .await?;
7005 }
7006 30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => {
7007 log::info!("{}: hanging up", username);
7008 active_call.update(cx, |call, cx| call.hang_up(cx))?;
7009 }
7010 _ => {}
7011 }
7012 }
7013
7014 let remote_projects =
7015 if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) {
7016 room.read_with(cx, |room, _| {
7017 room.remote_participants()
7018 .values()
7019 .flat_map(|participant| participant.projects.clone())
7020 .collect::<Vec<_>>()
7021 })
7022 } else {
7023 Default::default()
7024 };
7025 let project = if remote_projects.is_empty() || rng.lock().gen() {
7026 if client.local_projects.is_empty() || rng.lock().gen() {
7027 let dir_paths = client.fs.directories().await;
7028 let local_project = if dir_paths.is_empty() || rng.lock().gen() {
7029 let root_path = format!(
7030 "/{}-root-{}",
7031 username,
7032 post_inc(&mut client.next_root_dir_id)
7033 );
7034 let root_path = Path::new(&root_path);
7035 client.fs.create_dir(root_path).await.unwrap();
7036 client
7037 .fs
7038 .create_file(&root_path.join("main.rs"), Default::default())
7039 .await
7040 .unwrap();
7041 log::info!("{}: opening local project at {:?}", username, root_path);
7042 client.build_local_project(root_path, cx).await.0
7043 } else {
7044 let root_path = dir_paths.choose(&mut *rng.lock()).unwrap();
7045 log::info!("{}: opening local project at {:?}", username, root_path);
7046 client.build_local_project(root_path, cx).await.0
7047 };
7048 client.local_projects.push(local_project.clone());
7049 local_project
7050 } else {
7051 client
7052 .local_projects
7053 .choose(&mut *rng.lock())
7054 .unwrap()
7055 .clone()
7056 }
7057 } else {
7058 if client.remote_projects.is_empty() || rng.lock().gen() {
7059 let remote_project_id = remote_projects.choose(&mut *rng.lock()).unwrap().id;
7060 let remote_project = if let Some(project) =
7061 client.remote_projects.iter().find(|project| {
7062 project.read_with(cx, |project, _| {
7063 project.remote_id() == Some(remote_project_id)
7064 })
7065 }) {
7066 project.clone()
7067 } else {
7068 log::info!("{}: opening remote project {}", username, remote_project_id);
7069 let remote_project = Project::remote(
7070 remote_project_id,
7071 client.client.clone(),
7072 client.user_store.clone(),
7073 client.language_registry.clone(),
7074 FakeFs::new(cx.background()),
7075 cx.to_async(),
7076 )
7077 .await?;
7078 client.remote_projects.push(remote_project.clone());
7079 remote_project
7080 };
7081
7082 remote_project
7083 } else {
7084 client
7085 .remote_projects
7086 .choose(&mut *rng.lock())
7087 .unwrap()
7088 .clone()
7089 }
7090 };
7091
7092 if active_call.read_with(cx, |call, _| call.room().is_some()) {
7093 if let Err(error) = active_call
7094 .update(cx, |call, cx| call.share_project(project.clone(), cx))
7095 .await
7096 {
7097 log::error!("{}: error sharing project, {:?}", username, error);
7098 }
7099 }
7100
7101 let buffers = client.buffers.entry(project.clone()).or_default();
7102 let buffer = if buffers.is_empty() || rng.lock().gen() {
7103 let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
7104 project
7105 .worktrees(cx)
7106 .filter(|worktree| {
7107 let worktree = worktree.read(cx);
7108 worktree.is_visible() && worktree.entries(false).any(|e| e.is_file())
7109 })
7110 .choose(&mut *rng.lock())
7111 }) {
7112 worktree
7113 } else {
7114 cx.background().simulate_random_delay().await;
7115 return Ok(());
7116 };
7117
7118 let (worktree_root_name, project_path) = worktree.read_with(cx, |worktree, _| {
7119 let entry = worktree
7120 .entries(false)
7121 .filter(|e| e.is_file())
7122 .choose(&mut *rng.lock())
7123 .unwrap();
7124 (
7125 worktree.root_name().to_string(),
7126 (worktree.id(), entry.path.clone()),
7127 )
7128 });
7129 log::info!(
7130 "{}: opening path {:?} in worktree {} ({})",
7131 username,
7132 project_path.1,
7133 project_path.0,
7134 worktree_root_name,
7135 );
7136 let buffer = project
7137 .update(cx, |project, cx| {
7138 project.open_buffer(project_path.clone(), cx)
7139 })
7140 .await?;
7141 log::info!(
7142 "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
7143 username,
7144 project_path.1,
7145 project_path.0,
7146 worktree_root_name,
7147 buffer.read_with(cx, |buffer, _| buffer.remote_id())
7148 );
7149 buffers.insert(buffer.clone());
7150 buffer
7151 } else {
7152 buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
7153 };
7154
7155 let choice = rng.lock().gen_range(0..100);
7156 match choice {
7157 0..=9 => {
7158 cx.update(|cx| {
7159 log::info!(
7160 "{}: dropping buffer {:?}",
7161 username,
7162 buffer.read(cx).file().unwrap().full_path(cx)
7163 );
7164 buffers.remove(&buffer);
7165 drop(buffer);
7166 });
7167 }
7168 10..=19 => {
7169 let completions = project.update(cx, |project, cx| {
7170 log::info!(
7171 "{}: requesting completions for buffer {} ({:?})",
7172 username,
7173 buffer.read(cx).remote_id(),
7174 buffer.read(cx).file().unwrap().full_path(cx)
7175 );
7176 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7177 project.completions(&buffer, offset, cx)
7178 });
7179 let completions = cx.background().spawn(async move {
7180 completions
7181 .await
7182 .map_err(|err| anyhow!("completions request failed: {:?}", err))
7183 });
7184 if rng.lock().gen_bool(0.3) {
7185 log::info!("{}: detaching completions request", username);
7186 cx.update(|cx| completions.detach_and_log_err(cx));
7187 } else {
7188 completions.await?;
7189 }
7190 }
7191 20..=29 => {
7192 let code_actions = project.update(cx, |project, cx| {
7193 log::info!(
7194 "{}: requesting code actions for buffer {} ({:?})",
7195 username,
7196 buffer.read(cx).remote_id(),
7197 buffer.read(cx).file().unwrap().full_path(cx)
7198 );
7199 let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
7200 project.code_actions(&buffer, range, cx)
7201 });
7202 let code_actions = cx.background().spawn(async move {
7203 code_actions
7204 .await
7205 .map_err(|err| anyhow!("code actions request failed: {:?}", err))
7206 });
7207 if rng.lock().gen_bool(0.3) {
7208 log::info!("{}: detaching code actions request", username);
7209 cx.update(|cx| code_actions.detach_and_log_err(cx));
7210 } else {
7211 code_actions.await?;
7212 }
7213 }
7214 30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
7215 let (requested_version, save) = buffer.update(cx, |buffer, cx| {
7216 log::info!(
7217 "{}: saving buffer {} ({:?})",
7218 username,
7219 buffer.remote_id(),
7220 buffer.file().unwrap().full_path(cx)
7221 );
7222 (buffer.version(), buffer.save(cx))
7223 });
7224 let save = cx.background().spawn(async move {
7225 let (saved_version, _, _) = save
7226 .await
7227 .map_err(|err| anyhow!("save request failed: {:?}", err))?;
7228 assert!(saved_version.observed_all(&requested_version));
7229 Ok::<_, anyhow::Error>(())
7230 });
7231 if rng.lock().gen_bool(0.3) {
7232 log::info!("{}: detaching save request", username);
7233 cx.update(|cx| save.detach_and_log_err(cx));
7234 } else {
7235 save.await?;
7236 }
7237 }
7238 40..=44 => {
7239 let prepare_rename = project.update(cx, |project, cx| {
7240 log::info!(
7241 "{}: preparing rename for buffer {} ({:?})",
7242 username,
7243 buffer.read(cx).remote_id(),
7244 buffer.read(cx).file().unwrap().full_path(cx)
7245 );
7246 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7247 project.prepare_rename(buffer, offset, cx)
7248 });
7249 let prepare_rename = cx.background().spawn(async move {
7250 prepare_rename
7251 .await
7252 .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
7253 });
7254 if rng.lock().gen_bool(0.3) {
7255 log::info!("{}: detaching prepare rename request", username);
7256 cx.update(|cx| prepare_rename.detach_and_log_err(cx));
7257 } else {
7258 prepare_rename.await?;
7259 }
7260 }
7261 45..=49 => {
7262 let definitions = project.update(cx, |project, cx| {
7263 log::info!(
7264 "{}: requesting definitions for buffer {} ({:?})",
7265 username,
7266 buffer.read(cx).remote_id(),
7267 buffer.read(cx).file().unwrap().full_path(cx)
7268 );
7269 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7270 project.definition(&buffer, offset, cx)
7271 });
7272 let definitions = cx.background().spawn(async move {
7273 definitions
7274 .await
7275 .map_err(|err| anyhow!("definitions request failed: {:?}", err))
7276 });
7277 if rng.lock().gen_bool(0.3) {
7278 log::info!("{}: detaching definitions request", username);
7279 cx.update(|cx| definitions.detach_and_log_err(cx));
7280 } else {
7281 buffers.extend(definitions.await?.into_iter().map(|loc| loc.target.buffer));
7282 }
7283 }
7284 50..=54 => {
7285 let highlights = project.update(cx, |project, cx| {
7286 log::info!(
7287 "{}: requesting highlights for buffer {} ({:?})",
7288 username,
7289 buffer.read(cx).remote_id(),
7290 buffer.read(cx).file().unwrap().full_path(cx)
7291 );
7292 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7293 project.document_highlights(&buffer, offset, cx)
7294 });
7295 let highlights = cx.background().spawn(async move {
7296 highlights
7297 .await
7298 .map_err(|err| anyhow!("highlights request failed: {:?}", err))
7299 });
7300 if rng.lock().gen_bool(0.3) {
7301 log::info!("{}: detaching highlights request", username);
7302 cx.update(|cx| highlights.detach_and_log_err(cx));
7303 } else {
7304 highlights.await?;
7305 }
7306 }
7307 55..=59 => {
7308 let search = project.update(cx, |project, cx| {
7309 let query = rng.lock().gen_range('a'..='z');
7310 log::info!("{}: project-wide search {:?}", username, query);
7311 project.search(SearchQuery::text(query, false, false), cx)
7312 });
7313 let search = cx.background().spawn(async move {
7314 search
7315 .await
7316 .map_err(|err| anyhow!("search request failed: {:?}", err))
7317 });
7318 if rng.lock().gen_bool(0.3) {
7319 log::info!("{}: detaching search request", username);
7320 cx.update(|cx| search.detach_and_log_err(cx));
7321 } else {
7322 buffers.extend(search.await?.into_keys());
7323 }
7324 }
7325 60..=79 => {
7326 let worktree = project
7327 .read_with(cx, |project, cx| {
7328 project
7329 .worktrees(cx)
7330 .filter(|worktree| {
7331 let worktree = worktree.read(cx);
7332 worktree.is_visible()
7333 && worktree.entries(false).any(|e| e.is_file())
7334 && worktree.root_entry().map_or(false, |e| e.is_dir())
7335 })
7336 .choose(&mut *rng.lock())
7337 })
7338 .unwrap();
7339 let (worktree_id, worktree_root_name) = worktree
7340 .read_with(cx, |worktree, _| {
7341 (worktree.id(), worktree.root_name().to_string())
7342 });
7343
7344 let mut new_name = String::new();
7345 for _ in 0..10 {
7346 let letter = rng.lock().gen_range('a'..='z');
7347 new_name.push(letter);
7348 }
7349
7350 let is_dir = rng.lock().gen::<bool>();
7351 let mut new_path = PathBuf::new();
7352 new_path.push(new_name);
7353 if !is_dir {
7354 new_path.set_extension("rs");
7355 }
7356 log::info!(
7357 "{}: creating {:?} in worktree {} ({})",
7358 username,
7359 new_path,
7360 worktree_id,
7361 worktree_root_name,
7362 );
7363 project
7364 .update(cx, |project, cx| {
7365 project.create_entry((worktree_id, new_path), is_dir, cx)
7366 })
7367 .unwrap()
7368 .await?;
7369 }
7370 _ => {
7371 buffer.update(cx, |buffer, cx| {
7372 log::info!(
7373 "{}: updating buffer {} ({:?})",
7374 username,
7375 buffer.remote_id(),
7376 buffer.file().unwrap().full_path(cx)
7377 );
7378 if rng.lock().gen_bool(0.7) {
7379 buffer.randomly_edit(&mut *rng.lock(), 5, cx);
7380 } else {
7381 buffer.randomly_undo_redo(&mut *rng.lock(), cx);
7382 }
7383 });
7384 }
7385 }
7386
7387 Ok(())
7388 }
7389
7390 // Setup language server
7391 let mut language = Language::new(
7392 LanguageConfig {
7393 name: "Rust".into(),
7394 path_suffixes: vec!["rs".to_string()],
7395 ..Default::default()
7396 },
7397 None,
7398 );
7399 let _fake_language_servers = language
7400 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7401 name: "the-fake-language-server",
7402 capabilities: lsp::LanguageServer::full_capabilities(),
7403 initializer: Some(Box::new({
7404 let rng = rng.clone();
7405 let fs = self.fs.clone();
7406 move |fake_server: &mut FakeLanguageServer| {
7407 fake_server.handle_request::<lsp::request::Completion, _, _>(
7408 |_, _| async move {
7409 Ok(Some(lsp::CompletionResponse::Array(vec![
7410 lsp::CompletionItem {
7411 text_edit: Some(lsp::CompletionTextEdit::Edit(
7412 lsp::TextEdit {
7413 range: lsp::Range::new(
7414 lsp::Position::new(0, 0),
7415 lsp::Position::new(0, 0),
7416 ),
7417 new_text: "the-new-text".to_string(),
7418 },
7419 )),
7420 ..Default::default()
7421 },
7422 ])))
7423 },
7424 );
7425
7426 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
7427 |_, _| async move {
7428 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
7429 lsp::CodeAction {
7430 title: "the-code-action".to_string(),
7431 ..Default::default()
7432 },
7433 )]))
7434 },
7435 );
7436
7437 fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
7438 |params, _| async move {
7439 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
7440 params.position,
7441 params.position,
7442 ))))
7443 },
7444 );
7445
7446 fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
7447 let fs = fs.clone();
7448 let rng = rng.clone();
7449 move |_, _| {
7450 let fs = fs.clone();
7451 let rng = rng.clone();
7452 async move {
7453 let files = fs.files().await;
7454 let mut rng = rng.lock();
7455 let count = rng.gen_range::<usize, _>(1..3);
7456 let files = (0..count)
7457 .map(|_| files.choose(&mut *rng).unwrap())
7458 .collect::<Vec<_>>();
7459 log::info!("LSP: Returning definitions in files {:?}", &files);
7460 Ok(Some(lsp::GotoDefinitionResponse::Array(
7461 files
7462 .into_iter()
7463 .map(|file| lsp::Location {
7464 uri: lsp::Url::from_file_path(file).unwrap(),
7465 range: Default::default(),
7466 })
7467 .collect(),
7468 )))
7469 }
7470 }
7471 });
7472
7473 fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
7474 {
7475 let rng = rng.clone();
7476 move |_, _| {
7477 let mut highlights = Vec::new();
7478 let highlight_count = rng.lock().gen_range(1..=5);
7479 for _ in 0..highlight_count {
7480 let start_row = rng.lock().gen_range(0..100);
7481 let start_column = rng.lock().gen_range(0..100);
7482 let start = PointUtf16::new(start_row, start_column);
7483 let end_row = rng.lock().gen_range(0..100);
7484 let end_column = rng.lock().gen_range(0..100);
7485 let end = PointUtf16::new(end_row, end_column);
7486 let range =
7487 if start > end { end..start } else { start..end };
7488 highlights.push(lsp::DocumentHighlight {
7489 range: range_to_lsp(range.clone()),
7490 kind: Some(lsp::DocumentHighlightKind::READ),
7491 });
7492 }
7493 highlights.sort_unstable_by_key(|highlight| {
7494 (highlight.range.start, highlight.range.end)
7495 });
7496 async move { Ok(Some(highlights)) }
7497 }
7498 },
7499 );
7500 }
7501 })),
7502 ..Default::default()
7503 }))
7504 .await;
7505 self.language_registry.add(Arc::new(language));
7506
7507 while op_start_signal.next().await.is_some() {
7508 if let Err(error) = tick(&mut self, &username, rng.clone(), &mut cx).await {
7509 log::error!("{} error: {:?}", username, error);
7510 }
7511
7512 cx.background().simulate_random_delay().await;
7513 }
7514 log::info!("{}: done", username);
7515
7516 (self, cx)
7517 }
7518}
7519
7520impl Drop for TestClient {
7521 fn drop(&mut self) {
7522 self.client.teardown();
7523 }
7524}
7525
7526#[derive(Debug, Eq, PartialEq)]
7527struct RoomParticipants {
7528 remote: Vec<String>,
7529 pending: Vec<String>,
7530}
7531
7532fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomParticipants {
7533 room.read_with(cx, |room, _| {
7534 let mut remote = room
7535 .remote_participants()
7536 .iter()
7537 .map(|(_, participant)| participant.user.github_login.clone())
7538 .collect::<Vec<_>>();
7539 let mut pending = room
7540 .pending_participants()
7541 .iter()
7542 .map(|user| user.github_login.clone())
7543 .collect::<Vec<_>>();
7544 remote.sort();
7545 pending.sort();
7546 RoomParticipants { remote, pending }
7547 })
7548}