goose 0.7.4

A load testing tool inspired by Locust.
Documentation
//! Helpers and objects for building Goose load tests.
//!
//! Goose manages load tests with a series of objects:
//!
//! - [`GooseTaskSet`](./struct.GooseTaskSet.html) each client is assigned a task set, which is a collection of tasks.
//! - [`GooseTask`](./struct.GooseTask.html) tasks define one or more web requests and are assigned to task sets.
//! - [`GooseClient`](./struct.GooseClient.html) a client state responsible for repeatedly running all tasks in the assigned task set.
//! - [`GooseRequest`](./struct.GooseRequest.html) optional statistics collected for each URL/method pair.
//!
//! ## Creating Task Sets
//!
//! A [`GooseTaskSet`](./struct.GooseTaskSet.html) is created by passing in a `&str` name to the `new` function, for example:
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut loadtest_tasks = taskset!("LoadtestTasks");
//! ```
//!
//! ### Task Set Weight
//!
//! A weight can be assigned to a task set, controlling how often it is assigned to client
//! threads. The larger the value of weight, the more it will be assigned to clients. In the
//! following example, `FooTasks` will be assigned to clients twice as often as `Bar` tasks.
//! We could have just added a weight of `2` to `FooTasks` and left the default weight of `1`
//! assigned to `BarTasks` for the same weighting:
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut foo_tasks = taskset!("FooTasks").set_weight(10);
//!     let mut bar_tasks = taskset!("BarTasks").set_weight(5);
//! ```
//!
//! ### Task Set Host
//!
//! A default host can be assigned to a task set, which will be used only if the `--host`
//! CLI option is not set at run-time. For example, this can configure your load test to
//! run against your local development environment by default, allowing the `--host` option
//! to override host when you want to load test production. You can also assign different
//! hosts to different task sets if this is desirable:
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut foo_tasks = taskset!("FooTasks").set_host("http://www.local");
//!     let mut bar_tasks = taskset!("BarTasks").set_host("http://www2.local");
//! ```
//!
//! ### Task Set Wait Time
//!
//! Wait time is specified as a low-high integer range. Each time a task completes in
//! the task set, the client will pause for a random number of seconds inclusively between
//! the low and high wait times. In the following example, Clients loading `foo` tasks will
//! sleep 0 to 3 seconds after each task completes, and Clients loading `bar` tasks will
//! sleep 5 to 10 seconds after each task completes.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut foo_tasks = taskset!("FooTasks").set_wait_time(0, 3);
//!     let mut bar_tasks = taskset!("BarTasks").set_wait_time(5, 10);
//! ```
//! ## Creating Tasks
//!
//! A [`GooseTask`](./struct.GooseTask.html) must include a pointer to a function which
//! will be executed each time the task is run.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut a_task = task!(task_function);
//!
//!     /// A very simple task that simply loads the front page.
//!     async fn task_function(client: &GooseClient) {
//!       let _response = client.get("/");
//!     }
//! ```
//!
//! ### Task Name
//!
//! A name can be assigned to a task, and will be displayed in statistics about all requests
//! made by the task.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut a_task = task!(task_function).set_name("a");
//!
//!     /// A very simple task that simply loads the front page.
//!     async fn task_function(client: &GooseClient) {
//!       let _response = client.get("/");
//!     }
//! ```
//!
//! ### Task Weight
//!
//! Individual tasks can be assigned a weight, controlling how often the task runs. The
//! larger the value of weight, the more it will run. In the following example, `a_task`
//! runs 3 times as often as `b_task`:
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut a_task = task!(a_task_function).set_weight(9);
//!     let mut b_task = task!(b_task_function).set_weight(3);
//!
//!     /// A very simple task that simply loads the "a" page.
//!     async fn a_task_function(client: &GooseClient) {
//!       let _response = client.get("/a/");
//!     }
//!
//!     /// Another very simple task that simply loads the "b" page.
//!     async fn b_task_function(client: &GooseClient) {
//!       let _response = client.get("/b/");
//!     }
//! ```
//!
//! ### Task Sequence
//!
//! Tasks can also be configured to run in a sequence. For example, a task with a sequence
//! value of `1` will always run before a task with a sequence value of `2`. Weight can
//! be applied to sequenced tasks, so for example a task with a weight of `2` and a sequence
//! of `1` will run two times before a task with a sequence of `2`. Task sets can contain
//! tasks with sequence values and without sequence values, and in this case all tasks with
//! a sequence value will run before tasks without a sequence value. In the folllowing example,
//! `a_task` runs before `b_task`, which runs before `c_task`:
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut a_task = task!(a_task_function).set_sequence(1);
//!     let mut b_task = task!(b_task_function).set_sequence(2);
//!     let mut c_task = task!(c_task_function);
//!
//!     /// A very simple task that simply loads the "a" page.
//!     async fn a_task_function(client: &GooseClient) {
//!       let _response = client.get("/a/");
//!     }
//!
//!     /// Another very simple task that simply loads the "b" page.
//!     async fn b_task_function(client: &GooseClient) {
//!       let _response = client.get("/b/");
//!     }
//!
//!     /// Another very simple task that simply loads the "c" page.
//!     async fn c_task_function(client: &GooseClient) {
//!       let _response = client.get("/c/");
//!     }
//! ```
//!
//! ### Task On Start
//!
//! Tasks can be flagged to only run when a client first starts. This can be useful if you'd
//! like your load test to use a logged-in user. It is possible to assign sequences and weights
//! to `on_start` functions if you want to have multiple tasks run in a specific order at start
//! time, and/or the tasks to run multiple times. A task can be flagged to run both on start
//! and on stop.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut a_task = task!(a_task_function).set_sequence(1).set_on_start();
//!
//!     /// A very simple task that simply loads the "a" page.
//!     async fn a_task_function(client: &GooseClient) {
//!       let _response = client.get("/a/");
//!     }
//! ```
//!
//! ### Task On Stop
//!
//! Tasks can be flagged to only run when a client stops. This can be useful if you'd like your
//! load test to simluate a user logging out when it finishes. It is possible to assign sequences
//! and weights to `on_stop` functions if you want to have multiple tasks run in a specific order
//! at stop time, and/or the tasks to run multiple times. A task can be flagged to run both on
//! start and on stop.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut b_task = task!(b_task_function).set_sequence(2).set_on_stop();
//!
//!     /// Another very simple task that simply loads the "b" page.
//!     async fn b_task_function(client: &GooseClient) {
//!       let _response = client.get("/b/");
//!     }
//! ```
//!
//! ## Controlling Clients
//!
//! When Goose starts, it creates one or more [`GooseClient`](./struct.GooseClient.html)s,
//! assigning a single [`GooseTaskSet`](./struct.GooseTaskSet.html) to each. This client is
//! then used to generate load. Behind the scenes, Goose is leveraging the
//! [`reqwest::blocking::client`](https://docs.rs/reqwest/*/reqwest/blocking/struct.Client.html)
//! to load web pages, and Goose can therefor do anything Reqwest can do.
//!
//! The most common request types are [`GET`](./struct.GooseClient.html#method.get) and
//! [`POST`](./struct.GooseClient.html#method.post), but [`HEAD`](./struct.GooseClient.html#method.head),
//! PUT, PATCH and [`DELETE`](./struct.GooseClient.html#method.delete) are also supported.
//!
//! ### GET
//!
//! A helper to make a `GET` request of a path and collect relevant statistics.
//! Automatically prepends the correct host.
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut task = task!(get_function);
//!
//!     /// A very simple task that makes a GET request.
//!     async  fn get_function(client: &GooseClient) {
//!       let _response = client.get("/path/to/foo/");
//!     }
//! ```
//!
//! The returned response is a [`reqwest::blocking::Response`](https://docs.rs/reqwest/*/reqwest/blocking/struct.Response.html)
//! struct. You can use it as you would any Reqwest Response.
//!
//!
//! ### POST
//!
//! A helper to make a `POST` request of a string value to the path and collect relevant
//! statistics. Automatically prepends the correct host. The returned response is a
//! [`reqwest::blocking::Response`](https://docs.rs/reqwest/*/reqwest/blocking/struct.Response.html)
//!
//! ```rust
//!     use goose::prelude::*;
//!
//!     let mut task = task!(post_function);
//!
//!     /// A very simple task that makes a POST request.
//!     async fn post_function(client: &GooseClient) {
//!       let _response = client.post("/path/to/foo/", "string value to post");
//!     }
//! ```
//!
//! ## License
//!
//! Copyright 2020 Jeremy Andrews
//!
//! Licensed under the Apache License, Version 2.0 (the "License");
//! you may not use this file except in compliance with the License.
//! You may obtain a copy of the License at
//!
//! http://www.apache.org/licenses/LICENSE-2.0
//!
//! Unless required by applicable law or agreed to in writing, software
//! distributed under the License is distributed on an "AS IS" BASIS,
//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//! See the License for the specific language governing permissions and
//! limitations under the License.

