use gumdrop::Options;
use serde::{Deserialize, Serialize};
use simplelog::*;
use std::path::PathBuf;
use std::str::FromStr;
use crate::logger::GooseLogFormat;
use crate::metrics::GooseCoordinatedOmissionMitigation;
use crate::test_plan::TestPlan;
use crate::util;
use crate::{GooseAttack, GooseError};
#[derive(Options, Debug, Clone, Default, Serialize, Deserialize)]
#[options(
help = r#"Goose is a modern, high-performance, distributed HTTP(S) load testing tool,
written in Rust. Visit https://book.goose.rs/ for more information.
The following runtime options are available when launching a Goose load test:"#
)]
pub struct GooseConfiguration {
#[options(short = "h")]
pub help: bool,
#[options(short = "V")]
pub version: bool,
#[options(short = "l", help = "Lists all transactions and exits\n")]
pub list: bool,
#[options(short = "H")]
pub host: String,
#[options(short = "u")]
pub users: Option<usize>,
#[options(short = "r", meta = "RATE")]
pub hatch_rate: Option<String>,
#[options(short = "s", meta = "TIME")]
pub startup_time: String,
#[options(short = "t", meta = "TIME")]
pub run_time: String,
#[options(short = "G", meta = "NAME")]
pub goose_log: String,
#[options(short = "g", count)]
pub log_level: u8,
#[options(count, short = "q", help = "Decreases Goose verbosity (-q, -qq, etc)")]
pub quiet: u8,
#[options(
count,
short = "v",
// Add a blank line and then a 'Metrics:' header after this option
help = "Increases Goose verbosity (-v, -vv, etc)\n\nMetrics:"
)]
pub verbose: u8,
#[options(no_short, meta = "TIME")]
pub running_metrics: Option<usize>,
#[options(no_short)]
pub no_reset_metrics: bool,
#[options(no_short)]
pub no_metrics: bool,
#[options(no_short)]
pub no_transaction_metrics: bool,
#[options(no_short)]
pub no_scenario_metrics: bool,
#[options(no_short)]
pub no_print_metrics: bool,
#[options(no_short)]
pub no_error_summary: bool,
#[options(no_short, meta = "NAME")]
pub report_file: Vec<String>,
#[options(no_short)]
pub no_granular_report: bool,
#[options(short = "R", meta = "NAME")]
pub request_log: String,
#[options(no_short, meta = "FORMAT")]
pub request_format: Option<GooseLogFormat>,
#[options(no_short)]
pub request_body: bool,
#[options(short = "T", meta = "NAME")]
pub transaction_log: String,
#[options(no_short, meta = "FORMAT")]
pub transaction_format: Option<GooseLogFormat>,
#[options(short = "S", meta = "NAME")]
pub scenario_log: String,
#[options(no_short, meta = "FORMAT")]
pub scenario_format: Option<GooseLogFormat>,
#[options(short = "E", meta = "NAME")]
pub error_log: String,
#[options(no_short, meta = "FORMAT")]
pub error_format: Option<GooseLogFormat>,
#[options(short = "D", meta = "NAME")]
pub debug_log: String,
#[options(no_short, meta = "FORMAT")]
pub debug_format: Option<GooseLogFormat>,
#[options(no_short)]
pub no_debug_body: bool,
#[options(no_short, help = "Do not track status code metrics\n\nAdvanced:")]
pub no_status_codes: bool,
#[options(no_short, meta = "\"TESTPLAN\"")]
pub(crate) test_plan: Option<TestPlan>,
#[options(no_short)]
pub iterations: usize,
#[options(no_short, meta = "\"SCENARIO\"")]
pub scenarios: Scenarios,
#[options(no_short)]
pub scenarios_list: bool,
#[options(no_short)]
pub no_telnet: bool,
#[options(no_short, meta = "HOST")]
pub telnet_host: String,
#[options(no_short, meta = "PORT")]
pub telnet_port: u16,
#[options(no_short)]
pub no_websocket: bool,
#[options(no_short, meta = "HOST")]
pub websocket_host: String,
#[options(no_short, meta = "PORT")]
pub websocket_port: u16,
#[options(no_short)]
pub no_autostart: bool,
#[options(no_short)]
pub no_gzip: bool,
#[options(no_short, meta = "VALUE")]
pub timeout: Option<String>,
#[options(no_short, meta = "STRATEGY")]
pub co_mitigation: Option<GooseCoordinatedOmissionMitigation>,
#[options(no_short, meta = "VALUE")]
pub throttle_requests: usize,
#[options(no_short)]
pub sticky_follow: bool,
#[options(no_short)]
pub accept_invalid_certs: bool,
}
#[derive(Options, Default, Debug, Clone, Serialize, Deserialize)]
pub struct Scenarios {
pub active: Vec<String>,
}
impl FromStr for Scenarios {
type Err = GooseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut active: Vec<String> = Vec::new();
let lines = s.split(',');
for line in lines {
let scenario = line.trim().to_lowercase();
if scenario.chars().all(char::is_alphanumeric) {
active.push(scenario);
} else {
eprintln!("ERROR: invalid `configuration.scenarios` value: '{line}'");
eprintln!(" Expected format: --scenarios \"{{one}},{{two}},{{three}}\"");
eprintln!(" {{one}}, {{two}}, {{three}}, etc must be alphanumeric");
eprintln!(" To view valid scenario names invoke `--scenarios-list`");
return Err(GooseError::InvalidOption {
option: "`configuration.scenarios".to_string(),
value: line.to_string(),
detail: "invalid `configuration.scenarios` value.".to_string(),
});
}
}
Ok(Scenarios { active })
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct GooseDefaults {
pub host: Option<String>,
pub users: Option<usize>,
pub hatch_rate: Option<String>,
pub startup_time: Option<usize>,
pub run_time: Option<usize>,
pub test_plan: Option<TestPlan>,
pub iterations: Option<usize>,
pub scenarios: Option<Scenarios>,
pub log_level: Option<u8>,
pub goose_log: Option<String>,
pub quiet: Option<u8>,
pub verbose: Option<u8>,
pub running_metrics: Option<usize>,
pub no_reset_metrics: Option<bool>,
pub no_metrics: Option<bool>,
pub no_transaction_metrics: Option<bool>,
pub no_scenario_metrics: Option<bool>,
pub no_print_metrics: Option<bool>,
pub no_error_summary: Option<bool>,
pub report_file: Option<Vec<String>>,
pub no_granular_report: Option<bool>,
pub request_log: Option<String>,
pub request_format: Option<GooseLogFormat>,
pub request_body: Option<bool>,
pub transaction_log: Option<String>,
pub transaction_format: Option<GooseLogFormat>,
pub scenario_log: Option<String>,
pub scenario_format: Option<GooseLogFormat>,
pub error_log: Option<String>,
pub error_format: Option<GooseLogFormat>,
pub debug_log: Option<String>,
pub debug_format: Option<GooseLogFormat>,
pub no_debug_body: Option<bool>,
pub no_telnet: Option<bool>,
pub no_websocket: Option<bool>,
pub no_autostart: Option<bool>,
pub no_gzip: Option<bool>,
pub timeout: Option<String>,
pub co_mitigation: Option<GooseCoordinatedOmissionMitigation>,
pub no_status_codes: Option<bool>,
pub throttle_requests: Option<usize>,
pub sticky_follow: Option<bool>,
pub telnet_host: Option<String>,
pub telnet_port: Option<u16>,
pub websocket_host: Option<String>,
pub websocket_port: Option<u16>,
pub accept_invalid_certs: Option<bool>,
}
#[derive(Debug)]
pub enum GooseDefault {
Host,
Users,
HatchRate,
StartupTime,
RunTime,
TestPlan,
Iterations,
Scenarios,
LogLevel,
GooseLog,
Quiet,
Verbose,
RunningMetrics,
NoResetMetrics,
NoMetrics,
NoTransactionMetrics,
NoScenarioMetrics,
NoPrintMetrics,
NoErrorSummary,
ReportFile,
NoGranularData,
RequestLog,
RequestFormat,
RequestBody,
TransactionLog,
TransactionFormat,
ScenarioLog,
ScenarioFormat,
ErrorLog,
ErrorFormat,
DebugLog,
DebugFormat,
NoDebugBody,
NoTelnet,
NoWebSocket,
CoordinatedOmissionMitigation,
NoAutoStart,
Timeout,
NoGzip,
NoStatusCodes,
ThrottleRequests,
StickyFollow,
TelnetHost,
TelnetPort,
WebSocketHost,
WebSocketPort,
AcceptInvalidCerts,
}
pub trait GooseDefaultType<T> {
fn set_default(self, key: GooseDefault, value: T) -> Result<Box<Self>, GooseError>;
}
impl GooseDefaultType<&str> for GooseAttack {
fn set_default(mut self, key: GooseDefault, value: &str) -> Result<Box<Self>, GooseError> {
match key {
GooseDefault::DebugLog => self.defaults.debug_log = Some(value.to_string()),
GooseDefault::ErrorLog => self.defaults.error_log = Some(value.to_string()),
GooseDefault::GooseLog => self.defaults.goose_log = Some(value.to_string()),
GooseDefault::HatchRate => self.defaults.hatch_rate = Some(value.to_string()),
GooseDefault::Host => {
self.defaults.host = if value.is_empty() {
None
} else {
Some(value.to_string())
}
}
GooseDefault::ReportFile => self.defaults.report_file = Some(vec![value.to_string()]),
GooseDefault::RequestLog => self.defaults.request_log = Some(value.to_string()),
GooseDefault::ScenarioLog => self.defaults.scenario_log = Some(value.to_string()),
GooseDefault::Scenarios => {
self.defaults.scenarios = Some(value.parse::<Scenarios>().unwrap())
}
GooseDefault::TelnetHost => self.defaults.telnet_host = Some(value.to_string()),
GooseDefault::TestPlan => {
self.defaults.test_plan = Some(value.parse::<TestPlan>().unwrap())
}
GooseDefault::Timeout => self.defaults.timeout = Some(value.to_string()),
GooseDefault::TransactionLog => self.defaults.transaction_log = Some(value.to_string()),
GooseDefault::WebSocketHost => self.defaults.websocket_host = Some(value.to_string()),
GooseDefault::Users
| GooseDefault::StartupTime
| GooseDefault::RunTime
| GooseDefault::Iterations
| GooseDefault::LogLevel
| GooseDefault::Quiet
| GooseDefault::Verbose
| GooseDefault::ThrottleRequests
| GooseDefault::TelnetPort
| GooseDefault::WebSocketPort => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected usize value, received &str"
),
});
}
GooseDefault::RunningMetrics
| GooseDefault::NoResetMetrics
| GooseDefault::NoMetrics
| GooseDefault::NoTransactionMetrics
| GooseDefault::NoScenarioMetrics
| GooseDefault::RequestBody
| GooseDefault::NoPrintMetrics
| GooseDefault::NoErrorSummary
| GooseDefault::NoDebugBody
| GooseDefault::NoTelnet
| GooseDefault::NoWebSocket
| GooseDefault::NoAutoStart
| GooseDefault::NoGzip
| GooseDefault::NoStatusCodes
| GooseDefault::StickyFollow
| GooseDefault::NoGranularData
| GooseDefault::AcceptInvalidCerts => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected bool value, received &str"
),
});
}
GooseDefault::DebugFormat
| GooseDefault::ErrorFormat
| GooseDefault::TransactionFormat
| GooseDefault::ScenarioFormat
| GooseDefault::RequestFormat => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseLogFormat value, received &str"
),
});
}
GooseDefault::CoordinatedOmissionMitigation => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseCoordinatedOmissionMitigation value, received &str"
),
});
}
}
Ok(Box::new(self))
}
}
impl GooseDefaultType<usize> for GooseAttack {
fn set_default(mut self, key: GooseDefault, value: usize) -> Result<Box<Self>, GooseError> {
match key {
GooseDefault::Users => self.defaults.users = Some(value),
GooseDefault::StartupTime => self.defaults.startup_time = Some(value),
GooseDefault::RunTime => self.defaults.run_time = Some(value),
GooseDefault::Iterations => self.defaults.iterations = Some(value),
GooseDefault::RunningMetrics => self.defaults.running_metrics = Some(value),
GooseDefault::LogLevel => self.defaults.log_level = Some(value as u8),
GooseDefault::Quiet => self.defaults.quiet = Some(value as u8),
GooseDefault::Verbose => self.defaults.verbose = Some(value as u8),
GooseDefault::ThrottleRequests => self.defaults.throttle_requests = Some(value),
GooseDefault::TelnetPort => self.defaults.telnet_port = Some(value as u16),
GooseDefault::WebSocketPort => self.defaults.websocket_port = Some(value as u16),
GooseDefault::DebugLog
| GooseDefault::ErrorLog
| GooseDefault::GooseLog
| GooseDefault::HatchRate
| GooseDefault::Host
| GooseDefault::ReportFile
| GooseDefault::RequestLog
| GooseDefault::ScenarioLog
| GooseDefault::Scenarios
| GooseDefault::TelnetHost
| GooseDefault::TestPlan
| GooseDefault::Timeout
| GooseDefault::TransactionLog
| GooseDefault::WebSocketHost => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected &str value, received usize"
),
})
}
GooseDefault::NoResetMetrics
| GooseDefault::NoMetrics
| GooseDefault::NoTransactionMetrics
| GooseDefault::NoScenarioMetrics
| GooseDefault::RequestBody
| GooseDefault::NoPrintMetrics
| GooseDefault::NoErrorSummary
| GooseDefault::NoDebugBody
| GooseDefault::NoTelnet
| GooseDefault::NoWebSocket
| GooseDefault::NoAutoStart
| GooseDefault::NoGzip
| GooseDefault::NoStatusCodes
| GooseDefault::StickyFollow
| GooseDefault::NoGranularData
| GooseDefault::AcceptInvalidCerts => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected bool value, received usize"
),
})
}
GooseDefault::RequestFormat
| GooseDefault::DebugFormat
| GooseDefault::ErrorFormat
| GooseDefault::ScenarioFormat
| GooseDefault::TransactionFormat => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseLogFormat value, received usize"
),
});
}
GooseDefault::CoordinatedOmissionMitigation => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseCoordinatedOmissionMitigation value, received usize"
),
});
}
}
Ok(Box::new(self))
}
}
impl GooseDefaultType<bool> for GooseAttack {
fn set_default(mut self, key: GooseDefault, value: bool) -> Result<Box<Self>, GooseError> {
match key {
GooseDefault::NoResetMetrics => self.defaults.no_reset_metrics = Some(value),
GooseDefault::NoMetrics => self.defaults.no_metrics = Some(value),
GooseDefault::NoTransactionMetrics => {
self.defaults.no_transaction_metrics = Some(value)
}
GooseDefault::NoScenarioMetrics => self.defaults.no_scenario_metrics = Some(value),
GooseDefault::RequestBody => self.defaults.request_body = Some(value),
GooseDefault::NoPrintMetrics => self.defaults.no_print_metrics = Some(value),
GooseDefault::NoErrorSummary => self.defaults.no_error_summary = Some(value),
GooseDefault::NoDebugBody => self.defaults.no_debug_body = Some(value),
GooseDefault::NoTelnet => self.defaults.no_telnet = Some(value),
GooseDefault::NoWebSocket => self.defaults.no_websocket = Some(value),
GooseDefault::NoAutoStart => self.defaults.no_autostart = Some(value),
GooseDefault::NoGzip => self.defaults.no_gzip = Some(value),
GooseDefault::AcceptInvalidCerts => self.defaults.accept_invalid_certs = Some(value),
GooseDefault::NoStatusCodes => self.defaults.no_status_codes = Some(value),
GooseDefault::StickyFollow => self.defaults.sticky_follow = Some(value),
GooseDefault::NoGranularData => self.defaults.no_granular_report = Some(value),
GooseDefault::DebugLog
| GooseDefault::ErrorLog
| GooseDefault::GooseLog
| GooseDefault::HatchRate
| GooseDefault::Host
| GooseDefault::ReportFile
| GooseDefault::RequestLog
| GooseDefault::ScenarioLog
| GooseDefault::Scenarios
| GooseDefault::TelnetHost
| GooseDefault::TestPlan
| GooseDefault::Timeout
| GooseDefault::TransactionLog
| GooseDefault::WebSocketHost => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected &str value, received bool"
),
})
}
GooseDefault::Users
| GooseDefault::StartupTime
| GooseDefault::RunTime
| GooseDefault::RunningMetrics
| GooseDefault::Iterations
| GooseDefault::LogLevel
| GooseDefault::Quiet
| GooseDefault::Verbose
| GooseDefault::ThrottleRequests
| GooseDefault::TelnetPort
| GooseDefault::WebSocketPort => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected usize value, received bool"
),
})
}
GooseDefault::RequestFormat
| GooseDefault::DebugFormat
| GooseDefault::ErrorFormat
| GooseDefault::ScenarioFormat
| GooseDefault::TransactionFormat => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseLogFormat value, received bool"
),
});
}
GooseDefault::CoordinatedOmissionMitigation => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: value.to_string(),
detail: format!(
"set_default(GooseDefault::{key:?}, {value}) expected GooseCoordinatedOmissionMitigation value, received bool"
),
});
}
}
Ok(Box::new(self))
}
}
impl GooseDefaultType<GooseCoordinatedOmissionMitigation> for GooseAttack {
fn set_default(
mut self,
key: GooseDefault,
value: GooseCoordinatedOmissionMitigation,
) -> Result<Box<Self>, GooseError> {
match key {
GooseDefault::CoordinatedOmissionMitigation => self.defaults.co_mitigation = Some(value),
GooseDefault::NoResetMetrics
| GooseDefault::NoMetrics
| GooseDefault::NoTransactionMetrics
| GooseDefault::NoScenarioMetrics
| GooseDefault::RequestBody
| GooseDefault::NoPrintMetrics
| GooseDefault::NoErrorSummary
| GooseDefault::NoDebugBody
| GooseDefault::NoTelnet
| GooseDefault::NoWebSocket
| GooseDefault::NoAutoStart
| GooseDefault::NoGzip
| GooseDefault::NoStatusCodes
| GooseDefault::StickyFollow
| GooseDefault::NoGranularData
| GooseDefault::AcceptInvalidCerts => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected bool value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::DebugLog
| GooseDefault::ErrorLog
| GooseDefault::GooseLog
| GooseDefault::HatchRate
| GooseDefault::Host
| GooseDefault::ReportFile
| GooseDefault::RequestLog
| GooseDefault::ScenarioLog
| GooseDefault::Scenarios
| GooseDefault::TelnetHost
| GooseDefault::TestPlan
| GooseDefault::Timeout
| GooseDefault::TransactionLog
| GooseDefault::WebSocketHost => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected &str value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::Users
| GooseDefault::StartupTime
| GooseDefault::RunTime
| GooseDefault::RunningMetrics
| GooseDefault::Iterations
| GooseDefault::LogLevel
| GooseDefault::Quiet
| GooseDefault::Verbose
| GooseDefault::ThrottleRequests
| GooseDefault::TelnetPort
| GooseDefault::WebSocketPort => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected usize value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::RequestFormat
| GooseDefault::DebugFormat
| GooseDefault::ErrorFormat
| GooseDefault::ScenarioFormat
| GooseDefault::TransactionFormat => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected GooseLogFormat value, received GooseCoordinatedOmissionMitigation"
),
})
}
}
Ok(Box::new(self))
}
}
impl GooseDefaultType<GooseLogFormat> for GooseAttack {
fn set_default(
mut self,
key: GooseDefault,
value: GooseLogFormat,
) -> Result<Box<Self>, GooseError> {
match key {
GooseDefault::RequestFormat => self.defaults.request_format = Some(value),
GooseDefault::DebugFormat => self.defaults.debug_format = Some(value),
GooseDefault::ErrorFormat => self.defaults.error_format = Some(value),
GooseDefault::TransactionFormat => self.defaults.transaction_format = Some(value),
GooseDefault::ScenarioFormat => self.defaults.scenario_format = Some(value),
GooseDefault::NoResetMetrics
| GooseDefault::NoMetrics
| GooseDefault::NoTransactionMetrics
| GooseDefault::NoScenarioMetrics
| GooseDefault::RequestBody
| GooseDefault::NoPrintMetrics
| GooseDefault::NoErrorSummary
| GooseDefault::NoDebugBody
| GooseDefault::NoTelnet
| GooseDefault::NoWebSocket
| GooseDefault::NoAutoStart
| GooseDefault::NoGzip
| GooseDefault::NoStatusCodes
| GooseDefault::StickyFollow
| GooseDefault::NoGranularData
| GooseDefault::AcceptInvalidCerts => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected bool value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::DebugLog
| GooseDefault::ErrorLog
| GooseDefault::GooseLog
| GooseDefault::HatchRate
| GooseDefault::Host
| GooseDefault::ReportFile
| GooseDefault::RequestLog
| GooseDefault::ScenarioLog
| GooseDefault::Scenarios
| GooseDefault::TelnetHost
| GooseDefault::TestPlan
| GooseDefault::Timeout
| GooseDefault::TransactionLog
| GooseDefault::WebSocketHost => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected &str value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::Users
| GooseDefault::StartupTime
| GooseDefault::RunTime
| GooseDefault::RunningMetrics
| GooseDefault::Iterations
| GooseDefault::LogLevel
| GooseDefault::Quiet
| GooseDefault::Verbose
| GooseDefault::ThrottleRequests
| GooseDefault::TelnetPort
| GooseDefault::WebSocketPort => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected usize value, received GooseCoordinatedOmissionMitigation"
),
})
}
GooseDefault::CoordinatedOmissionMitigation => {
return Err(GooseError::InvalidOption {
option: format!("GooseDefault::{key:?}"),
value: format!("{value:?}"),
detail: format!(
"set_default(GooseDefault::{key:?}, {value:?}) expected GooseCoordinatedOmissionMitigation value, received GooseLogFormat"
),
})
}
}
Ok(Box::new(self))
}
}
#[derive(Debug, Clone)]
pub(crate) struct GooseValue<'a, T> {
pub(crate) value: Option<T>,
pub(crate) filter: bool,
pub(crate) message: &'a str,
}
pub(crate) trait GooseConfigure<T> {
fn get_value(&self, values: Vec<GooseValue<T>>) -> Option<T>;
}
impl GooseConfigure<usize> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<usize>>) -> Option<usize> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<u16> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<u16>>) -> Option<u16> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<u8> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<u8>>) -> Option<u8> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<f32> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<f32>>) -> Option<f32> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<TestPlan> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<TestPlan>>) -> Option<TestPlan> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<String> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<String>>) -> Option<String> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<Vec<String>> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<Vec<String>>>) -> Option<Vec<String>> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<bool> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<bool>>) -> Option<bool> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<GooseLogFormat> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<GooseLogFormat>>) -> Option<GooseLogFormat> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<GooseCoordinatedOmissionMitigation> for GooseConfiguration {
fn get_value(
&self,
values: Vec<GooseValue<GooseCoordinatedOmissionMitigation>>,
) -> Option<GooseCoordinatedOmissionMitigation> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfigure<Scenarios> for GooseConfiguration {
fn get_value(&self, values: Vec<GooseValue<Scenarios>>) -> Option<Scenarios> {
for value in values {
if let Some(v) = value.value {
if value.filter {
continue;
} else {
if !value.message.is_empty() {
info!("{} = {:?}", value.message, v)
}
return Some(v);
}
}
}
None
}
}
impl GooseConfiguration {
pub(crate) fn configure(&mut self, defaults: &GooseDefaults) {
self.quiet = self
.get_value(vec![
GooseValue {
value: Some(self.quiet),
filter: self.quiet == 0,
message: "",
},
GooseValue {
value: defaults.quiet,
filter: defaults.quiet.is_none(),
message: "",
},
])
.unwrap_or(0);
self.verbose = self
.get_value(vec![
GooseValue {
value: Some(self.verbose),
filter: self.verbose == 0,
message: "",
},
GooseValue {
value: defaults.verbose,
filter: defaults.verbose.is_none(),
message: "",
},
])
.unwrap_or(0);
self.log_level = self
.get_value(vec![
GooseValue {
value: Some(self.log_level),
filter: self.log_level == 0,
message: "",
},
GooseValue {
value: defaults.log_level,
filter: defaults.log_level.is_none(),
message: "",
},
])
.unwrap_or(0);
self.goose_log = self
.get_value(vec![
GooseValue {
value: Some(self.goose_log.to_string()),
filter: self.goose_log.is_empty(),
message: "",
},
GooseValue {
value: defaults.goose_log.clone(),
filter: defaults.goose_log.is_none(),
message: "",
},
])
.unwrap_or_default();
self.initialize_goose_logger();
self.configure_loggers(defaults);
self.test_plan = self.get_value(vec![
GooseValue {
value: self.test_plan.clone(),
filter: self.test_plan.is_none(),
message: "test_plan",
},
GooseValue {
value: defaults.test_plan.clone(),
filter: defaults.test_plan.is_none(),
message: "test_plan",
},
]);
let default_users = match std::thread::available_parallelism() {
Ok(ap) => Some(ap.get()),
Err(e) => {
info!("failed to detect available_parallelism: {e}");
Some(1)
}
};
self.users = self.get_value(vec![
GooseValue {
value: self.users,
filter: false,
message: "users",
},
GooseValue {
value: defaults.users,
filter: defaults.users.is_none(),
message: "users",
},
GooseValue {
value: default_users,
filter: self.test_plan.is_some(),
message: "users defaulted to number of CPUs",
},
]);
self.startup_time = self
.get_value(vec![
GooseValue {
value: Some(util::parse_timespan(&self.startup_time)),
filter: util::parse_timespan(&self.startup_time) == 0,
message: "startup_time",
},
GooseValue {
value: defaults.startup_time,
filter: defaults.startup_time.is_none(),
message: "startup_time",
},
])
.map_or_else(|| "0".to_string(), |v| v.to_string());
self.run_time = self
.get_value(vec![
GooseValue {
value: Some(util::parse_timespan(&self.run_time)),
filter: util::parse_timespan(&self.run_time) == 0,
message: "run_time",
},
GooseValue {
value: defaults.run_time,
filter: defaults.run_time.is_none(),
message: "run_time",
},
])
.map_or_else(|| "0".to_string(), |v| v.to_string());
self.hatch_rate = self
.get_value(vec![
GooseValue {
value: Some(util::get_hatch_rate(self.hatch_rate.clone())),
filter: self.hatch_rate.is_none(),
message: "hatch_rate",
},
GooseValue {
value: Some(util::get_hatch_rate(defaults.hatch_rate.clone())),
filter: defaults.hatch_rate.is_none(),
message: "hatch_rate",
},
])
.map(|v| v.to_string());
self.timeout = self
.get_value(vec![
GooseValue {
value: util::get_float_from_string(self.timeout.clone()),
filter: self.timeout.is_none(),
message: "timeout",
},
GooseValue {
value: util::get_float_from_string(defaults.timeout.clone()),
filter: defaults.timeout.is_none(),
message: "timeout",
},
])
.map(|v| v.to_string());
self.running_metrics = self.get_value(vec![
GooseValue {
value: self.running_metrics,
filter: self.running_metrics.is_none(),
message: "running_metrics",
},
GooseValue {
value: defaults.running_metrics,
filter: defaults.running_metrics.is_none(),
message: "running_metrics",
},
]);
self.no_reset_metrics = self
.get_value(vec![
GooseValue {
value: Some(self.no_reset_metrics),
filter: !self.no_reset_metrics,
message: "no_reset_metrics",
},
GooseValue {
value: defaults.no_reset_metrics,
filter: defaults.no_reset_metrics.is_none(),
message: "no_reset_metrics",
},
])
.unwrap_or(false);
self.no_metrics = self
.get_value(vec![
GooseValue {
value: Some(self.no_metrics),
filter: !self.no_metrics,
message: "no_metrics",
},
GooseValue {
value: defaults.no_metrics,
filter: defaults.no_metrics.is_none(),
message: "no_metrics",
},
])
.unwrap_or(false);
self.no_transaction_metrics = self
.get_value(vec![
GooseValue {
value: Some(self.no_transaction_metrics),
filter: !self.no_transaction_metrics,
message: "no_transaction_metrics",
},
GooseValue {
value: defaults.no_transaction_metrics,
filter: defaults.no_transaction_metrics.is_none(),
message: "no_transaction_metrics",
},
])
.unwrap_or(false);
self.no_scenario_metrics = self
.get_value(vec![
GooseValue {
value: Some(self.no_scenario_metrics),
filter: !self.no_scenario_metrics,
message: "no_scenario_metrics",
},
GooseValue {
value: defaults.no_scenario_metrics,
filter: defaults.no_scenario_metrics.is_none(),
message: "no_scenario_metrics",
},
])
.unwrap_or(false);
self.no_print_metrics = self
.get_value(vec![
GooseValue {
value: Some(self.no_print_metrics),
filter: !self.no_print_metrics,
message: "no_print_metrics",
},
GooseValue {
value: defaults.no_print_metrics,
filter: defaults.no_print_metrics.is_none(),
message: "no_print_metrics",
},
])
.unwrap_or(false);
self.no_error_summary = self
.get_value(vec![
GooseValue {
value: Some(self.no_error_summary),
filter: !self.no_error_summary,
message: "no_error_summary",
},
GooseValue {
value: defaults.no_error_summary,
filter: defaults.no_error_summary.is_none(),
message: "no_error_summary",
},
])
.unwrap_or(false);
self.report_file = self
.get_value(vec![
GooseValue {
value: Some(self.report_file.clone()),
filter: self.report_file.is_empty(),
message: "report_file",
},
GooseValue {
value: defaults.report_file.clone(),
filter: defaults.report_file.is_none(),
message: "report_file",
},
])
.unwrap_or_default();
self.no_debug_body = self
.get_value(vec![
GooseValue {
value: Some(self.no_granular_report),
filter: !self.no_granular_report,
message: "no_granular_report",
},
GooseValue {
value: defaults.no_debug_body,
filter: defaults.no_debug_body.is_none(),
message: "no_granular_report",
},
])
.unwrap_or(false);
self.iterations = self
.get_value(vec![
GooseValue {
value: Some(self.iterations),
filter: false,
message: "iterations",
},
GooseValue {
value: defaults.iterations,
filter: defaults.iterations.is_none(),
message: "iterations",
},
])
.unwrap_or(0);
self.scenarios = self
.get_value(vec![
GooseValue {
value: Some(self.scenarios.clone()),
filter: self.scenarios.active.is_empty(),
message: "scenarios",
},
GooseValue {
value: defaults.scenarios.clone(),
filter: defaults.scenarios.is_none(),
message: "scenarios",
},
])
.unwrap_or_else(|| Scenarios { active: Vec::new() });
self.no_debug_body = self
.get_value(vec![
GooseValue {
value: Some(self.no_debug_body),
filter: !self.no_debug_body,
message: "no_debug_body",
},
GooseValue {
value: defaults.no_debug_body,
filter: defaults.no_debug_body.is_none(),
message: "no_debug_body",
},
])
.unwrap_or(false);
self.no_status_codes = self
.get_value(vec![
GooseValue {
value: Some(self.no_status_codes),
filter: !self.no_status_codes,
message: "no_status_codes",
},
GooseValue {
value: defaults.no_status_codes,
filter: defaults.no_status_codes.is_none(),
message: "no_status_codes",
},
])
.unwrap_or(false);
self.no_telnet = self
.get_value(vec![
GooseValue {
value: Some(self.no_telnet),
filter: !self.no_telnet,
message: "no_telnet",
},
GooseValue {
value: defaults.no_telnet,
filter: defaults.no_telnet.is_none(),
message: "",
},
])
.unwrap_or(false);
self.no_websocket = self
.get_value(vec![
GooseValue {
value: Some(self.no_websocket),
filter: !self.no_websocket,
message: "no_websocket",
},
GooseValue {
value: defaults.no_websocket,
filter: defaults.no_websocket.is_none(),
message: "",
},
])
.unwrap_or(false);
self.no_autostart = self
.get_value(vec![
GooseValue {
value: Some(self.no_autostart),
filter: !self.no_autostart,
message: "no_autostart",
},
GooseValue {
value: defaults.no_autostart,
filter: defaults.no_autostart.is_none(),
message: "no_autostart",
},
])
.unwrap_or(false);
self.no_gzip = self
.get_value(vec![
GooseValue {
value: Some(self.no_gzip),
filter: !self.no_gzip,
message: "no_gzip",
},
GooseValue {
value: defaults.no_gzip,
filter: defaults.no_gzip.is_none(),
message: "no_gzip",
},
])
.unwrap_or(false);
self.accept_invalid_certs = self
.get_value(vec![
GooseValue {
value: Some(self.accept_invalid_certs),
filter: !self.accept_invalid_certs,
message: "accept_invalid_certs",
},
GooseValue {
value: defaults.accept_invalid_certs,
filter: defaults.accept_invalid_certs.is_none(),
message: "accept_invalid_certs",
},
])
.unwrap_or(false);
self.co_mitigation = self.get_value(vec![
GooseValue {
value: self.co_mitigation.clone(),
filter: self.co_mitigation.is_none(),
message: "co_mitigation",
},
GooseValue {
value: defaults.co_mitigation.clone(),
filter: defaults.co_mitigation.is_none(),
message: "co_mitigation",
},
GooseValue {
value: Some(GooseCoordinatedOmissionMitigation::Disabled),
filter: false,
message: "",
},
]);
self.throttle_requests = self
.get_value(vec![
GooseValue {
value: Some(self.throttle_requests),
filter: self.throttle_requests == 0,
message: "throttle_requests",
},
GooseValue {
value: defaults.throttle_requests,
filter: defaults.throttle_requests.is_none(),
message: "throttle_requests",
},
])
.unwrap_or(0);
self.sticky_follow = self
.get_value(vec![
GooseValue {
value: Some(self.sticky_follow),
filter: !self.sticky_follow,
message: "sticky_follow",
},
GooseValue {
value: defaults.sticky_follow,
filter: defaults.sticky_follow.is_none(),
message: "sticky_follow",
},
])
.unwrap_or(false);
}
pub(crate) fn validate(&self) -> Result<(), GooseError> {
if self.verbose > 0 && self.quiet > 0 {
return Err(GooseError::InvalidOption {
option: "`configuration.verbose`".to_string(),
value: self.verbose.to_string(),
detail: "`configuration.verbose` can not be set with `configuration.quiet`."
.to_string(),
});
}
if let Some(hatch_rate) = self.hatch_rate.as_ref() {
if hatch_rate == "0" {
return Err(GooseError::InvalidOption {
option: "`configuration.hatch_rate`".to_string(),
value: hatch_rate.to_string(),
detail: "`configuration.hatch_rate` must be set to at least 1.".to_string(),
});
}
}
if let Some(timeout) = self.timeout.as_ref() {
if crate::util::get_float_from_string(self.timeout.clone())
.expect("failed to re-convert string to float")
<= 0.0
{
return Err(GooseError::InvalidOption {
option: "`configuration.timeout`".to_string(),
value: timeout.to_string(),
detail: "`configuration.timeout` must be greater than 0.".to_string(),
});
}
}
if let Some(users) = self.users.as_ref() {
if users == &0 {
return Err(GooseError::InvalidOption {
option: "configuration.users".to_string(),
value: users.to_string(),
detail: "`configuration.users` must be set to at least 1.".to_string(),
});
}
}
if self.startup_time != "0" {
if self.hatch_rate.is_some() {
return Err(GooseError::InvalidOption {
option: "`configuration.startup_time`".to_string(),
value: self.startup_time.to_string(),
detail: "`configuration.startup_time` can not be set with `configuration.hatch_rate`.".to_string(),
});
}
if let Some(users) = self.users.as_ref() {
if users < &2 {
return Err(GooseError::InvalidOption {
option: "configuration.users".to_string(),
value: users.to_string(),
detail: "`configuration.users` must be set to at least 2 when `configuration.startup_time` is set.".to_string(),
});
}
}
}
if self.test_plan.is_some() {
if let Some(users) = self.users.as_ref() {
return Err(GooseError::InvalidOption {
option: "`configuration.users`".to_string(),
value: users.to_string(),
detail: "`configuration.users` can not be set with `configuration.test_plan`."
.to_string(),
});
}
if self.startup_time != "0" {
return Err(GooseError::InvalidOption {
option: "`configuration.startup_time`".to_string(),
value: self.startup_time.to_string(),
detail: "`configuration.startup_time` can not be set with `configuration.test_plan`.".to_string(),
});
}
if let Some(hatch_rate) = self.hatch_rate.as_ref() {
return Err(GooseError::InvalidOption {
option: "`configuration.hatch_rate`".to_string(),
value: hatch_rate.to_string(),
detail:
"`configuration.hatch_rate` can not be set with `configuration.test_plan`."
.to_string(),
});
}
if self.run_time != "0" {
return Err(GooseError::InvalidOption {
option: "`configuration.run_time`".to_string(),
value: self.run_time.to_string(),
detail:
"`configuration.run_time` can not be set with `configuration.test_plan`."
.to_string(),
});
}
if self.no_reset_metrics {
return Err(GooseError::InvalidOption {
option: "`configuration.no_reset_metrics".to_string(),
value: self.no_reset_metrics.to_string(),
detail: "`configuration.no_reset_metrics` can not be set with `configuration.test_plan` (metrics are not reset)."
.to_string(),
});
}
}
if self.iterations > 0 {
if self.run_time != "0" {
return Err(GooseError::InvalidOption {
option: "`configuration.run_time`".to_string(),
value: self.run_time.to_string(),
detail:
"`configuration.run_time` can not be set with `configuration.iterations`."
.to_string(),
});
}
if self.test_plan.is_some() {
return Err(GooseError::InvalidOption {
option: "`configuration.iterations`".to_string(),
value: self.iterations.to_string(),
detail:
"`configuration.iteratoins` can not be set with `configuration.test_plan`."
.to_string(),
});
}
if self.no_reset_metrics {
return Err(GooseError::InvalidOption {
option: "`configuration.no_reset_metrics".to_string(),
value: self.no_reset_metrics.to_string(),
detail: "`configuration.no_reset_metrics` can not be set with `configuration.iterations` (metrics are not reset)."
.to_string(),
});
}
}
if self.no_metrics {
if !self.request_log.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.request_log`".to_string(),
value: self.request_log.to_string(),
detail: "`configuration.request_log` can not be set with `configuration.no_metrics`.".to_string(),
});
} else if !self.transaction_log.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.transaction_log`".to_string(),
value: self.transaction_log.to_string(),
detail:
"`configuration.transaction_log` can not be set with `configuration.no_metrics`."
.to_string(),
});
} else if !self.scenario_log.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.scenario_log`".to_string(),
value: self.scenario_log.to_string(),
detail:
"`configuration.scenario_log` can not be set with `configuration.no_metrics`."
.to_string(),
});
} else if !self.error_log.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.error_log`".to_string(),
value: self.error_log.to_string(),
detail:
"`configuration.error_log` can not be set with `configuration.no_metrics`."
.to_string(),
});
} else if !self.report_file.is_empty() {
return Err(GooseError::InvalidOption {
option: "`configuration.report_file`".to_string(),
value: format!("{:?}", self.report_file),
detail:
"`configuration.report_file` can not be set with `configuration.no_metrics`."
.to_string(),
});
} else if self.co_mitigation.as_ref().unwrap()
!= &GooseCoordinatedOmissionMitigation::Disabled
{
return Err(GooseError::InvalidOption {
option: "`configuration.co_mitigation`".to_string(),
value: format!("{:?}", self.co_mitigation.as_ref().unwrap()),
detail: "`configuration.co_mitigation` can not be set with `configuration.no_metrircs`."
.to_string(),
});
}
}
if self.report_file.is_empty() && self.no_granular_report {
return Err(GooseError::InvalidOption {
option: "`configuration.no_granular_report`".to_string(),
value: true.to_string(),
detail:
"`configuration.no_granular_report` can not be set without `configuration.report_file`."
.to_string(),
});
}
if self.no_autostart && self.no_telnet && self.no_websocket {
return Err(GooseError::InvalidOption {
option: "`configuration.no_autostart`".to_string(),
value: true.to_string(),
detail: "`configuration.no_autostart` requires at least one Controller be enabled"
.to_string(),
});
}
if self.throttle_requests > 0 {
if self.throttle_requests == 0 {
return Err(GooseError::InvalidOption {
option: "`configuration.throttle_requests`".to_string(),
value: self.throttle_requests.to_string(),
detail: "`configuration.throttle_requests` must be set to at least 1 request per second.".to_string(),
});
} else if self.throttle_requests > 1_000_000 {
return Err(GooseError::InvalidOption {
option: "`configuration.throttle_requests`".to_string(),
value: self.throttle_requests.to_string(),
detail: "`configuration.throttle_requests` can not be set to more than 1,000,000 request per second.".to_string(),
});
}
}
Ok(())
}
pub(crate) fn initialize_goose_logger(&self) {
let debug_level = match self.verbose {
0 => match self.quiet {
0 => LevelFilter::Info,
_ => LevelFilter::Warn,
},
1 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
let log_level = match self.log_level {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
let goose_log: Option<PathBuf> = if !self.goose_log.is_empty() {
Some(PathBuf::from(&self.goose_log))
} else {
None
};
if let Some(log_to_file) = goose_log {
match CombinedLogger::init(vec![
SimpleLogger::new(debug_level, Config::default()),
WriteLogger::new(
log_level,
Config::default(),
std::fs::File::create(&log_to_file).unwrap(),
),
]) {
Ok(_) => (),
Err(e) => {
info!("failed to initialize CombinedLogger: {e}");
}
}
info!("Writing to log file: {}", log_to_file.display());
} else {
match CombinedLogger::init(vec![SimpleLogger::new(debug_level, Config::default())]) {
Ok(_) => (),
Err(e) => {
info!("failed to initialize CombinedLogger: {e}");
}
}
}
info!("Output verbosity level: {debug_level}");
info!("Logfile verbosity level: {log_level}");
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn set_defaults() {
let host = "http://example.com/".to_string();
let users: usize = 10;
let run_time: usize = 10;
let hatch_rate = "2".to_string();
let timeout = "45".to_string();
let log_level: usize = 1;
let goose_log = "custom-goose.log".to_string();
let quiet: usize = 0;
let verbose: usize = 0;
let report_file = "custom-goose-report.html".to_string();
let request_log = "custom-goose-request.log".to_string();
let transaction_log = "custom-goose-transaction.log".to_string();
let scenario_log = "custom-goose-scenario.log".to_string();
let debug_log = "custom-goose-debug.log".to_string();
let error_log = "custom-goose-error.log".to_string();
let throttle_requests: usize = 25;
let goose_attack = GooseAttack::initialize()
.unwrap()
.set_default(GooseDefault::Host, host.as_str())
.unwrap()
.set_default(GooseDefault::Users, users)
.unwrap()
.set_default(GooseDefault::RunTime, run_time)
.unwrap()
.set_default(GooseDefault::HatchRate, hatch_rate.as_str())
.unwrap()
.set_default(GooseDefault::LogLevel, log_level)
.unwrap()
.set_default(GooseDefault::GooseLog, goose_log.as_str())
.unwrap()
.set_default(GooseDefault::Quiet, quiet)
.unwrap()
.set_default(GooseDefault::Verbose, verbose)
.unwrap()
.set_default(GooseDefault::Timeout, timeout.as_str())
.unwrap()
.set_default(GooseDefault::RunningMetrics, 15)
.unwrap()
.set_default(GooseDefault::NoResetMetrics, true)
.unwrap()
.set_default(GooseDefault::NoMetrics, true)
.unwrap()
.set_default(GooseDefault::NoTransactionMetrics, true)
.unwrap()
.set_default(GooseDefault::NoScenarioMetrics, true)
.unwrap()
.set_default(GooseDefault::NoPrintMetrics, true)
.unwrap()
.set_default(GooseDefault::NoErrorSummary, true)
.unwrap()
.set_default(GooseDefault::NoTelnet, true)
.unwrap()
.set_default(GooseDefault::NoWebSocket, true)
.unwrap()
.set_default(GooseDefault::NoAutoStart, true)
.unwrap()
.set_default(GooseDefault::NoGzip, true)
.unwrap()
.set_default(GooseDefault::ReportFile, report_file.as_str())
.unwrap()
.set_default(GooseDefault::RequestLog, request_log.as_str())
.unwrap()
.set_default(GooseDefault::RequestFormat, GooseLogFormat::Raw)
.unwrap()
.set_default(GooseDefault::RequestBody, true)
.unwrap()
.set_default(GooseDefault::TransactionLog, transaction_log.as_str())
.unwrap()
.set_default(GooseDefault::TransactionFormat, GooseLogFormat::Raw)
.unwrap()
.set_default(GooseDefault::ScenarioLog, scenario_log.as_str())
.unwrap()
.set_default(GooseDefault::ScenarioFormat, GooseLogFormat::Raw)
.unwrap()
.set_default(GooseDefault::ErrorLog, error_log.as_str())
.unwrap()
.set_default(GooseDefault::ErrorFormat, GooseLogFormat::Csv)
.unwrap()
.set_default(GooseDefault::DebugLog, debug_log.as_str())
.unwrap()
.set_default(GooseDefault::DebugFormat, GooseLogFormat::Csv)
.unwrap()
.set_default(GooseDefault::NoDebugBody, true)
.unwrap()
.set_default(GooseDefault::NoStatusCodes, true)
.unwrap()
.set_default(
GooseDefault::CoordinatedOmissionMitigation,
GooseCoordinatedOmissionMitigation::Disabled,
)
.unwrap()
.set_default(GooseDefault::ThrottleRequests, throttle_requests)
.unwrap()
.set_default(GooseDefault::StickyFollow, true)
.unwrap();
assert!(goose_attack.defaults.host == Some(host));
assert!(goose_attack.defaults.users == Some(users));
assert!(goose_attack.defaults.run_time == Some(run_time));
assert!(goose_attack.defaults.hatch_rate == Some(hatch_rate));
assert!(goose_attack.defaults.log_level == Some(log_level as u8));
assert!(goose_attack.defaults.goose_log == Some(goose_log));
assert!(goose_attack.defaults.request_body == Some(true));
assert!(goose_attack.defaults.no_debug_body == Some(true));
assert!(goose_attack.defaults.quiet == Some(quiet as u8));
assert!(goose_attack.defaults.verbose == Some(verbose as u8));
assert!(goose_attack.defaults.running_metrics == Some(15));
assert!(goose_attack.defaults.no_reset_metrics == Some(true));
assert!(goose_attack.defaults.no_metrics == Some(true));
assert!(goose_attack.defaults.no_transaction_metrics == Some(true));
assert!(goose_attack.defaults.no_scenario_metrics == Some(true));
assert!(goose_attack.defaults.no_print_metrics == Some(true));
assert!(goose_attack.defaults.no_error_summary == Some(true));
assert!(goose_attack.defaults.no_telnet == Some(true));
assert!(goose_attack.defaults.no_websocket == Some(true));
assert!(goose_attack.defaults.no_autostart == Some(true));
assert!(goose_attack.defaults.timeout == Some(timeout));
assert!(goose_attack.defaults.no_gzip == Some(true));
assert!(goose_attack.defaults.report_file == Some(vec![report_file]));
assert!(goose_attack.defaults.request_log == Some(request_log));
assert!(goose_attack.defaults.request_format == Some(GooseLogFormat::Raw));
assert!(goose_attack.defaults.error_log == Some(error_log));
assert!(goose_attack.defaults.error_format == Some(GooseLogFormat::Csv));
assert!(goose_attack.defaults.debug_log == Some(debug_log));
assert!(goose_attack.defaults.debug_format == Some(GooseLogFormat::Csv));
assert!(goose_attack.defaults.no_status_codes == Some(true));
assert!(
goose_attack.defaults.co_mitigation
== Some(GooseCoordinatedOmissionMitigation::Disabled)
);
assert!(goose_attack.defaults.throttle_requests == Some(throttle_requests));
assert!(goose_attack.defaults.sticky_follow == Some(true));
}
}