Introduce calls to randomized collaboration test

Antonio Scandurra created

Change summary

crates/collab/src/integration_tests.rs | 271 +++++++++++++++------------
1 file changed, 146 insertions(+), 125 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -5792,20 +5792,6 @@ async fn test_random_collaboration(
     let mut server = TestServer::start(cx.foreground(), cx.background()).await;
     let db = server.app_state.db.clone();
 
-    let room_creator_user_id = db
-        .create_user(
-            "room-creator@example.com",
-            false,
-            NewUserParams {
-                github_login: "room-creator".into(),
-                github_user_id: 0,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap()
-        .user_id;
-
     let mut available_guests = Vec::new();
     for ix in 0..max_peers {
         let username = format!("guest-{}", ix + 1);
@@ -5822,23 +5808,25 @@ async fn test_random_collaboration(
             .await
             .unwrap()
             .user_id;
-        server
-            .app_state
-            .db
-            .send_contact_request(user_id, room_creator_user_id)
-            .await
-            .unwrap();
-        server
-            .app_state
-            .db
-            .respond_to_contact_request(room_creator_user_id, user_id, true)
-            .await
-            .unwrap();
-        available_guests.push(username);
+        available_guests.push((user_id, username));
     }
 
-    let _room_creator = server.create_client(cx, "room-creator").await;
-    let active_call = cx.read(ActiveCall::global);
+    for (ix, (user_id_a, _)) in available_guests.iter().enumerate() {
+        for (user_id_b, _) in &available_guests[ix + 1..] {
+            server
+                .app_state
+                .db
+                .send_contact_request(*user_id_a, *user_id_b)
+                .await
+                .unwrap();
+            server
+                .app_state
+                .db
+                .respond_to_contact_request(*user_id_b, *user_id_a, true)
+                .await
+                .unwrap();
+        }
+    }
 
     let mut clients = Vec::new();
     let mut user_ids = Vec::new();
@@ -5850,9 +5838,9 @@ async fn test_random_collaboration(
     while operations < max_operations {
         let distribution = rng.lock().gen_range(0..100);
         match distribution {
-            _ if !available_guests.is_empty() => {
+            0..=19 if !available_guests.is_empty() => {
                 let guest_ix = rng.lock().gen_range(0..available_guests.len());
-                let guest_username = available_guests.remove(guest_ix);
+                let (_, guest_username) = available_guests.remove(guest_ix);
                 log::info!("Adding new connection for {}", guest_username);
                 next_entity_id += 100000;
                 let mut guest_cx = TestAppContext::new(
@@ -5866,26 +5854,9 @@ async fn test_random_collaboration(
                     cx.function_name.clone(),
                 );
 
-                deterministic.start_waiting();
-                let guest = server.create_client(&mut guest_cx, &guest_username).await;
-                let guest_user_id = guest.current_user_id(&guest_cx);
-
-                active_call
-                    .update(cx, |call, cx| {
-                        call.invite(guest_user_id.to_proto(), None, cx)
-                    })
-                    .await
-                    .unwrap();
-                deterministic.run_until_parked();
-                guest_cx
-                    .read(ActiveCall::global)
-                    .update(&mut guest_cx, |call, cx| call.accept_incoming(cx))
-                    .await
-                    .unwrap();
-                deterministic.finish_waiting();
-
                 let op_start_signal = futures::channel::mpsc::unbounded();
-                user_ids.push(guest_user_id);
+                let guest = server.create_client(&mut guest_cx, &guest_username).await;
+                user_ids.push(guest.current_user_id(&guest_cx));
                 peer_ids.push(guest.peer_id().unwrap());
                 op_start_signals.push(op_start_signal.0);
                 clients.push(guest_cx.foreground().spawn(guest.simulate(
@@ -5898,21 +5869,7 @@ async fn test_random_collaboration(
                 log::info!("Added connection for {}", guest_username);
                 operations += 1;
             }
-            0..=69 if !op_start_signals.is_empty() => {
-                while operations < max_operations && rng.lock().gen_bool(0.7) {
-                    op_start_signals
-                        .choose(&mut *rng.lock())
-                        .unwrap()
-                        .unbounded_send(())
-                        .unwrap();
-                    operations += 1;
-                }
-
-                if rng.lock().gen_bool(0.8) {
-                    deterministic.run_until_parked();
-                }
-            }
-            70..=79 if clients.len() > 1 => {
+            20..=29 if clients.len() > 1 => {
                 let guest_ix = rng.lock().gen_range(1..clients.len());
                 log::info!("Removing guest {}", user_ids[guest_ix]);
                 let removed_guest_id = user_ids.remove(guest_ix);
@@ -5950,7 +5907,7 @@ async fn test_random_collaboration(
                 }
 
                 log::info!("{} removed", guest.username);
-                available_guests.push(guest.username.clone());
+                available_guests.push((removed_guest_id, guest.username.clone()));
                 guest_cx.update(|cx| {
                     cx.clear_globals();
                     drop(guest);
@@ -5958,6 +5915,20 @@ async fn test_random_collaboration(
 
                 operations += 1;
             }
+            _ if !op_start_signals.is_empty() => {
+                while operations < max_operations && rng.lock().gen_bool(0.7) {
+                    op_start_signals
+                        .choose(&mut *rng.lock())
+                        .unwrap()
+                        .unbounded_send(())
+                        .unwrap();
+                    operations += 1;
+                }
+
+                if rng.lock().gen_bool(0.8) {
+                    deterministic.run_until_parked();
+                }
+            }
             _ => {}
         }
     }
@@ -5980,63 +5951,73 @@ async fn test_random_collaboration(
                     Some((project, cx))
                 });
 
-                if let Some((host_project, host_cx)) = host_project {
-                    let host_worktree_snapshots =
-                        host_project.read_with(host_cx, |host_project, cx| {
-                            host_project
-                                .worktrees(cx)
-                                .map(|worktree| {
-                                    let worktree = worktree.read(cx);
-                                    (worktree.id(), worktree.snapshot())
-                                })
-                                .collect::<BTreeMap<_, _>>()
-                        });
-                    let guest_worktree_snapshots = guest_project
-                        .worktrees(cx)
-                        .map(|worktree| {
-                            let worktree = worktree.read(cx);
-                            (worktree.id(), worktree.snapshot())
-                        })
-                        .collect::<BTreeMap<_, _>>();
-
-                    assert_eq!(
-                        guest_worktree_snapshots.keys().collect::<Vec<_>>(),
-                        host_worktree_snapshots.keys().collect::<Vec<_>>(),
-                        "{} has different worktrees than the host",
-                        guest_client.username
-                    );
+                if !guest_project.is_read_only() {
+                    if let Some((host_project, host_cx)) = host_project {
+                        let host_worktree_snapshots =
+                            host_project.read_with(host_cx, |host_project, cx| {
+                                host_project
+                                    .worktrees(cx)
+                                    .map(|worktree| {
+                                        let worktree = worktree.read(cx);
+                                        (worktree.id(), worktree.snapshot())
+                                    })
+                                    .collect::<BTreeMap<_, _>>()
+                            });
+                        let guest_worktree_snapshots = guest_project
+                            .worktrees(cx)
+                            .map(|worktree| {
+                                let worktree = worktree.read(cx);
+                                (worktree.id(), worktree.snapshot())
+                            })
+                            .collect::<BTreeMap<_, _>>();
 
-                    for (id, host_snapshot) in &host_worktree_snapshots {
-                        let guest_snapshot = &guest_worktree_snapshots[id];
-                        assert_eq!(
-                            guest_snapshot.root_name(),
-                            host_snapshot.root_name(),
-                            "{} has different root name than the host for worktree {}",
-                            guest_client.username,
-                            id
-                        );
                         assert_eq!(
-                            guest_snapshot.entries(false).collect::<Vec<_>>(),
-                            host_snapshot.entries(false).collect::<Vec<_>>(),
-                            "{} has different snapshot than the host for worktree {}",
-                            guest_client.username,
-                            id
+                            guest_worktree_snapshots.keys().collect::<Vec<_>>(),
+                            host_worktree_snapshots.keys().collect::<Vec<_>>(),
+                            "{} has different worktrees than the host",
+                            guest_client.username
                         );
-                        assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
+
+                        for (id, host_snapshot) in &host_worktree_snapshots {
+                            let guest_snapshot = &guest_worktree_snapshots[id];
+                            assert_eq!(
+                                guest_snapshot.root_name(),
+                                host_snapshot.root_name(),
+                                "{} has different root name than the host for worktree {}",
+                                guest_client.username,
+                                id
+                            );
+                            assert_eq!(
+                                guest_snapshot.entries(false).collect::<Vec<_>>(),
+                                host_snapshot.entries(false).collect::<Vec<_>>(),
+                                "{} has different snapshot than the host for worktree {}",
+                                guest_client.username,
+                                id
+                            );
+                            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
+                        }
                     }
-                } else {
-                    assert!(guest_project.is_read_only());
                 }
 
                 guest_project.check_invariants(cx);
             });
         }
 
-        for (project_id, guest_buffers) in &guest_client.buffers {
+        for (guest_project, guest_buffers) in &guest_client.buffers {
+            let project_id = if guest_project.read_with(guest_cx, |project, _| {
+                project.is_local() || project.is_read_only()
+            }) {
+                continue;
+            } else {
+                guest_project
+                    .read_with(guest_cx, |project, _| project.remote_id())
+                    .unwrap()
+            };
+
             let host_project = clients.iter().find_map(|(client, cx)| {
                 let project = client.local_projects.iter().find(|host_project| {
                     host_project.read_with(cx, |host_project, _| {
-                        host_project.remote_id() == Some(*project_id)
+                        host_project.remote_id() == Some(project_id)
                     })
                 })?;
                 Some((project, cx))
@@ -6383,7 +6364,7 @@ struct TestClient {
     pub project_store: ModelHandle<ProjectStore>,
     language_registry: Arc<LanguageRegistry>,
     fs: Arc<FakeFs>,
-    buffers: HashMap<u64, HashSet<ModelHandle<language::Buffer>>>,
+    buffers: HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>,
 }
 
 impl Deref for TestClient {
@@ -6512,17 +6493,54 @@ impl TestClient {
             cx: &mut TestAppContext,
         ) -> anyhow::Result<()> {
             let active_call = cx.read(ActiveCall::global);
-            let room = active_call.read_with(cx, |call, _| {
-                call.room()
-                    .ok_or_else(|| anyhow!("no active call"))
-                    .cloned()
-            })?;
-            let remote_projects = room.read_with(cx, |room, _| {
-                room.remote_participants()
-                    .values()
-                    .flat_map(|participant| participant.projects.clone())
-                    .collect::<Vec<_>>()
-            });
+            if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
+                if rng.lock().gen() {
+                    log::info!("{}: accepting incoming call", username);
+                    active_call
+                        .update(cx, |call, cx| call.accept_incoming(cx))
+                        .await?;
+                } else {
+                    log::info!("{}: declining incoming call", username);
+                    active_call.update(cx, |call, _| call.decline_incoming())?;
+                }
+            } else {
+                let available_contacts = client.user_store.read_with(cx, |user_store, _| {
+                    user_store
+                        .contacts()
+                        .iter()
+                        .filter(|contact| contact.online && !contact.busy)
+                        .cloned()
+                        .collect::<Vec<_>>()
+                });
+
+                let distribution = rng.lock().gen_range(0..100);
+                match distribution {
+                    0..=29 if !available_contacts.is_empty() => {
+                        let contact = available_contacts.choose(&mut *rng.lock()).unwrap();
+                        log::info!("{}: inviting {}", username, contact.user.github_login);
+                        active_call
+                            .update(cx, |call, cx| call.invite(contact.user.id, None, cx))
+                            .await?;
+                    }
+                    30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => {
+                        log::info!("{}: hanging up", username);
+                        active_call.update(cx, |call, cx| call.hang_up(cx))?;
+                    }
+                    _ => {}
+                }
+            }
+
+            let remote_projects =
+                if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) {
+                    room.read_with(cx, |room, _| {
+                        room.remote_participants()
+                            .values()
+                            .flat_map(|participant| participant.projects.clone())
+                            .collect::<Vec<_>>()
+                    })
+                } else {
+                    Default::default()
+                };
             let project = if remote_projects.is_empty() || rng.lock().gen() {
                 if client.local_projects.is_empty() || rng.lock().gen() {
                     let dir_paths = client.fs.directories().await;
@@ -6590,11 +6608,14 @@ impl TestClient {
                         .clone()
                 }
             };
-            let project_id = active_call
+            if let Err(error) = active_call
                 .update(cx, |call, cx| call.share_project(project.clone(), cx))
-                .await?;
+                .await
+            {
+                log::error!("{}: error sharing project {:?}", username, error);
+            }
 
-            let buffers = client.buffers.entry(project_id).or_default();
+            let buffers = client.buffers.entry(project.clone()).or_default();
             let buffer = if buffers.is_empty() || rng.lock().gen() {
                 let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
                     project