Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --retry and --retry-interval option to retry request until asserts and captures are ok #871

Merged
merged 3 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add --retry and --retry-interval options.
  • Loading branch information
jcamiel committed Oct 16, 2022
commit a58d4f27e0c0922a6814ed0a85aa75f984aa2f7b
23 changes: 22 additions & 1 deletion packages/hurl/src/cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub struct CliOptions {
pub output: Option<String>,
pub output_type: OutputType,
pub proxy: Option<String>,
pub retry: bool,
pub retry_interval: Duration,
pub test: bool,
pub timeout: Duration,
pub to_entry: Option<usize>,
Expand Down Expand Up @@ -272,6 +274,21 @@ pub fn app(version: &str) -> Command {
.help("Generate HTML report to DIR")
.num_args(1)
)
.arg(
clap::Arg::new("retry")
.long("retry")
.help("Retry requests on errors")
.action(ArgAction::SetTrue)
)
.arg(
clap::Arg::new("retry_interval")
.long("retry-interval")
.value_name("MILLISECONDS")
.help("Interval in milliseconds before a retry")
.value_parser(value_parser!(u64))
.default_value("1000")
.num_args(1)
)
.arg(
clap::Arg::new("test")
.long("test")
Expand Down Expand Up @@ -401,6 +418,9 @@ pub fn parse_options(matches: &ArgMatches) -> Result<CliOptions, CliError> {
OutputType::ResponseBody
};
let proxy = get::<String>(matches, "proxy");
let retry = has_flag(matches, "retry");
let retry_interval = get::<u64>(matches, "retry_interval").unwrap();
let retry_interval = Duration::from_millis(retry_interval);
let timeout = get::<u64>(matches, "max_time").unwrap();
let timeout = Duration::from_secs(timeout);
let to_entry = get::<u32>(matches, "to_entry").map(|x| x as usize);
Expand Down Expand Up @@ -432,6 +452,8 @@ pub fn parse_options(matches: &ArgMatches) -> Result<CliOptions, CliError> {
output,
output_type,
proxy,
retry,
retry_interval,
test,
timeout,
to_entry,
Expand Down Expand Up @@ -549,7 +571,6 @@ fn get<T: Clone + Send + Sync + 'static>(matches: &ArgMatches, name: &str) -> Op
matches.get_one::<T>(name).cloned()
}

/// Returns a list of `String` from the command line options `matches` given the option `name`.
pub fn get_strings(matches: &ArgMatches, name: &str) -> Option<Vec<String>> {
matches
.get_many::<String>(name)
Expand Down
9 changes: 5 additions & 4 deletions packages/hurl/src/json/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ impl HurlResult {
.map(|e| e.to_json(&self.filename, content))
.collect();
map.insert("entries".to_string(), serde_json::Value::Array(entries));
map.insert(
"success".to_string(),
serde_json::Value::Bool(self.clone().success),
);
map.insert("success".to_string(), serde_json::Value::Bool(self.success));
map.insert(
"time".to_string(),
serde_json::Value::Number(serde_json::Number::from(self.time_in_ms as u64)),
Expand All @@ -53,6 +50,10 @@ impl EntryResult {
fn to_json(&self, filename: &str, content: &str) -> serde_json::Value {
let mut map = serde_json::Map::new();

map.insert(
"index".to_string(),
serde_json::Value::Number(serde_json::Number::from(self.entry_index)),
);
let calls = self.calls.iter().map(|c| c.to_json()).collect();
map.insert("calls".to_string(), calls);
let captures = self.captures.iter().map(|c| c.to_json()).collect();
Expand Down
40 changes: 18 additions & 22 deletions packages/hurl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,7 @@ struct Progress {
pub total: usize,
}

/// Runs a Hurl file and returns a result.
///
/// # Arguments
///
/// * `filename` - Filename of the Hurl file, "-" is used for stdin
/// * `content` - Content of the Hurl file
/// * `current_dir` - The current directory of execution (absolute)
/// * `cli_options` - Options for this run
/// * `progress` - The progression of this execution
/// * `logger` - The logger
/// Runs a Hurl format `content` originated form the file `filename` and returns a result.
fn execute(
filename: &str,
content: &str,
Expand All @@ -114,14 +105,15 @@ fn execute(
Ok(hurl_file) => {
logger.debug_important("Options:");
logger.debug(format!(" fail fast: {}", cli_options.fail_fast).as_str());
logger.debug(format!(" insecure: {}", cli_options.insecure).as_str());
logger.debug(format!(" follow redirect: {}", cli_options.follow_location).as_str());
logger.debug(format!(" insecure: {}", cli_options.insecure).as_str());
if let Some(n) = cli_options.max_redirect {
logger.debug(format!(" max redirect: {}", n).as_str());
}
if let Some(proxy) = &cli_options.proxy {
logger.debug(format!(" proxy: {}", proxy).as_str());
}
logger.debug(format!(" retry: {}", cli_options.retry).as_str());
if !cli_options.variables.is_empty() {
logger.debug_important("Variables:");
for (name, value) in cli_options.variables.clone() {
Expand Down Expand Up @@ -176,29 +168,33 @@ fn execute(
let fail_fast = cli_options.fail_fast;
let variables = cli_options.variables.clone();
let to_entry = cli_options.to_entry;
let retry = cli_options.retry;
let retry_interval = cli_options.retry_interval;
let ignore_asserts = cli_options.ignore_asserts;
let very_verbose = cli_options.very_verbose;
let runner_options = RunnerOptions {
cacert_file,
compressed,
fail_fast,
to_entry,
user,
connect_timeout,
context_dir,
cookie_input_file,
fail_fast,
follow_location,
ignore_asserts,
insecure,
max_redirect,
very_verbose,
no_proxy,
post_entry,
pre_entry,
proxy,
post_entry,
connect_timeout,
cookie_input_file,
follow_location,
no_proxy,
retry,
retry_interval,
timeout,
to_entry,
user,
user_agent,
verbosity,
very_verbose,
};
let mut client = http::Client::new(runner_options.cookie_input_file.clone());
let result = runner::run(
Expand Down Expand Up @@ -335,7 +331,7 @@ fn main() {
hurl_results.push(hurl_result.clone());

if matches!(cli_options.output_type, OutputType::ResponseBody)
&& hurl_result.errors().is_empty()
&& hurl_result.success
&& !cli_options.interactive
{
// By default, we output the body response bytes of the last entry
Expand Down Expand Up @@ -444,7 +440,7 @@ fn exit_code(hurl_results: &[HurlResult]) -> i32 {
let mut count_errors_runner = 0;
let mut count_errors_assert = 0;
for hurl_result in hurl_results {
let errors = hurl_result.clone().errors();
let errors = hurl_result.errors();
if errors.is_empty() {
} else if errors.iter().filter(|e| !e.assert).count() == 0 {
count_errors_assert += 1;
Expand Down
4 changes: 3 additions & 1 deletion packages/hurl/src/report/junit/testcase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl Testcase {
let mut errors = vec![];

for error in hurl_result.errors() {
let message = cli::error_string_no_color(&hurl_result.filename, content, &error);
let message = cli::error_string_no_color(&hurl_result.filename, content, error);
if error.assert {
failures.push(message);
} else {
Expand Down Expand Up @@ -133,6 +133,7 @@ HTTP/1.0 200
let hurl_result = HurlResult {
filename: "test.hurl".to_string(),
entries: vec![EntryResult {
entry_index: 1,
calls: vec![],
captures: vec![],
asserts: vec![],
Expand Down Expand Up @@ -172,6 +173,7 @@ HTTP/1.0 200
let hurl_result = HurlResult {
filename: "test.hurl".to_string(),
entries: vec![EntryResult {
entry_index: 1,
calls: vec![],
captures: vec![],
asserts: vec![],
Expand Down
25 changes: 19 additions & 6 deletions packages/hurl/src/runner/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct RunnerOptions {
pub post_entry: Option<fn() -> bool>,
pub pre_entry: Option<fn(Entry) -> bool>,
pub proxy: Option<String>,
pub retry: bool,
pub retry_interval: Duration,
pub timeout: Duration,
pub to_entry: Option<usize>,
pub user: Option<String>,
Expand Down Expand Up @@ -71,6 +73,8 @@ impl Default for RunnerOptions {
post_entry: None,
pre_entry: None,
proxy: None,
retry: false,
retry_interval: Duration::from_millis(1000),
timeout: Duration::from_secs(300),
to_entry: None,
user: None,
Expand All @@ -91,17 +95,26 @@ pub struct HurlResult {
}

impl HurlResult {
pub fn errors(&self) -> Vec<Error> {
self.entries.iter().flat_map(|e| e.errors.clone()).collect()
}

pub fn success(&self) -> bool {
self.errors().is_empty()
pub fn errors(&self) -> Vec<&Error> {
let mut errors = vec![];
let mut next_entries = self.entries.iter().skip(1);
for entry in self.entries.iter() {
match next_entries.next() {
None => errors.extend(&entry.errors),
Some(next) => {
if next.entry_index != entry.entry_index {
errors.extend(&entry.errors)
}
}
}
}
errors
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EntryResult {
pub entry_index: usize,
pub calls: Vec<Call>,
pub captures: Vec<CaptureResult>,
pub asserts: Vec<AssertResult>,
Expand Down
5 changes: 5 additions & 0 deletions packages/hurl/src/runner/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::runner::template::eval_template;
/// by captures.
pub fn run(
entry: &Entry,
entry_index: usize,
http_client: &mut http::Client,
variables: &mut HashMap<String, Value>,
runner_options: &RunnerOptions,
Expand All @@ -46,6 +47,7 @@ pub fn run(
Ok(r) => r,
Err(error) => {
return EntryResult {
entry_index,
calls: vec![],
captures: vec![],
asserts: vec![],
Expand Down Expand Up @@ -100,6 +102,7 @@ pub fn run(
assert: false,
};
return EntryResult {
entry_index,
calls: vec![],
captures: vec![],
asserts: vec![],
Expand Down Expand Up @@ -128,6 +131,7 @@ pub fn run(
Ok(captures) => captures,
Err(e) => {
return EntryResult {
entry_index,
calls,
captures: vec![],
asserts: vec![],
Expand Down Expand Up @@ -181,6 +185,7 @@ pub fn run(
.collect();

EntryResult {
entry_index,
calls,
captures,
asserts,
Expand Down
Loading