use httpmock::{Method::GET, MockRef, MockServer};
mod common;
use swanling::prelude::*;
use swanling::SwanlingConfiguration;
const INDEX_PATH: &str = "/";
const ABOUT_PATH: &str = "/about.html";
const INDEX_KEY: usize = 0;
const ABOUT_KEY: usize = 1;
const REQUEST_LOG: &str = "throttle-metrics.log";
const THROTTLE_REQUESTS: usize = 25;
const USERS: usize = 5;
const RUN_TIME: usize = 3;
const EXPECT_WORKERS: usize = 2;
pub async fn get_index(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(INDEX_PATH).await?;
Ok(())
}
pub async fn get_about(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(ABOUT_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<MockRef> {
vec![
server.mock(|when, then| {
when.method(GET).path(INDEX_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(ABOUT_PATH);
then.status(200);
}),
]
}
fn common_build_configuration(
server: &MockServer,
request_log: &str,
throttle_requests: usize,
users: usize,
run_time: usize,
worker: Option<bool>,
manager: Option<usize>,
) -> SwanlingConfiguration {
if let Some(expect_workers) = manager {
common::build_configuration(
&server,
vec![
"--manager",
"--expect-workers",
&expect_workers.to_string(),
"--users",
&users.to_string(),
"--hatch-rate",
&users.to_string(),
"--run-time",
&run_time.to_string(),
],
)
} else if worker.is_some() {
common::build_configuration(
&server,
vec![
"--worker",
"--throttle-requests",
&throttle_requests.to_string(),
"--request-log",
request_log,
],
)
} else {
common::build_configuration(
&server,
vec![
"--users",
&users.to_string(),
"--hatch-rate",
&users.to_string(),
"--run-time",
&run_time.to_string(),
"--throttle-requests",
&throttle_requests.to_string(),
"--request-log",
request_log,
],
)
}
}
fn validate_test(
mock_endpoints: &[MockRef],
request_logs: &[String],
throttle_value: usize,
previous_requests_file_lines: Option<usize>,
) -> usize {
let mut current_requests_file_lines = 0;
for request_log in request_logs {
assert!(std::path::Path::new(request_log).exists());
current_requests_file_lines += common::file_length(request_log);
}
assert!(mock_endpoints[INDEX_KEY].hits() > 0);
assert!(mock_endpoints[ABOUT_KEY].hits() > 0);
assert!(current_requests_file_lines <= (RUN_TIME + 1) * throttle_value);
if let Some(previous_lines) = previous_requests_file_lines {
assert!(current_requests_file_lines > previous_lines * 4);
assert!(current_requests_file_lines < previous_lines * 6);
}
for request_log in request_logs {
std::fs::remove_file(request_log).expect("failed to delete metrics log file");
}
current_requests_file_lines
}
fn get_tasks() -> SwanlingTaskSet {
taskset!("LoadTest")
.register_task(task!(get_index))
.register_task(task!(get_about))
}
#[test]
fn test_throttle() {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let configuration = common_build_configuration(
&server,
REQUEST_LOG,
THROTTLE_REQUESTS,
USERS,
RUN_TIME,
None,
None,
);
common::run_load_test(
common::build_load_test(configuration, &get_tasks(), None, None),
None,
);
let test1_lines = validate_test(
&mock_endpoints,
&[REQUEST_LOG.to_string()],
THROTTLE_REQUESTS,
None,
);
let increased_throttle = THROTTLE_REQUESTS * 5;
let configuration = common_build_configuration(
&server,
REQUEST_LOG,
increased_throttle,
USERS,
RUN_TIME,
None,
None,
);
common::run_load_test(
common::build_load_test(configuration, &get_tasks(), None, None),
None,
);
let _ = validate_test(
&mock_endpoints,
&[REQUEST_LOG.to_string()],
increased_throttle,
Some(test1_lines),
);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
fn test_throttle_gaggle() {
let request_log = "gaggle-".to_string() + REQUEST_LOG;
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let configuration =
common_build_configuration(&server, "", THROTTLE_REQUESTS, 0, 0, Some(true), None);
let mut worker_handles = Vec::new();
let mut request_logs = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.request_log = request_log.clone() + &i.to_string();
request_logs.push(worker_configuration.request_log.clone());
let worker_swanling_attack =
common::build_load_test(worker_configuration.clone(), &get_tasks(), None, None);
worker_handles.push(std::thread::spawn(move || {
common::run_load_test(worker_swanling_attack, None);
}));
}
let manager_configuration = common_build_configuration(
&server,
&request_log,
0,
USERS,
RUN_TIME,
None,
Some(EXPECT_WORKERS),
);
let manager_swanling_attack =
common::build_load_test(manager_configuration.clone(), &get_tasks(), None, None);
common::run_load_test(manager_swanling_attack, Some(worker_handles));
let test1_lines = validate_test(
&mock_endpoints,
&request_logs,
THROTTLE_REQUESTS * EXPECT_WORKERS,
None,
);
let increased_throttle = THROTTLE_REQUESTS * 5;
let configuration =
common_build_configuration(&server, "", increased_throttle, 0, 0, Some(true), None);
let mut worker_handles = Vec::new();
let mut request_logs = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.request_log = request_log.clone() + &i.to_string();
request_logs.push(worker_configuration.request_log.clone());
let worker_swanling_attack =
common::build_load_test(worker_configuration.clone(), &get_tasks(), None, None);
worker_handles.push(std::thread::spawn(move || {
common::run_load_test(worker_swanling_attack, None);
}));
}
let manager_swanling_attack =
common::build_load_test(manager_configuration, &get_tasks(), None, None);
common::run_load_test(manager_swanling_attack, Some(worker_handles));
let _ = validate_test(
&mock_endpoints,
&request_logs,
increased_throttle * EXPECT_WORKERS,
Some(test1_lines),
);
}