Tweak operation rates

Max Brunsfeld created

Change summary

crates/collab/src/tests/randomized_integration_tests.rs | 237 ++++++----
1 file changed, 133 insertions(+), 104 deletions(-)

Detailed changes

crates/collab/src/tests/randomized_integration_tests.rs 🔗

@@ -230,11 +230,7 @@ async fn test_random_collaboration(
                 i += 1;
             }
 
-            Operation::RunUntilParked => {
-                deterministic.run_until_parked();
-            }
-
-            Operation::MutateClients(user_ids) => {
+            Operation::MutateClients { user_ids, quiesce } => {
                 for user_id in user_ids {
                     let client_ix = clients
                         .iter()
@@ -243,6 +239,10 @@ async fn test_random_collaboration(
                     operation_channels[client_ix].unbounded_send(()).unwrap();
                     i += 1;
                 }
+
+                if quiesce {
+                    deterministic.run_until_parked();
+                }
             }
         }
     }
@@ -444,12 +444,20 @@ struct UserTestPlan {
 
 #[derive(Debug)]
 enum Operation {
-    AddConnection { user_id: UserId },
-    RemoveConnection { user_id: UserId },
-    BounceConnection { user_id: UserId },
+    AddConnection {
+        user_id: UserId,
+    },
+    RemoveConnection {
+        user_id: UserId,
+    },
+    BounceConnection {
+        user_id: UserId,
+    },
     RestartServer,
-    RunUntilParked,
-    MutateClients(Vec<UserId>),
+    MutateClients {
+        user_ids: Vec<UserId>,
+        quiesce: bool,
+    },
 }
 
 #[derive(Debug)]
@@ -490,7 +498,7 @@ impl TestPlan {
     async fn next_operation(&mut self, clients: &[(Rc<TestClient>, TestAppContext)]) -> Operation {
         let operation = loop {
             break match self.rng.gen_range(0..100) {
-                0..=19 if clients.len() < self.users.len() => {
+                0..=29 if clients.len() < self.users.len() => {
                     let user = self
                         .users
                         .iter()
@@ -501,20 +509,19 @@ impl TestPlan {
                         user_id: user.user_id,
                     }
                 }
-                20..=24 if clients.len() > 1 && self.allow_client_disconnection => {
+                30..=34 if clients.len() > 1 && self.allow_client_disconnection => {
                     let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
                     let user_id = client.current_user_id(cx);
                     Operation::RemoveConnection { user_id }
                 }
-                25..=29 if clients.len() > 1 && self.allow_client_reconnection => {
+                35..=39 if clients.len() > 1 && self.allow_client_reconnection => {
                     let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
                     let user_id = client.current_user_id(cx);
                     Operation::BounceConnection { user_id }
                 }
-                30..=34 if self.allow_server_restarts && clients.len() > 1 => {
+                40..=44 if self.allow_server_restarts && clients.len() > 1 => {
                     Operation::RestartServer
                 }
-                35..=39 => Operation::RunUntilParked,
                 _ if !clients.is_empty() => {
                     let user_ids = (0..self.rng.gen_range(0..10))
                         .map(|_| {
@@ -523,7 +530,10 @@ impl TestPlan {
                             client.current_user_id(cx)
                         })
                         .collect();
-                    Operation::MutateClients(user_ids)
+                    Operation::MutateClients {
+                        user_ids,
+                        quiesce: self.rng.gen(),
+                    }
                 }
                 _ => continue,
             };
@@ -541,78 +551,95 @@ impl TestPlan {
         let operation = loop {
             match self.rng.gen_range(0..100) {
                 // Mutate the call
-                0..=19 => match self.rng.gen_range(0..100_u32) {
+                0..=29 => {
                     // Respond to an incoming call
-                    0..=39 => {
-                        if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
-                            break if self.rng.gen_bool(0.7) {
-                                ClientOperation::AcceptIncomingCall
-                            } else {
-                                ClientOperation::RejectIncomingCall
-                            };
-                        }
+                    if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
+                        break if self.rng.gen_bool(0.7) {
+                            ClientOperation::AcceptIncomingCall
+                        } else {
+                            ClientOperation::RejectIncomingCall
+                        };
                     }
 
-                    // Invite a contact to the current call
-                    30..=89 => {
-                        let available_contacts =
-                            client.user_store.read_with(cx, |user_store, _| {
-                                user_store
-                                    .contacts()
-                                    .iter()
-                                    .filter(|contact| contact.online && !contact.busy)
-                                    .cloned()
-                                    .collect::<Vec<_>>()
-                            });
-                        if !available_contacts.is_empty() {
-                            let contact = available_contacts.choose(&mut self.rng).unwrap();
-                            break ClientOperation::InviteContactToCall {
-                                user_id: UserId(contact.user.id as i32),
-                            };
+                    match self.rng.gen_range(0..100_u32) {
+                        // Invite a contact to the current call
+                        0..=70 => {
+                            let available_contacts =
+                                client.user_store.read_with(cx, |user_store, _| {
+                                    user_store
+                                        .contacts()
+                                        .iter()
+                                        .filter(|contact| contact.online && !contact.busy)
+                                        .cloned()
+                                        .collect::<Vec<_>>()
+                                });
+                            if !available_contacts.is_empty() {
+                                let contact = available_contacts.choose(&mut self.rng).unwrap();
+                                break ClientOperation::InviteContactToCall {
+                                    user_id: UserId(contact.user.id as i32),
+                                };
+                            }
                         }
-                    }
 
-                    // Leave the current call
-                    90.. => {
-                        if self.allow_client_disconnection
-                            && call.read_with(cx, |call, _| call.room().is_some())
-                        {
-                            break ClientOperation::LeaveCall;
+                        // Leave the current call
+                        71.. => {
+                            if self.allow_client_disconnection
+                                && call.read_with(cx, |call, _| call.room().is_some())
+                            {
+                                break ClientOperation::LeaveCall;
+                            }
                         }
                     }
-                },
+                }
 
                 // Mutate projects
-                20..=39 => match self.rng.gen_range(0..100_u32) {
-                    // Open a remote project
-                    0..=30 => {
+                39..=59 => match self.rng.gen_range(0..100_u32) {
+                    // Open a new project
+                    0..=70 => {
+                        // Open a remote project
                         if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {
-                            let remote_projects = room.read_with(cx, |room, _| {
+                            let existing_remote_project_ids = cx.read(|cx| {
+                                client
+                                    .remote_projects()
+                                    .iter()
+                                    .map(|p| p.read(cx).remote_id().unwrap())
+                                    .collect::<Vec<_>>()
+                            });
+                            let new_remote_projects = room.read_with(cx, |room, _| {
                                 room.remote_participants()
                                     .values()
                                     .flat_map(|participant| {
-                                        participant.projects.iter().map(|project| {
-                                            (
-                                                UserId::from_proto(participant.user.id),
-                                                project.worktree_root_names[0].clone(),
-                                            )
+                                        participant.projects.iter().filter_map(|project| {
+                                            if existing_remote_project_ids.contains(&project.id) {
+                                                None
+                                            } else {
+                                                Some((
+                                                    UserId::from_proto(participant.user.id),
+                                                    project.worktree_root_names[0].clone(),
+                                                ))
+                                            }
                                         })
                                     })
                                     .collect::<Vec<_>>()
                             });
-                            if !remote_projects.is_empty() {
+                            if !new_remote_projects.is_empty() {
                                 let (host_id, first_root_name) =
-                                    remote_projects.choose(&mut self.rng).unwrap().clone();
+                                    new_remote_projects.choose(&mut self.rng).unwrap().clone();
                                 break ClientOperation::OpenRemoteProject {
                                     host_id,
                                     first_root_name,
                                 };
                             }
                         }
+                        // Open a local project
+                        else {
+                            let first_root_name = self.next_root_dir_name(user_id);
+                            break ClientOperation::OpenLocalProject { first_root_name };
+                        }
                     }
 
                     // Close a remote project
-                    31..=40 => {
+                    71..=80 => {
                         if !client.remote_projects().is_empty() {
                             let project = client
                                 .remote_projects()
@@ -626,14 +653,8 @@ impl TestPlan {
                         }
                     }
 
-                    // Open a local project
-                    41..=60 => {
-                        let first_root_name = self.next_root_dir_name(user_id);
-                        break ClientOperation::OpenLocalProject { first_root_name };
-                    }
-
                     // Add a worktree to a local project
-                    61.. => {
+                    81.. => {
                         if !client.local_projects().is_empty() {
                             let project = client
                                 .local_projects()
@@ -659,7 +680,7 @@ impl TestPlan {
                 },
 
                 // Mutate buffers
-                40..=79 => {
+                60.. => {
                     let Some(project) = choose_random_project(client, &mut self.rng) else { continue };
                     let project_root_name = root_name_for_project(&project, cx);
 
@@ -871,9 +892,8 @@ async fn simulate_client(
         let operation = plan.lock().next_client_operation(&client, &cx).await;
         if let Err(error) = apply_client_operation(&client, plan.clone(), operation, &mut cx).await
         {
-            log::error!("{} error: {:?}", client.username, error);
+            log::error!("{} error: {}", client.username, error);
         }
-
         cx.background().simulate_random_delay().await;
     }
     log::info!("{}: done", client.username);
@@ -928,34 +948,7 @@ async fn apply_client_operation(
                 .await
                 .unwrap();
             let project = client.build_local_project(root_path, cx).await.0;
-
-            let active_call = cx.read(ActiveCall::global);
-            if active_call.read_with(cx, |call, _| call.room().is_some())
-                && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
-            {
-                match active_call
-                    .update(cx, |call, cx| call.share_project(project.clone(), cx))
-                    .await
-                {
-                    Ok(project_id) => {
-                        log::info!(
-                            "{}: shared project {} with id {}",
-                            client.username,
-                            first_root_name,
-                            project_id
-                        );
-                    }
-                    Err(error) => {
-                        log::error!(
-                            "{}: error sharing project {}: {:?}",
-                            client.username,
-                            first_root_name,
-                            error
-                        );
-                    }
-                }
-            }
-
+            ensure_project_shared(&project, client, cx).await;
             client.local_projects_mut().push(project.clone());
         }
 
@@ -971,6 +964,7 @@ async fn apply_client_operation(
             );
             let project = project_for_root_name(client, &project_root_name, cx)
                 .expect("invalid project in test operation");
+            ensure_project_shared(&project, client, cx).await;
             if !client.fs.paths().await.contains(&new_root_path) {
                 client.fs.create_dir(&new_root_path).await.unwrap();
             }
@@ -984,13 +978,13 @@ async fn apply_client_operation(
 
         ClientOperation::CloseRemoteProject { project_root_name } => {
             log::info!(
-                "{}: dropping project with root path {}",
+                "{}: closing remote project with root path {}",
                 client.username,
                 project_root_name,
             );
             let ix = project_ix_for_root_name(&*client.remote_projects(), &project_root_name, cx)
                 .expect("invalid project in test operation");
-            client.remote_projects_mut().remove(ix);
+            cx.update(|_| client.remote_projects_mut().remove(ix));
         }
 
         ClientOperation::OpenRemoteProject {
@@ -1027,13 +1021,14 @@ async fn apply_client_operation(
             full_path,
         } => {
             log::info!(
-                "{}: opening path {:?} in project {}",
+                "{}: opening buffer {:?} in project {}",
                 client.username,
                 full_path,
                 project_root_name,
             );
             let project = project_for_root_name(client, &project_root_name, cx)
                 .expect("invalid project in test operation");
+            // ensure_project_shared(&project, client, cx).await;
             let mut components = full_path.components();
             let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
             let path = components.as_path();
@@ -1086,10 +1081,10 @@ async fn apply_client_operation(
             });
         }
 
-        _ => {
+        ClientOperation::Other => {
             let choice = plan.lock().rng.gen_range(0..100);
             match choice {
-                50..=59
+                0..=59
                     if !client.local_projects().is_empty()
                         || !client.remote_projects().is_empty() =>
                 {
@@ -1147,6 +1142,40 @@ fn root_name_for_project(project: &ModelHandle<Project>, cx: &TestAppContext) ->
     })
 }
 
+async fn ensure_project_shared(
+    project: &ModelHandle<Project>,
+    client: &TestClient,
+    cx: &mut TestAppContext,
+) {
+    let first_root_name = root_name_for_project(project, cx);
+    let active_call = cx.read(ActiveCall::global);
+    if active_call.read_with(cx, |call, _| call.room().is_some())
+        && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
+    {
+        match active_call
+            .update(cx, |call, cx| call.share_project(project.clone(), cx))
+            .await
+        {
+            Ok(project_id) => {
+                log::info!(
+                    "{}: shared project {} with id {}",
+                    client.username,
+                    first_root_name,
+                    project_id
+                );
+            }
+            Err(error) => {
+                log::error!(
+                    "{}: error sharing project {}: {:?}",
+                    client.username,
+                    first_root_name,
+                    error
+                );
+            }
+        }
+    }
+}
+
 async fn randomly_mutate_fs(client: &TestClient, plan: &Arc<Mutex<TestPlan>>) {
     let is_dir = plan.lock().rng.gen::<bool>();
     let mut new_path = client