use httpmock::{Method::GET, Mock, MockServer};
use serial_test::serial;
mod common;
use goose::config::GooseConfiguration;
use goose::goose::Scenario;
use goose::prelude::*;
const INDEX_PATH: &str = "/";
const REDIRECT_PATH: &str = "/redirect";
const REDIRECT2_PATH: &str = "/redirect2";
const REDIRECT3_PATH: &str = "/redirect3";
const ABOUT_PATH: &str = "/about.php";
const INDEX_KEY: usize = 0;
const REDIRECT_KEY: usize = 1;
const REDIRECT_KEY2: usize = 2;
const REDIRECT_KEY3: usize = 3;
const ABOUT_KEY: usize = 4;
const SERVER1_INDEX_KEY: usize = 0;
const SERVER1_ABOUT_KEY: usize = 1;
const SERVER1_REDIRECT_KEY: usize = 2;
const SERVER2_INDEX_KEY: usize = 3;
const SERVER2_ABOUT_KEY: usize = 4;
const EXPECT_WORKERS: usize = 4;
const USERS: usize = 9;
const RUN_TIME: usize = 3;
#[derive(Clone)]
enum TestType {
Chain,
Domain,
Sticky,
}
pub async fn get_index(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(INDEX_PATH).await?;
Ok(())
}
pub async fn get_about(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(ABOUT_PATH).await?;
Ok(())
}
pub async fn get_redirect(user: &mut GooseUser) -> TransactionResult {
let mut goose = user.get(REDIRECT_PATH).await?;
if let Ok(r) = goose.response {
match r.text().await {
Ok(html) => {
if !html.contains("about page") {
return user.set_failure(
"about page body wrong",
&mut goose.request,
None,
None,
);
}
}
Err(e) => {
return user.set_failure(
format!("unexpected error parsing about page: {e}").as_str(),
&mut goose.request,
None,
None,
);
}
}
}
Ok(())
}
pub async fn get_domain_redirect(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(REDIRECT_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints<'a>(
test_type: &TestType,
server: &'a MockServer,
server2: Option<&'a MockServer>,
) -> Vec<Mock<'a>> {
match test_type {
TestType::Chain => {
vec![
server.mock(|when, then| {
when.method(GET).path(INDEX_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(REDIRECT_PATH);
then.status(301).header("Location", REDIRECT2_PATH);
}),
server.mock(|when, then| {
when.method(GET).path(REDIRECT2_PATH);
then.status(302).header("Location", REDIRECT3_PATH);
}),
server.mock(|when, then| {
when.method(GET).path(REDIRECT3_PATH);
then.status(303).header("Location", ABOUT_PATH);
}),
server.mock(|when, then| {
when.method(GET).path(ABOUT_PATH);
then.status(200)
.body("<HTML><BODY>about page</BODY></HTML>");
}),
]
}
TestType::Domain | TestType::Sticky => {
vec![
server.mock(|when, then| {
when.method(GET).path(INDEX_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(ABOUT_PATH);
then.status(200)
.body("<HTML><BODY>about page</BODY></HTML>");
}),
server.mock(|when, then| {
when.method(GET).path(REDIRECT_PATH);
then.status(301)
.header("Location", server2.unwrap().url(INDEX_PATH));
}),
server2.unwrap().mock(|when, then| {
when.method(GET).path(INDEX_PATH);
then.status(200);
}),
server2.unwrap().mock(|when, then| {
when.method(GET).path(ABOUT_PATH);
then.status(200);
}),
]
}
}
}
fn common_build_configuration(
server: &MockServer,
sticky: bool,
worker: Option<bool>,
manager: Option<usize>,
) -> GooseConfiguration {
if let Some(expect_workers) = manager {
if sticky {
common::build_configuration(
server,
vec![
"--sticky-follow",
"--manager",
"--expect-workers",
&expect_workers.to_string(),
"--users",
&USERS.to_string(),
"--hatch-rate",
&USERS.to_string(),
"--run-time",
&RUN_TIME.to_string(),
],
)
} else {
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(),
],
)
}
} else if worker.is_some() {
common::build_configuration(server, vec!["--worker"])
} else if sticky {
common::build_configuration(
server,
vec![
"--sticky-follow",
"--users",
&USERS.to_string(),
"--hatch-rate",
&USERS.to_string(),
"--run-time",
&RUN_TIME.to_string(),
],
)
} else {
common::build_configuration(
server,
vec![
"--users",
&USERS.to_string(),
"--hatch-rate",
&USERS.to_string(),
"--run-time",
&RUN_TIME.to_string(),
],
)
}
}
fn validate_redirect(test_type: &TestType, mock_endpoints: &[Mock]) {
match test_type {
TestType::Chain => {
assert!(mock_endpoints[INDEX_KEY].hits() > 0);
assert!(mock_endpoints[REDIRECT_KEY].hits() > 0);
assert!(mock_endpoints[REDIRECT_KEY2].hits() > 0);
assert!(mock_endpoints[REDIRECT_KEY3].hits() > 0);
assert!(mock_endpoints[ABOUT_KEY].hits() > 0);
mock_endpoints[REDIRECT_KEY].assert_hits(mock_endpoints[REDIRECT_KEY2].hits());
mock_endpoints[REDIRECT_KEY].assert_hits(mock_endpoints[REDIRECT_KEY3].hits());
mock_endpoints[REDIRECT_KEY].assert_hits(mock_endpoints[ABOUT_KEY].hits());
}
TestType::Domain => {
assert!(mock_endpoints[SERVER1_INDEX_KEY].hits() > 0);
assert!(mock_endpoints[SERVER1_REDIRECT_KEY].hits() > 0);
assert!(mock_endpoints[SERVER1_ABOUT_KEY].hits() > 0);
assert!(mock_endpoints[SERVER2_INDEX_KEY].hits() > 0);
mock_endpoints[SERVER2_ABOUT_KEY].assert_hits(0);
}
TestType::Sticky => {
println!(
"SERVER1_REDIRECT: {}, USERS: {}",
mock_endpoints[SERVER1_REDIRECT_KEY].hits(),
USERS,
);
println!(
"SERVER1_INDEX: {}, SERVER1_ABOUT: {}",
mock_endpoints[SERVER1_INDEX_KEY].hits(),
mock_endpoints[SERVER1_ABOUT_KEY].hits(),
);
println!(
"SERVER2_INDEX: {}, SERVER2_ABOUT: {}",
mock_endpoints[SERVER2_INDEX_KEY].hits(),
mock_endpoints[SERVER2_ABOUT_KEY].hits(),
);
mock_endpoints[SERVER1_REDIRECT_KEY].assert_hits(USERS);
mock_endpoints[SERVER1_INDEX_KEY].assert_hits(0);
mock_endpoints[SERVER1_ABOUT_KEY].assert_hits(0);
assert!(mock_endpoints[SERVER2_INDEX_KEY].hits() > 0);
assert!(mock_endpoints[SERVER2_ABOUT_KEY].hits() > 0);
}
}
}
fn get_transactions(test_type: &TestType) -> Scenario {
match test_type {
TestType::Chain => {
scenario!("LoadTest")
.register_transaction(transaction!(get_index))
.register_transaction(transaction!(get_redirect))
}
TestType::Domain | TestType::Sticky => {
scenario!("LoadTest")
.register_transaction(transaction!(get_domain_redirect))
.register_transaction(transaction!(get_index))
.register_transaction(transaction!(get_about))
}
}
}
async fn run_standalone_test(test_type: TestType) {
let server1 = MockServer::start();
let server2 = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&test_type, &server1, Some(&server2));
let sticky = match test_type {
TestType::Sticky => true,
TestType::Chain | TestType::Domain => false,
};
let configuration = common_build_configuration(&server1, sticky, None, None);
common::run_load_test(
common::build_load_test(
configuration,
vec![get_transactions(&test_type)],
None,
None,
),
None,
)
.await;
validate_redirect(&test_type, &mock_endpoints);
}
async fn run_gaggle_test(test_type: TestType) {
let server1 = MockServer::start();
let server2 = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&test_type, &server1, Some(&server2));
let sticky = match test_type {
TestType::Sticky => true,
TestType::Chain | TestType::Domain => false,
};
let worker_configuration = common_build_configuration(&server1, sticky, Some(true), None);
let worker_handles = common::launch_gaggle_workers(EXPECT_WORKERS, || {
common::build_load_test(
worker_configuration.clone(),
vec![get_transactions(&test_type)],
None,
None,
)
});
let manager_configuration =
common_build_configuration(&server1, sticky, None, Some(EXPECT_WORKERS));
let manager_goose_attack = common::build_load_test(
manager_configuration,
vec![get_transactions(&test_type)],
None,
None,
);
common::run_load_test(manager_goose_attack, Some(worker_handles)).await;
validate_redirect(&test_type, &mock_endpoints);
}
#[tokio::test]
async fn test_redirect() {
run_standalone_test(TestType::Chain).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 6)]
#[serial]
async fn test_redirect_gaggle() {
run_gaggle_test(TestType::Chain).await;
}
#[tokio::test]
async fn test_domain_redirect() {
run_standalone_test(TestType::Domain).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_domain_redirect_gaggle() {
run_gaggle_test(TestType::Domain).await;
}
#[tokio::test]
async fn test_sticky_domain_redirect() {
run_standalone_test(TestType::Sticky).await;
}
#[ignore]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
#[serial]
async fn test_sticky_domain_redirect_gaggle() {
run_gaggle_test(TestType::Sticky).await;
}