use http::method::Method;
use http::StatusCode;
use reqwest::Error;
use reqwest::{RequestBuilder, Response};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::hash::{Hash, Hasher};
use std::{future::Future, pin::Pin, time::Instant};
use tokio::sync::mpsc;
use url::Url;

use crate::{GooseConfiguration, CLIENT};

/// task!(foo) expands to GooseTask::new(foo), but also does some boxing to work around a limitation in the compiler.
#[macro_export]
macro_rules! task {
    ($task_func:ident) => {
        GooseTask::new(move |s| std::boxed::Box::pin($task_func(s)))
    };
}

/// taskset!("foo") expands to GooseTaskSet::new("foo").
#[macro_export]
macro_rules! taskset {
    ($name:tt) => {
        GooseTaskSet::new($name)
    };
}

/// An individual task set.
#[derive(Clone, Hash)]
pub struct GooseTaskSet {
    /// The name of the task set.
    pub name: String,
    /// An integer reflecting where this task set lives in the internal `GooseTest.task_sets` vector.
    pub task_sets_index: usize,
    /// An integer value that controls the frequency that this task set will be assigned to a client.
    pub weight: usize,
    /// An integer value indicating the minimum number of seconds a client will sleep after running a task.
    pub min_wait: usize,
    /// An integer value indicating the maximum number of seconds a client will sleep after running a task.
    pub max_wait: usize,
    /// A vector containing one copy of each GooseTask that will run by clients running this task set.
    pub tasks: Vec<GooseTask>,
    /// A vector of vectors of integers, controlling the sequence and order GooseTasks are run.
    pub weighted_tasks: Vec<Vec<usize>>,
    /// A vector of vectors of integers, controlling the sequence and order on_start GooseTasks are run when the client first starts.
    pub weighted_on_start_tasks: Vec<Vec<usize>>,
    /// A vector of vectors of integers, controlling the sequence and order on_stop GooseTasks are run when a client stops.
    pub weighted_on_stop_tasks: Vec<Vec<usize>>,
    /// An optional default host to run this TaskSet against.
    pub host: Option<String>,
}
impl GooseTaskSet {
    /// Creates a new GooseTaskSet. Once created, GooseTasks must be assigned to it, and finally it must be
    /// registered with the GooseAttack object. The returned object must be stored in a mutable value.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut example_tasks = taskset!("ExampleTasks");
    /// ```
    pub fn new(name: &str) -> Self {
        trace!("new taskset: name: {}", &name);
        GooseTaskSet {
            name: name.to_string(),
            task_sets_index: usize::max_value(),
            weight: 1,
            min_wait: 0,
            max_wait: 0,
            tasks: Vec::new(),
            weighted_tasks: Vec::new(),
            weighted_on_start_tasks: Vec::new(),
            weighted_on_stop_tasks: Vec::new(),
            host: None,
        }
    }

    /// Registers a GooseTask with a GooseTaskSet, where it is stored in the GooseTaskSet.tasks vector. The
    /// function associated with the task will be run during the load test.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut example_tasks = taskset!("ExampleTasks");
    ///     example_tasks.register_task(task!(a_task_function));
    ///
    ///     /// A very simple task that simply loads the "a" page.
    ///     async fn a_task_function(client: &GooseClient) {
    ///       let _response = client.get("/a/");
    ///     }
    /// ```
    pub fn register_task(mut self, mut task: GooseTask) -> Self {
        trace!("{} register_task: {}", self.name, task.name);
        task.tasks_index = self.tasks.len();
        self.tasks.push(task);
        self
    }

    /// Sets a weight on a task set. The larger the value of weight, the more often the task set will
    /// be assigned to clients. For example, if you have task set foo with a weight of 3, and task set
    /// bar with a weight of 1, and you spin up a load test with 8 clients, 6 of them will be running
    /// the foo task set, and 2 will be running the bar task set.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut example_tasks = taskset!("ExampleTasks").set_weight(3);
    /// ```
    pub fn set_weight(mut self, weight: usize) -> Self {
        trace!("{} set_weight: {}", self.name, weight);
        if weight < 1 {
            error!("{} weight of {} not allowed", self.name, weight);
            std::process::exit(1);
        } else {
            self.weight = weight;
        }
        self
    }

    /// Set a default host for the task set. If no `--host` flag is set when running the load test, this
    /// host will be pre-pended on all requests. For example, this can configure your load test to run
    /// against your local development environment by default, and the `--host` option could be used to
    /// override host when running the load test against production.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut example_tasks = taskset!("ExampleTasks").set_host("http://10.1.1.42");
    /// ```
    pub fn set_host(mut self, host: &str) -> Self {
        trace!("{} set_host: {}", self.name, host);
        // Host validation happens in main() at startup.
        self.host = Some(host.to_string());
        self
    }

    /// Configure a task_set to to pause after running each task. The length of the pause will be randomly
    /// selected from `min_weight` to `max_wait` inclusively.  For example, if `min_wait` is `0` and
    /// `max_weight` is `2`, the client will randomly sleep for 0, 1 or 2 seconds after each task completes.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut example_tasks = taskset!("ExampleTasks").set_wait_time(0, 1);
    /// ```
    pub fn set_wait_time(mut self, min_wait: usize, max_wait: usize) -> Self {
        trace!(
            "{} set_wait time: min: {} max: {}",
            self.name,
            min_wait,
            max_wait
        );
        if min_wait > max_wait {
            error!(
                "min_wait({}) can't be larger than max_weight({})",
                min_wait, max_wait
            );
            std::process::exit(1);
        }
        self.min_wait = min_wait;
        self.max_wait = max_wait;
        self
    }
}

/// Commands sent between the parent and client threads, and between manager and
/// worker processes.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GooseClientCommand {
    /// Tell worker process to pause load test.
    WAIT,
    /// Tell worker process to start load test.
    RUN,
    /// Tell client thread to exit.
    EXIT,
}

/// Supported HTTP methods.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum GooseMethod {
    DELETE,
    GET,
    HEAD,
    PATCH,
    POST,
    PUT,
}

fn goose_method_from_method(method: Method) -> GooseMethod {
    match method {
        Method::DELETE => GooseMethod::DELETE,
        Method::GET => GooseMethod::GET,
        Method::HEAD => GooseMethod::HEAD,
        Method::PATCH => GooseMethod::PATCH,
        Method::POST => GooseMethod::POST,
        Method::PUT => GooseMethod::PUT,
        _ => {
            error!("unsupported method: {}", method);
            std::process::exit(1);
        }
    }
}

/// The request that Goose is making. Client threads send this data to the parent thread
/// when statistics are enabled. This request object must be provided to calls to
/// [`set_success`](https://docs.rs/goose/*/goose/goose/struct.GooseClient.html#method.set_success)
/// or
/// [`set_failure`](https://docs.rs/goose/*/goose/goose/struct.GooseClient.html#method.set_failure)
/// so Goose knows which request is being updated.
#[derive(Debug, Clone)]
pub struct GooseRawRequest {
    /// The method being used (ie, GET, POST, etc).
    pub method: GooseMethod,
    /// The optional name of the request.
    pub name: String,
    /// How many milliseconds the request took.
    pub response_time: u128,
    /// The HTTP response code (optional).
    pub status_code: Option<StatusCode>,
    /// Whether or not the request was successful.
    pub success: bool,
    /// Whether or not we're updating a previous request, modifies how the parent thread records it.
    pub update: bool,
}
impl GooseRawRequest {
    pub fn new(method: GooseMethod, name: &str) -> Self {
        let name_string = name.to_string();
        GooseRawRequest {
            method,
            name: name_string,
            response_time: 0,
            status_code: None,
            success: true,
            update: false,
        }
    }

    fn set_response_time(&mut self, response_time: u128) {
        self.response_time = response_time;
    }

    fn set_status_code(&mut self, status_code: Option<StatusCode>) {
        self.status_code = status_code;
    }
}

