Repository: tag1consulting/goose (Rust)

Issue:
not testing actual run-time flags and options
During a recent `--help` and general documentation cleanup, I reworked the Goose `--workers-expected` option to use the Structopt `required_if` attribute, as it is required if `--master` is enabled. The implementation seems to actually be Clap: [`required_if`](https://docs.rs/clap/*/clap/struct.Arg.html#method.required_if).

Possibly it's as simple as `--master` is a flag, not an option, and `required_if` only works with options -- this isn't clear to me from the documentation.

The bigger problem is that our tests did not catch this, only manually running Goose caught it. This issue is to follow up so we are properly testing configuration options to avoid something similar to this from slipping through again. (When actually running Goose, we use `GooseConfiguration::from_args()` to instantiate the configuration struct. When testing, we use `tests/common/build_configuration()` to manually construct a configuration struct.


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0186b07fe..abdb7b278 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
     o rewrote/simplified configuration descriptions to fit standard console width
  - update prelude documentation
  - increase precision of metrics for smaller values
+ - consistently build configuration from arguments
 
 ## 0.9.1 Aug 1, 2020
  - return `GooseStats` from `GooseAttack` `.execute()`
diff --git a/src/goose.rs b/src/goose.rs
index 7eb0c5d3b..fd826dad0 100644
--- a/src/goose.rs
+++ b/src/goose.rs
@@ -2207,11 +2207,14 @@ impl Hash for GooseTask {
 mod tests {
     use super::*;
 
+    use gumdrop::Options;
     use httpmock::Method::{GET, POST};
     use httpmock::{Mock, MockServer};
 
+    const EMPTY_ARGS: Vec<&str> = vec![];
+
     async fn setup_user(server: &MockServer) -> Result<GooseUser, GooseError> {
-        let configuration = GooseConfiguration::default();
+        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
         let base_url = get_base_url(Some(server.url("/")), None, None).unwrap();
         GooseUser::single(base_url, &configuration)
     }
@@ -2639,7 +2642,7 @@ mod tests {
     #[tokio::test]
     async fn goose_user() {
         const HOST: &str = "http://example.com/";
-        let configuration = GooseConfiguration::default();
+        let configuration = GooseConfiguration::parse_args_default(&EMPTY_ARGS).unwrap();
         let base_url = get_base_url(Some(HOST.to_string()), None, None).unwrap();
         let user = GooseUser::new(0, base_url, 0, 0, &configuration, 0).unwrap();
         assert_eq!(user.task_sets_index, 0);
diff --git a/src/lib.rs b/src/lib.rs
index c37859d7e..0484ea13d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -505,16 +505,18 @@ impl GooseAttack {
     ///     let mut goose_attack = GooseAttack::initialize();
     /// ```
     pub fn initialize() -> Result<GooseAttack, GooseError> {
+        let configuration = GooseConfiguration::parse_args_default_or_exit();
+        let users = GooseAttack::calculate_users(&configuration)?;
         let goose_attack = GooseAttack {
             test_start_task: None,
             test_stop_task: None,
             task_sets: Vec::new(),
             weighted_users: Vec::new(),
             host: None,
-            configuration: GooseConfiguration::parse_args_default_or_exit(),
+            configuration,
             number_of_cpus: num_cpus::get(),
             run_time: 0,
-            users: 0,
+            users,
             started: None,
             metrics: GooseMetrics::default(),
         };
@@ -532,20 +534,23 @@ impl GooseAttack {
     ///     let configuration = GooseConfiguration::parse_args_default_or_exit();
     ///     let mut goose_attack = GooseAttack::initialize_with_config(configuration);
     /// ```
-    pub fn initialize_with_config(config: GooseConfiguration) -> GooseAttack {
-        GooseAttack {
+    pub fn initialize_with_config(
+        configuration: GooseConfiguration,
+    ) -> Result<GooseAttack, GooseError> {
+        let users = GooseAttack::calculate_users(&configuration)?;
+        Ok(GooseAttack {
             test_start_task: None,
             test_stop_task: None,
             task_sets: Vec::new(),
             weighted_users: Vec::new(),
             host: None,
-            configuration: config,
+            configuration,
             number_of_cpus: num_cpus::get(),
             run_time: 0,
-            users: 0,
+            users,
             started: None,
             metrics: GooseMetrics::default(),
-        }
+        })
     }
 
     pub fn initialize_logger(&self) {
@@ -592,6 +597,43 @@ impl GooseAttack {
         info!("Writing to log file: {}", log_file.display());
     }
 
+    // Helper to calculate the number of user threads to launch, defaulting to
+    // the number of CPU cores available.
+    fn calculate_users(config: &GooseConfiguration) -> Result<usize, GooseError> {
+        match config.users {
+            Some(u) => {
+                if u == 0 {
+                    if config.worker {
+                        return Err(GooseError::InvalidOption {
+                            option: "--users".to_string(),
+                            value: u.to_string(),
+                            detail: "The --users option must be set to at least 1.".to_string(),
+                        });
+                    }
+                    Ok(0)
+                } else {
+                    if config.worker {
+                        return Err(GooseError::InvalidOption {
+                            option: "--users".to_string(),
+                            value: u.to_string(),
+                            detail:
+                                "The --users option can not be set together with the --worker flag."
+                                    .to_string(),
+                        });
+                    }
+                    Ok(u)
+                }
+            }
+            None => {
+                let u = num_cpus::get();
+                if !config.manager && !config.worker {
+                    info!("concurrent users defaulted to {} (number of CPUs)", u);
+                }
+                Ok(u)
+            }
+        }
+    }
+
     pub fn setup(mut self) -> Result<Self, GooseError> {
         // If version flag is set, display package name and version and exit.
         if self.configuration.version {
@@ -706,40 +748,6 @@ impl GooseAttack {
             self.run_time = 0;
         }
 
-        // Configure number of user threads to launch, default to the number of CPU cores available.
-        self.users = match self.configuration.users {
-            Some(u) => {
-                if u == 0 {
-                    if self.configuration.worker {
-                        return Err(GooseError::InvalidOption {
-                            option: "--users".to_string(),
-                            value: self.users.to_string(),
-                            detail: "The --users option must be set to at least 1.".to_string(),
-                        });
-                    }
-                    0
-                } else {
-                    if self.configuration.worker {
-                        return Err(GooseError::InvalidOption {
-                            option: "--users".to_string(),
-                            value: self.users.to_string(),
-                            detail:
-                                "The --users option can not be set together with the --worker flag."
-                                    .to_string(),
-                        });
-                    }
-                    u
-                }
-            }
-            None => {
-                let u = self.number_of_cpus;
-                if !self.configuration.manager && !self.configuration.worker {
-                    info!("concurrent users defaulted to {} (number of CPUs)", u);
-                }
-                u
-            }
-        };
-
         if !self.configuration.manager && !self.configuration.worker {
             debug!("users = {}", self.users);
         }
@@ -1856,7 +1864,7 @@ impl GooseAttack {
 }
 
 /// Options available when launching a Goose load test.
-#[derive(Options, Debug, Default, Clone, Serialize, Deserialize)]
+#[derive(Options, Debug, Clone, Serialize, Deserialize)]
 pub struct GooseConfiguration {
     /// Displays this help
     #[options(short = "h")]
diff --git a/src/manager.rs b/src/manager.rs
index 30c46669b..82ebab997 100644
--- a/src/manager.rs
+++ b/src/manager.rs
@@ -506,25 +506,29 @@ pub async fn manager_main(mut goose_attack: GooseAttack) -> GooseAttack {
 mod tests {
     use super::*;
 
+    use gumdrop::Options;
+
     #[test]
     fn test_distribute_users() {
-        let config = GooseConfiguration::default();
-        let mut goose_attack = GooseAttack::initialize_with_config(config);
-
-        goose_attack.users = 10;
-        goose_attack.configuration.expect_workers = 2;
+        let ten_users_two_workers: Vec<&str> = vec!["--users", "10", "--expect-workers", "2"];
+        let config = GooseConfiguration::parse_args_default(&ten_users_two_workers).unwrap();
+        let goose_attack = GooseAttack::initialize_with_config(config).unwrap();
         let (users_per_process, users_remainder) = distribute_users(&goose_attack);
         assert_eq!(users_per_process, 5);
         assert_eq!(users_remainder, 0);
 
-        goose_attack.users = 1;
-        goose_attack.configuration.expect_workers = 1;
+        let one_user_one_worker: Vec<&str> = vec!["--users", "1", "--expect-workers", "1"];
+        let config = GooseConfiguration::parse_args_default(&one_user_one_worker).unwrap();
+        let goose_attack = GooseAttack::initialize_with_config(config).unwrap();
         let (users_per_process, users_remainder) = distribute_users(&goose_attack);
         assert_eq!(users_per_process, 1);
         assert_eq!(users_remainder, 0);
 
-        goose_attack.users = 100;
-        goose_attack.configuration.expect_workers = 21;
+        let onehundred_users_twentyone_workers: Vec<&str> =
+            vec!["--users", "100", "--expect-workers", "21"];
+        let config =
+            GooseConfiguration::parse_args_default(&onehundred_users_twentyone_workers).unwrap();
+        let goose_attack = GooseAttack::initialize_with_config(config).unwrap();
         let (users_per_process, users_remainder) = distribute_users(&goose_attack);
         assert_eq!(users_per_process, 4);
         assert_eq!(users_remainder, 16);
diff --git a/src/worker.rs b/src/worker.rs
index 87db72838..962c577f4 100644
--- a/src/worker.rs
+++ b/src/worker.rs
@@ -1,9 +1,12 @@
+use gumdrop::Options;
 use nng::*;
 use serde::{Deserialize, Serialize};
 use std::sync::atomic::Ordering;
 use std::{thread, time};
 use url::Url;
 
+const EMPTY_ARGS: Vec<&str> = vec![];
+
 use crate::goose::{GooseUser, GooseUserCommand};
 use crate::manager::GooseUserInitializer;
 use crate::metrics::{GooseRequestMetrics, GooseTaskMetrics};
@@ -84,7 +87,8 @@ pub async fn worker_main(goose_attack: &GooseAttack) -> GooseAttack {
     );
 
     let mut hatch_rate: Option<f32> = None;
-    let mut config: GooseConfiguration = GooseConfiguration::default();
+    let mut config: GooseConfiguration = GooseConfiguration::parse_args_default(&EMPTY_ARGS)
+        .expect("failed to generate default configuration");
     let mut weighted_users: Vec<GooseUser> = Vec::new();
 
     // Wait for the manager to send user parameters.
@@ -200,7 +204,10 @@ pub async fn worker_main(goose_attack: &GooseAttack) -> GooseAttack {
     );
     let sleep_duration = time::Duration::from_secs_f32(hatch_rate.unwrap());
 
-    let mut worker_goose_attack = GooseAttack::initialize_with_config(config.clone());
+    let mut worker_goose_attack = GooseAttack::initialize_with_config(config.clone())
+        .map_err(|error| eprintln!("{:?} worker_id({})", error, get_worker_id()))
+        .expect("failed to launch GooseAttack");
+
     worker_goose_attack.started = Some(time::Instant::now());
     worker_goose_attack.task_sets = goose_attack.task_sets.clone();
     if config.run_time != "" {


Test patch:
diff --git a/tests/common.rs b/tests/common.rs
index c894151fd..1e0115c63 100644
--- a/tests/common.rs
+++ b/tests/common.rs
@@ -3,25 +3,45 @@ use httpmock::MockServer;
 
 use goose::GooseConfiguration;
 
-pub fn build_configuration(server: &MockServer) -> GooseConfiguration {
-    // Using default options, except for those specified below
-    GooseConfiguration::parse_args_default(&[
-        // Set --host to server URL
-        "--host",
-        &server.url("/"),
-        // Set --users to 1
-        "--users",
-        "1",
-        // Set --hatch-rate to 1
-        "--hatch-rate",
-        "1",
-        // Set --run-time to 1
-        "--run-time",
-        "1",
-        // Set --no-metrics flag
-        "--no-metrics",
-        // Set --no-task-metrics flag
-        "--no-task-metrics",
-    ])
-    .unwrap()
+/// The following options are configured by default, if not set to a custom value
+/// and if not building a Worker configuration:
+///  --host <mock-server>
+///  --users 1
+///  --hatch-rate 1
+///  --run-time 1
+pub fn build_configuration(server: &MockServer, custom: Vec<&str>) -> GooseConfiguration {
+    // Start with an empty configuration.
+    let mut configuration: Vec<&str> = vec![];
+    // Declare server_url here no matter what, so its lifetime is sufficient when needed.
+    let server_url = server.url("/");
+
+    // Merge in all custom options first.
+    configuration.extend_from_slice(&custom);
+
+    // If not building a Worker configuration, set some defaults.
+    if !configuration.contains(&"--worker") {
+        // Default to using mock server if not otherwise configured.
+        if !configuration.contains(&"--host") {
+            configuration.extend_from_slice(&["--host", &server_url]);
+        }
+
+        // Default to testing with 1 user if not otherwise configured.
+        if !configuration.contains(&"--users") {
+            configuration.extend_from_slice(&["--users", "1"]);
+        }
+
+        // Default to hatch 1 user per second if not otherwise configured.
+        if !configuration.contains(&"--hatch-rate") {
+            configuration.extend_from_slice(&["--hatch-rate", "1"]);
+        }
+
+        // Default to running for 1 second if not otherwise configured.
+        if !configuration.contains(&"--run-time") {
+            configuration.extend_from_slice(&["--run-time", "1"]);
+        }
+    }
+
+    // Parse these options to generate a GooseConfiguration.
+    GooseConfiguration::parse_args_default(&configuration)
+        .expect("failed to parse options and generate a configuration")
 }
diff --git a/tests/gaggle.rs b/tests/gaggle.rs
index a8f0a47cc..3cbe0e710 100644
--- a/tests/gaggle.rs
+++ b/tests/gaggle.rs
@@ -41,19 +41,13 @@ fn test_gaggle() {
     // Launch workers in their own threads, storing the thread handle.
     let mut worker_handles = Vec::new();
     // Each worker has the same identical configuration.
-    let mut worker_configuration = common::build_configuration(&server);
-    worker_configuration.worker = true;
-    worker_configuration.host = "".to_string();
-    worker_configuration.users = None;
-    worker_configuration.no_metrics = false;
-    worker_configuration.run_time = "".to_string();
-    // Can't change this on the worker.
-    worker_configuration.no_task_metrics = false;
+    let worker_configuration = common::build_configuration(&server, vec!["--worker"]);
     for _ in 0..2 {
         let configuration = worker_configuration.clone();
         // Start worker instance of the load test.
         worker_handles.push(thread::spawn(move || {
             let _goose_metrics = crate::GooseAttack::initialize_with_config(configuration)
+                .unwrap()
                 .setup()
                 .unwrap()
                 .register_taskset(taskset!("User1").register_task(task!(get_index)))
@@ -64,17 +58,24 @@ fn test_gaggle() {
     }
 
     // Start manager instance in current thread and run a distributed load test.
-    let mut manager_configuration = common::build_configuration(&server);
-    manager_configuration.users = Some(2);
-    manager_configuration.hatch_rate = 4;
-    manager_configuration.manager = true;
-    manager_configuration.expect_workers = 2;
-    manager_configuration.run_time = "3".to_string();
-    // Enable statistics so we can validate they are merged to the manager correctly.
-    manager_configuration.no_metrics = false;
-    manager_configuration.no_task_metrics = false;
-    manager_configuration.no_reset_metrics = true;
+    let manager_configuration = common::build_configuration(
+        &server,
+        vec![
+            "--manager",
+            "--expect-workers",
+            "2",
+            "--users",
+            "2",
+            "--hatch-rate",
+            "4",
+            "--run-time",
+            "3",
+            // Enable statistics so we can validate they are merged to the manager correctly.
+            "--no-reset-metrics",
+        ],
+    );
     let goose_metrics = crate::GooseAttack::initialize_with_config(manager_configuration)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(taskset!("User1").register_task(task!(get_index)))
diff --git a/tests/logs.rs b/tests/logs.rs
index 6360eeb10..fe6743b07 100644
--- a/tests/logs.rs
+++ b/tests/logs.rs
@@ -52,10 +52,9 @@ fn test_metrics_logs_json() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.metrics_file = METRICS_FILE.to_string();
-    config.no_metrics = false;
+    let config = common::build_configuration(&server, vec!["--metrics-file", METRICS_FILE]);
     let goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(taskset!("LoadTest").register_task(task!(get_index)))
@@ -88,11 +87,12 @@ fn test_metrics_logs_csv() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.metrics_file = METRICS_FILE.to_string();
-    config.metrics_format = "csv".to_string();
-    config.no_metrics = false;
+    let config = common::build_configuration(
+        &server,
+        vec!["--metrics-file", METRICS_FILE, "--metrics-format", "csv"],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(taskset!("LoadTest").register_task(task!(get_index)))
@@ -122,11 +122,12 @@ fn test_metrics_logs_raw() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.metrics_file = METRICS_FILE.to_string();
-    config.metrics_format = "raw".to_string();
-    config.no_metrics = false;
+    let config = common::build_configuration(
+        &server,
+        vec!["--metrics-file", METRICS_FILE, "--metrics-format", "raw"],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(taskset!("LoadTest").register_task(task!(get_index)))
@@ -161,10 +162,12 @@ fn test_debug_logs_raw() {
         .return_status(503)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.debug_file = DEBUG_FILE.to_string();
-    config.debug_format = "raw".to_string();
+    let config = common::build_configuration(
+        &server,
+        vec!["--debug-file", DEBUG_FILE, "--debug-format", "raw"],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
@@ -204,9 +207,9 @@ fn test_debug_logs_json() {
         .return_status(503)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.debug_file = DEBUG_FILE.to_string();
+    let config = common::build_configuration(&server, vec!["--debug-file", DEBUG_FILE]);
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
@@ -246,12 +249,19 @@ fn test_metrics_and_debug_logs() {
         .return_status(503)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.metrics_file = METRICS_FILE.to_string();
-    config.metrics_format = "raw".to_string();
-    config.no_metrics = false;
-    config.debug_file = DEBUG_FILE.to_string();
+    let config = common::build_configuration(
+        &server,
+        vec![
+            "--metrics-file",
+            METRICS_FILE,
+            "--metrics-format",
+            "raw",
+            "--debug-file",
+            DEBUG_FILE,
+        ],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
diff --git a/tests/no_normal_tasks.rs b/tests/no_normal_tasks.rs
index 900e01309..5884036f0 100644
--- a/tests/no_normal_tasks.rs
+++ b/tests/no_normal_tasks.rs
@@ -35,17 +35,20 @@ fn test_no_normal_tasks() {
         .return_status(200)
         .create_on(&server);
 
-    let _goose_stats =
-        crate::GooseAttack::initialize_with_config(common::build_configuration(&server))
-            .setup()
-            .unwrap()
-            .register_taskset(
-                taskset!("LoadTest")
-                    .register_task(task!(login).set_on_start())
-                    .register_task(task!(logout).set_on_stop()),
-            )
-            .execute()
-            .unwrap();
+    let _goose_stats = crate::GooseAttack::initialize_with_config(common::build_configuration(
+        &server,
+        vec!["--no-metrics"],
+    ))
+    .unwrap()
+    .setup()
+    .unwrap()
+    .register_taskset(
+        taskset!("LoadTest")
+            .register_task(task!(login).set_on_start())
+            .register_task(task!(logout).set_on_stop()),
+    )
+    .execute()
+    .unwrap();
 
     // Confirm that the on_start and on_exit tasks actually ran.
     assert!(login_path.times_called() == 1);
diff --git a/tests/one_taskset.rs b/tests/one_taskset.rs
index 46f436a56..86f38ad9b 100644
--- a/tests/one_taskset.rs
+++ b/tests/one_taskset.rs
@@ -37,14 +37,20 @@ fn test_single_taskset() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.no_metrics = false;
-    // Start users in .5 seconds.
-    config.users = Some(2);
-    config.hatch_rate = 4;
-    config.status_codes = true;
-    config.no_reset_metrics = true;
+    let config = common::build_configuration(
+        &server,
+        vec![
+            "--users",
+            "2",
+            // Start users in .5 seconds.
+            "--hatch-rate",
+            "4",
+            "--status-codes",
+            "--no-reset-metrics",
+        ],
+    );
     let goose_metrics = crate::GooseAttack::initialize_with_config(config.clone())
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
@@ -64,6 +70,8 @@ fn test_single_taskset() {
     let difference = about.times_called() as i32 - one_third_index as i32;
     assert!(difference >= -2 && difference <= 2);
 
+    println!("{:?}", &goose_metrics);
+
     let index_metrics = goose_metrics
         .requests
         .get(&format!("GET {}", INDEX_PATH))
@@ -112,13 +120,12 @@ fn test_single_taskset_empty_config_host() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
+    let mut config = common::build_configuration(&server, vec!["--no-reset-metrics"]);
     // Leaves an empty string in config.host.
     let host = std::mem::take(&mut config.host);
-    // Enable statistics to confirm Goose and web server agree.
-    config.no_metrics = false;
-    config.no_reset_metrics = true;
+    // Enable metrics to confirm Goose and web server agree.
     let goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
@@ -188,13 +195,18 @@ fn test_single_taskset_closure() {
     let server = MockServer::start();
 
     // Build configuration.
-    let mut config = common::build_configuration(&server);
-    config.no_metrics = false;
-    // Start users in .5 seconds.
-    config.users = Some(test_endpoints.len());
-    config.hatch_rate = 2 * test_endpoints.len();
-    config.status_codes = true;
-    config.no_reset_metrics = true;
+    let config = common::build_configuration(
+        &server,
+        vec![
+            "--no-reset-metrics",
+            "--no-task-metrics",
+            "--status-codes",
+            "--users",
+            &test_endpoints.len().to_string(),
+            "--hatch-rate",
+            &(2 * test_endpoints.len()).to_string(),
+        ],
+    );
 
     // Setup mock endpoints.
     let mut mock_endpoints = Vec::with_capacity(test_endpoints.len());
@@ -235,6 +247,7 @@ fn test_single_taskset_closure() {
 
     // Run the loadtest.
     let goose_metrics = crate::GooseAttack::initialize_with_config(config.clone())
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(taskset)
@@ -320,13 +333,19 @@ fn test_single_taskset_reset_metrics() {
         .return_status(200)
         .create_on(&server);
 
-    let mut config = common::build_configuration(&server);
-    config.no_metrics = false;
-    // Start users in .5 seconds.
-    config.users = Some(2);
-    config.hatch_rate = 4;
-    config.status_codes = true;
+    let config = common::build_configuration(
+        &server,
+        vec![
+            "--no-task-metrics",
+            "--status-codes",
+            "--users",
+            "2",
+            "--hatch-rate",
+            "4",
+        ],
+    );
     let goose_metrics = crate::GooseAttack::initialize_with_config(config.clone())
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
diff --git a/tests/redirect.rs b/tests/redirect.rs
index 43c940f46..283a3084f 100644
--- a/tests/redirect.rs
+++ b/tests/redirect.rs
@@ -95,20 +95,23 @@ fn test_redirect() {
         .return_body("<HTML><BODY>about page</BODY></HTML>")
         .create_on(&server1);
 
-    let _goose_stats =
-        crate::GooseAttack::initialize_with_config(common::build_configuration(&server1))
-            .setup()
-            .unwrap()
-            .register_taskset(
-                taskset!("LoadTest")
-                    // Load index directly.
-                    .register_task(task!(get_index))
-                    // Load redirect path, redirect to redirect2 path, redirect to
-                    // redirect3 path, redirect to about.
-                    .register_task(task!(get_redirect)),
-            )
-            .execute()
-            .unwrap();
+    let _goose_stats = crate::GooseAttack::initialize_with_config(common::build_configuration(
+        &server1,
+        vec!["--no-metrics"],
+    ))
+    .unwrap()
+    .setup()
+    .unwrap()
+    .register_taskset(
+        taskset!("LoadTest")
+            // Load index directly.
+            .register_task(task!(get_index))
+            // Load redirect path, redirect to redirect2 path, redirect to
+            // redirect3 path, redirect to about.
+            .register_task(task!(get_redirect)),
+    )
+    .execute()
+    .unwrap();
 
     // Confirm that we loaded the mock endpoints; while we never load the about page
     // directly, we should follow the redirects and load it.
@@ -161,21 +164,24 @@ fn test_domain_redirect() {
         .return_status(200)
         .create_on(&server2);
 
-    let _goose_stats =
-        crate::GooseAttack::initialize_with_config(common::build_configuration(&server1))
-            .setup()
-            .unwrap()
-            .register_taskset(
-                taskset!("LoadTest")
-                    // First load redirect, takes this request only to another domain.
-                    .register_task(task!(get_domain_redirect).set_on_start())
-                    // Load index directly.
-                    .register_task(task!(get_index))
-                    // Load about directly, always on original domain.
-                    .register_task(task!(get_about)),
-            )
-            .execute()
-            .unwrap();
+    let _goose_stats = crate::GooseAttack::initialize_with_config(common::build_configuration(
+        &server1,
+        vec!["--no-metrics"],
+    ))
+    .unwrap()
+    .setup()
+    .unwrap()
+    .register_taskset(
+        taskset!("LoadTest")
+            // First load redirect, takes this request only to another domain.
+            .register_task(task!(get_domain_redirect).set_on_start())
+            // Load index directly.
+            .register_task(task!(get_index))
+            // Load about directly, always on original domain.
+            .register_task(task!(get_about)),
+    )
+    .execute()
+    .unwrap();
 
     // Confirm that we load the index, about and redirect pages on the orginal domain.
     assert!(server1_index.times_called() > 0);
@@ -224,9 +230,10 @@ fn test_sticky_domain_redirect() {
         .create_on(&server2);
 
     // Enable sticky_follow option.
-    let mut configuration = common::build_configuration(&server1);
-    configuration.sticky_follow = true;
+    let configuration =
+        common::build_configuration(&server1, vec!["--no-metrics", "--sticky-follow"]);
     let _goose_stats = crate::GooseAttack::initialize_with_config(configuration)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
diff --git a/tests/setup_teardown.rs b/tests/setup_teardown.rs
index 702fb1158..17531ffc9 100644
--- a/tests/setup_teardown.rs
+++ b/tests/setup_teardown.rs
@@ -47,16 +47,17 @@ fn test_start() {
         .return_status(200)
         .create_on(&server);
 
-    let _goose_stats =
-        crate::GooseAttack::initialize_with_config(common::build_configuration(&server))
-            .setup()
-            .unwrap()
-            .test_start(task!(setup))
-            .register_taskset(
-                taskset!("LoadTest").register_task(task!(get_index).set_weight(9).unwrap()),
-            )
-            .execute()
-            .unwrap();
+    let _goose_stats = crate::GooseAttack::initialize_with_config(common::build_configuration(
+        &server,
+        vec!["--no-metrics"],
+    ))
+    .unwrap()
+    .setup()
+    .unwrap()
+    .test_start(task!(setup))
+    .register_taskset(taskset!("LoadTest").register_task(task!(get_index).set_weight(9).unwrap()))
+    .execute()
+    .unwrap();
 
     // Confirm the load test ran.
     assert!(index.times_called() > 0);
@@ -89,16 +90,17 @@ fn test_stop() {
         .return_status(200)
         .create_on(&server);
 
-    let _goose_stats =
-        crate::GooseAttack::initialize_with_config(common::build_configuration(&server))
-            .setup()
-            .unwrap()
-            .test_stop(task!(teardown))
-            .register_taskset(
-                taskset!("LoadTest").register_task(task!(get_index).set_weight(9).unwrap()),
-            )
-            .execute()
-            .unwrap();
+    let _goose_stats = crate::GooseAttack::initialize_with_config(common::build_configuration(
+        &server,
+        vec!["--no-metrics"],
+    ))
+    .unwrap()
+    .setup()
+    .unwrap()
+    .test_stop(task!(teardown))
+    .register_taskset(taskset!("LoadTest").register_task(task!(get_index).set_weight(9).unwrap()))
+    .execute()
+    .unwrap();
 
     // Confirm the load test ran.
     assert!(index.times_called() > 0);
@@ -130,12 +132,14 @@ fn test_setup_teardown() {
         .return_status(200)
         .create_on(&server);
 
-    let mut configuration = common::build_configuration(&server);
     // Launch several user threads, confirm we still only setup and teardown one time.
-    configuration.users = Some(5);
-    configuration.hatch_rate = 5;
+    let configuration = common::build_configuration(
+        &server,
+        vec!["--no-metrics", "--users", "5", "--hatch-rate", "5"],
+    );
 
     let _goose_stats = crate::GooseAttack::initialize_with_config(configuration)
+        .unwrap()
         .setup()
         .unwrap()
         .test_start(task!(setup))
diff --git a/tests/throttle.rs b/tests/throttle.rs
index f7cf669d5..039d45cee 100644
--- a/tests/throttle.rs
+++ b/tests/throttle.rs
@@ -40,18 +40,26 @@ fn test_throttle() {
     let users = 5;
     let run_time = 3;
 
-    let mut config = common::build_configuration(&server);
-    // Record all requests so we can confirm throttle is working.
-    config.metrics_file = METRICS_FILE.to_string();
-    config.no_metrics = false;
-    // Enable the throttle.
-    config.throttle_requests = Some(throttle_requests);
-    config.users = Some(users);
-    // Start all users in half a second.
-    config.hatch_rate = users;
-    // Run for a few seconds to be sure throttle really works.
-    config.run_time = run_time.to_string();
+    let config = common::build_configuration(
+        &server,
+        vec![
+            // Record all requests so we can confirm throttle is working.
+            "--metrics-file",
+            METRICS_FILE,
+            // Enable the throttle.
+            "--throttle-requests",
+            &throttle_requests.to_string(),
+            "--users",
+            &users.to_string(),
+            "--hatch-rate",
+            &users.to_string(),
+            // Run for a few seconds to be sure throttle really works.
+            "--run-time",
+            &run_time.to_string(),
+        ],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(
@@ -83,17 +91,24 @@ fn test_throttle() {
     // and confirm the throttle is actually working.
     throttle_requests *= 5;
 
-    let mut config = common::build_configuration(&server);
-    // Record all requests so we can confirm throttle is working.
-    config.metrics_file = METRICS_FILE.to_string();
-    config.no_metrics = false;
-    // Enable the throttle.
-    config.throttle_requests = Some(throttle_requests);
-    config.users = Some(users);
-    // Start all users in half a second.
-    config.hatch_rate = users;
-    config.run_time = run_time.to_string();
+    let config = common::build_configuration(
+        &server,
+        vec![
+            // Record all requests so we can confirm throttle is working.
+            "--metrics-file",
+            METRICS_FILE,
+            "--throttle-requests",
+            &throttle_requests.to_string(),
+            "--users",
+            &users.to_string(),
+            "--hatch-rate",
+            &users.to_string(),
+            "--run-time",
+            &run_time.to_string(),
+        ],
+    );
     let _goose_metrics = crate::GooseAttack::initialize_with_config(config)
+        .unwrap()
         .setup()
         .unwrap()
         .register_taskset(