use httpmock::{Method::GET, Mock, MockServer};
use nix::sys::signal::{kill, SIGINT};
use nix::unistd::getpid;
use serial_test::serial;
use tokio::time::{sleep, Duration};
mod common;
use goose::config::GooseConfiguration;
use goose::goose::GooseMethod;
use goose::prelude::*;
const INDEX_PATH: &str = "/";
const INDEX_KEY: usize = 0;
const EXPECT_WORKERS: usize = 2;
enum TestType {
NoRunTime,
RunTime,
TestPlanIncrease,
TestPlanDecrease,
TestPlanMaintain,
Iterations,
}
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(200);
}),
]
}
fn common_build_configuration(server: &MockServer, test_type: &TestType) -> GooseConfiguration {
let configuration = match test_type {
TestType::RunTime => {
vec![
"--throttle-requests",
"5",
"--users",
"10",
"--hatch-rate",
"5",
"--run-time",
"10",
"--no-reset-metrics",
]
}
TestType::NoRunTime => {
vec![
"--throttle-requests",
"5",
"--users",
"10",
"--hatch-rate",
"5",
"--no-reset-metrics",
]
}
TestType::TestPlanIncrease => {
vec!["--throttle-requests", "5", "--test-plan", "10,1m"]
}
TestType::TestPlanDecrease => {
vec!["--throttle-requests", "5", "--test-plan", "10,1s;1,1m"]
}
TestType::TestPlanMaintain => {
vec!["--throttle-requests", "5", "--test-plan", "10,1s"]
}
TestType::Iterations => {
vec![
"--throttle-requests",
"5",
"--users",
"10",
"--hatch-rate",
"5",
"--iterations",
"5",
]
}
};
let mut configuration = common::build_configuration(server, configuration);
match test_type {
TestType::Iterations | TestType::NoRunTime => {
configuration.run_time = "".to_string();
}
TestType::TestPlanIncrease | TestType::TestPlanDecrease | TestType::TestPlanMaintain => {
configuration.run_time = "".to_string();
configuration.hatch_rate = None;
configuration.users = None;
}
TestType::RunTime => {
}
}
configuration
}
fn validate_one_scenario(
goose_metrics: &GooseMetrics,
mock_endpoints: &[Mock],
configuration: &GooseConfiguration,
test_type: TestType,
is_gaggle: bool,
) {
if is_gaggle {
assert!(mock_endpoints[INDEX_KEY].hits() <= 40);
} else {
assert!(mock_endpoints[INDEX_KEY].hits() <= 20);
}
assert!(mock_endpoints[INDEX_KEY].hits() > 10);
let index_metrics = goose_metrics
.requests
.get(&format!("GET {INDEX_PATH}"))
.unwrap();
assert!(index_metrics.path == INDEX_PATH);
assert!(index_metrics.method == GooseMethod::Get);
assert!(index_metrics.fail_count == 0);
if !is_gaggle {
assert!(goose_metrics.duration == 3);
}
match test_type {
TestType::RunTime => {
assert!(goose_metrics.total_users == configuration.users.unwrap());
if !is_gaggle {
assert!(goose_metrics.history.len() == 4);
}
assert!(goose_metrics.maximum_users == 10);
assert!(goose_metrics.total_users == 10);
}
TestType::NoRunTime => {
assert!(goose_metrics.total_users == configuration.users.unwrap());
if !is_gaggle {
assert!(goose_metrics.history.len() == 4);
}
assert!(goose_metrics.maximum_users == 10);
assert!(goose_metrics.total_users == 10);
}
TestType::TestPlanIncrease => {
assert!(goose_metrics.history.len() == 3);
assert!(goose_metrics.maximum_users < 10);
assert!(goose_metrics.total_users < 10);
}
TestType::TestPlanDecrease => {
assert!(goose_metrics.history.len() == 4);
assert!(goose_metrics.maximum_users == 10);
assert!(goose_metrics.total_users == 10);
}
TestType::TestPlanMaintain => {
assert!(goose_metrics.history.len() == 4);
assert!(goose_metrics.maximum_users == 10);
assert!(goose_metrics.total_users == 10);
}
TestType::Iterations => {
if !is_gaggle {
assert!(goose_metrics.history.len() == 4);
}
assert!(goose_metrics.maximum_users == 10);
assert!(goose_metrics.total_users == 10);
}
}
}
fn get_transactions() -> Scenario {
scenario!("LoadTest").register_transaction(transaction!(get_index).set_weight(9).unwrap())
}
async fn cancel_load_test(duration: Duration) {
sleep(duration).await;
kill(getpid(), SIGINT).expect("failed to send SIGNINT");
}
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, &test_type);
let _ignored_joinhandle = tokio::spawn(cancel_load_test(Duration::from_secs(3)));
let goose_metrics = common::run_load_test(
common::build_load_test(configuration.clone(), vec![get_transactions()], None, None),
None,
)
.await;
validate_one_scenario(
&goose_metrics,
&mock_endpoints,
&configuration,
test_type,
false,
);
}
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, vec!["--worker", "--throttle-requests", "5"]);
let worker_handles = common::launch_gaggle_workers(EXPECT_WORKERS, || {
common::build_load_test(
worker_configuration.clone(),
vec![get_transactions()],
None,
None,
)
});
let manager_configuration = match test_type {
TestType::RunTime => common::build_configuration(
&server,
vec![
"--manager",
"--expect-workers",
&EXPECT_WORKERS.to_string(),
"--no-reset-metrics",
"--users",
"10",
"--hatch-rate",
"5",
"--run-time",
"10",
],
),
TestType::NoRunTime => common::build_configuration(
&server,
vec![
"--manager",
"--expect-workers",
&EXPECT_WORKERS.to_string(),
"--no-reset-metrics",
"--users",
"10",
"--hatch-rate",
"5",
],
),
TestType::Iterations => common::build_configuration(
&server,
vec![
"--manager",
"--expect-workers",
&EXPECT_WORKERS.to_string(),
"--users",
"10",
"--hatch-rate",
"5",
],
),
TestType::TestPlanIncrease | TestType::TestPlanDecrease | TestType::TestPlanMaintain => {
panic!("test plan configuration not supported in gaggle mode")
}
};
let manager_goose_attack = common::build_load_test(
manager_configuration.clone(),
vec![get_transactions()],
None,
None,
);
let _ignored_joinhandle = tokio::spawn(cancel_load_test(Duration::from_secs(3)));
let goose_metrics = common::run_load_test(manager_goose_attack, Some(worker_handles)).await;
validate_one_scenario(
&goose_metrics,
&mock_endpoints,
&manager_configuration,
test_type,
true,
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_runtime() {
run_standalone_test(TestType::RunTime).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_runtime_gaggle() {
run_gaggle_test(TestType::RunTime).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_noruntime() {
run_standalone_test(TestType::NoRunTime).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_noruntime_gaggle() {
run_gaggle_test(TestType::NoRunTime).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_iterations() {
run_standalone_test(TestType::Iterations).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_iterations_gaggle() {
run_gaggle_test(TestType::Iterations).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_testplan_increase() {
run_standalone_test(TestType::TestPlanIncrease).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_testplan_decrease() {
run_standalone_test(TestType::TestPlanDecrease).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_cancel_testplan_maintain() {
run_standalone_test(TestType::TestPlanMaintain).await;
}