use httpmock::{Method::GET, Mock, MockServer};
use std::sync::Arc;
mod common;
use goose::config::GooseConfiguration;
use goose::goose::GooseMethod;
use goose::prelude::*;
const INDEX_PATH: &str = "/";
const ABOUT_PATH: &str = "/about.html";
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(())
}
#[derive(Debug)]
struct LoadtestEndpoint<'a> {
pub path: &'a str,
pub status_code: u16,
pub weight: usize,
}
fn configure_mock_endpoints<'a>() -> Vec<LoadtestEndpoint<'a>> {
vec![
LoadtestEndpoint {
path: INDEX_PATH,
status_code: 200,
weight: 9,
},
LoadtestEndpoint {
path: ABOUT_PATH,
status_code: 200,
weight: 3,
},
]
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<Mock> {
let test_endpoints = configure_mock_endpoints();
let mut mock_endpoints = Vec::with_capacity(test_endpoints.len());
for (idx, item) in test_endpoints.iter().enumerate() {
let path = item.path;
let mock_endpoint = server.mock(|when, then| {
when.method(GET).path(path);
then.status(item.status_code);
});
assert!(idx == mock_endpoints.len());
mock_endpoints.push(mock_endpoint);
}
mock_endpoints
}
fn common_build_configuration(
server: &MockServer,
users: 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(),
"--no-reset-metrics",
"--no-transaction-metrics",
"--users",
&users.to_string(),
"--hatch-rate",
&(users * 2).to_string(),
],
)
} else if worker.is_some() {
common::build_configuration(server, vec!["--worker"])
} else {
common::build_configuration(
server,
vec![
"--no-reset-metrics",
"--no-transaction-metrics",
"--users",
&users.to_string(),
"--hatch-rate",
&(users * 2).to_string(),
],
)
}
}
fn build_scenario() -> Scenario {
let test_endpoints = configure_mock_endpoints();
let mut scenario = Scenario::new("LoadTest");
for item in &test_endpoints {
let path = item.path;
let weight = item.weight;
let closure: TransactionFunction = Arc::new(move |user| {
Box::pin(async move {
let _goose = user.get(path).await?;
Ok(())
})
});
let transaction = Transaction::new(closure).set_weight(weight).unwrap();
let new_scenario = scenario.register_transaction(transaction);
scenario = new_scenario;
}
scenario
}
fn validate_closer_test(
mock_endpoints: &[Mock],
goose_metrics: &GooseMetrics,
configuration: &GooseConfiguration,
) {
let test_endpoints = configure_mock_endpoints();
for (idx, item) in test_endpoints.iter().enumerate() {
let mock_endpoint = &mock_endpoints[idx];
assert!(
mock_endpoint.hits() > 0,
"Endpoint was not called > 0 for item: {:#?}",
&item
);
let expect_error = format!("Item does not exist in goose_metrics: {:#?}", &item);
let endpoint_metrics = goose_metrics
.requests
.get(&format!("GET {}", item.path))
.expect(&expect_error);
assert!(
endpoint_metrics.path == item.path,
"{} != {} for item: {:#?}",
endpoint_metrics.path,
item.path,
&item
);
assert!(endpoint_metrics.method == GooseMethod::Get);
let status_code: u16 = item.status_code;
assert!(
endpoint_metrics.raw_data.counter == mock_endpoint.hits(),
"response_time_counter != hits() for item: {:#?}",
&item
);
assert!(
endpoint_metrics.status_code_counts[&status_code] == mock_endpoint.hits(),
"status_code_counts != hits() for item: {:#?}",
&item
);
assert!(
endpoint_metrics.success_count == mock_endpoint.hits(),
"success_count != hits() for item: {:#?}",
&item
);
assert!(
endpoint_metrics.fail_count == 0,
"fail_count != 0 for item: {:#?}",
&item
);
}
let index = &mock_endpoints[0];
let about = &mock_endpoints[1];
let one_third_index = index.hits() / 3;
let difference = about.hits() as i32 - one_third_index as i32;
assert!((-2..=2).contains(&difference));
assert!(goose_metrics.total_users == configuration.users.unwrap());
}
async fn run_load_test(is_gaggle: bool) {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let test_endpoints = configure_mock_endpoints();
let (configuration, goose_metrics) = match is_gaggle {
false => {
let configuration =
common_build_configuration(&server, test_endpoints.len(), None, None);
let goose_metrics = common::run_load_test(
common::build_load_test(configuration.clone(), vec![build_scenario()], None, None),
None,
)
.await;
(configuration, goose_metrics)
}
true => {
let worker_configuration =
common_build_configuration(&server, test_endpoints.len(), Some(true), None);
let worker_handles = common::launch_gaggle_workers(EXPECT_WORKERS, || {
common::build_load_test(
worker_configuration.clone(),
vec![build_scenario()],
None,
None,
)
});
let manager_configuration = common_build_configuration(
&server,
test_endpoints.len(),
None,
Some(EXPECT_WORKERS),
);
let goose_metrics = common::run_load_test(
common::build_load_test(
manager_configuration.clone(),
vec![build_scenario()],
None,
None,
),
Some(worker_handles),
)
.await;
(manager_configuration, goose_metrics)
}
};
validate_closer_test(&mock_endpoints, &goose_metrics, &configuration);
}
#[tokio::test]
async fn test_single_scenario_closure() {
run_load_test(false).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn test_single_scenario_closure_gaggle() {
run_load_test(true).await;
}