Implement repeat for sequential runner.

This commit is contained in:
Jean-Christophe Amiel 2024-06-10 17:32:03 +02:00
parent 16f902a7e1
commit 54f3bf5b1a
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
13 changed files with 248 additions and 5 deletions

View File

@ -0,0 +1,8 @@
from app import app
from flask import request
@app.route("/repeat/hello")
def repeat_hello():
name = request.args.get("name")
return f"Hello {name}!\n"

View File

@ -0,0 +1,2 @@
GET http://localhost:8000/repeat/hello?name=A
HTTP 200

View File

@ -0,0 +1,2 @@
GET http://localhost:8000/repeat/hello?name=B
HTTP 200

View File

@ -0,0 +1,2 @@
GET http://localhost:8000/repeat/hello?name=C
HTTP 200

View File

@ -0,0 +1,14 @@
TAP version 13
1..12
ok 1 - tests_ok/repeat_a.hurl
ok 2 - tests_ok/repeat_b.hurl
ok 3 - tests_ok/repeat_c.hurl
ok 4 - tests_ok/repeat_a.hurl
ok 5 - tests_ok/repeat_b.hurl
ok 6 - tests_ok/repeat_c.hurl
ok 7 - tests_ok/repeat_a.hurl
ok 8 - tests_ok/repeat_b.hurl
ok 9 - tests_ok/repeat_c.hurl
ok 10 - tests_ok/repeat_a.hurl
ok 11 - tests_ok/repeat_b.hurl
ok 12 - tests_ok/repeat_c.hurl

View File

@ -0,0 +1,12 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
if (Test-Path build/repeat/tap.txt) {
Remove-Item build/repeat/tap.txt
}
hurl --repeat 4 --parallel --report-tap build/repeat/tap.txt --no-output `
tests_ok/repeat_a.hurl `
tests_ok/repeat_b.hurl `
tests_ok/repeat_c.hurl
Write-Host (Get-Content build/repeat/tap.txt -Raw) -NoNewLine

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -Eeuo pipefail
rm -f build/repeat/tap.txt
hurl --repeat 4 --parallel --report-tap build/repeat/tap.txt --no-output \
tests_ok/repeat_a.hurl \
tests_ok/repeat_b.hurl \
tests_ok/repeat_c.hurl
cat build/repeat/tap.txt

View File

@ -0,0 +1,12 @@
Hello A!
Hello B!
Hello C!
Hello A!
Hello B!
Hello C!
Hello A!
Hello B!
Hello C!
Hello A!
Hello B!
Hello C!

View File

@ -0,0 +1,7 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --repeat 4 `
tests_ok/repeat_a.hurl `
tests_ok/repeat_b.hurl `
tests_ok/repeat_c.hurl

View File

@ -0,0 +1,7 @@
#!/bin/bash
set -Eeuo pipefail
hurl --repeat 4 \
tests_ok/repeat_a.hurl \
tests_ok/repeat_b.hurl \
tests_ok/repeat_c.hurl

View File

