Add env vars to store and load test plan from JSON files

This commit is contained in:
Max Brunsfeld 2023-01-06 17:12:15 -08:00
parent 2351f2bd0c
commit c503ba00b6

View file

@ -15,6 +15,7 @@ use lsp::FakeLanguageServer;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{search::SearchQuery, Project, ProjectPath}; use project::{search::SearchQuery, Project, ProjectPath};
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize};
use std::{ use std::{
env, env,
ops::Range, ops::Range,
@ -28,18 +29,20 @@ use util::ResultExt;
async fn test_random_collaboration( async fn test_random_collaboration(
cx: &mut TestAppContext, cx: &mut TestAppContext,
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
mut rng: StdRng, rng: StdRng,
) { ) {
deterministic.forbid_parking(); deterministic.forbid_parking();
let max_peers = env::var("MAX_PEERS") let max_peers = env::var("MAX_PEERS")
.map(|i| i.parse().expect("invalid `MAX_PEERS` variable")) .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
.unwrap_or(5); .unwrap_or(3);
let max_operations = env::var("OPERATIONS") let max_operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10); .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 mut server = TestServer::start(&deterministic).await;
let db = server.app_state.db.clone(); let db = server.app_state.db.clone();
@ -64,6 +67,7 @@ async fn test_random_collaboration(
username, username,
online: false, online: false,
next_root_id: 0, next_root_id: 0,
operation_ix: 0,
}); });
} }
@ -84,15 +88,12 @@ async fn test_random_collaboration(
} }
} }
let plan = Arc::new(Mutex::new(TestPlan { let plan = Arc::new(Mutex::new(TestPlan::new(rng, users, max_operations)));
allow_server_restarts: rng.gen_bool(0.7),
allow_client_reconnection: rng.gen_bool(0.7), if let Some(path) = &plan_load_path {
allow_client_disconnection: rng.gen_bool(0.1), eprintln!("loaded plan from path {:?}", path);
operation_ix: 0, plan.lock().load(path);
max_operations, }
users,
rng,
}));
let mut clients = Vec::new(); let mut clients = Vec::new();
let mut client_tasks = Vec::new(); let mut client_tasks = Vec::new();
@ -250,6 +251,11 @@ async fn test_random_collaboration(
deterministic.finish_waiting(); deterministic.finish_waiting();
deterministic.run_until_parked(); 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 (client, client_cx) in &clients {
for guest_project in client.remote_projects().iter() { for guest_project in client.remote_projects().iter() {
guest_project.read_with(client_cx, |guest_project, cx| { guest_project.read_with(client_cx, |guest_project, cx| {
@ -760,12 +766,14 @@ async fn apply_client_operation(
ClientOperation::SearchProject { ClientOperation::SearchProject {
project_root_name, project_root_name,
is_local,
query, query,
detach, detach,
} => { } => {
log::info!( log::info!(
"{}: search project {} for {:?}{}", "{}: search {} project {} for {:?}{}",
client.username, client.username,
if is_local { "local" } else { "remote" },
project_root_name, project_root_name,
query, query,
if detach { ", detaching" } else { ", awaiting" } if detach { ", detaching" } else { ", awaiting" }
@ -811,6 +819,8 @@ async fn apply_client_operation(
struct TestPlan { struct TestPlan {
rng: StdRng, rng: StdRng,
replay: bool,
stored_operations: Vec<StoredOperation>,
max_operations: usize, max_operations: usize,
operation_ix: usize, operation_ix: usize,
users: Vec<UserTestPlan>, users: Vec<UserTestPlan>,
@ -823,10 +833,21 @@ struct UserTestPlan {
user_id: UserId, user_id: UserId,
username: String, username: String,
next_root_id: usize, next_root_id: usize,
operation_ix: usize,
online: bool, 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 { enum Operation {
AddConnection { AddConnection {
user_id: UserId, user_id: UserId,
@ -844,7 +865,7 @@ enum Operation {
}, },
} }
#[derive(Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
enum ClientOperation { enum ClientOperation {
AcceptIncomingCall, AcceptIncomingCall,
RejectIncomingCall, RejectIncomingCall,
@ -873,6 +894,7 @@ enum ClientOperation {
}, },
SearchProject { SearchProject {
project_root_name: String, project_root_name: String,
is_local: bool,
query: String, query: String,
detach: bool, detach: bool,
}, },
@ -913,7 +935,7 @@ enum ClientOperation {
}, },
} }
#[derive(Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
enum LspRequestKind { enum LspRequestKind {
Rename, Rename,
Completion, Completion,
@ -923,15 +945,109 @@ enum LspRequestKind {
} }
impl TestPlan { impl TestPlan {
fn new(mut rng: StdRng, users: Vec<UserTestPlan>, 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( async fn next_operation(
&mut self, &mut self,
clients: &[(Rc<TestClient>, TestAppContext)], clients: &[(Rc<TestClient>, TestAppContext)],
) -> Option<Operation> {
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<ClientOperation> {
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 == &current_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<TestClient>, TestAppContext)],
) -> Option<Operation> { ) -> Option<Operation> {
if self.operation_ix == self.max_operations { if self.operation_ix == self.max_operations {
return None; return None;
} }
let operation = loop { Some(loop {
break match self.rng.gen_range(0..100) { break match self.rng.gen_range(0..100) {
0..=29 if clients.len() < self.users.len() => { 0..=29 if clients.len() < self.users.len() => {
let user = self let user = self
@ -980,12 +1096,12 @@ impl TestPlan {
} }
_ => continue, _ => continue,
}; };
}; })
Some(operation)
} }
async fn next_client_operation( async fn generate_client_operation(
&mut self, &mut self,
user_id: UserId,
client: &TestClient, client: &TestClient,
cx: &TestAppContext, cx: &TestAppContext,
) -> Option<ClientOperation> { ) -> Option<ClientOperation> {
@ -993,9 +1109,9 @@ impl TestPlan {
return None; return None;
} }
let user_id = client.current_user_id(cx); self.operation_ix += 1;
let call = cx.read(ActiveCall::global); let call = cx.read(ActiveCall::global);
let operation = loop { Some(loop {
match self.rng.gen_range(0..100_u32) { match self.rng.gen_range(0..100_u32) {
// Mutate the call // Mutate the call
0..=29 => { 0..=29 => {
@ -1237,6 +1353,7 @@ impl TestPlan {
let detach = self.rng.gen_bool(0.3); let detach = self.rng.gen_bool(0.3);
break ClientOperation::SearchProject { break ClientOperation::SearchProject {
project_root_name, project_root_name,
is_local,
query, query,
detach, detach,
}; };
@ -1293,9 +1410,7 @@ impl TestPlan {
break ClientOperation::CreateFsEntry { path, is_dir }; break ClientOperation::CreateFsEntry { path, is_dir };
} }
} }
}; })
self.operation_ix += 1;
Some(operation)
} }
fn next_root_dir_name(&mut self, user_id: UserId) -> String { fn next_root_dir_name(&mut self, user_id: UserId) -> String {
@ -1572,3 +1687,16 @@ fn gen_file_name(rng: &mut StdRng) -> String {
} }
name name
} }
fn path_env_var(name: &str) -> Option<PathBuf> {
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)
}