use httpmock::{
Method::{GET, POST},
Mock, MockServer,
};
use serial_test::serial;
mod common;
use goose::config::GooseConfiguration;
use goose::prelude::*;
const INDEX_PATH: &str = "/";
const SETUP_PATH: &str = "/setup";
const TEARDOWN_PATH: &str = "/teardown";
const INDEX_KEY: usize = 0;
const SETUP_KEY: usize = 1;
const TEARDOWN_KEY: usize = 2;
const EXPECT_WORKERS: usize = 2;
const USERS: &str = "4";
#[derive(Clone)]
enum TestType {
Start,
Stop,
StartAndStop,
}
pub async fn setup(user: &mut GooseUser) -> TransactionResult {
let _goose = user.post(SETUP_PATH, "setting up load test").await?;
Ok(())
}
pub async fn teardown(user: &mut GooseUser) -> TransactionResult {
let _goose = user
.post(TEARDOWN_PATH, "cleaning up after load test")
.await?;
Ok(())
}
pub async fn get_index(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(INDEX_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(201);
}),
server.mock(|when, then| {
when.method(POST).path(SETUP_PATH);
then.status(205);
}),
server.mock(|when, then| {
when.method(POST).path(TEARDOWN_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,
"--hatch-rate",
USERS,
],
)
} else if worker.is_some() {
common::build_configuration(server, vec!["--worker"])
} else {
common::build_configuration(server, vec!["--users", USERS, "--hatch-rate", USERS])
}
}
fn validate_test(test_type: &TestType, mock_endpoints: &[Mock]) {
assert!(mock_endpoints[INDEX_KEY].hits() > 0);
match test_type {
TestType::Start => {
mock_endpoints[SETUP_KEY].assert_hits(1);
mock_endpoints[TEARDOWN_KEY].assert_hits(0);
}
TestType::Stop => {
mock_endpoints[SETUP_KEY].assert_hits(0);
mock_endpoints[TEARDOWN_KEY].assert_hits(1);
}
TestType::StartAndStop => {
mock_endpoints[SETUP_KEY].assert_hits(1);
mock_endpoints[TEARDOWN_KEY].assert_hits(1);
}
}
}
fn build_goose_attack(test_type: &TestType, configuration: GooseConfiguration) -> GooseAttack {
let scenario =
scenario!("LoadTest").register_transaction(transaction!(get_index).set_weight(9).unwrap());
let start_transaction = transaction!(setup);
let stop_transaction = transaction!(teardown);
match test_type {
TestType::Start => common::build_load_test(
configuration,
vec![scenario],
Some(&start_transaction),
None,
),
TestType::Stop => {
common::build_load_test(configuration, vec![scenario], None, Some(&stop_transaction))
}
TestType::StartAndStop => common::build_load_test(
configuration,
vec![scenario],
Some(&start_transaction),
Some(&stop_transaction),
),
}
}
async fn run_standalone_test(test_type: TestType) {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let configuration = common_build_configuration(&server, None, None);
let goose_attack = build_goose_attack(&test_type, configuration);
common::run_load_test(goose_attack, None).await;
validate_test(&test_type, &mock_endpoints);
}
async fn run_gaggle_test(test_type: TestType) {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let worker_configuration = common_build_configuration(&server, Some(true), None);
let worker_handles = common::launch_gaggle_workers(EXPECT_WORKERS, || {
build_goose_attack(&test_type, worker_configuration.clone())
});
let manager_configuration = common_build_configuration(&server, None, Some(EXPECT_WORKERS));
let goose_attack = build_goose_attack(&test_type, manager_configuration);
common::run_load_test(goose_attack, Some(worker_handles)).await;
validate_test(&test_type, &mock_endpoints);
}
#[tokio::test]
async fn test_setup() {
run_standalone_test(TestType::Start).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_setup_gaggle() {
run_gaggle_test(TestType::Start).await;
}
#[tokio::test]
async fn test_teardown() {
run_standalone_test(TestType::Stop).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_teardown_gaggle() {
run_gaggle_test(TestType::Stop).await;
}
#[tokio::test]
async fn test_setup_teardown() {
run_standalone_test(TestType::StartAndStop).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_setup_teardown_gaggle() {
run_gaggle_test(TestType::StartAndStop).await;
}