use httpmock::{Method::GET, Mock, MockServer};
use serial_test::serial;
mod common;
use goose::config::GooseConfiguration;
use goose::prelude::*;
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: &mut GooseUser) -> TransactionResult {
let _goose = user.get(INDEX_PATH).await?;
Ok(())
}
pub async fn get_about(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(ABOUT_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<Mock<'_>> {
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>,
) -> 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(),
"--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: &[Mock],
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_transactions() -> Scenario {
scenario!("LoadTest")
.register_transaction(transaction!(get_index))
.register_transaction(transaction!(get_about))
}
#[tokio::test]
#[serial]
async 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, vec![get_transactions()], None, None),
None,
)
.await;
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, vec![get_transactions()], None, None),
None,
)
.await;
let _ = validate_test(
&mock_endpoints,
&[REQUEST_LOG.to_string()],
increased_throttle,
Some(test1_lines),
);
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async 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_goose_attack = common::build_load_test(
worker_configuration.clone(),
vec![get_transactions()],
None,
None,
);
worker_handles.push(tokio::spawn(common::run_load_test(
worker_goose_attack,
None,
)));
}
let manager_configuration = common_build_configuration(
&server,
&request_log,
0,
USERS,
RUN_TIME,
None,
Some(EXPECT_WORKERS),
);
let manager_goose_attack = common::build_load_test(
manager_configuration.clone(),
vec![get_transactions()],
None,
None,
);
common::run_load_test(manager_goose_attack, Some(worker_handles)).await;
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_goose_attack = common::build_load_test(
worker_configuration.clone(),
vec![get_transactions()],
None,
None,
);
worker_handles.push(tokio::spawn(common::run_load_test(
worker_goose_attack,
None,
)));
}
let manager_goose_attack =
common::build_load_test(manager_configuration, vec![get_transactions()], None, None);
common::run_load_test(manager_goose_attack, Some(worker_handles)).await;
let _ = validate_test(
&mock_endpoints,
&request_logs,
increased_throttle * EXPECT_WORKERS,
Some(test1_lines),
);
}