/// Statistics collected about a path-method pair, (for example `/index`-`GET`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GooseRequest {
    /// The path for which statistics are being collected.
    pub path: String,
    /// The method for which statistics are being collected.
    pub method: GooseMethod,
    /// Per-response-time counters, tracking how often pages are returned with this response time.
    pub response_times: BTreeMap<usize, usize>,
    /// The shortest response time seen so far.
    pub min_response_time: usize,
    /// The longest response time seen so far.
    pub max_response_time: usize,
    /// Total combined response times seen so far.
    pub total_response_time: usize,
    /// Total number of response times seen so far.
    pub response_time_counter: usize,
    /// Per-status-code counters, tracking how often each response code was returned for this request.
    pub status_code_counts: HashMap<u16, usize>,
    /// Total number of times this path-method request resulted in a successful (2xx) status code.
    pub success_count: usize,
    /// Total number of times this path-method request resulted in a non-successful (non-2xx) status code.
    pub fail_count: usize,
    /// Load test hash.
    pub load_test_hash: u64,
}
impl GooseRequest {
    /// Create a new GooseRequest object.
    pub fn new(path: &str, method: GooseMethod, load_test_hash: u64) -> Self {
        trace!("new request");
        GooseRequest {
            path: path.to_string(),
            method,
            response_times: BTreeMap::new(),
            min_response_time: 0,
            max_response_time: 0,
            total_response_time: 0,
            response_time_counter: 0,
            status_code_counts: HashMap::new(),
            success_count: 0,
            fail_count: 0,
            load_test_hash,
        }
    }

    /// Track response time.
    pub fn set_response_time(&mut self, response_time: u128) {
        // Perform this conversin only once, then re-use throughout this funciton.
        let response_time_usize = response_time as usize;

        // Update minimum if this one is fastest yet.
        if self.min_response_time == 0 || response_time_usize < self.min_response_time {
            self.min_response_time = response_time_usize;
        }

        // Update maximum if this one is slowest yet.
        if response_time_usize > self.max_response_time {
            self.max_response_time = response_time_usize;
        }

        // Update total_response time, adding in this one.
        self.total_response_time += response_time_usize;

        // Each time we store a new response time, increment counter by one.
        self.response_time_counter += 1;

        // Round the response time so we can combine similar times together and
        // minimize required memory to store and push upstream to the parent.
        let rounded_response_time: usize;

        // No rounding for 1-100ms response times.
        if response_time < 100 {
            rounded_response_time = response_time_usize;
        }
        // Round to nearest 10 for 100-500ms response times.
        else if response_time < 500 {
            rounded_response_time = ((response_time as f64 / 10.0).round() * 10.0) as usize;
        }
        // Round to nearest 100 for 500-1000ms response times.
        else if response_time < 1000 {
            rounded_response_time = ((response_time as f64 / 100.0).round() * 100.0) as usize;
        }
        // Round to nearest 1000 for all larger response times.
        else {
            rounded_response_time = ((response_time as f64 / 1000.0).round() * 1000.0) as usize;
        }

        let counter = match self.response_times.get(&rounded_response_time) {
            // We've seen this response_time before, increment counter.
            Some(c) => {
                debug!("got {:?} counter: {}", rounded_response_time, c);
                *c + 1
            }
            // First time we've seen this response time, initialize counter.
            None => {
                debug!("no match for counter: {}", rounded_response_time);
                1
            }
        };
        self.response_times.insert(rounded_response_time, counter);
        debug!("incremented {} counter: {}", rounded_response_time, counter);
    }

    /// Increment counter for status code, creating new counter if first time seeing status code.
    pub fn set_status_code(&mut self, status_code: Option<StatusCode>) {
        let status_code_u16 = match status_code {
            Some(s) => s.as_u16(),
            _ => 0,
        };
        let counter = match self.status_code_counts.get(&status_code_u16) {
            // We've seen this status code before, increment counter.
            Some(c) => {
                debug!("got {:?} counter: {}", status_code, c);
                *c + 1
            }
            // First time we've seen this status code, initialize counter.
            None => {
                debug!("no match for counter: {}", status_code_u16);
                1
            }
        };
        self.status_code_counts.insert(status_code_u16, counter);
        debug!("incremented {} counter: {}", status_code_u16, counter);
    }
}

/// The response to a GooseRequest
#[derive(Debug)]
pub struct GooseResponse {
    pub request: GooseRawRequest,
    pub response: Result<Response, Error>,
}
impl GooseResponse {
    pub fn new(request: GooseRawRequest, response: Result<Response, Error>) -> Self {
        GooseResponse { request, response }
    }
}

/// An individual client state, repeatedly running all GooseTasks in a specific GooseTaskSet.
#[derive(Debug, Clone)]
pub struct GooseClient {
    /// An index into the internal `GooseTest.task_sets` vector, indicating which GooseTaskSet is running.
    pub task_sets_index: usize,
    /// Channel
    pub parent: Option<mpsc::UnboundedSender<GooseRawRequest>>,
    /// Optional global host, can be overridden per-task-set or via the cli.
    pub default_host: Option<String>,
    /// Optional per-task-set .host.
    pub task_set_host: Option<String>,
    /// Minimum amount of time to sleep after running a task.
    pub min_wait: usize,
    /// Maximum amount of time to sleep after running a task.
    pub max_wait: usize,
    /// A local copy of the global GooseConfiguration.
    pub config: GooseConfiguration,
    /// An index into the internal `GooseTest.weighted_clients, indicating which weighted GooseTaskSet is running.
    pub weighted_clients_index: usize,
    /// A weighted list of all tasks that run when the client first starts.
    pub weighted_on_start_tasks: Vec<Vec<usize>>,
    /// A weighted list of all tasks that this client runs once started.
    pub weighted_tasks: Vec<Vec<usize>>,
    /// A weighted list of all tasks that run when the client stops.
    pub weighted_on_stop_tasks: Vec<Vec<usize>>,
    /// Optional name of all requests made within the current task.
    pub task_request_name: Option<String>,
    /// Optional name of all requests made within the current task.
    pub request_name: Option<String>,
    /// Load test hash.
    pub load_test_hash: u64,
}
impl GooseClient {
    /// Create a new client state.
    pub fn new(
        task_sets_index: usize,
        default_host: Option<String>,
        task_set_host: Option<String>,
        min_wait: usize,
        max_wait: usize,
        configuration: &GooseConfiguration,
        load_test_hash: u64,
    ) -> Self {
        trace!("new client");
        GooseClient {
            task_sets_index,
            default_host,
            task_set_host,
            parent: None,
            config: configuration.clone(),
            min_wait,
            max_wait,
            // A value of max_value() indicates this client isn't fully initialized yet.
            weighted_clients_index: usize::max_value(),
            weighted_on_start_tasks: Vec::new(),
            weighted_tasks: Vec::new(),
            weighted_on_stop_tasks: Vec::new(),
            task_request_name: None,
            request_name: None,
            load_test_hash,
        }
    }

    /// A helper that pre-pends a hostname to the path. For example, if you pass in `/foo`
    /// and `--host` is set to `http://127.0.0.1` it will return `http://127.0.0.1/foo`.
    /// Respects per-`GooseTaskSet` `host` configuration, global `GooseAttack` `host`
    /// configuration, and `--host` CLI configuration option.
    ///
    /// If `path` is passed in with a hard-coded host, this will be used instead.
    ///
    /// Host is defined in the following order:
    ///  - If `path` includes the host, use this
    ///  - Otherwise, if `--host` is defined, use this
    ///  - Otherwise, if `GooseTaskSet.host` is defined, use this
    ///  - Otherwise, use global `GooseAttack.host`.
    pub fn build_url(&self, path: &str) -> String {
        // If URL includes a host, use it.
        if let Ok(parsed_path) = Url::parse(path) {
            if let Some(_uri) = parsed_path.host() {
                return path.to_string();
            }
        }

        let base_url = if !self.config.host.is_empty() {
            // If the `--host` CLI option is set, use it to build the URL
            Url::parse(&self.config.host).unwrap()
        } else {
            match &self.task_set_host {
                // Otherwise, if `GooseTaskSet.host` is defined, usee this
                Some(host) => Url::parse(host).unwrap(),
                // Otherwise, use global `GooseAttack.host`. `unwrap` okay as host validation was done at startup.
                None => Url::parse(&self.default_host.clone().unwrap()).unwrap(),
            }
        };
        match base_url.join(path) {
            Ok(url) => url.to_string(),
            Err(e) => {
                error!(
                    "failed to build url from base {} and path {} for task {}: {}",
                    &base_url, &path, self.task_sets_index, e
                );
                std::process::exit(1);
            }
        }
    }

