Add env vars to store and load test plan from JSON files
This commit is contained in:
parent
2351f2bd0c
commit
c503ba00b6
1 changed files with 153 additions and 25 deletions
|
@ -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 == ¤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<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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue