diff --git a/integration/tests_ok/option_retry.err.pattern b/integration/tests_ok/option_retry.err.pattern index 919a21ced..d68476252 100644 --- a/integration/tests_ok/option_retry.err.pattern +++ b/integration/tests_ok/option_retry.err.pattern @@ -35,6 +35,10 @@ * ------------------------------------------------------------------------------ * Executing entry 2 * +* Entry options: +* retry: true +* retry-interval: 100 +* * Cookie store: * * Request: @@ -58,9 +62,9 @@ < * * Assert failure -* --> tests_ok/retry.hurl:16:0 +* --> tests_ok/option_retry.hurl:19:0 * | -* 16 | jsonpath "$.state" == "COMPLETED" +* 19 | jsonpath "$.state" == "COMPLETED" * | actual: string * | expected: string * | @@ -69,6 +73,10 @@ * ------------------------------------------------------------------------------ * Executing entry 2 * +* Entry options: +* retry: true +* retry-interval: 100 +* * Cookie store: * * Request: @@ -92,9 +100,9 @@ < * * Assert failure -* --> tests_ok/retry.hurl:16:0 +* --> tests_ok/option_retry.hurl:19:0 * | -* 16 | jsonpath "$.state" == "COMPLETED" +* 19 | jsonpath "$.state" == "COMPLETED" * | actual: string * | expected: string * | @@ -103,6 +111,10 @@ * ------------------------------------------------------------------------------ * Executing entry 2 * +* Entry options: +* retry: true +* retry-interval: 100 +* * Cookie store: * * Request: @@ -126,9 +138,9 @@ < * * Assert failure -* --> tests_ok/retry.hurl:16:0 +* --> tests_ok/option_retry.hurl:19:0 * | -* 16 | jsonpath "$.state" == "COMPLETED" +* 19 | jsonpath "$.state" == "COMPLETED" * | actual: string * | expected: string * | @@ -137,6 +149,10 @@ * ------------------------------------------------------------------------------ * Executing entry 2 * +* Entry options: +* retry: true +* retry-interval: 100 +* * Cookie store: * * Request: @@ -160,9 +176,9 @@ < * * Assert failure -* --> tests_ok/retry.hurl:16:0 +* --> tests_ok/option_retry.hurl:19:0 * | -* 16 | jsonpath "$.state" == "COMPLETED" +* 19 | jsonpath "$.state" == "COMPLETED" * | actual: string * | expected: string * | @@ -171,6 +187,10 @@ * ------------------------------------------------------------------------------ * Executing entry 2 * +* Entry options: +* retry: true +* retry-interval: 100 +* * Cookie store: * * Request: diff --git a/integration/tests_ok/option_retry.html b/integration/tests_ok/option_retry.html new file mode 100644 index 000000000..02de95d04 --- /dev/null +++ b/integration/tests_ok/option_retry.html @@ -0,0 +1,28 @@ +
# Create a new job
+POST http://localhost:8000/jobs
+
+HTTP/* 201
+[Captures]
+job_id: jsonpath "$.id"
+[Asserts]
+jsonpath "$.state" == "RUNNING"
+
+
+# Pull job status until it is completed
+GET http://localhost:8000/jobs/{{job_id}}
+[Options]
+retry: true
+retry-interval: 100
+
+HTTP/* 200
+[Asserts]
+jsonpath "$.state" == "COMPLETED"
+
+
+# Delete the job
+DELETE http://localhost:8000/jobs/{{job_id}}
+HTTP/* 200
+
+GET http://localhost:8000/jobs/{{job_id}}
+HTTP/* 404
+
\ No newline at end of file diff --git a/integration/tests_ok/option_retry.hurl b/integration/tests_ok/option_retry.hurl index 372480618..35e4bdbe7 100644 --- a/integration/tests_ok/option_retry.hurl +++ b/integration/tests_ok/option_retry.hurl @@ -12,6 +12,7 @@ jsonpath "$.state" == "RUNNING" GET http://localhost:8000/jobs/{{job_id}} [Options] retry: true +retry-interval: 100 HTTP/* 200 [Asserts] diff --git a/integration/tests_ok/option_retry.options b/integration/tests_ok/option_retry.options new file mode 100644 index 000000000..b80df0a8c --- /dev/null +++ b/integration/tests_ok/option_retry.options @@ -0,0 +1,2 @@ +--verbose +--json \ No newline at end of file diff --git a/integration/tests_ok/option_retry.out.pattern b/integration/tests_ok/option_retry.out.pattern index 480f9251d..6c2521d3d 100644 --- a/integration/tests_ok/option_retry.out.pattern +++ b/integration/tests_ok/option_retry.out.pattern @@ -1 +1 @@ -{"cookies":[],"entries":[{"asserts":[{"line":4,"success":true},{"line":4,"success":true},{"line":8,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"POST","queryString":[],"url":"http://localhost:8000/jobs"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":201}}],"captures":[{"name":"job_id","value":"~~~"}],"index":1,"time":~~~},{"asserts":[{"line":14,"success":true},{"line":14,"success":true},{"line":16,"message":"Assert failure\n --> tests_ok/retry.hurl:16:0\n |\n16 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":14,"success":true},{"line":14,"success":true},{"line":16,"message":"Assert failure\n --> tests_ok/retry.hurl:16:0\n |\n16 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":14,"success":true},{"line":14,"success":true},{"line":16,"message":"Assert failure\n --> tests_ok/retry.hurl:16:0\n |\n16 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":14,"success":true},{"line":14,"success":true},{"line":16,"message":"Assert failure\n --> tests_ok/retry.hurl:16:0\n |\n16 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":14,"success":true},{"line":14,"success":true},{"line":16,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"62"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":21,"success":true},{"line":21,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"DELETE","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Server","value":"Flask Server"},{"name":"Content-Length","value":"0"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":3,"time":~~~},{"asserts":[{"line":24,"success":true},{"line":24,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"42"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":404}}],"captures":[],"index":4,"time":~~~}],"filename":"tests_ok/retry.hurl","success":true,"time":~~~} +{"cookies":[],"entries":[{"asserts":[{"line":4,"success":true},{"line":4,"success":true},{"line":8,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"POST","queryString":[],"url":"http://localhost:8000/jobs"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":201}}],"captures":[{"name":"job_id","value":"~~~"}],"index":1,"time":~~~},{"asserts":[{"line":17,"success":true},{"line":17,"success":true},{"line":19,"message":"Assert failure\n --> tests_ok/option_retry.hurl:19:0\n |\n19 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":17,"success":true},{"line":17,"success":true},{"line":19,"message":"Assert failure\n --> tests_ok/option_retry.hurl:19:0\n |\n19 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":17,"success":true},{"line":17,"success":true},{"line":19,"message":"Assert failure\n --> tests_ok/option_retry.hurl:19:0\n |\n19 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":17,"success":true},{"line":17,"success":true},{"line":19,"message":"Assert failure\n --> tests_ok/option_retry.hurl:19:0\n |\n19 | jsonpath \"$.state\" == \"COMPLETED\"\n | actual: string \n | expected: string \n |","success":false}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"60"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":17,"success":true},{"line":17,"success":true},{"line":19,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"62"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":2,"time":~~~},{"asserts":[{"line":24,"success":true},{"line":24,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"DELETE","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Server","value":"Flask Server"},{"name":"Content-Length","value":"0"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":3,"time":~~~},{"asserts":[{"line":27,"success":true},{"line":27,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/jobs/~~~"},"response":{"cookies":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Content-Length","value":"42"},{"name":"Server","value":"Flask Server"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":404}}],"captures":[],"index":4,"time":~~~}],"filename":"tests_ok/option_retry.hurl","success":true,"time":~~~} diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index aca5bd994..a30f65afc 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -16,6 +16,7 @@ * */ use std::collections::HashMap; +use std::time::Duration; use crate::cli::Logger; use crate::http; @@ -301,6 +302,10 @@ pub fn get_entry_options( runner_options.retry = option.value; logger.debug(format!("retry: {}", option.value).as_str()); } + EntryOption::RetryInterval(option) => { + runner_options.retry_interval = Duration::from_millis(option.value); + logger.debug(format!("retry-interval: {}", option.value).as_str()); + } EntryOption::Variable(VariableOption { value: VariableDefinition { name, value, .. }, .. diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index 9bbcc8331..88ddd6c3f 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -677,6 +677,7 @@ pub enum EntryOption { FollowLocation(FollowLocationOption), MaxRedirect(MaxRedirectOption), Retry(RetryOption), + RetryInterval(RetryIntervalOption), Variable(VariableOption), Verbose(VerboseOption), VeryVerbose(VeryVerboseOption), @@ -722,6 +723,16 @@ pub struct RetryOption { pub line_terminator0: LineTerminator, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RetryIntervalOption { + pub line_terminators: Vec, + pub space0: Whitespace, + pub space1: Whitespace, + pub space2: Whitespace, + pub value: u64, + pub line_terminator0: LineTerminator, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct VerboseOption { pub line_terminators: Vec, diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 501904af4..65290bc20 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -238,6 +238,7 @@ impl Htmlable for EntryOption { EntryOption::FollowLocation(option) => option.to_html(), EntryOption::MaxRedirect(option) => option.to_html(), EntryOption::Retry(option) => option.to_html(), + EntryOption::RetryInterval(option) => option.to_html(), EntryOption::Variable(option) => option.to_html(), EntryOption::Verbose(option) => option.to_html(), EntryOption::VeryVerbose(option) => option.to_html(), @@ -347,6 +348,23 @@ impl Htmlable for RetryOption { } } +impl Htmlable for RetryIntervalOption { + fn to_html(&self) -> String { + let mut buffer = String::from(""); + add_line_terminators(&mut buffer, self.line_terminators.clone()); + buffer.push_str(""); + buffer.push_str(self.space0.to_html().as_str()); + buffer.push_str("retry-interval"); + buffer.push_str(self.space1.to_html().as_str()); + buffer.push_str(":"); + buffer.push_str(self.space2.to_html().as_str()); + buffer.push_str(format!("{}", self.value).as_str()); + buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); + buffer + } +} + impl Htmlable for VariableOption { fn to_html(&self) -> String { let mut buffer = String::from(""); diff --git a/packages/hurl_core/src/parser/sections.rs b/packages/hurl_core/src/parser/sections.rs index 86cd2a31d..8ca2aa583 100644 --- a/packages/hurl_core/src/parser/sections.rs +++ b/packages/hurl_core/src/parser/sections.rs @@ -350,6 +350,7 @@ fn option(reader: &mut Reader) -> ParseResult<'static, EntryOption> { option_follow_location, option_max_redirect, option_retry, + option_retry_interval, option_variable, option_verbose, option_very_verbose, @@ -492,6 +493,28 @@ fn option_retry(reader: &mut Reader) -> ParseResult<'static, EntryOption> { Ok(EntryOption::Retry(option)) } +fn option_retry_interval(reader: &mut Reader) -> ParseResult<'static, EntryOption> { + let line_terminators = optional_line_terminators(reader)?; + let space0 = zero_or_more_spaces(reader)?; + try_literal("retry-interval", reader)?; + let space1 = zero_or_more_spaces(reader)?; + try_literal(":", reader)?; + let space2 = zero_or_more_spaces(reader)?; + let value = nonrecover(natural, reader)?; + let line_terminator0 = line_terminator(reader)?; + + let option = RetryIntervalOption { + line_terminators, + space0, + space1, + space2, + value, + line_terminator0, + }; + + Ok(EntryOption::RetryInterval(option)) +} + fn option_variable(reader: &mut Reader) -> ParseResult<'static, EntryOption> { let line_terminators = optional_line_terminators(reader)?; let space0 = zero_or_more_spaces(reader)?; diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index 6f366e365..ed695a150 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -825,6 +825,7 @@ impl Tokenizable for EntryOption { EntryOption::FollowLocation(option) => option.tokenize(), EntryOption::MaxRedirect(option) => option.tokenize(), EntryOption::Retry(option) => option.tokenize(), + EntryOption::RetryInterval(option) => option.tokenize(), EntryOption::Variable(option) => option.tokenize(), EntryOption::Verbose(option) => option.tokenize(), EntryOption::VeryVerbose(option) => option.tokenize(), @@ -958,6 +959,27 @@ impl Tokenizable for RetryOption { } } +impl Tokenizable for RetryIntervalOption { + fn tokenize(&self) -> Vec { + let mut tokens: Vec = vec![]; + tokens.append( + &mut self + .line_terminators + .iter() + .flat_map(|e| e.tokenize()) + .collect(), + ); + tokens.append(&mut self.space0.tokenize()); + tokens.push(Token::String("retry-interval".to_string())); + tokens.append(&mut self.space1.tokenize()); + tokens.push(Token::Colon(String::from(":"))); + tokens.append(&mut self.space2.tokenize()); + tokens.push(Token::Number(self.value.to_string())); + tokens.append(&mut self.line_terminator0.tokenize()); + tokens + } +} + impl Tokenizable for VariableOption { fn tokenize(&self) -> Vec { let mut tokens: Vec = vec![];