use httpmock::Method::GET;
use httpmock::{Mock, MockRef, MockServer};
use std::sync::Arc;
mod common;
use goose::goose::GooseMethod;
use goose::prelude::*;
use goose::GooseConfiguration;
const INDEX_PATH: &str = "/";
const ABOUT_PATH: &str = "/about.html";
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(())
}
#[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<MockRef> {
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 = Mock::new()
.expect_method(GET)
.expect_path(path)
.return_status(item.status_code.into())
.create_on(&server);
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-task-metrics",
"--status-codes",
"--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-task-metrics",
"--status-codes",
"--users",
&users.to_string(),
"--hatch-rate",
&(users * 2).to_string(),
],
)
}
}
fn build_taskset() -> GooseTaskSet {
let test_endpoints = configure_mock_endpoints();
let mut taskset = GooseTaskSet::new("LoadTest");
for item in &test_endpoints {
let path = item.path;
let weight = item.weight;
let closure: GooseTaskFunction = Arc::new(move |user| {
Box::pin(async move {
let _goose = user.get(path).await?;
Ok(())
})
});
let task = GooseTask::new(closure).set_weight(weight).unwrap();
let new_taskset = taskset.register_task(task);
taskset = new_taskset;
}
taskset
}
fn validate_closer_test(
mock_endpoints: &[MockRef],
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];
let format_item = |message, assert_item| {
return format!("{} for item = {:#?}", message, assert_item);
};
assert!(
mock_endpoint.times_called() > 0,
format_item("Endpoint was not called > 0", &item)
);
let expect_error = format_item("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,
format_item(
&format!("{} != {}", endpoint_metrics.path, item.path),
&item
)
);
assert!(endpoint_metrics.method == GooseMethod::GET);
let status_code: u16 = item.status_code;
assert!(
endpoint_metrics.response_time_counter == mock_endpoint.times_called(),
format_item("response_time_counter != times_called()", &item)
);
assert!(
endpoint_metrics.status_code_counts[&status_code] == mock_endpoint.times_called(),
format_item("status_code_counts != times_called()", &item)
);
assert!(
endpoint_metrics.success_count == mock_endpoint.times_called(),
format_item("success_count != times_called()", &item)
);
assert!(
endpoint_metrics.fail_count == 0,
format_item("fail_count != 0", &item)
);
}
let index = &mock_endpoints[0];
let about = &mock_endpoints[1];
let one_third_index = index.times_called() / 3;
let difference = about.times_called() as i32 - one_third_index as i32;
assert!(difference >= -2 && difference <= 2);
assert!(goose_metrics.users == configuration.users.unwrap());
}
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(), &build_taskset(), None, None),
None,
);
(configuration, goose_metrics)
}
true => {
let worker_configuration =
common_build_configuration(&server, test_endpoints.len(), Some(true), None);
let worker_handles = common::launch_gaggle_workers(
common::build_load_test(worker_configuration, &build_taskset(), None, None),
EXPECT_WORKERS,
);
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(),
&build_taskset(),
None,
None,
),
Some(worker_handles),
);
(manager_configuration, goose_metrics)
}
};
validate_closer_test(&mock_endpoints, &goose_metrics, &configuration);
}
#[test]
fn test_single_taskset_closure() {
run_load_test(false);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
fn test_single_taskset_closure_gaggle() {
run_load_test(true);
}