    /// A helper to make a `GET` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host.
    ///
    /// (If you need to set headers, change timeouts, or otherwise make use of the
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object, you can instead call `goose_get` which returns a RequestBuilder, then
    /// call `goose_send` to invoke the request.)
    ///
    /// Calls to `client.get` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(get_function);
    ///
    ///     /// A very simple task that makes a GET request.
    ///     async fn get_function(client: &GooseClient) {
    ///       let _response = client.get("/path/to/foo/");
    ///     }
    /// ```
    pub async fn get(&self, path: &str) -> GooseResponse {
        let request_builder = self.goose_get(path).await;
        self.goose_send(request_builder, None).await
    }

    /// A helper to make a named `GET` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host. Naming a request only affects collected
    /// statistics.
    ///
    /// Calls to `client.get_named` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(get_function);
    ///
    ///     /// A very simple task that makes a GET request.
    ///     async fn get_function(client: &GooseClient) {
    ///       let _response = client.get_named("/path/to/foo/", "foo");
    ///     }
    /// ```
    pub async fn get_named(&self, path: &str, request_name: &str) -> GooseResponse {
        let request_builder = self.goose_get(path).await;
        self.goose_send(request_builder, Some(request_name)).await
    }

    /// A helper to make a `POST` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host.
    ///
    /// (If you need to set headers, change timeouts, or otherwise make use of the
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object, you can instead call `goose_post` which returns a RequestBuilder, then
    /// call `goose_send` to invoke the request.)
    ///
    /// Calls to `client.post` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(post_function);
    ///
    ///     /// A very simple task that makes a POST request.
    ///     async fn post_function(client: &GooseClient) {
    ///       let _response = client.post("/path/to/foo/", "BODY BEING POSTED");
    ///     }
    /// ```
    pub async fn post(&self, path: &str, body: &str) -> GooseResponse {
        let request_builder = self.goose_post(path).await.body(body.to_string());
        self.goose_send(request_builder, None).await
    }

    /// A helper to make a named `POST` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host. Naming a request only affects collected
    /// statistics.
    ///
    /// Calls to `client.post` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(post_function);
    ///
    ///     /// A very simple task that makes a POST request.
    ///     async fn post_function(client: &GooseClient) {
    ///       let _response = client.post_named("/path/to/foo/", "foo", "BODY BEING POSTED");
    ///     }
    /// ```
    pub async fn post_named(&self, path: &str, request_name: &str, body: &str) -> GooseResponse {
        let request_builder = self.goose_post(path).await.body(body.to_string());
        self.goose_send(request_builder, Some(request_name)).await
    }

    /// A helper to make a `HEAD` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host.
    ///
    /// (If you need to set headers, change timeouts, or otherwise make use of the
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object, you can instead call `goose_head` which returns a RequestBuilder, then
    /// call `goose_send` to invoke the request.)
    ///
    /// Calls to `client.head` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(head_function);
    ///
    ///     /// A very simple task that makes a HEAD request.
    ///     async fn head_function(client: &GooseClient) {
    ///       let _response = client.head("/path/to/foo/");
    ///     }
    /// ```
    pub async fn head(&self, path: &str) -> GooseResponse {
        let request_builder = self.goose_head(path).await;
        self.goose_send(request_builder, None).await
    }

    /// A helper to make a named `HEAD` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host. Naming a request only affects collected
    /// statistics.
    ///
    /// Calls to `client.head` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(head_function);
    ///
    ///     /// A very simple task that makes a HEAD request.
    ///     async fn head_function(client: &GooseClient) {
    ///       let _response = client.head_named("/path/to/foo/", "foo");
    ///     }
    /// ```
    pub async fn head_named(&self, path: &str, request_name: &str) -> GooseResponse {
        let request_builder = self.goose_head(path).await;
        self.goose_send(request_builder, Some(request_name)).await
    }

    /// A helper to make a `DELETE` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host.
    ///
    /// (If you need to set headers, change timeouts, or otherwise make use of the
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object, you can instead call `goose_delete` which returns a RequestBuilder,
    /// then call `goose_send` to invoke the request.)
    ///
    /// Calls to `client.delete` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(delete_function);
    ///
    ///     /// A very simple task that makes a DELETE request.
    ///     async fn delete_function(client: &GooseClient) {
    ///       let _response = client.delete("/path/to/foo/");
    ///     }
    /// ```
    pub async fn delete(&self, path: &str) -> GooseResponse {
        let request_builder = self.goose_delete(path).await;
        self.goose_send(request_builder, None).await
    }

    /// A helper to make a named `DELETE` request of a path and collect relevant statistics.
    /// Automatically prepends the correct host. Naming a request only affects collected
    /// statistics.
    ///
    /// Calls to `client.delete` return a `GooseResponse` object which contains a copy of
    /// the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(delete_function);
    ///
    ///     /// A very simple task that makes a DELETE request.
    ///     async fn delete_function(client: &GooseClient) {
    ///       let _response = client.delete_named("/path/to/foo/", "foo");
    ///     }
    /// ```
    pub async fn delete_named(&self, path: &str, request_name: &str) -> GooseResponse {
        let request_builder = self.goose_delete(path).await;
        self.goose_send(request_builder, Some(request_name)).await
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `GET` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(get_function);
    ///
    ///     /// A simple task that makes a GET request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn get_function(client: &GooseClient) {
    ///       let request_builder = client.goose_get("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_get(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .get(&url)
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `POST` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(post_function);
    ///
    ///     /// A simple task that makes a POST request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn post_function(client: &GooseClient) {
    ///       let request_builder = client.goose_post("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_post(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .post(&url)
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `HEAD` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(head_function);
    ///
    ///     /// A simple task that makes a HEAD request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn head_function(client: &GooseClient) {
    ///       let request_builder = client.goose_head("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_head(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .head(&url)
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `PUT` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(put_function);
    ///
    ///     /// A simple task that makes a PUT request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn put_function(client: &GooseClient) {
    ///       let request_builder = client.goose_put("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_put(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .put(&url)
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `PATCH` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(patch_function);
    ///
    ///     /// A simple task that makes a PUT request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn patch_function(client: &GooseClient) {
    ///       let request_builder = client.goose_patch("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_patch(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .patch(&url)
    }

