From 652ff5e0f34d1485731d5dee612d99473f7fa9ca Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Fri, 29 Oct 2021 17:57:55 +0200 Subject: [PATCH] introduce GooseRequest and builder pattern --- CHANGELOG.md | 3 +- Cargo.toml | 2 +- examples/drupal_memcache.rs | 15 +- examples/simple.rs | 4 +- examples/simple_with_session.rs | 13 +- examples/umami/admin.rs | 9 +- examples/umami/common.rs | 15 +- src/goose.rs | 548 +++++++++++--------------------- src/logger.rs | 3 +- src/prelude.rs | 3 +- src/user.rs | 4 +- tests/no_normal_tasks.rs | 3 +- 12 files changed, 221 insertions(+), 401 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15eb547f..a8fa5ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog -## 0.14.2-dev +## 0.15.0-dev - [#372](https://github.com/tag1consulting/goose/pull/372) de-deduplicate documentation, favoring [The Goose Book](https://book.goose.rs) + - @TODO: errors ... ## 0.14.1 October 13, 2021 - [#364](https://github.com/tag1consulting/goose/pull/364) add link from the [Developer Documentation](https://docs.rs/goose) to [The Git Book](https://book.goose.rs) diff --git a/Cargo.toml b/Cargo.toml index e4512a6a..01ef5428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "goose" -version = "0.14.2-dev" +version = "0.15.0-dev" authors = ["Jeremy Andrews "] edition = "2018" description = "A load testing framework inspired by Locust." diff --git a/examples/drupal_memcache.rs b/examples/drupal_memcache.rs index 11dacc19..51a04640 100644 --- a/examples/drupal_memcache.rs +++ b/examples/drupal_memcache.rs @@ -104,7 +104,14 @@ async fn drupal_memcache_front_page(user: &mut GooseUser) -> GooseTaskResult { } } for asset in &urls { - let _ = user.get_named(asset, "static asset").await; + let _ = user + .request( + GooseRequest::builder() + .path(&*asset.to_string()) + .name("static asset") + .build(), + ) + .await; } } Err(e) => { @@ -185,8 +192,7 @@ async fn drupal_memcache_login(user: &mut GooseUser) -> GooseTaskResult { ("form_id", "user_login"), ("op", "Log+in"), ]; - let request_builder = user.goose_post("/user")?; - let _goose = user.goose_send(request_builder.form(¶ms), None).await; + let _goose = user.post_form("/user", ¶ms).await?; // @TODO: verify that we actually logged in. } Err(e) => { @@ -302,8 +308,7 @@ async fn drupal_memcache_post_comment(user: &mut GooseUser) -> GooseTaskResult { ]; // Post the comment. - let request_builder = user.goose_post(&comment_path)?; - let mut goose = user.goose_send(request_builder.form(¶ms), None).await?; + let mut goose = user.post_form(&comment_path, ¶ms).await?; // Verify that the comment posted. match goose.response { diff --git a/examples/simple.rs b/examples/simple.rs index 09f86581..168d1123 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -45,10 +45,8 @@ async fn main() -> Result<(), GooseError> { /// on_start task when registering it above. This means it only runs one time /// per user, when the user thread first starts. async fn website_login(user: &mut GooseUser) -> GooseTaskResult { - let request_builder = user.goose_post("/login")?; - // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; - let _goose = user.goose_send(request_builder.form(¶ms), None).await?; + let _goose = user.post_form("/login", ¶ms).await?; Ok(()) } diff --git a/examples/simple_with_session.rs b/examples/simple_with_session.rs index e077e42a..6d263067 100644 --- a/examples/simple_with_session.rs +++ b/examples/simple_with_session.rs @@ -55,11 +55,9 @@ async fn main() -> Result<(), GooseError> { /// on_start task when registering it above. This means it only runs one time /// per user, when the user thread first starts. async fn website_signup(user: &mut GooseUser) -> GooseTaskResult { - let request_builder = user.goose_post("/signup")?; - // https://docs.rs/reqwest/*/reqwest/blocking/struct.RequestBuilder.html#method.form let params = [("username", "test_user"), ("password", "")]; let response = user - .goose_send(request_builder.form(¶ms), None) + .post_form("/signup", ¶ms) .await? .response? .json::() @@ -77,8 +75,13 @@ async fn authenticated_index(user: &mut GooseUser) -> GooseTaskResult { // This will panic if the session is missing or if the session is not of the right type // use `get_session_data` to handle missing session let session = user.get_session_data_unchecked::(); - let request = user.goose_get("/")?.bearer_auth(&session.jwt_token); - let _goose = user.goose_send(request, None).await?; + let url = user.build_url("/")?; + let request_builder = user.client.get(url).bearer_auth(&session.jwt_token); + let goose_request = GooseRequest::builder() + .request_builder(request_builder) + .build(); + + user.request(goose_request).await?; Ok(()) } diff --git a/examples/umami/admin.rs b/examples/umami/admin.rs index ac10721e..4ade505f 100644 --- a/examples/umami/admin.rs +++ b/examples/umami/admin.rs @@ -66,8 +66,7 @@ pub async fn log_in(user: &mut GooseUser) -> GooseTaskResult { ("form_id", &"user_login_form".to_string()), ("op", &"Log+in".to_string()), ]; - let request_builder = user.goose_post("/en/user/login")?; - logged_in_user = user.goose_send(request_builder.form(¶ms), None).await?; + logged_in_user = user.post_form("/en/user/login", ¶ms).await?; // A successful log in is redirected. if !logged_in_user.request.redirected { @@ -169,9 +168,9 @@ pub async fn edit_article(user: &mut GooseUser) -> GooseTaskResult { ("form_id", &"node_article_edit_form".to_string()), ("op", &"Save (this translation)".to_string()), ]; - let request_builder = - user.goose_post(&format!("/en/node/{}/edit", article.unwrap().nid))?; - saved_article = user.goose_send(request_builder.form(¶ms), None).await?; + saved_article = user + .post_form(&format!("/en/node/{}/edit", article.unwrap().nid), ¶ms) + .await?; // A successful node save is redirected. if !saved_article.request.redirected { diff --git a/examples/umami/common.rs b/examples/umami/common.rs index acbed53a..85379764 100644 --- a/examples/umami/common.rs +++ b/examples/umami/common.rs @@ -443,7 +443,14 @@ pub async fn load_static_elements(user: &mut GooseUser, html: &str) { // Load all the static assets found on the page. for asset in &urls { - let _ = user.get_named(asset, "static asset").await; + let _ = user + .request( + GooseRequest::builder() + .path(&*asset.to_string()) + .name("static asset") + .build(), + ) + .await; } } @@ -565,8 +572,7 @@ pub async fn anonymous_contact_form(user: &mut GooseUser, english: bool) -> Goos ("form_id", "contact_message_feedback_form"), ("op", "Send+message"), ]; - let request_builder = user.goose_post(contact_form_url)?; - contact_form = user.goose_send(request_builder.form(¶ms), None).await?; + contact_form = user.post_form(contact_form_url, ¶ms).await?; } Err(e) => { return user.set_failure( @@ -694,8 +700,7 @@ pub async fn search(user: &mut GooseUser, english: bool) -> GooseTaskResult { ("form_id", "search_form"), ("op", "Search"), ]; - let request_builder = user.goose_post(search_form_url)?; - search_form = user.goose_send(request_builder.form(¶ms), None).await?; + search_form = user.post_form(search_form_url, ¶ms).await?; // A successful search is redirected. if !search_form.request.redirected { diff --git a/src/goose.rs b/src/goose.rs index 65c4a509..43c100bb 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -670,7 +670,7 @@ impl fmt::Display for GooseMethod { } } -/// Convert [`http::method::Method`](https://docs.rs/http/0.2.4/http/method/struct.Method.html) +/// Convert [`http::method::Method`](https://docs.rs/http/*/http/method/struct.Method.html) /// to [`GooseMethod`](./enum.GooseMethod.html). pub fn goose_method_from_method(method: Method) -> Result { Ok(match method { @@ -1119,40 +1119,7 @@ impl GooseUser { /// } /// ``` pub async fn get(&mut self, path: &str) -> Result { - let request_builder = self.goose_get(path)?; - - Ok(self.goose_send(request_builder, None).await?) - } - - /// A helper to make a named `GET` request of a path and collect relevant metrics. - /// Automatically prepends the correct host. Naming a request only affects collected - /// metrics. - /// - /// Calls to `get_named()` return a [`GooseResponse`](./struct.GooseResponse.html) object which - /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), - /// and the response ([`reqwest::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(user: &mut GooseUser) -> GooseTaskResult { - /// let _goose = user.get_named("/path/to/foo/", "foo").await?; - /// - /// Ok(()) - /// } - /// ``` - pub async fn get_named( - &mut self, - path: &str, - request_name: &str, - ) -> Result { - let request_builder = self.goose_get(path)?; - - Ok(self.goose_send(request_builder, Some(request_name)).await?) + Ok(self.request(GooseRequest::get(path)).await?) } /// A helper to make a `POST` request of a path and collect relevant metrics. @@ -1181,42 +1148,34 @@ impl GooseUser { /// Ok(()) /// } /// ``` - pub async fn post(&mut self, path: &str, body: &str) -> Result { - let request_builder = self.goose_post(path)?.body(body.to_string()); + pub async fn post>( + &mut self, + path: &str, + body: T, + ) -> Result { + let url = self.build_url(path)?; + let request_builder = self.client.post(url); + let goose_request = GooseRequest::builder() + .method(GooseMethod::Post) + .request_builder(request_builder.body(body)) + .build(); - Ok(self.goose_send(request_builder, None).await?) + Ok(self.request(goose_request).await?) } - /// A helper to make a named `POST` request of a path and collect relevant metrics. - /// Automatically prepends the correct host. Naming a request only affects collected - /// metrics. - /// - /// Calls to `post_named()` return a [`GooseResponse`](./struct.GooseResponse.html) object which - /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), - /// and the response ([`reqwest::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(user: &mut GooseUser) -> GooseTaskResult { - /// let _goose = user.post_named("/path/to/foo/", "foo", "BODY BEING POSTED").await?; - /// - /// Ok(()) - /// } - /// ``` - pub async fn post_named( + pub async fn post_form( &mut self, path: &str, - request_name: &str, - body: &str, + form: &T, ) -> Result { - let request_builder = self.goose_post(path)?.body(body.to_string()); + let url = self.build_url(path)?; + let request_builder = self.client.post(url); + let goose_request = GooseRequest::builder() + .method(GooseMethod::Post) + .request_builder(request_builder.form(&form)) + .build(); - Ok(self.goose_send(request_builder, Some(request_name)).await?) + Ok(self.request(goose_request).await?) } /// A helper to make a `HEAD` request of a path and collect relevant metrics. @@ -1246,40 +1205,7 @@ impl GooseUser { /// } /// ``` pub async fn head(&mut self, path: &str) -> Result { - let request_builder = self.goose_head(path)?; - - Ok(self.goose_send(request_builder, None).await?) - } - - /// A helper to make a named `HEAD` request of a path and collect relevant metrics. - /// Automatically prepends the correct host. Naming a request only affects collected - /// metrics. - /// - /// Calls to `head_named()` return a [`GooseResponse`](./struct.GooseResponse.html) object which - /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), - /// and the response ([`reqwest::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(user: &mut GooseUser) -> GooseTaskResult { - /// let _goose = user.head_named("/path/to/foo/", "foo").await?; - /// - /// Ok(()) - /// } - /// ``` - pub async fn head_named( - &mut self, - path: &str, - request_name: &str, - ) -> Result { - let request_builder = self.goose_head(path)?; - - Ok(self.goose_send(request_builder, Some(request_name)).await?) + Ok(self.request(GooseRequest::head(path)).await?) } /// A helper to make a `DELETE` request of a path and collect relevant metrics. @@ -1309,208 +1235,7 @@ impl GooseUser { /// } /// ``` pub async fn delete(&mut self, path: &str) -> Result { - let request_builder = self.goose_delete(path)?; - - Ok(self.goose_send(request_builder, None).await?) - } - - /// A helper to make a named `DELETE` request of a path and collect relevant metrics. - /// Automatically prepends the correct host. Naming a request only affects collected - /// metrics. - /// - /// Calls to `delete_named()` return a [`GooseResponse`](./struct.GooseResponse.html) object which - /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), - /// and the response ([`reqwest::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(user: &mut GooseUser) -> GooseTaskResult { - /// let _goose = user.delete_named("/path/to/foo/", "foo").await?; - /// - /// Ok(()) - /// } - /// ``` - pub async fn delete_named( - &mut self, - path: &str, - request_name: &str, - ) -> Result { - let request_builder = self.goose_delete(path)?; - - Ok(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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_get("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_get(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_post("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_post(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_head("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_head(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_put("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_put(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_patch("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_patch(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.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`](./struct.GooseUser.html#method.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(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_delete("/path/to/foo")?; - /// let _goose = user.goose_send(request_builder, None).await?; - /// - /// Ok(()) - /// } - /// ``` - pub fn goose_delete(&self, path: &str) -> Result { - let url = self.build_url(path)?; - - Ok(self.client.delete(&url)) + Ok(self.request(GooseRequest::delete(path)).await?) } /// Builds the provided @@ -1539,20 +1264,36 @@ impl GooseUser { /// /// /// A simple task that makes a GET request, exposing the Reqwest /// /// request builder. + /// /// @TODO /// async fn get_function(user: &mut GooseUser) -> GooseTaskResult { - /// let request_builder = user.goose_get("/path/to/foo")?; - /// let goose = user.goose_send(request_builder, None).await?; + /// let goose = user.get("/path/to/foo").await?; /// /// // Do stuff with goose.request and/or goose.response here. /// /// Ok(()) /// } /// ``` - pub async fn goose_send( + pub async fn request<'a>( &mut self, - request_builder: RequestBuilder, - request_name: Option<&str>, + mut request: GooseRequest<'_>, ) -> Result { + let request_builder = if request.request_builder.is_some() { + request.request_builder.take().unwrap() + } else { + let url = self.build_url(request.path)?; + match request.method { + GooseMethod::Delete => self.client.delete(&url), + GooseMethod::Get => self.client.get(&url), + GooseMethod::Head => self.client.head(&url), + GooseMethod::Patch => self.client.patch(&url), + GooseMethod::Post => self.client.post(&url), + GooseMethod::Put => self.client.put(&url), + } + }; + + // Determine the name for this request. + let request_name = self.get_request_name(&request); + // If throttle-requests is enabled... if self.is_throttled && self.throttle.is_some() { // ...wait until there's room to add a token to the throttle channel before proceeding. @@ -1561,24 +1302,24 @@ impl GooseUser { self.throttle.clone().unwrap().send_async(true).await?; }; + // The request is officially started let started = Instant::now(); - let request = request_builder.build()?; + + let built_request = request_builder.build()?; // String version of request path. - let path = match Url::parse(&request.url().to_string()) { + let path = match Url::parse(&built_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); // Grab a copy of any headers set by this request, included in the request log // and the debug log. let mut headers: Vec = Vec::new(); - for header in request.headers() { + for header in built_request.headers() { headers.push(format!("{:?}", header)); } @@ -1586,7 +1327,7 @@ impl GooseUser { // the debug log. let body = if self.config.request_body { // Get a bytes representation of the body, if any. - let body_bytes = match request.body() { + let body_bytes = match built_request.body() { Some(b) => b.as_bytes().unwrap_or(b""), None => b"", }; @@ -1597,7 +1338,12 @@ impl GooseUser { }; // Record the complete client request, included in the request log and the debug log. - let raw_request = GooseRawRequest::new(method, request.url().as_str(), headers, body); + let raw_request = GooseRawRequest::new( + request.method.clone(), + built_request.url().as_str(), + headers, + body, + ); // Record information about the request. let mut request_metric = GooseRequestMetric::new( @@ -1608,18 +1354,24 @@ impl GooseUser { ); // Make the actual request. - let response = self.client.execute(request).await; + let response = self.client.execute(built_request).await; request_metric.set_response_time(started.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() { + + if let Some(expect_status_code) = request.expect_status_code { + if status_code != expect_status_code { + request_metric.success = false; + request_metric.error = format!("{}: {}", status_code, request_name); + } + } else if !status_code.is_success() { request_metric.success = false; request_metric.error = format!("{}: {}", status_code, request_name); } + request_metric.set_status_code(Some(status_code)); request_metric.set_final_url(r.url().as_str()); @@ -1830,18 +1582,17 @@ impl GooseUser { /// If `request_name` is set, unwrap and use this. Otherwise, if the GooseTask has a name /// set use it. Otherwise use the path. - fn get_request_name<'a>(&'a self, path: &'a str, request_name: Option<&'a str>) -> &'a str { - match request_name { - // If a request_name was passed in, unwrap and return a copy of it. + fn get_request_name<'a>(&'a self, request: &'a GooseRequest) -> &'a str { + match request.name { + // If a request.name is set, unwrap and return it. Some(rn) => rn, None => { - // Otherwise determine if the current GooseTask is named, and if so return - // a copy of it. + // Otherwise determine if the current GooseTask is named, and if so return it. if let Some(task_name) = &self.task_name { task_name } else { // Otherwise return a copy of the the path. - path + request.path } } } @@ -1916,7 +1667,7 @@ impl GooseUser { /// let mut task = task!(loadtest_index_page); /// /// async fn loadtest_index_page(user: &mut GooseUser) -> GooseTaskResult { - /// let mut goose = user.get_named("/", "index").await?; + /// let mut goose = user.get("/").await?; /// /// if let Ok(response) = goose.response { /// // We only need to check pages that returned a success status code. @@ -2286,6 +2037,112 @@ impl GooseUser { } } +/// A GooseRequest ... +pub struct GooseRequest<'a> { + // Defaults to "" + path: &'a str, + // Defaults to GET + method: GooseMethod, + // Defaults to None + name: Option<&'a str>, + // Defaults to None + expect_status_code: Option, + // Defaults to None + request_builder: Option, +} +impl<'a> GooseRequest<'a> { + /// Convenience function to bring [`GooseRequestBuilder`] into scope. + pub fn builder() -> GooseRequestBuilder<'a> { + GooseRequestBuilder::new() + } + + pub fn get(path: &str) -> GooseRequest { + GooseRequest::builder().path(path).build() + } + + pub fn post(path: &str) -> GooseRequest { + GooseRequest::builder() + .path(path) + .method(GooseMethod::Post) + .build() + } + + pub fn head(path: &str) -> GooseRequest { + GooseRequest::builder() + .path(path) + .method(GooseMethod::Head) + .build() + } + + pub fn delete(path: &str) -> GooseRequest { + GooseRequest::builder() + .path(path) + .method(GooseMethod::Delete) + .build() + } +} + +pub struct GooseRequestBuilder<'a> { + path: &'a str, + method: GooseMethod, + name: Option<&'a str>, + expect_status_code: Option, + request_builder: Option, +} +impl<'a> GooseRequestBuilder<'a> { + fn new() -> Self { + Self { + path: "", + method: GooseMethod::Get, + name: None, + expect_status_code: None, + request_builder: None, + } + } + + pub fn path(mut self, path: impl Into<&'a str>) -> Self { + self.path = path.into(); + self + } + + pub fn method(mut self, method: GooseMethod) -> Self { + self.method = method; + self + } + + pub fn name(mut self, name: impl Into<&'a str>) -> Self { + self.name = Some(name.into()); + self + } + + pub fn expect_status_code(mut self, status_code: u16) -> Self { + self.expect_status_code = Some(status_code); + self + } + + pub fn request_builder(mut self, request_builder: RequestBuilder) -> Self { + self.request_builder = Some(request_builder); + self + } + + pub fn build(self) -> GooseRequest<'a> { + let Self { + path, + method, + name, + expect_status_code, + request_builder, + } = self; + GooseRequest { + path, + method, + name, + expect_status_code, + request_builder, + } + } +} + /// Remove path from Reqwest error to avoid having a lot of distincts error /// when path parameters are used. fn clean_reqwest_error(e: &reqwest::Error, request_name: &str) -> String { @@ -2866,52 +2723,6 @@ mod tests { let url = user2.build_url("https://example.com/foo").unwrap(); assert_eq!(url, "https://example.com/foo"); - // Recreate user2. - let server = MockServer::start(); - let user2 = setup_user(&server).unwrap(); - - // Create a GET request. - let mut goose_request = user2.goose_get("/foo").unwrap(); - let mut built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::GET); - assert_eq!(built_request.url().as_str(), server.url("/foo")); - assert_eq!(built_request.timeout(), None); - - // Create a POST request. - goose_request = user2.goose_post("/path/to/post").unwrap(); - built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::POST); - assert_eq!(built_request.url().as_str(), server.url("/path/to/post")); - assert_eq!(built_request.timeout(), None); - - // Create a PUT request. - goose_request = user2.goose_put("/path/to/put").unwrap(); - built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::PUT); - assert_eq!(built_request.url().as_str(), server.url("/path/to/put")); - assert_eq!(built_request.timeout(), None); - - // Create a PATCH request. - goose_request = user2.goose_patch("/path/to/patch").unwrap(); - built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::PATCH); - assert_eq!(built_request.url().as_str(), server.url("/path/to/patch")); - assert_eq!(built_request.timeout(), None); - - // Create a DELETE request. - goose_request = user2.goose_delete("/path/to/delete").unwrap(); - built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::DELETE); - assert_eq!(built_request.url().as_str(), server.url("/path/to/delete")); - assert_eq!(built_request.timeout(), None); - - // Create a HEAD request. - goose_request = user2.goose_head("/path/to/head").unwrap(); - built_request = goose_request.build().unwrap(); - assert_eq!(built_request.method(), &Method::HEAD); - assert_eq!(built_request.url().as_str(), server.url("/path/to/head")); - assert_eq!(built_request.timeout(), None); - // Confirm Goose can build a base_url that includes a path. const HOST_WITH_PATH: &str = "http://example.com/with/path/"; let base_url = get_base_url(Some(HOST_WITH_PATH.to_string()), None, None).unwrap(); @@ -2999,7 +2810,6 @@ mod tests { let body = unwrapped_response.text().await.unwrap(); assert_eq!(body, "foo"); assert_eq!(goose.request.raw.method, GooseMethod::Post); - assert_eq!(goose.request.name, COMMENT_PATH); assert!(goose.request.success); assert!(!goose.request.update); assert_eq!(goose.request.status_code, 200); diff --git a/src/logger.rs b/src/logger.rs index 76d159b3..8274d2ce 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -84,8 +84,7 @@ //! None, //! )?; //! -//! let request_builder = user.goose_post(path)?; -//! let goose = user.goose_send(request_builder.form(¶ms), None).await?; +//! let goose = user.post_form(path, ¶ms).await?; //! //! // Log the form parameters that were posted together with details about the entire //! // request that was sent to the server. diff --git a/src/prelude.rs b/src/prelude.rs index 7b160e88..9c8e4ded 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,8 @@ pub use crate::config::{GooseDefault, GooseDefaultType}; pub use crate::goose::{ - GooseTask, GooseTaskError, GooseTaskFunction, GooseTaskResult, GooseTaskSet, GooseUser, + GooseMethod, GooseRequest, GooseTask, GooseTaskError, GooseTaskFunction, GooseTaskResult, + GooseTaskSet, GooseUser, }; pub use crate::metrics::{GooseCoordinatedOmissionMitigation, GooseMetrics}; pub use crate::{task, taskset, GooseAttack, GooseError, GooseScheduler}; diff --git a/src/user.rs b/src/user.rs index 98e22925..28fc1bca 100644 --- a/src/user.rs +++ b/src/user.rs @@ -62,8 +62,8 @@ pub(crate) async fn user_main( // Determine which task we're going to run next. let function = &thread_task_set.tasks[*thread_task_index].function; debug!( - "launching on_start {} task from {}", - thread_task_name, thread_task_set.name + "[user {}]: launching {} task from {}", + thread_number, thread_task_name, thread_task_set.name ); // Invoke the task function. let _todo = invoke_task_function( diff --git a/tests/no_normal_tasks.rs b/tests/no_normal_tasks.rs index b61ba728..b3d861f9 100644 --- a/tests/no_normal_tasks.rs +++ b/tests/no_normal_tasks.rs @@ -24,9 +24,8 @@ const RUN_TIME: usize = 2; // Test task. pub async fn login(user: &mut GooseUser) -> GooseTaskResult { - let request_builder = user.goose_post(LOGIN_PATH)?; let params = [("username", "me"), ("password", "s3crET!")]; - let _goose = user.goose_send(request_builder.form(¶ms), None).await?; + let _goose = user.post_form(LOGIN_PATH, ¶ms).await?; Ok(()) }