@ -156,6 +156,12 @@ pub enum Repeat {
Forever, Forever,
} }
impl Default for Repeat {
fn default() -> Self {
Repeat::Count(1)
}
}
fn get_version() -> String { fn get_version() -> String {
let libcurl_version = http::libcurl_version_info(); let libcurl_version = http::libcurl_version_info();
format!( format!(

View File

@ -128,9 +128,9 @@ impl Iterator for JobQueue<'_> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.jobs.len() { if self.index >= self.jobs.len() {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
match self.repeat { match self.repeat {
Repeat::Count(n) => { Repeat::Count(n) => {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
if self.repeat_index >= n { if self.repeat_index >= n {
None None
} else { } else {
@ -149,3 +149,61 @@ impl Iterator for JobQueue<'_> {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::parallel::job::{Job, JobQueue};
use crate::parallel::runner::Repeat;
use crate::runner::{Input, RunnerOptionsBuilder};
use crate::util::logger::LoggerOptionsBuilder;
use std::collections::HashMap;
fn new_job(file: &str, index: usize) -> Job {
let variables = HashMap::new();
let runner_options = RunnerOptionsBuilder::default().build();
let logger_options = LoggerOptionsBuilder::default().build();
Job::new(
&Input::new(file),
index,
&runner_options,
&variables,
&logger_options,
)
}
#[test]
fn job_queue_is_finite() {
let jobs = [
new_job("a.hurl", 0),
new_job("b.hurl", 1),
new_job("c.hurl", 2),
];
let mut queue = JobQueue::new(&jobs, Repeat::Count(2));
assert_eq!(queue.next(), Some(new_job("a.hurl", 0)));
assert_eq!(queue.next(), Some(new_job("b.hurl", 1)));
assert_eq!(queue.next(), Some(new_job("c.hurl", 2)));
assert_eq!(queue.next(), Some(new_job("a.hurl", 3)));
assert_eq!(queue.next(), Some(new_job("b.hurl", 4)));
assert_eq!(queue.next(), Some(new_job("c.hurl", 5)));
assert_eq!(queue.next(), None);
assert_eq!(queue.jobs_count(), Some(6));
}
#[test]
fn input_queue_is_infinite() {
let jobs = [new_job("foo.hurl", 0)];
let mut queue = JobQueue::new(&jobs, Repeat::Forever);
assert_eq!(queue.next(), Some(new_job("foo.hurl", 0)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 1)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 2)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 3)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 4)));
// etc...
assert_eq!(queue.jobs_count(), None);
}
}

View File

@ -39,7 +39,10 @@ pub fn run_seq(
) -> Result<Vec<HurlRun>, CliError> { ) -> Result<Vec<HurlRun>, CliError> {
let mut runs = vec![]; let mut runs = vec![];
for filename in files.iter() { let repeat = options.repeat.unwrap_or_default();
let queue = InputQueue::new(files, repeat);
for filename in queue {
let content = filename.read_to_string(); let content = filename.read_to_string();
let content = match content { let content = match content {
Ok(c) => c, Ok(c) => c,
@ -49,8 +52,8 @@ pub fn run_seq(
} }
}; };
let variables = &options.variables; let variables = &options.variables;
let runner_options = options.to_runner_options(filename, current_dir); let runner_options = options.to_runner_options(&filename, current_dir);
let logger_options = options.to_logger_options(filename); let logger_options = options.to_logger_options(&filename);
// Run our Hurl file now, we can only fail if there is a parsing error. // Run our Hurl file now, we can only fail if there is a parsing error.
// The parsing error is displayed in the `execute` call, that's why we gobble the error // The parsing error is displayed in the `execute` call, that's why we gobble the error
@ -64,7 +67,7 @@ pub fn run_seq(
// representation of the full Hurl result. // representation of the full Hurl result.
// In sequential run, we use an immediate (non-buffered) standard output. // In sequential run, we use an immediate (non-buffered) standard output.
let mut stdout = Stdout::new(WriteMode::Immediate); let mut stdout = Stdout::new(WriteMode::Immediate);
print_output(&hurl_result, &content, filename, options, &mut stdout)?; print_output(&hurl_result, &content, &filename, options, &mut stdout)?;
let run = HurlRun { let run = HurlRun {
content, content,
@ -208,3 +211,103 @@ impl From<Repeat> for parallel::runner::Repeat {
} }
} }
} }
/// An input queue to manage a queue of [`Input`].
///
/// The queue implements [`Iterator`] trait, and can return a new input to use each time its
/// `next` method is called. This queue can repeat its input sequence a certain number of times, or
/// can loop forever.
pub struct InputQueue<'a> {
/// The input list.
inputs: &'a [Input],
/// Current index of the input, referencing the input list.
index: usize,
/// Repeat mode of this queue (finite or infinite).
repeat: Repeat,
/// Current index of the repeat.
repeat_index: usize,
}
impl<'a> InputQueue<'a> {
/// Create a new queue, with a list of `inputs` and a `repeat` mode.
pub fn new(inputs: &'a [Input], repeat: Repeat) -> Self {
InputQueue {
inputs,
index: 0,
repeat,
repeat_index: 0,
}
}
/// Returns a new input at the given `index`.
fn input_at(&self, index: usize) -> Input {
self.inputs[index].clone()
}
}
impl Iterator for InputQueue<'_> {
type Item = Input;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.inputs.len() {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
match self.repeat {
Repeat::Count(n) => {
if self.repeat_index >= n {
None
} else {
self.index = 1;
Some(self.input_at(0))
}
}
Repeat::Forever => {
self.index = 1;
Some(self.input_at(0))
}
}
} else {
self.index += 1;
Some(self.input_at(self.index - 1))
}
}
}
#[cfg(test)]
mod tests {
use crate::cli::options::Repeat;
use crate::run::InputQueue;
use hurl::runner::Input;
#[test]
fn input_queue_is_finite() {
let files = [Input::new("a"), Input::new("b"), Input::new("c")];
let mut queue = InputQueue::new(&files, Repeat::Count(4));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), None);
}
#[test]
fn input_queue_is_infinite() {
let files = [Input::new("a")];
let mut queue = InputQueue::new(&files, Repeat::Forever);
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a")));
// etc...
}
}