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 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<Deterministic>,
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<StoredOperation>,
max_operations: usize,
operation_ix: usize,
users: Vec<UserTestPlan>,
@ -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<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(
&mut self,
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> {
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<ClientOperation> {
@ -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<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)
}