    /// Prepends the correct host on the path, then prepares a
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object for making a `DELETE` request.
    ///
    /// (You must then call `goose_send` on this object to actually execute the request.)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(delete_function);
    ///
    ///     /// A simple task that makes a DELETE request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn delete_function(client: &GooseClient) {
    ///       let request_builder = client.goose_delete("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_delete(&self, path: &str) -> RequestBuilder {
        let url = self.build_url(path);
        CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .delete(&url)
    }

    /// Builds the provided
    /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html)
    /// object and then executes the response. If statistics are being displayed, it
    /// also captures request statistics.
    ///
    /// It is possible to build and execute a `RequestBuilder` object directly with
    /// Reqwest without using this helper function, but then Goose is unable to capture
    /// statistics.
    ///
    /// Calls to `client.goose_send` return a `GooseResponse` object which contains a
    /// copy of the request you made
    /// ([`response.request`](/goose/*/goose/struct.GooseRawRequest)), and the response
    /// ([`response.response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)).
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(get_function);
    ///
    ///     /// A simple task that makes a GET request, exposing the Reqwest
    ///     /// request builder.
    ///     async fn get_function(client: &GooseClient) {
    ///       let request_builder = client.goose_get("/path/to/foo").await;
    ///       let response = client.goose_send(request_builder, None).await;
    ///     }
    /// ```
    pub async fn goose_send(
        &self,
        request_builder: RequestBuilder,
        request_name: Option<&str>,
    ) -> GooseResponse {
        let started = Instant::now();
        let request = match request_builder.build() {
            Ok(r) => r,
            Err(e) => {
                error!("goose_send failed to build request: {}", e);
                std::process::exit(1);
            }
        };

        // String version of request path.
        let path = match Url::parse(&request.url().to_string()) {
            Ok(u) => u.path().to_string(),
            Err(e) => {
                error!("failed to parse url: {}", e);
                "".to_string()
            }
        };
        let method = goose_method_from_method(request.method().clone());
        let request_name = self.get_request_name(&path, request_name);
        let mut raw_request = GooseRawRequest::new(method, &request_name);

        // Make the actual request.
        let response = CLIENT.read().await[self.weighted_clients_index]
            .client
            .lock()
            .await
            .execute(request)
            .await;
        let elapsed = started.elapsed();

        // Create a raw request object if we're tracking statistics.
        if !self.config.no_stats {
            raw_request.set_response_time(elapsed.as_millis());
            match &response {
                Ok(r) => {
                    let status_code = r.status();
                    debug!("{:?}: status_code {}", &path, status_code);
                    // @TODO: match/handle all is_foo() https://docs.rs/http/0.2.1/http/status/struct.StatusCode.html
                    if !status_code.is_success() {
                        raw_request.success = false;
                    }
                    raw_request.set_status_code(Some(status_code));
                }
                Err(e) => {
                    // @TODO: what can we learn from a reqwest error?
                    warn!("{:?}: {}", &path, e);
                    raw_request.success = false;
                    raw_request.set_status_code(None);
                }
            };

            self.send_to_parent(&raw_request);
        }

        GooseResponse::new(raw_request, response)
    }

    fn send_to_parent(&self, raw_request: &GooseRawRequest) {
        match self.parent.clone() {
            Some(p) => {
                let parent = p;
                match parent.send(raw_request.clone()) {
                    Ok(_) => (),
                    Err(e) => {
                        info!("unable to communicate with parent thread, exiting: {}", e);
                        std::process::exit(1);
                    }
                }
            }
            // Parent is not defined when running test_start_task, test_stop_task,
            // and during testing.
            None => (),
        }
    }

    /// If `request_name` is set, unwrap and use this. Otherwise, if `task_request_name`
    /// is set, use this. Otherwise, use path.
    fn get_request_name(&self, path: &str, request_name: Option<&str>) -> String {
        match request_name {
            Some(rn) => rn.to_string(),
            None => match &self.task_request_name {
                Some(trn) => trn.to_string(),
                None => path.to_string(),
            },
        }
    }

    /// Manually mark a request as a success.
    ///
    /// By default, Goose will consider any response with a 2xx status code as a success.
    /// It may be valid in your test for a non-2xx HTTP status code to be returned. A copy
    /// of your original request is returned with the response, and a mutable copy must be
    /// included when setting a request as a success.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(get_function);
    ///
    ///     /// A simple task that makes a GET request.
    ///     async fn get_function(client: &GooseClient) {
    ///         let mut response = client.get("/404").await;
    ///         match &response.response {
    ///             Ok(r) => {
    ///                 // We expect a 404 here.
    ///                 if r.status() == 404 {
    ///                     client.set_success(&mut response.request);
    ///                 }
    ///             },
    ///             Err(_) => (),
    ///         }
    ///     }
    /// ````
    pub fn set_success(&self, request: &mut GooseRawRequest) {
        // Only send update if this was previously not a success.
        if !request.success {
            request.success = true;
            request.update = true;
            self.send_to_parent(&request);
        }
    }

    /// Manually mark a request as a failure.
    ///
    /// By default, Goose will consider any response with a 2xx status code as a success.
    /// You may require more advanced logic, in which a 2xx status code is actually a
    /// failure. A copy of your original request is returned with the response, and a
    /// mutable copy must be included when setting a request as a failure.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let mut task = task!(loadtest_index_page);
    ///
    ///     async fn loadtest_index_page(client: &GooseClient) {
    ///         let mut response = client.get_named("/", "index").await;
    ///         // Extract the response Result.
    ///         match response.response {
    ///             Ok(r) => {
    ///                 // We only need to check pages that returned a success status code.
    ///                 if r.status().is_success() {
    ///                     match r.text().await {
    ///                         Ok(text) => {
    ///                             // If the expected string doesn't exist, this page load
    ///                             // was a failure.
    ///                             if !text.contains("this string must exist") {
    ///                                 // As this is a named request, pass in the name not the URL
    ///                                 client.set_failure(&mut response.request);
    ///                             }
    ///                         }
    ///                         // Empty page, this is a failure.
    ///                         Err(_) => client.set_failure(&mut response.request),
    ///                     }
    ///                 }
    ///             },
    ///             // Invalid response, this is already a failure.
    ///             Err(_) => (),
    ///         }
    ///     }
    /// ````
    pub fn set_failure(&self, request: &mut GooseRawRequest) {
        // Only send update if this was previously a success.
        if request.success {
            request.success = false;
            request.update = true;
            self.send_to_parent(&request);
        }
    }
}

/// An individual task within a `GooseTaskSet`.
#[derive(Clone)]
pub struct GooseTask {
    /// An index into GooseTaskSet.task, indicating which task this is.
    pub tasks_index: usize,
    /// An optional name for the task, used when displaying statistics about requests made.
    pub name: String,
    /// An integer value that controls the frequency that this task will be run.
    pub weight: usize,
    /// An integer value that controls when this task runs compared to other tasks in the same GooseTaskSet.
    pub sequence: usize,
    /// A flag indicating that this task runs when the client starts.
    pub on_start: bool,
    /// A flag indicating that this task runs when the client stops.
    pub on_stop: bool,
    /// A required function that is executed each time this task runs.
    pub function: for<'r> fn(&'r GooseClient) -> Pin<Box<dyn Future<Output = ()> + Send + 'r>>,
}
impl GooseTask {
    pub fn new(
        function: for<'r> fn(&'r GooseClient) -> Pin<Box<dyn Future<Output = ()> + Send + 'r>>,
    ) -> Self {
        trace!("new task");
        GooseTask {
            tasks_index: usize::max_value(),
            name: "".to_string(),
            weight: 1,
            sequence: 0,
            on_start: false,
            on_stop: false,
            function,
        }
    }

    /// Set an optional name for the task, used when displaying statistics about
    /// requests made by the task.
    ///
    /// @TODO: rewrite:
    /// Individual requests can also be named withing your load test. See the
    /// documentation for `GooseClient`.[`set_request_name()`](./struct.GooseClient.html#method.set_request_name)
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     task!(my_task_function).set_name("foo");
    ///
    ///     async fn my_task_function(client: &GooseClient) {
    ///       let _response = client.get("/");
    ///     }
    /// ```
    pub fn set_name(mut self, name: &str) -> Self {
        trace!("[{}] set_name: {}", self.tasks_index, self.name);
        self.name = name.to_string();
        self
    }

    /// Set an optional flag indicating that this task should be run when
    /// a client first starts. This could be used to log the user in, and
    /// so all subsequent tasks are done as a logged in user. A task with
    /// this flag set will only run at start time (and optionally at stop
    /// time as well, if that flag is also set).
    ///
    /// On-start tasks can be sequenced and weighted. Sequences allow
    /// multiple on-start tasks to run in a controlled order. Weights allow
    /// on-start tasks to run multiple times when a client starts.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     task!(my_on_start_function).set_on_start();
    ///
    ///     async fn my_on_start_function(client: &GooseClient) {
    ///       let _response = client.get("/");
    ///     }
    /// ```
    pub fn set_on_start(mut self) -> Self {
        trace!("{} [{}] set_on_start task", self.name, self.tasks_index);
        self.on_start = true;
        self
    }

    /// Set an optional flag indicating that this task should be run when
    /// a client stops. This could be used to log a user out when the client
    /// finishes its load test. A task with this flag set will only run at
    /// stop time (and optionally at start time as well, if that flag is
    /// also set).
    ///
    /// On-stop tasks can be sequenced and weighted. Sequences allow
    /// multiple on-stop tasks to run in a controlled order. Weights allow
    /// on-stop tasks to run multiple times when a client stops.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     task!(my_on_stop_function).set_on_stop();
    ///
    ///     async fn my_on_stop_function(client: &GooseClient) {
    ///       let _response = client.get("/");
    ///     }
    /// ```
    pub fn set_on_stop(mut self) -> Self {
        trace!("{} [{}] set_on_stop task", self.name, self.tasks_index);
        self.on_stop = true;
        self
    }

    /// Sets a weight on an individual task. The larger the value of weight, the more often it will be run
    /// in the TaskSet. For example, if one task has a weight of 3 and another task has a weight of 1, the
    /// first task will run 3 times as often.
    ///
    /// # Example
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     task!(task_function).set_weight(3);
    ///
    ///     async fn task_function(client: &GooseClient) {
    ///       let _response = client.get("/");
    ///     }
    /// ```
    pub fn set_weight(mut self, weight: usize) -> Self {
        trace!(
            "{} [{}] set_weight: {}",
            self.name,
            self.tasks_index,
            weight
        );
        if weight < 1 {
            error!("{} weight of {} not allowed", self.name, weight);
            std::process::exit(1);
        } else {
            self.weight = weight;
        }
        self
    }

    /// Defines the sequence value of an individual tasks. Tasks are run in order of their sequence value,
    /// so a task with a sequence value of 1 will run before a task with a sequence value of 2. Tasks with
    /// no sequence value (or a sequence value of 0) will run last, after all tasks with positive sequence
    /// values.
    ///
    /// All tasks with the same sequence value will run in a random order. Tasks can be assigned both
    /// squence values and weights.
    ///
    /// # Examples
    /// In this first example, the variable names indicate the order the tasks will be run in:
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let runs_first = task!(first_task_function).set_sequence(3);
    ///     let runs_second = task!(second_task_function).set_sequence(5835);
    ///     let runs_last = task!(third_task_function);
    ///
    ///     async fn first_task_function(client: &GooseClient) {
    ///       let _response = client.get("/1");
    ///     }
    ///
    ///     async fn second_task_function(client: &GooseClient) {
    ///       let _response = client.get("/2");
    ///     }
    ///
    ///     async fn third_task_function(client: &GooseClient) {
    ///       let _response = client.get("/3");
    ///     }
    /// ```
    ///
    /// In the following example, the `runs_first` task runs two times, then one instance of `runs_second`
    /// and two instances of `also_runs_second` are all three run. The client will do this over and over
    /// the entire time it runs, with `runs_first` always running first, then the other tasks being
    /// run in a random and weighted order:
    /// ```rust
    ///     use goose::prelude::*;
    ///
    ///     let runs_first = task!(first_task_function).set_sequence(1).set_weight(2);
    ///     let runs_second = task!(second_task_function_a).set_sequence(2);
    ///     let also_runs_second = task!(second_task_function_b).set_sequence(2).set_weight(2);
    ///
    ///     async fn first_task_function(client: &GooseClient) {
    ///       let _response = client.get("/1");
    ///     }
    ///
    ///     async fn second_task_function_a(client: &GooseClient) {
    ///       let _response = client.get("/2a");
    ///     }
    ///
    ///     async fn second_task_function_b(client: &GooseClient) {
    ///       let _response = client.get("/2b");
    ///     }
    /// ```
    pub fn set_sequence(mut self, sequence: usize) -> Self {
        trace!(
            "{} [{}] set_sequence: {}",
            self.name,
            self.tasks_index,
            sequence
        );
        if sequence < 1 {
            info!(
                "setting sequence to 0 for task {} is unnecessary, sequence disabled",
                self.name
            );
        }
        self.sequence = sequence;
        self
    }
}
impl Hash for GooseTask {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.tasks_index.hash(state);
        self.name.hash(state);
        self.weight.hash(state);
        self.sequence.hash(state);
        self.on_start.hash(state);
        self.on_stop.hash(state);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use httpmock::Method::{GET, POST};
    use httpmock::{mock, with_mock_server};

