use httpmock::Method::GET;
use httpmock::{Mock, 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> {
let mut endpoints: Vec<MockRef> = Vec::new();
endpoints.push(
Mock::new()
.expect_method(GET)
.expect_path(INDEX_PATH)
.return_status(200)
.create_on(&server),
);
endpoints.push(
Mock::new()
.expect_method(GET)
.expect_path(ABOUT_PATH)
.return_status(200)
.create_on(&server),
);
endpoints
}
fn common_build_configuration(
server: &MockServer,
metrics_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(),
"--metrics-file",
metrics_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(),
"--metrics-file",
metrics_file,
],
)
}
}
fn validate_test(
mock_endpoints: &[MockRef],
metrics_files: &[String],
throttle_value: usize,
previous_metrics_file_lines: Option<usize>,
) -> usize {
let mut current_metrics_file_lines = 0;
for metrics_file in metrics_files {
assert!(std::path::Path::new(metrics_file).exists());
current_metrics_file_lines += common::file_length(metrics_file);
}
assert!(mock_endpoints[INDEX_KEY].times_called() > 0);
assert!(mock_endpoints[ABOUT_KEY].times_called() > 0);
assert!(current_metrics_file_lines <= (RUN_TIME + 1) * throttle_value);
if let Some(previous_lines) = previous_metrics_file_lines {
assert!(current_metrics_file_lines > previous_lines * 4);
assert!(current_metrics_file_lines < previous_lines * 6);
}
for metrics_file in metrics_files {
std::fs::remove_file(metrics_file).expect("failed to delete metrics log file");
}
current_metrics_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 metrics_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 metrics_files = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.metrics_file = metrics_file.clone() + &i.to_string();
metrics_files.push(worker_configuration.metrics_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,
&metrics_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, &metrics_files, THROTTLE_REQUESTS, 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 metrics_files = Vec::new();
for i in 0..EXPECT_WORKERS {
let mut worker_configuration = configuration.clone();
worker_configuration.metrics_file = metrics_file.clone() + &i.to_string();
metrics_files.push(worker_configuration.metrics_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,
&metrics_files,
increased_throttle,
Some(test1_lines),
);
}