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 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 == ¤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> {
|
) -> 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)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue