From c503ba00b63b9b04d167b9b17ce0ac20c0584e9b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 6 Jan 2023 17:12:15 -0800 Subject: [PATCH] Add env vars to store and load test plan from JSON files --- .../src/tests/randomized_integration_tests.rs | 178 +++++++++++++++--- 1 file changed, 153 insertions(+), 25 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 8ed290bcf833f9ba8cd713a985cfa0adabdecb1d..243d275e13dc39b554afdea21224ea984829cab8 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -15,6 +15,7 @@ use lsp::FakeLanguageServer; use parking_lot::Mutex; use project::{search::SearchQuery, Project, ProjectPath}; use rand::prelude::*; +use serde::{Deserialize, Serialize}; use std::{ env, ops::Range, @@ -28,18 +29,20 @@ use util::ResultExt; async fn test_random_collaboration( cx: &mut TestAppContext, deterministic: Arc, - mut rng: StdRng, + rng: StdRng, ) { deterministic.forbid_parking(); let max_peers = env::var("MAX_PEERS") .map(|i| i.parse().expect("invalid `MAX_PEERS` variable")) - .unwrap_or(5); - + .unwrap_or(3); let max_operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); + let plan_load_path = path_env_var("LOAD_PLAN"); + let plan_save_path = path_env_var("SAVE_PLAN"); + let mut server = TestServer::start(&deterministic).await; let db = server.app_state.db.clone(); @@ -64,6 +67,7 @@ async fn test_random_collaboration( username, online: false, next_root_id: 0, + operation_ix: 0, }); } @@ -84,15 +88,12 @@ async fn test_random_collaboration( } } - let plan = Arc::new(Mutex::new(TestPlan { - allow_server_restarts: rng.gen_bool(0.7), - allow_client_reconnection: rng.gen_bool(0.7), - allow_client_disconnection: rng.gen_bool(0.1), - operation_ix: 0, - max_operations, - users, - rng, - })); + let plan = Arc::new(Mutex::new(TestPlan::new(rng, users, max_operations))); + + if let Some(path) = &plan_load_path { + eprintln!("loaded plan from path {:?}", path); + plan.lock().load(path); + } let mut clients = Vec::new(); let mut client_tasks = Vec::new(); @@ -250,6 +251,11 @@ async fn test_random_collaboration( deterministic.finish_waiting(); deterministic.run_until_parked(); + if let Some(path) = &plan_save_path { + eprintln!("saved test plan to path {:?}", path); + plan.lock().save(path); + } + for (client, client_cx) in &clients { for guest_project in client.remote_projects().iter() { guest_project.read_with(client_cx, |guest_project, cx| { @@ -760,12 +766,14 @@ async fn apply_client_operation( ClientOperation::SearchProject { project_root_name, + is_local, query, detach, } => { log::info!( - "{}: search project {} for {:?}{}", + "{}: search {} project {} for {:?}{}", client.username, + if is_local { "local" } else { "remote" }, project_root_name, query, if detach { ", detaching" } else { ", awaiting" } @@ -811,6 +819,8 @@ async fn apply_client_operation( struct TestPlan { rng: StdRng, + replay: bool, + stored_operations: Vec, max_operations: usize, operation_ix: usize, users: Vec, @@ -823,10 +833,21 @@ struct UserTestPlan { user_id: UserId, username: String, next_root_id: usize, + operation_ix: usize, online: bool, } -#[derive(Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +enum StoredOperation { + Server(Operation), + Client { + user_id: UserId, + operation: ClientOperation, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] enum Operation { AddConnection { user_id: UserId, @@ -844,7 +865,7 @@ enum Operation { }, } -#[derive(Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] enum ClientOperation { AcceptIncomingCall, RejectIncomingCall, @@ -873,6 +894,7 @@ enum ClientOperation { }, SearchProject { project_root_name: String, + is_local: bool, query: String, detach: bool, }, @@ -913,7 +935,7 @@ enum ClientOperation { }, } -#[derive(Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] enum LspRequestKind { Rename, Completion, @@ -923,15 +945,109 @@ enum LspRequestKind { } impl TestPlan { + fn new(mut rng: StdRng, users: Vec, max_operations: usize) -> Self { + Self { + replay: false, + allow_server_restarts: rng.gen_bool(0.7), + allow_client_reconnection: rng.gen_bool(0.7), + allow_client_disconnection: rng.gen_bool(0.1), + stored_operations: Vec::new(), + operation_ix: 0, + max_operations, + users, + rng, + } + } + + fn load(&mut self, path: &Path) { + let json = std::fs::read_to_string(path).unwrap(); + self.replay = true; + self.stored_operations = serde_json::from_str(&json).unwrap(); + } + + fn save(&mut self, path: &Path) { + // Format each operation as one line + let mut json = Vec::new(); + json.push(b'['); + for (i, stored_operation) in self.stored_operations.iter().enumerate() { + if i > 0 { + json.push(b','); + } + json.extend_from_slice(b"\n "); + serde_json::to_writer(&mut json, stored_operation).unwrap(); + } + json.extend_from_slice(b"\n]\n"); + std::fs::write(path, &json).unwrap(); + } + async fn next_operation( &mut self, clients: &[(Rc, TestAppContext)], + ) -> Option { + if self.replay { + while let Some(stored_operation) = self.stored_operations.get(self.operation_ix) { + self.operation_ix += 1; + if let StoredOperation::Server(operation) = stored_operation { + return Some(operation.clone()); + } + } + None + } else { + let operation = self.generate_operation(clients).await; + if let Some(operation) = &operation { + self.stored_operations + .push(StoredOperation::Server(operation.clone())) + } + operation + } + } + + async fn next_client_operation( + &mut self, + client: &TestClient, + cx: &TestAppContext, + ) -> Option { + let current_user_id = client.current_user_id(cx); + let user_ix = self + .users + .iter() + .position(|user| user.user_id == current_user_id) + .unwrap(); + let user_plan = &mut self.users[user_ix]; + + if self.replay { + while let Some(stored_operation) = self.stored_operations.get(user_plan.operation_ix) { + user_plan.operation_ix += 1; + if let StoredOperation::Client { user_id, operation } = stored_operation { + if user_id == ¤t_user_id { + return Some(operation.clone()); + } + } + } + None + } else { + let operation = self + .generate_client_operation(current_user_id, client, cx) + .await; + if let Some(operation) = &operation { + self.stored_operations.push(StoredOperation::Client { + user_id: current_user_id, + operation: operation.clone(), + }) + } + operation + } + } + + async fn generate_operation( + &mut self, + clients: &[(Rc, TestAppContext)], ) -> Option { if self.operation_ix == self.max_operations { return None; } - let operation = loop { + Some(loop { break match self.rng.gen_range(0..100) { 0..=29 if clients.len() < self.users.len() => { let user = self @@ -980,12 +1096,12 @@ impl TestPlan { } _ => continue, }; - }; - Some(operation) + }) } - async fn next_client_operation( + async fn generate_client_operation( &mut self, + user_id: UserId, client: &TestClient, cx: &TestAppContext, ) -> Option { @@ -993,9 +1109,9 @@ impl TestPlan { return None; } - let user_id = client.current_user_id(cx); + self.operation_ix += 1; let call = cx.read(ActiveCall::global); - let operation = loop { + Some(loop { match self.rng.gen_range(0..100_u32) { // Mutate the call 0..=29 => { @@ -1237,6 +1353,7 @@ impl TestPlan { let detach = self.rng.gen_bool(0.3); break ClientOperation::SearchProject { project_root_name, + is_local, query, detach, }; @@ -1293,9 +1410,7 @@ impl TestPlan { break ClientOperation::CreateFsEntry { path, is_dir }; } } - }; - self.operation_ix += 1; - Some(operation) + }) } fn next_root_dir_name(&mut self, user_id: UserId) -> String { @@ -1572,3 +1687,16 @@ fn gen_file_name(rng: &mut StdRng) -> String { } name } + +fn path_env_var(name: &str) -> Option { + let value = env::var(name).ok()?; + let mut path = PathBuf::from(value); + if path.is_relative() { + let mut abs_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + abs_path.pop(); + abs_path.pop(); + abs_path.push(path); + path = abs_path + } + Some(path) +}