use httpmock::{Method::GET, Mock, MockServer};
use serial_test::serial;
use tokio::time::{sleep, Duration};
mod common;
use goose::config::GooseConfiguration;
use goose::prelude::*;
const ONE_PATH: &str = "/one";
const TWO_PATH: &str = "/two";
const THREE_PATH: &str = "/three";
const START_ONE_PATH: &str = "/start/one";
const STOP_ONE_PATH: &str = "/stop/one";
const ONE_KEY: usize = 0;
const TWO_KEY: usize = 1;
const THREE_KEY: usize = 2;
const START_ONE_KEY: usize = 3;
const STOP_ONE_KEY: usize = 4;
const EXPECT_WORKERS: usize = 2;
const USERS: usize = 4;
const RUN_TIME: usize = 2;
#[derive(Clone)]
enum TestType {
NotSequenced,
SequencedRoundRobin,
SequencedSerial,
}
pub async fn one(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(ONE_PATH).await?;
Ok(())
}
pub async fn two_with_delay(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(TWO_PATH).await?;
sleep(Duration::from_secs(RUN_TIME as u64 + 2)).await;
Ok(())
}
pub async fn three(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(THREE_PATH).await?;
Ok(())
}
pub async fn start_one(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(START_ONE_PATH).await?;
Ok(())
}
pub async fn stop_one(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(STOP_ONE_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<Mock<'_>> {
vec![
server.mock(|when, then| {
when.method(GET).path(ONE_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(TWO_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(THREE_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(START_ONE_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(STOP_ONE_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(test_type: &TestType, mock_endpoints: &[Mock]) {
mock_endpoints[START_ONE_KEY].assert_hits(1);
match test_type {
TestType::NotSequenced => {
mock_endpoints[ONE_KEY].assert_hits(USERS);
mock_endpoints[THREE_KEY].assert_hits(USERS);
mock_endpoints[TWO_KEY].assert_hits(USERS);
}
TestType::SequencedRoundRobin => {
mock_endpoints[ONE_KEY].assert_hits(USERS * 3);
mock_endpoints[TWO_KEY].assert_hits(USERS);
mock_endpoints[THREE_KEY].assert_hits(0);
}
TestType::SequencedSerial => {
mock_endpoints[ONE_KEY].assert_hits(USERS * 4);
mock_endpoints[TWO_KEY].assert_hits(USERS);
mock_endpoints[THREE_KEY].assert_hits(0);
}
}
mock_endpoints[STOP_ONE_KEY].assert_hits(1);
}
fn get_transactions(test_type: &TestType) -> (Scenario, Transaction, Transaction) {
match test_type {
TestType::NotSequenced => (
scenario!("LoadTest")
.register_transaction(transaction!(one).set_weight(2).unwrap())
.register_transaction(transaction!(three))
.register_transaction(transaction!(two_with_delay)),
transaction!(start_one),
transaction!(stop_one),
),
TestType::SequencedRoundRobin => (
scenario!("LoadTest")
.register_transaction(transaction!(one).set_sequence(1).set_weight(2).unwrap())
.register_transaction(transaction!(three).set_sequence(3))
.register_transaction(transaction!(one).set_sequence(2).set_weight(2).unwrap())
.register_transaction(transaction!(two_with_delay).set_sequence(2)),
transaction!(start_one),
transaction!(stop_one),
),
TestType::SequencedSerial => (
scenario!("LoadTest")
.register_transaction(transaction!(one).set_sequence(1).set_weight(2).unwrap())
.register_transaction(transaction!(three).set_sequence(3))
.register_transaction(transaction!(one).set_sequence(2).set_weight(2).unwrap())
.register_transaction(transaction!(two_with_delay).set_sequence(2)),
transaction!(start_one),
transaction!(stop_one),
),
}
}
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 (scenario, start_transaction, stop_transaction) = get_transactions(&test_type);
let goose_attack = match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
crate::GooseAttack::initialize_with_config(configuration)
.unwrap()
.register_scenario(scenario)
.test_start(start_transaction)
.test_stop(stop_transaction)
.set_scheduler(GooseScheduler::RoundRobin)
}
TestType::SequencedSerial => {
crate::GooseAttack::initialize_with_config(configuration)
.unwrap()
.register_scenario(scenario)
.test_start(start_transaction)
.test_stop(stop_transaction)
.set_scheduler(GooseScheduler::Serial)
}
};
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 (scenario, start_transaction, stop_transaction) = get_transactions(&test_type);
let worker_handles = match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
common::launch_gaggle_workers(EXPECT_WORKERS, || {
crate::GooseAttack::initialize_with_config(worker_configuration.clone())
.unwrap()
.register_scenario(scenario.clone())
.test_start(start_transaction.clone())
.test_stop(stop_transaction.clone())
.set_scheduler(GooseScheduler::RoundRobin)
})
}
TestType::SequencedSerial => common::launch_gaggle_workers(EXPECT_WORKERS, || {
crate::GooseAttack::initialize_with_config(worker_configuration.clone())
.unwrap()
.register_scenario(scenario.clone())
.test_start(start_transaction.clone())
.test_stop(stop_transaction.clone())
.set_scheduler(GooseScheduler::Serial)
}),
};
let manager_configuration = common_build_configuration(&server, None, Some(EXPECT_WORKERS));
let manager_goose_attack = match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
crate::GooseAttack::initialize_with_config(manager_configuration)
.unwrap()
.register_scenario(scenario)
.test_start(start_transaction)
.test_stop(stop_transaction)
.set_scheduler(GooseScheduler::RoundRobin)
}
TestType::SequencedSerial => {
crate::GooseAttack::initialize_with_config(manager_configuration)
.unwrap()
.register_scenario(scenario)
.test_start(start_transaction)
.test_stop(stop_transaction)
.set_scheduler(GooseScheduler::Serial)
}
};
common::run_load_test(manager_goose_attack, Some(worker_handles)).await;
validate_test(&test_type, &mock_endpoints);
}
#[tokio::test]
async fn test_not_sequenced() {
run_standalone_test(TestType::NotSequenced).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_not_sequenced_gaggle() {
run_gaggle_test(TestType::NotSequenced).await;
}
#[tokio::test]
async fn test_sequenced_round_robin() {
run_standalone_test(TestType::SequencedRoundRobin).await;
}
#[tokio::test]
async fn test_sequenced_sequential() {
run_standalone_test(TestType::SequencedSerial).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_sequenced_round_robin_gaggle() {
run_gaggle_test(TestType::SequencedRoundRobin).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_sequenced_sequential_gaggle() {
run_gaggle_test(TestType::SequencedSerial).await;
}