Skip to content

Commit

Permalink
Add --retry and --retry-interval options.
Browse files Browse the repository at this point in the history
  • Loading branch information
jcamiel committed Oct 16, 2022
1 parent b912a6f commit a58d4f2
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 99 deletions.
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

0 comments on commit a58d4f2

Please sign in to comment.