use httpmock::{
Method::{DELETE, GET, HEAD, PATCH, POST, PUT},
Mock, MockServer,
};
use serial_test::serial;
mod common;
use goose::config::GooseConfiguration;
use goose::prelude::*;
const GET_PATH: &str = "/get";
const POST_PATH: &str = "/post";
const HEAD_PATH: &str = "/head";
const PATCH_PATH: &str = "/patch";
const PUT_PATH: &str = "/put";
const DELETE_PATH: &str = "/delete";
const GET_KEY: usize = 0;
const POST_KEY: usize = 1;
const HEAD_KEY: usize = 2;
const PATCH_KEY: usize = 3;
const PUT_KEY: usize = 4;
const DELETE_KEY: usize = 5;
const EXPECT_WORKERS: usize = 2;
const USERS: usize = 5;
const RUN_TIME: usize = 2;
pub async fn get_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(GET_PATH)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn get_nobuilder(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(GET_PATH).await?;
Ok(())
}
pub async fn post_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(POST_PATH)
.method(GooseMethod::Post)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn post_nobuilder(user: &mut GooseUser) -> TransactionResult {
let _goose = user.post(POST_PATH, "post body").await?;
Ok(())
}
pub async fn head_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(HEAD_PATH)
.method(GooseMethod::Head)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn head_nobuilder(user: &mut GooseUser) -> TransactionResult {
let _goose = user.head(HEAD_PATH).await?;
Ok(())
}
pub async fn patch_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(PATCH_PATH)
.method(GooseMethod::Patch)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn put_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(PUT_PATH)
.method(GooseMethod::Put)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn delete_builder(user: &mut GooseUser) -> TransactionResult {
let goose_request = GooseRequest::builder()
.path(DELETE_PATH)
.method(GooseMethod::Delete)
.build();
let _goose = user.request(goose_request).await?;
Ok(())
}
pub async fn delete_nobuilder(user: &mut GooseUser) -> TransactionResult {
let _goose = user.delete(DELETE_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<Mock<'_>> {
vec![
server.mock(|when, then| {
when.method(GET).path(GET_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(POST).path(POST_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(HEAD).path(HEAD_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(PATCH).path(PATCH_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(PUT).path(PUT_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(DELETE).path(DELETE_PATH);
then.status(200);
}),
]
}
fn common_build_configuration(
server: &MockServer,
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(),
"--no-reset-metrics",
],
)
} else if worker.is_some() {
common::build_configuration(server, vec!["--worker"])
} else {
common::build_configuration(
server,
vec![
"--users",
&USERS.to_string(),
"--hatch-rate",
&USERS.to_string(),
"--run-time",
&RUN_TIME.to_string(),
"--no-reset-metrics",
],
)
}
}
fn validate_test(is_builder: bool, mock_endpoints: &[Mock], goose_metrics: GooseMetrics) {
assert!(mock_endpoints[GET_KEY].hits() > 0);
assert!(mock_endpoints[POST_KEY].hits() > 0);
assert!(mock_endpoints[HEAD_KEY].hits() > 0);
assert!(mock_endpoints[DELETE_KEY].hits() > 0);
if is_builder {
assert!(mock_endpoints[PATCH_KEY].hits() > 0);
assert!(mock_endpoints[PUT_KEY].hits() > 0);
}
let get_metrics = goose_metrics
.requests
.get(&format!("GET {GET_PATH}"))
.unwrap();
assert!(get_metrics.path == GET_PATH);
assert!(get_metrics.method == GooseMethod::Get);
assert!(get_metrics.fail_count == 0);
assert!(get_metrics.success_count > 0);
let post_metrics = goose_metrics
.requests
.get(&format!("POST {POST_PATH}"))
.unwrap();
assert!(post_metrics.path == POST_PATH);
assert!(post_metrics.method == GooseMethod::Post);
assert!(post_metrics.fail_count == 0);
assert!(post_metrics.success_count > 0);
let head_metrics = goose_metrics
.requests
.get(&format!("HEAD {HEAD_PATH}"))
.unwrap();
assert!(head_metrics.path == HEAD_PATH);
assert!(head_metrics.method == GooseMethod::Head);
assert!(head_metrics.fail_count == 0);
assert!(head_metrics.success_count > 0);
let patch_metrics = goose_metrics
.requests
.get(&format!("DELETE {DELETE_PATH}"))
.unwrap();
assert!(patch_metrics.path == DELETE_PATH);
assert!(patch_metrics.method == GooseMethod::Delete);
assert!(patch_metrics.fail_count == 0);
assert!(patch_metrics.success_count > 0);
if is_builder {
let patch_metrics = goose_metrics
.requests
.get(&format!("PATCH {PATCH_PATH}"))
.unwrap();
assert!(patch_metrics.path == PATCH_PATH);
assert!(patch_metrics.method == GooseMethod::Patch);
assert!(patch_metrics.fail_count == 0);
assert!(patch_metrics.success_count > 0);
let patch_metrics = goose_metrics
.requests
.get(&format!("PUT {PUT_PATH}"))
.unwrap();
assert!(patch_metrics.path == PUT_PATH);
assert!(patch_metrics.method == GooseMethod::Put);
assert!(patch_metrics.fail_count == 0);
assert!(patch_metrics.success_count > 0);
}
}
fn get_transactions(is_builder: bool) -> Scenario {
if is_builder {
scenario!("LoadTest")
.register_transaction(transaction!(get_builder))
.register_transaction(transaction!(post_builder))
.register_transaction(transaction!(head_builder))
.register_transaction(transaction!(patch_builder))
.register_transaction(transaction!(put_builder))
.register_transaction(transaction!(delete_builder))
} else {
scenario!("LoadTest")
.register_transaction(transaction!(get_nobuilder))
.register_transaction(transaction!(post_nobuilder))
.register_transaction(transaction!(head_nobuilder))
.register_transaction(transaction!(delete_nobuilder))
}
}
async fn run_load_test(is_builder: bool, is_gaggle: bool) {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let metrics = match is_gaggle {
false => {
let configuration = common_build_configuration(&server, None, None);
common::run_load_test(
common::build_load_test(
configuration,
vec![get_transactions(is_builder)],
None,
None,
),
None,
)
.await
}
true => {
let worker_configuration = common_build_configuration(&server, Some(true), None);
let worker_handles = common::launch_gaggle_workers(EXPECT_WORKERS, || {
common::build_load_test(
worker_configuration.clone(),
vec![get_transactions(is_builder)],
None,
None,
)
});
let manager_configuration =
common_build_configuration(&server, None, Some(EXPECT_WORKERS));
common::run_load_test(
common::build_load_test(
manager_configuration,
vec![get_transactions(is_builder)],
None,
None,
),
Some(worker_handles),
)
.await
}
};
validate_test(is_builder, &mock_endpoints, metrics);
}
#[tokio::test]
async fn test_request_builder() {
run_load_test(true, false).await;
}
#[tokio::test]
async fn test_request_no_builder() {
run_load_test(false, false).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_request_builder_gaggle() {
run_load_test(true, true).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_request_no_builder_gaggle() {
run_load_test(false, true).await;
}