use httpmock::{Method::GET, MockRef, MockServer};
mod common;
use goose::prelude::*;
use goose::GooseConfiguration;
const INDEX_PATH: &str = "/";
const ABOUT_PATH: &str = "/about.html";
const INDEX_KEY: usize = 0;
const ABOUT_KEY: usize = 1;
const METRICS_FILE: &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: &GooseUser) -> GooseTaskResult {
let _goose = user.get(INDEX_PATH).await?;
Ok(())
}
pub async fn get_about(user: &GooseUser) -> GooseTaskResult {
let _goose = 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,
requests_file: &str,
throttle_requests: usize,
users: usize,
run_time: usize,
worker: Option<bool>,
manager: Option<usize>,
) -> GooseConfiguration {
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(),
"--requests-file",
requests_file,
],
)
} 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(),
"--requests-file",
requests_file,
],
)
}
}
fn validate_test(
mock_endpoints: &[MockRef],
requests_files: &[String],
throttle_value: usize,
previous_requests_file_lines: Option<usize>,
) -> usize {
let mut current_requests_file_lines = 0;
for requests_file in requests_files {
assert!(std::path::Path::new(requests_file).exists());
current_requests_file_lines += common::file_length(requests_file);
}
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 requests_file in requests_files {
std::fs::remove_file(requests_file).expect("failed to delete metrics log file");
}
current_requests_file_lines
}
fn get_tasks() -> GooseTaskSet {
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,
METRICS_FILE,
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,
&[METRICS_FILE.to_string()],
THROTTLE_REQUESTS,
None,
);
let increased_throttle = THROTTLE_REQUESTS * 5;
let configuration = common_build_configuration(
&server,
METRICS_FILE,
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,
&[METRICS_FILE.to_string()],
increased_throttle,
Some(test1_lines),
);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
fn test_throttle_gaggle() {
let requests_file = "gaggle-".to_string() + METRICS_FILE;
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 requests_files = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.requests_file = requests_file.clone() + &i.to_string();
requests_files.push(worker_configuration.requests_file.clone());
let worker_goose_attack =
common::build_load_test(worker_configuration.clone(), &get_tasks(), None, None);
worker_handles.push(std::thread::spawn(move || {
common::run_load_test(worker_goose_attack, None);
}));
}
let manager_configuration = common_build_configuration(
&server,
&requests_file,
0,
USERS,
RUN_TIME,
None,
Some(EXPECT_WORKERS),
);
let manager_goose_attack =
common::build_load_test(manager_configuration.clone(), &get_tasks(), None, None);
common::run_load_test(manager_goose_attack, Some(worker_handles));
let test1_lines = validate_test(
&mock_endpoints,
&requests_files,
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 requests_files = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.requests_file = requests_file.clone() + &i.to_string();
requests_files.push(worker_configuration.requests_file.clone());
let worker_goose_attack =
common::build_load_test(worker_configuration.clone(), &get_tasks(), None, None);
worker_handles.push(std::thread::spawn(move || {
common::run_load_test(worker_goose_attack, None);
}));
}
let manager_goose_attack =
common::build_load_test(manager_configuration, &get_tasks(), None, None);
common::run_load_test(manager_goose_attack, Some(worker_handles));
let _ = validate_test(
&mock_endpoints,
&requests_files,
increased_throttle * EXPECT_WORKERS,
Some(test1_lines),
);
}