    async fn setup_client() -> GooseClient {
        // Set up global client state.
        crate::GooseClientState::initialize(1).await;
        let configuration = GooseConfiguration::default();
        let mut client = GooseClient::new(
            0,
            Some("http://127.0.0.1:5000".to_string()),
            None,
            0,
            0,
            &configuration,
            0,
        );
        client.weighted_clients_index = 0;
        client
    }

    #[test]
    fn goose_task_set() {
        // Simplistic test task functions.
        async fn test_function_a(client: &GooseClient) -> () {
            let _response = client.get("/a/").await;
        }

        async fn test_function_b(client: &GooseClient) -> () {
            let _response = client.get("/b/").await;
        }

        let mut task_set = taskset!("foo");
        assert_eq!(task_set.name, "foo");
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.weight, 1);
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);
        assert_eq!(task_set.host, None);
        assert_eq!(task_set.tasks.len(), 0);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.weighted_on_start_tasks.len(), 0);
        assert_eq!(task_set.weighted_on_stop_tasks.len(), 0);

        // Registering a task adds it to tasks, but doesn't update weighted_tasks.
        task_set = task_set.register_task(task!(test_function_a));
        assert_eq!(task_set.tasks.len(), 1);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.weight, 1);
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);
        assert_eq!(task_set.host, None);

        // Different task can be registered.
        task_set = task_set.register_task(task!(test_function_b));
        assert_eq!(task_set.tasks.len(), 2);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.weight, 1);
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);
        assert_eq!(task_set.host, None);

        // Same task can be registered again.
        task_set = task_set.register_task(task!(test_function_a));
        assert_eq!(task_set.tasks.len(), 3);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.weight, 1);
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);
        assert_eq!(task_set.host, None);

        // Setting weight only affects weight field.
        task_set = task_set.set_weight(50);
        assert_eq!(task_set.weight, 50);
        assert_eq!(task_set.tasks.len(), 3);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);
        assert_eq!(task_set.host, None);

        // Weight can be changed.
        task_set = task_set.set_weight(5);
        assert_eq!(task_set.weight, 5);

        // Setting host only affects host field.
        task_set = task_set.set_host("http://foo.example.com/");
        assert_eq!(task_set.host, Some("http://foo.example.com/".to_string()));
        assert_eq!(task_set.weight, 5);
        assert_eq!(task_set.tasks.len(), 3);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());
        assert_eq!(task_set.min_wait, 0);
        assert_eq!(task_set.max_wait, 0);

        // Host field can be changed.
        task_set = task_set.set_host("https://bar.example.com/");
        assert_eq!(task_set.host, Some("https://bar.example.com/".to_string()));

        // Wait time only affects wait time fields.
        task_set = task_set.set_wait_time(1, 10);
        assert_eq!(task_set.min_wait, 1);
        assert_eq!(task_set.max_wait, 10);
        assert_eq!(task_set.host, Some("https://bar.example.com/".to_string()));
        assert_eq!(task_set.weight, 5);
        assert_eq!(task_set.tasks.len(), 3);
        assert_eq!(task_set.weighted_tasks.len(), 0);
        assert_eq!(task_set.task_sets_index, usize::max_value());

        // Wait time can be changed.
        task_set = task_set.set_wait_time(3, 9);
        assert_eq!(task_set.min_wait, 3);
        assert_eq!(task_set.max_wait, 9);
    }

    #[test]
    fn goose_task() {
        // Simplistic test task functions.
        async fn test_function_a(client: &GooseClient) -> () {
            let _response = client.get("/a/");
        }

        // Initialize task set.
        let mut task = task!(test_function_a);
        assert_eq!(task.tasks_index, usize::max_value());
        assert_eq!(task.name, "".to_string());
        assert_eq!(task.weight, 1);
        assert_eq!(task.sequence, 0);
        assert_eq!(task.on_start, false);
        assert_eq!(task.on_stop, false);

        // Name can be set, without affecting other fields.
        task = task.set_name("foo");
        assert_eq!(task.name, "foo".to_string());
        assert_eq!(task.weight, 1);
        assert_eq!(task.sequence, 0);
        assert_eq!(task.on_start, false);
        assert_eq!(task.on_stop, false);

        // Name can be set multiple times.
        task = task.set_name("bar");
        assert_eq!(task.name, "bar".to_string());

        // On start flag can be set, without affecting other fields.
        task = task.set_on_start();
        assert_eq!(task.on_start, true);
        assert_eq!(task.name, "bar".to_string());
        assert_eq!(task.weight, 1);
        assert_eq!(task.sequence, 0);
        assert_eq!(task.on_stop, false);

        // Setting on start flag twice doesn't change anything.
        task = task.set_on_start();
        assert_eq!(task.on_start, true);

        // On stop flag can be set, without affecting other fields.
        // It's possible to set both on_start and on_stop for same task.
        task = task.set_on_stop();
        assert_eq!(task.on_stop, true);
        assert_eq!(task.on_start, true);
        assert_eq!(task.name, "bar".to_string());
        assert_eq!(task.weight, 1);
        assert_eq!(task.sequence, 0);

        // Setting on stop flag twice doesn't change anything.
        task = task.set_on_stop();
        assert_eq!(task.on_stop, true);

        // Setting weight doesn't change anything else.
        task = task.set_weight(2);
        assert_eq!(task.weight, 2);
        assert_eq!(task.on_stop, true);
        assert_eq!(task.on_start, true);
        assert_eq!(task.name, "bar".to_string());
        assert_eq!(task.sequence, 0);

        // Weight field can be changed multiple times.
        task = task.set_weight(3);
        assert_eq!(task.weight, 3);

        // Setting sequence doesn't change anything else.
        task = task.set_sequence(4);
        assert_eq!(task.sequence, 4);
        assert_eq!(task.weight, 3);
        assert_eq!(task.on_stop, true);
        assert_eq!(task.on_start, true);
        assert_eq!(task.name, "bar".to_string());

        // Sequence field can be changed multiple times.
        task = task.set_sequence(8);
        assert_eq!(task.sequence, 8);
    }

    #[test]
    fn goose_raw_request() {
        let mut raw_request = GooseRawRequest::new(GooseMethod::GET, "/");
        assert_eq!(raw_request.name, "/".to_string());
        assert_eq!(raw_request.method, GooseMethod::GET);
        assert_eq!(raw_request.response_time, 0);
        assert_eq!(raw_request.status_code, None);
        assert_eq!(raw_request.success, true);
        assert_eq!(raw_request.update, false);

        let response_time = 123;
        raw_request.set_response_time(response_time);
        assert_eq!(raw_request.name, "/".to_string());
        assert_eq!(raw_request.method, GooseMethod::GET);
        assert_eq!(raw_request.response_time, response_time);
        assert_eq!(raw_request.status_code, None);
        assert_eq!(raw_request.success, true);
        assert_eq!(raw_request.update, false);

        let status_code = http::StatusCode::OK;
        raw_request.set_status_code(Some(status_code));
        assert_eq!(raw_request.name, "/".to_string());
        assert_eq!(raw_request.method, GooseMethod::GET);
        assert_eq!(raw_request.response_time, response_time);
        assert_eq!(raw_request.status_code, Some(status_code));
        assert_eq!(raw_request.success, true);
        assert_eq!(raw_request.update, false);
    }

    #[test]
    fn goose_request() {
        let mut request = GooseRequest::new("/", GooseMethod::GET, 0);
        assert_eq!(request.path, "/".to_string());
        assert_eq!(request.method, GooseMethod::GET);
        assert_eq!(request.response_times.len(), 0);
        assert_eq!(request.min_response_time, 0);
        assert_eq!(request.max_response_time, 0);
        assert_eq!(request.total_response_time, 0);
        assert_eq!(request.response_time_counter, 0);
        assert_eq!(request.status_code_counts.len(), 0);
        assert_eq!(request.success_count, 0);
        assert_eq!(request.fail_count, 0);

        // Tracking a response time updates several fields.
        request.set_response_time(1);
        // We've seen only one response time so far.
        assert_eq!(request.response_times.len(), 1);
        // We've seen one response time of length 1.
        assert_eq!(request.response_times[&1], 1);
        // The minimum response time seen so far is 1.
        assert_eq!(request.min_response_time, 1);
        // The maximum response time seen so far is 1.
        assert_eq!(request.max_response_time, 1);
        // We've seen a total of 1 ms of response time so far.
        assert_eq!(request.total_response_time, 1);
        // We've seen a total of 2 response times so far.
        assert_eq!(request.response_time_counter, 1);
        // Nothing else changes.
        assert_eq!(request.path, "/".to_string());
        assert_eq!(request.method, GooseMethod::GET);
        assert_eq!(request.status_code_counts.len(), 0);
        assert_eq!(request.success_count, 0);
        assert_eq!(request.fail_count, 0);

        // Tracking another response time updates all related fields.
        request.set_response_time(10);
        // We've added a new unique response time.
        assert_eq!(request.response_times.len(), 2);
        // We've seen the 10 ms response time 1 time.
        assert_eq!(request.response_times[&10], 1);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum is new response time.
        assert_eq!(request.max_response_time, 10);
        // Total combined response times is now 11 ms.
        assert_eq!(request.total_response_time, 11);
        // We've seen two response times so far.
        assert_eq!(request.response_time_counter, 2);
        // Nothing else changes.
        assert_eq!(request.path, "/".to_string());
        assert_eq!(request.method, GooseMethod::GET);
        assert_eq!(request.status_code_counts.len(), 0);
        assert_eq!(request.success_count, 0);
        assert_eq!(request.fail_count, 0);

        // Tracking another response time updates all related fields.
        request.set_response_time(10);
        // We've incremented the counter of an existing response time.
        assert_eq!(request.response_times.len(), 2);
        // We've seen the 10 ms response time 2 times.
        assert_eq!(request.response_times[&10], 2);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum doesn't change.
        assert_eq!(request.max_response_time, 10);
        // Total combined response times is now 21 ms.
        assert_eq!(request.total_response_time, 21);
        // We've seen three response times so far.
        assert_eq!(request.response_time_counter, 3);

        // Tracking another response time updates all related fields.
        request.set_response_time(101);
        // We've added a new response time for the first time.
        assert_eq!(request.response_times.len(), 3);
        // The response time was internally rounded to 100, which we've seen once.
        assert_eq!(request.response_times[&100], 1);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum increases to actual maximum, not rounded maximum.
        assert_eq!(request.max_response_time, 101);
        // Total combined response times is now 122 ms.
        assert_eq!(request.total_response_time, 122);
        // We've seen four response times so far.
        assert_eq!(request.response_time_counter, 4);

        // Tracking another response time updates all related fields.
        request.set_response_time(102);
        // Due to rounding, this increments the existing 100 ms response time.
        assert_eq!(request.response_times.len(), 3);
        // The response time was internally rounded to 100, which we've now seen twice.
        assert_eq!(request.response_times[&100], 2);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum increases to actual maximum, not rounded maximum.
        assert_eq!(request.max_response_time, 102);
        // Add 102 to the total response time so far.
        assert_eq!(request.total_response_time, 224);
        // We've seen five response times so far.
        assert_eq!(request.response_time_counter, 5);

        // Tracking another response time updates all related fields.
        request.set_response_time(155);
        // Adds a new response time.
        assert_eq!(request.response_times.len(), 4);
        // The response time was internally rounded to 160, seen for the first time.
        assert_eq!(request.response_times[&160], 1);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum increases to actual maximum, not rounded maximum.
        assert_eq!(request.max_response_time, 155);
        // Add 155 to the total response time so far.
        assert_eq!(request.total_response_time, 379);
        // We've seen six response times so far.
        assert_eq!(request.response_time_counter, 6);

        // Tracking another response time updates all related fields.
        request.set_response_time(2345);
        // Adds a new response time.
        assert_eq!(request.response_times.len(), 5);
        // The response time was internally rounded to 2000, seen for the first time.
        assert_eq!(request.response_times[&2000], 1);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum increases to actual maximum, not rounded maximum.
        assert_eq!(request.max_response_time, 2345);
        // Add 2345 to the total response time so far.
        assert_eq!(request.total_response_time, 2724);
        // We've seen seven response times so far.
        assert_eq!(request.response_time_counter, 7);

        // Tracking another response time updates all related fields.
        request.set_response_time(987654321);
        // Adds a new response time.
        assert_eq!(request.response_times.len(), 6);
        // The response time was internally rounded to 987654000, seen for the first time.
        assert_eq!(request.response_times[&987654000], 1);
        // Minimum doesn't change.
        assert_eq!(request.min_response_time, 1);
        // Maximum increases to actual maximum, not rounded maximum.
        assert_eq!(request.max_response_time, 987654321);
        // Add 987654321 to the total response time so far.
        assert_eq!(request.total_response_time, 987657045);
        // We've seen eight response times so far.
        assert_eq!(request.response_time_counter, 8);

        // Tracking status code updates all related fields.
        request.set_status_code(Some(StatusCode::OK));
        // We've seen only one status code.
        assert_eq!(request.status_code_counts.len(), 1);
        // First time seeing this status code.
        assert_eq!(request.status_code_counts[&200], 1);
        // As status code tracking is optional, we don't track success/fail here.
        assert_eq!(request.success_count, 0);
        assert_eq!(request.fail_count, 0);
        // Nothing else changes.
        assert_eq!(request.response_times.len(), 6);
        assert_eq!(request.min_response_time, 1);
        assert_eq!(request.max_response_time, 987654321);
        assert_eq!(request.total_response_time, 987657045);
        assert_eq!(request.response_time_counter, 8);

        // Tracking status code updates all related fields.
        request.set_status_code(Some(StatusCode::OK));
        // We've seen only one unique status code.
        assert_eq!(request.status_code_counts.len(), 1);
        // Second time seeing this status code.
        assert_eq!(request.status_code_counts[&200], 2);

        // Tracking status code updates all related fields.
        request.set_status_code(None);
        // We've seen two unique status codes.
        assert_eq!(request.status_code_counts.len(), 2);
        // First time seeing a client-side error.
        assert_eq!(request.status_code_counts[&0], 1);

        // Tracking status code updates all related fields.
        request.set_status_code(Some(StatusCode::INTERNAL_SERVER_ERROR));
        // We've seen three unique status codes.
        assert_eq!(request.status_code_counts.len(), 3);
        // First time seeing an internal server error.
        assert_eq!(request.status_code_counts[&500], 1);

        // Tracking status code updates all related fields.
        request.set_status_code(Some(StatusCode::PERMANENT_REDIRECT));
        // We've seen four unique status codes.
        assert_eq!(request.status_code_counts.len(), 4);
        // First time seeing an internal server error.
        assert_eq!(request.status_code_counts[&308], 1);

        // Tracking status code updates all related fields.
        request.set_status_code(Some(StatusCode::OK));
        // We've seen four unique status codes.
        assert_eq!(request.status_code_counts.len(), 4);
        // Third time seeing this status code.
        assert_eq!(request.status_code_counts[&200], 3);
        // Nothing else changes.
        assert_eq!(request.success_count, 0);
        assert_eq!(request.fail_count, 0);
        assert_eq!(request.response_times.len(), 6);
        assert_eq!(request.min_response_time, 1);
        assert_eq!(request.max_response_time, 987654321);
        assert_eq!(request.total_response_time, 987657045);
        assert_eq!(request.response_time_counter, 8);
    }

    #[tokio::test]
    async fn goose_client() {
        const HOST: &str = "http://example.com/";
        let configuration = GooseConfiguration::default();
        let client = GooseClient::new(0, Some(HOST.to_string()), None, 0, 0, &configuration, 0);
        assert_eq!(client.task_sets_index, 0);
        assert_eq!(client.default_host, Some(HOST.to_string()));
        assert_eq!(client.task_set_host, None);
        assert_eq!(client.min_wait, 0);
        assert_eq!(client.max_wait, 0);
        assert_eq!(client.weighted_clients_index, usize::max_value());
        assert_eq!(client.weighted_on_start_tasks.len(), 0);
        assert_eq!(client.weighted_tasks.len(), 0);
        assert_eq!(client.weighted_on_stop_tasks.len(), 0);
        assert_eq!(client.task_request_name, None);
        assert_eq!(client.request_name, None);

        // Confirm the URLs are correctly built using the default_host.
        let url = client.build_url("/foo");
        assert_eq!(&url, &[HOST, "foo"].concat());
        let url = client.build_url("bar/");
        assert_eq!(&url, &[HOST, "bar/"].concat());
        let url = client.build_url("/foo/bar");
        assert_eq!(&url, &[HOST, "foo/bar"].concat());

        // Confirm the URLs are built with their own specified host.
        let url = client.build_url("https://example.com/foo");
        assert_eq!(url, "https://example.com/foo");
        let url = client.build_url("https://www.example.com/path/to/resource");
        assert_eq!(url, "https://www.example.com/path/to/resource");

        // Create a second client, this time setting a task_set_host.
        let client2 = GooseClient::new(
            0,
            Some("http://www.example.com/".to_string()),
            Some("http://www2.example.com/".to_string()),
            1,
            3,
            &configuration,
            0,
        );
        assert_eq!(
            client2.default_host,
            Some("http://www.example.com/".to_string())
        );
        assert_eq!(
            client2.task_set_host,
            Some("http://www2.example.com/".to_string())
        );
        assert_eq!(client2.min_wait, 1);
        assert_eq!(client2.max_wait, 3);

        // Confirm the URLs are correctly built using the task_set_host.
        let url = client2.build_url("/foo");
        assert_eq!(url, "http://www2.example.com/foo");

        // Confirm URLs are still built with their own specified host.
        let url = client.build_url("https://example.com/foo");
        assert_eq!(url, "https://example.com/foo");

        // Recreate client.
        let client = setup_client().await;
        const MOCKHOST: &str = "http://127.0.0.1:5000/";

        // Create a GET request.
        let mut goose_request = client.goose_get("/foo").await;
        let mut built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::GET);
        assert_eq!(built_request.url().as_str(), &[MOCKHOST, "foo"].concat());
        assert_eq!(built_request.timeout(), None);

        // Create a POST request.
        goose_request = client.goose_post("/path/to/post").await;
        built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::POST);
        assert_eq!(
            built_request.url().as_str(),
            &[MOCKHOST, "path/to/post"].concat()
        );
        assert_eq!(built_request.timeout(), None);

        // Create a PUT request.
        goose_request = client.goose_put("/path/to/put").await;
        built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::PUT);
        assert_eq!(
            built_request.url().as_str(),
            &[MOCKHOST, "path/to/put"].concat()
        );
        assert_eq!(built_request.timeout(), None);

        // Create a PATCH request.
        goose_request = client.goose_patch("/path/to/patch").await;
        built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::PATCH);
        assert_eq!(
            built_request.url().as_str(),
            &[MOCKHOST, "path/to/patch"].concat()
        );
        assert_eq!(built_request.timeout(), None);

        // Create a DELETE request.
        goose_request = client.goose_delete("/path/to/delete").await;
        built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::DELETE);
        assert_eq!(
            built_request.url().as_str(),
            &[MOCKHOST, "path/to/delete"].concat()
        );
        assert_eq!(built_request.timeout(), None);

        // Create a HEAD request.
        goose_request = client.goose_head("/path/to/head").await;
        built_request = goose_request.build().unwrap();
        assert_eq!(built_request.method(), &Method::HEAD);
        assert_eq!(
            built_request.url().as_str(),
            &[MOCKHOST, "path/to/head"].concat()
        );
        assert_eq!(built_request.timeout(), None);
    }

    #[tokio::test]
    #[with_mock_server]
    async fn manual_requests() {
        let client = setup_client().await;

        // Set up a mock http server endpoint.
        let mock_index = mock(GET, "/").return_status(200).create();

        // Make a GET request to the mock http server and confirm we get a 200 response.
        assert_eq!(mock_index.times_called(), 0);
        let response = client.get("/").await;
        let status = response.response.unwrap().status();
        assert_eq!(status, 200);
        assert_eq!(mock_index.times_called(), 1);
        assert_eq!(response.request.method, GooseMethod::GET);
        assert_eq!(response.request.name, "/");
        assert_eq!(response.request.success, true);
        assert_eq!(response.request.update, false);
        assert_eq!(response.request.status_code, Some(http::StatusCode::OK));

        const NO_SUCH_PATH: &str = "/no/such/path";
        let mock_404 = mock(GET, NO_SUCH_PATH).return_status(404).create();

        // Make an invalid GET request to the mock http server and confirm we get a 404 response.
        assert_eq!(mock_404.times_called(), 0);
        let response = client.get(NO_SUCH_PATH).await;
        let status = response.response.unwrap().status();
        assert_eq!(status, 404);
        assert_eq!(mock_404.times_called(), 1);
        assert_eq!(response.request.method, GooseMethod::GET);
        assert_eq!(response.request.name, NO_SUCH_PATH);
        assert_eq!(response.request.success, false);
        assert_eq!(response.request.update, false);
        assert_eq!(
            response.request.status_code,
            Some(http::StatusCode::NOT_FOUND)
        );

        // Set up a mock http server endpoint.
        const COMMENT_PATH: &str = "/comment";
        let mock_comment = mock(POST, COMMENT_PATH)
            .return_status(200)
            .expect_body("foo")
            .return_body("foo")
            .create();

        // Make a POST request to the mock http server and confirm we get a 200 OK response.
        assert_eq!(mock_comment.times_called(), 0);
        let response = client.post(COMMENT_PATH, "foo").await;
        let unwrapped_response = response.response.unwrap();
        let status = unwrapped_response.status();
        assert_eq!(status, 200);
        let body = unwrapped_response.text().await.unwrap();
        assert_eq!(body, "foo");
        assert_eq!(mock_comment.times_called(), 1);
        assert_eq!(response.request.method, GooseMethod::POST);
        assert_eq!(response.request.name, COMMENT_PATH);
        assert_eq!(response.request.success, true);
        assert_eq!(response.request.update, false);
        assert_eq!(response.request.status_code, Some(http::StatusCode::OK));
    }
}