use httpmock::{Method::GET, MockRef, MockServer};
use serial_test::serial;
use tokio::time::{sleep, Duration};
mod common;
use swanling::prelude::*;
use swanling::SwanlingConfiguration;
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: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(ONE_PATH).await?;
Ok(())
}
pub async fn two_with_delay(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(TWO_PATH).await?;
sleep(Duration::from_secs(RUN_TIME as u64 + 1)).await;
Ok(())
}
pub async fn three(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(THREE_PATH).await?;
Ok(())
}
pub async fn start_one(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(START_ONE_PATH).await?;
Ok(())
}
pub async fn stop_one(user: &SwanlingUser) -> SwanlingTaskResult {
let _swanling = user.get(STOP_ONE_PATH).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<MockRef> {
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>,
) -> SwanlingConfiguration {
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: &[MockRef]) {
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_tasks(test_type: &TestType) -> (SwanlingTaskSet, SwanlingTask, SwanlingTask) {
match test_type {
TestType::NotSequenced => (
taskset!("LoadTest")
.register_task(task!(one).set_weight(2).unwrap())
.register_task(task!(three))
.register_task(task!(two_with_delay)),
task!(start_one),
task!(stop_one),
),
TestType::SequencedRoundRobin => (
taskset!("LoadTest")
.register_task(task!(one).set_sequence(1).set_weight(2).unwrap())
.register_task(task!(three).set_sequence(3))
.register_task(task!(one).set_sequence(2).set_weight(2).unwrap())
.register_task(task!(two_with_delay).set_sequence(2)),
task!(start_one),
task!(stop_one),
),
TestType::SequencedSerial => (
taskset!("LoadTest")
.register_task(task!(one).set_sequence(1).set_weight(2).unwrap())
.register_task(task!(three).set_sequence(3))
.register_task(task!(one).set_sequence(2).set_weight(2).unwrap())
.register_task(task!(two_with_delay).set_sequence(2)),
task!(start_one),
task!(stop_one),
),
}
}
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 (taskset, start_task, stop_task) = get_tasks(&test_type);
let swanling_attack;
match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
swanling_attack = crate::SwanlingAttack::initialize_with_config(configuration)
.unwrap()
.register_taskset(taskset)
.test_start(start_task)
.test_stop(stop_task)
.set_scheduler(SwanlingScheduler::RoundRobin)
}
TestType::SequencedSerial => {
swanling_attack = crate::SwanlingAttack::initialize_with_config(configuration)
.unwrap()
.register_taskset(taskset)
.test_start(start_task)
.test_stop(stop_task)
.set_scheduler(SwanlingScheduler::Serial)
}
}
common::run_load_test(swanling_attack, None);
validate_test(&test_type, &mock_endpoints);
}
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 (taskset, start_task, stop_task) = get_tasks(&test_type);
let swanling_attack;
match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
swanling_attack = crate::SwanlingAttack::initialize_with_config(worker_configuration)
.unwrap()
.register_taskset(taskset.clone())
.test_start(start_task.clone())
.test_stop(stop_task.clone())
.set_scheduler(SwanlingScheduler::RoundRobin);
}
TestType::SequencedSerial => {
swanling_attack = crate::SwanlingAttack::initialize_with_config(worker_configuration)
.unwrap()
.register_taskset(taskset.clone())
.test_start(start_task.clone())
.test_stop(stop_task.clone())
.set_scheduler(SwanlingScheduler::Serial);
}
}
let worker_handles = common::launch_gaggle_workers(swanling_attack, EXPECT_WORKERS);
let manager_configuration = common_build_configuration(&server, None, Some(EXPECT_WORKERS));
let manager_swanling_attack;
match test_type {
TestType::NotSequenced | TestType::SequencedRoundRobin => {
manager_swanling_attack =
crate::SwanlingAttack::initialize_with_config(manager_configuration)
.unwrap()
.register_taskset(taskset)
.test_start(start_task)
.test_stop(stop_task)
.set_scheduler(SwanlingScheduler::RoundRobin);
}
TestType::SequencedSerial => {
manager_swanling_attack =
crate::SwanlingAttack::initialize_with_config(manager_configuration)
.unwrap()
.register_taskset(taskset)
.test_start(start_task)
.test_stop(stop_task)
.set_scheduler(SwanlingScheduler::Serial);
}
}
common::run_load_test(manager_swanling_attack, Some(worker_handles));
validate_test(&test_type, &mock_endpoints);
}
#[test]
fn test_not_sequenced() {
run_standalone_test(TestType::NotSequenced);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
#[serial]
fn test_not_sequenced_gaggle() {
run_gaggle_test(TestType::NotSequenced);
}
#[test]
fn test_sequenced_round_robin() {
run_standalone_test(TestType::SequencedRoundRobin);
}
#[test]
fn test_sequenced_sequential() {
run_standalone_test(TestType::SequencedSerial);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
#[serial]
fn test_sequenced_round_robin_gaggle() {
run_gaggle_test(TestType::SequencedRoundRobin);
}
#[test]
#[cfg_attr(not(feature = "gaggle"), ignore)]
#[serial]
fn test_sequenced_sequential_gaggle() {
run_gaggle_test(TestType::SequencedSerial);
}