Update output

Can now be on the following:
- Binary response body
- JSON
- Textual Summary
This commit is contained in:
Fabrice Reix 2021-11-08 20:54:19 +01:00 committed by Fabrice Reix
parent 359f153e63
commit dd98920f80
19 changed files with 325 additions and 413 deletions

View File

@ -2,13 +2,10 @@
import json import json
import sys import sys
def check(expected, actual):
def check_file(expected_file, actual_file): expected_test = json.loads(expected)
expected_tests = json.loads(open(expected_file).read()) actual_test = json.loads(actual)
actual_tests = json.loads(open(actual_file).read()) check_output(expected_test, actual_test)
assert len(expected_tests) == 1
assert len(actual_tests) == 1
check_output(expected_tests[0], actual_tests[0])
def check_output(expected_test, actual_test): def check_output(expected_test, actual_test):
@ -77,7 +74,9 @@ def main():
sys.exit(1) sys.exit(1)
expected_file = sys.argv[1] expected_file = sys.argv[1]
actual_file = sys.argv[2] actual_file = sys.argv[2]
check_file(expected_file, actual_file) expected = open(expected_file).read()
actual = open(actual_file).read()
check_file(expected, actual)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -43,12 +43,9 @@ def test(hurl_file):
options = open(options_file).read().strip().split(' ') options = open(options_file).read().strip().split(' ')
if os.path.exists(curl_file): if os.path.exists(curl_file):
options.append('--verbose') options.append('--verbose')
if os.path.exists(json_output_file):
json_output_actual_file = os.path.join(tempfile.mkdtemp(), 'output.json')
options.append('--json')
options.append(json_output_actual_file)
if os.path.exists(json_output_file):
options.append('--json')
cmd = ['hurl', hurl_file] + options cmd = ['hurl', hurl_file] + options
print(' '.join(cmd)) print(' '.join(cmd))
@ -75,6 +72,12 @@ def test(hurl_file):
print(f'actual: <{actual}>\nexpected: <{expected}>') print(f'actual: <{actual}>\nexpected: <{expected}>')
sys.exit(1) sys.exit(1)
# stdout (json)
if os.path.exists(json_output_file):
expected = open(json_output_file).read()
actual = result.stdout
check_json_output.check(expected, actual)
# stderr # stderr
f = hurl_file.replace('.hurl', '.' + get_os() + '.err') f = hurl_file.replace('.hurl', '.' + get_os() + '.err')
if os.path.exists(f): if os.path.exists(f):
@ -119,9 +122,6 @@ def test(hurl_file):
print('actual: %s' % actual_commands[i]) print('actual: %s' % actual_commands[i])
sys.exit(1) sys.exit(1)
if os.path.exists(json_output_file):
check_json_output.check_file(json_output_file, json_output_actual_file)
def main(): def main():
for hurl_file in sys.argv[1:]: for hurl_file in sys.argv[1:]:

View File

@ -1,71 +1,69 @@
[ {
{ "filename": "tests/assert_header.hurl",
"filename": "tests/assert_header.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/assert-header"
"url": "http://localhost:8000/assert-header" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"line": 2,
"success": true
}, },
"response": { {
"httpVersion": "HTTP/1.0", "line": 2,
"status": 200 "success": true
}, },
"asserts": [ {
{ "line": 3,
"line": 2, "success": true
"success": true },
}, {
{ "line": 4,
"line": 2, "success": true
"success": true },
}, {
{ "line": 5,
"line": 3, "success": true
"success": true },
}, {
{ "line": 7,
"line": 4, "success": true
"success": true },
}, {
{ "line": 8,
"line": 5, "success": true
"success": true },
}, {
{ "line": 9,
"line": 7, "success": true
"success": true },
}, {
{ "line": 10,
"line": 8, "success": true
"success": true },
}, {
{ "line": 11,
"line": 9, "success": true
"success": true },
}, {
{ "line": 12,
"line": 10, "success": true
"success": true },
}, {
{ "line": 13,
"line": 11, "success": true
"success": true },
}, {
{ "line": 14,
"line": 12, "success": true
"success": true }
}, ]
{ }
"line": 13, ]
"success": true }
},
{
"line": 14,
"success": true
}
]
}
]
}
]

View File

@ -1,32 +1,31 @@
[ {
{ "filename": "tests/error_assert_match_utf8.hurl",
"filename": "tests/error_assert_match_utf8.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/error-assert/match-utf8"
"url": "http://localhost:8000/error-assert/match-utf8" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"success": true,
"line": 2
}, },
"response": { {
"httpVersion": "HTTP/1.0", "success": true,
"status": 200 "line": 2
}, },
"asserts": [ {
{ "success": false,
"success": true, "message": "Invalid Decoding\n --> tests/error_assert_match_utf8.hurl:4:1\n |\n 4 | body matches \".*\"\n | ^^^^ The body can not be decoded with charset 'utf-8'\n |",
"line": 2 "line": 4
}, }
{ ]
"success": true, }
"line": 2 ]
}, }
{
"success": false,
"message": "Invalid Decoding\n --> tests/error_assert_match_utf8.hurl:4:1\n |\n 4 | body matches \".*\"\n | ^^^^ The body can not be decoded with charset 'utf-8'\n |",
"line": 4
}
]
}
]
}
]

View File

@ -1,28 +1,26 @@
[ {
{ "filename": "tests/error_assert_status.hurl",
"filename": "tests/error_assert_status.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/not_found"
"url": "http://localhost:8000/not_found" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 404
},
"asserts": [
{
"success": true,
"line": 2
}, },
"response": { {
"httpVersion": "HTTP/1.0", "success": false,
"status": 404 "message": "Assert Status\n --> tests/error_assert_status.hurl:2:10\n |\n 2 | HTTP/1.0 200\n | ^^^ actual value is <404>\n |",
}, "line": 2
"asserts": [ }
{ ]
"success": true, }
"line": 2 ]
}, }
{
"success": false,
"message": "Assert Status\n --> tests/error_assert_status.hurl:2:10\n |\n 2 | HTTP/1.0 200\n | ^^^ actual value is <404>\n |",
"line": 2
}
]
}
]
}
]

View File

@ -0,0 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-template-variable-not-found"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"header","name":"content-type"},"predicate":{"type":"equal","value":"{{content_type}}"}}]}}]}

View File

@ -1,32 +1,30 @@
[ {
{ "filename": "tests/error_assert_template_variable_not_found.hurl",
"filename": "tests/error_assert_template_variable_not_found.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/error-assert-template-variable-not-found"
"url": "http://localhost:8000/error-assert-template-variable-not-found" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"success": true,
"line": 2
}, },
"response": { {
"httpVersion": "HTTP/1.0", "success": true,
"status": 200 "line": 2
}, },
"asserts": [ {
{ "success": false,
"success": true, "message": "Undefined Variable\n --> tests/error_assert_template_variable_not_found.hurl:4:33\n |\n 4 | header \"content-type\" equals \"{{content_type}}\"\n | ^^^^^^^^^^^^ You must set the variable content_type\n |",
"line": 2 "line": 4
}, }
{ ]
"success": true, }
"line": 2 ]
}, }
{
"success": false,
"message": "Undefined Variable\n --> tests/error_assert_template_variable_not_found.hurl:4:33\n |\n 4 | header \"content-type\" equals \"{{content_type}}\"\n | ^^^^^^^^^^^^ You must set the variable content_type\n |",
"line": 4
}
]
}
]
}
]

View File

@ -1,67 +1,65 @@
[ {
{ "filename": "tests/error_assert_value_error.hurl",
"filename": "tests/error_assert_value_error.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/error-assert-value"
"url": "http://localhost:8000/error-assert-value" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"success": true,
"line": 2
}, },
"response": { {
"httpVersion": "HTTP/1.0", "success": true,
"status": 200 "line": 2
}, },
"asserts": [ {
{ "success": false,
"success": true, "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:4:0\n |\n 4 | header \"content-type\" equals \"XXX\"\n | actual: string <text/html; charset=utf-8>\n | expected: string <XXX>\n |",
"line": 2 "line": 4
}, },
{ {
"success": true, "success": false,
"line": 2 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:5:0\n |\n 5 | header \"content-type\" notEquals \"text/html; charset=utf-8\"\n | actual: string <text/html; charset=utf-8>\n | expected: string <text/html; charset=utf-8>\n |",
}, "line": 5
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:4:0\n |\n 4 | header \"content-type\" equals \"XXX\"\n | actual: string <text/html; charset=utf-8>\n | expected: string <XXX>\n |", "success": false,
"line": 4 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:6:0\n |\n 6 | jsonpath \"$.id\" equals \"000001\"\n | actual: none\n | expected: string <000001>\n |",
}, "line": 6
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:5:0\n |\n 5 | header \"content-type\" notEquals \"text/html; charset=utf-8\"\n | actual: string <text/html; charset=utf-8>\n | expected: string <text/html; charset=utf-8>\n |", "success": false,
"line": 5 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:7:0\n |\n 7 | jsonpath \"$.values\" includes 100\n | actual: [int <1>, int <2>, int <3>]\n | expected: includes int <100>\n |",
}, "line": 7
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:6:0\n |\n 6 | jsonpath \"$.id\" equals \"000001\"\n | actual: none\n | expected: string <000001>\n |", "success": false,
"line": 6 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:8:0\n |\n 8 | jsonpath \"$.values\" not contains \"Hello\"\n | actual: [int <1>, int <2>, int <3>]\n | expected: not contains string <Hello>\n | >>> types between actual and expected are not consistent\n |",
}, "line": 8
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:7:0\n |\n 7 | jsonpath \"$.values\" includes 100\n | actual: [int <1>, int <2>, int <3>]\n | expected: includes int <100>\n |", "success": false,
"line": 7 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:9:0\n |\n 9 | jsonpath \"$.count\" greaterThan 5\n | actual: int <2>\n | expected: greater than int <5>\n |",
}, "line": 9
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:8:0\n |\n 8 | jsonpath \"$.values\" not contains \"Hello\"\n | actual: [int <1>, int <2>, int <3>]\n | expected: not contains string <Hello>\n | >>> types between actual and expected are not consistent\n |", "success": false,
"line": 8 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:10:0\n |\n10 | jsonpath \"$.count\" isFloat\n | actual: int <2>\n | expected: float\n |",
}, "line": 10
{ },
"success": false, {
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:9:0\n |\n 9 | jsonpath \"$.count\" greaterThan 5\n | actual: int <2>\n | expected: greater than int <5>\n |", "success": false,
"line": 9 "message": "Assert Failure\n --> tests/error_assert_value_error.hurl:11:0\n |\n11 | bytes contains hex,00;\n | actual: byte array <7b202276616c756573223a205b312c322c335d2c2022636f756e74223a20327d>\n | expected: contains byte array <00>\n |",
}, "line": 11
{ }
"success": false, ]
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:10:0\n |\n10 | jsonpath \"$.count\" isFloat\n | actual: int <2>\n | expected: float\n |", }
"line": 10 ]
}, }
{
"success": false,
"message": "Assert Failure\n --> tests/error_assert_value_error.hurl:11:0\n |\n11 | bytes contains hex,00;\n | actual: byte array <7b202276616c756573223a205b312c322c335d2c2022636f756e74223a20327d>\n | expected: contains byte array <00>\n |",
"line": 11
}
]
}
]
}
]

View File

@ -1,32 +1,30 @@
[ {
{ "filename": "tests/error_query_header_not_found.hurl",
"filename": "tests/error_query_header_not_found.hurl", "entries": [
"entries": [ {
{ "request": {
"request": { "method": "GET",
"method": "GET", "url": "http://localhost:8000/error-query-header-not-found"
"url": "http://localhost:8000/error-query-header-not-found" },
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"success": true,
"line": 2
}, },
"response": { {
"httpVersion": "HTTP/1.0", "success": true,
"status": 200 "line": 2
}, },
"asserts": [ {
{ "success": false,
"success": true, "message": "Header not Found\n --> tests/error_query_header_not_found.hurl:3:1\n |\n 3 | Custom: XXX\n | ^^^^^^ This header has not been found in the response\n |",
"line": 2 "line": 3
}, }
{ ]
"success": true, }
"line": 2 ]
}, }
{
"success": false,
"message": "Header not Found\n --> tests/error_query_header_not_found.hurl:3:1\n |\n 3 | Custom: XXX\n | ^^^^^^ This header has not been found in the response\n |",
"line": 3
}
]
}
]
}
]

View File

@ -1,62 +0,0 @@
[
{
"filename": "tests/hello.hurl",
"entries": [
{
"request": {
"method": "GET",
"url": "http://localhost:8000/hello"
},
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
},
"asserts": [
{
"success": true,
"line": 2
},
{
"success": true,
"line": 2
},
{
"success": true,
"line": 3
}
]
},
{
"request": {
"method": "GET",
"url": "http://localhost:8000/hello"
},
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
}
},
{
"request": {
"method": "GET",
"url": "http://localhost:8000/hello"
},
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
}
},
{
"request": {
"method": "GET",
"url": "http://localhost:8000/hello"
},
"response": {
"httpVersion": "HTTP/1.0",
"status": 200
}
}
],
"success": true
}
]

View File

@ -1 +0,0 @@
0

View File

@ -1,5 +0,0 @@
GET http://localhost:8000/test-mode
HTTP/1.0 200
```Hello World!```

View File

@ -1 +0,0 @@
--test

View File

@ -1,10 +0,0 @@
from tests import app
from flask import request
@app.route("/test-mode")
def test_mode():
assert 'Content-Type' not in request.headers
assert 'Content-Length' not in request.headers
assert len(request.data) == 0
return 'Hello World!'

View File

@ -26,7 +26,7 @@ pub use self::logger::{
pub use self::options::app; pub use self::options::app;
pub use self::options::output_color; pub use self::options::output_color;
pub use self::options::parse_options; pub use self::options::parse_options;
pub use self::options::CliOptions; pub use self::options::{CliOptions, OutputType};
pub use self::variables::parse as parse_variable; pub use self::variables::parse as parse_variable;
mod fs; mod fs;

View File

@ -28,16 +28,6 @@ use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
#[cfg(target_family = "unix")]
pub fn dev_null() -> String {
"/dev/null".to_string()
}
#[cfg(target_family = "windows")]
pub fn dev_null() -> String {
"nul".to_string()
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CliOptions { pub struct CliOptions {
pub cacert_file: Option<String>, pub cacert_file: Option<String>,
@ -54,14 +44,13 @@ pub struct CliOptions {
pub include: bool, pub include: bool,
pub insecure: bool, pub insecure: bool,
pub interactive: bool, pub interactive: bool,
pub json_file: Option<PathBuf>,
pub junit_file: Option<PathBuf>, pub junit_file: Option<PathBuf>,
pub max_redirect: Option<usize>, pub max_redirect: Option<usize>,
pub no_proxy: Option<String>, pub no_proxy: Option<String>,
pub output: Option<String>, pub output: Option<String>,
pub output_type: OutputType,
pub progress: bool, pub progress: bool,
pub proxy: Option<String>, pub proxy: Option<String>,
pub summary: bool,
pub timeout: Duration, pub timeout: Duration,
pub to_entry: Option<usize>, pub to_entry: Option<usize>,
pub user: Option<String>, pub user: Option<String>,
@ -69,6 +58,12 @@ pub struct CliOptions {
pub verbose: bool, pub verbose: bool,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OutputType {
ResponseBody,
Summary,
Json,
}
pub fn app() -> App<'static, 'static> { pub fn app() -> App<'static, 'static> {
App::new("hurl") App::new("hurl")
.about("Run hurl FILE(s) or standard input") .about("Run hurl FILE(s) or standard input")
@ -169,9 +164,8 @@ pub fn app() -> App<'static, 'static> {
.arg( .arg(
clap::Arg::with_name("json") clap::Arg::with_name("json")
.long("json") .long("json")
.value_name("FILE") .conflicts_with("summary")
.help("Write full session(s) to json file") .help("Write full session(s) to json output"),
.takes_value(true),
) )
.arg( .arg(
clap::Arg::with_name("junit") clap::Arg::with_name("junit")
@ -237,12 +231,13 @@ pub fn app() -> App<'static, 'static> {
.arg( .arg(
clap::Arg::with_name("summary") clap::Arg::with_name("summary")
.long("summary") .long("summary")
.conflicts_with("json")
.help("Print test metrics at the end of the run"), .help("Print test metrics at the end of the run"),
) )
.arg( .arg(
clap::Arg::with_name("test") clap::Arg::with_name("test")
.long("test") .long("test")
.help("Activate test mode; equals --output /dev/null --progress --summary"), .help("This option has been deprecated. It will be removed in the next release"),
) )
.arg( .arg(
clap::Arg::with_name("to_entry") clap::Arg::with_name("to_entry")
@ -355,12 +350,6 @@ pub fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
let include = matches.is_present("include"); let include = matches.is_present("include");
let insecure = matches.is_present("insecure"); let insecure = matches.is_present("insecure");
let interactive = matches.is_present("interactive"); let interactive = matches.is_present("interactive");
let json_file = if let Some(filename) = matches.value_of("json") {
let path = Path::new(filename);
Some(path.to_path_buf())
} else {
None
};
let junit_file = if let Some(filename) = matches.value_of("junit") { let junit_file = if let Some(filename) = matches.value_of("junit") {
let path = Path::new(filename); let path = Path::new(filename);
Some(path.to_path_buf()) Some(path.to_path_buf())
@ -380,16 +369,24 @@ pub fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
}, },
}; };
let no_proxy = matches.value_of("proxy").map(|x| x.to_string()); let no_proxy = matches.value_of("proxy").map(|x| x.to_string());
let output = if let Some(filename) = matches.value_of("output") { let output = matches
Some(filename.to_string()) .value_of("output")
} else if matches.is_present("test") { .map(|filename| filename.to_string());
Some(dev_null()) let output_type = if matches.is_present("summary") {
OutputType::Summary
} else if matches.is_present("json") {
OutputType::Json
} else { } else {
None OutputType::ResponseBody
}; };
let progress = matches.is_present("progress") || matches.is_present("test"); let progress = matches.is_present("progress") || matches.is_present("test");
let proxy = matches.value_of("proxy").map(|x| x.to_string()); let proxy = matches.value_of("proxy").map(|x| x.to_string());
let summary = matches.is_present("summary") || matches.is_present("test");
if matches.is_present("test") {
eprintln!("The option --test is deprecated");
eprintln!("It will be removed in the next version");
}
let timeout = match matches.value_of("max_time") { let timeout = match matches.value_of("max_time") {
None => ClientOptions::default().timeout, None => ClientOptions::default().timeout,
Some(s) => match s.parse::<u64>() { Some(s) => match s.parse::<u64>() {
@ -422,13 +419,12 @@ pub fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
insecure, insecure,
interactive, interactive,
junit_file, junit_file,
json_file,
max_redirect, max_redirect,
no_proxy, no_proxy,
output, output,
output_type,
progress, progress,
proxy, proxy,
summary,
timeout, timeout,
to_entry, to_entry,
user, user,

View File

@ -27,9 +27,8 @@ use colored::*;
use curl::Version; use curl::Version;
use hurl::cli; use hurl::cli;
use hurl::cli::{CliError, CliOptions}; use hurl::cli::{CliError, CliOptions, OutputType};
use hurl::http; use hurl::http;
use hurl::json;
use hurl::report; use hurl::report;
use hurl::runner; use hurl::runner;
use hurl::runner::{HurlResult, RunnerOptions}; use hurl::runner::{HurlResult, RunnerOptions};
@ -292,11 +291,6 @@ fn main() {
}; };
let start = Instant::now(); let start = Instant::now();
let mut json_results = vec![];
if let Some(file_path) = cli_options.json_file.clone() {
json_results = unwrap_or_exit(&log_error_message, json::parse_json(file_path));
}
for (current, filename) in filenames.iter().enumerate() { for (current, filename) in filenames.iter().enumerate() {
let contents = match cli::read_to_string(filename) { let contents = match cli::read_to_string(filename) {
@ -368,10 +362,13 @@ fn main() {
response.body response.body
}; };
output.append(&mut body.clone()); output.append(&mut body.clone());
unwrap_or_exit(
&log_error_message, if matches!(cli_options.output_type, OutputType::ResponseBody) {
write_output(output, cli_options.output.clone()), unwrap_or_exit(
); &log_error_message,
write_output(output, cli_options.output.clone()),
);
}
} else { } else {
cli::log_info("no response has been received"); cli::log_info("no response has been received");
} }
@ -390,25 +387,20 @@ fn main() {
hurl_results.push(hurl_result.clone()); hurl_results.push(hurl_result.clone());
if cli_options.json_file.is_some() { if matches!(cli_options.output_type, OutputType::Json) {
let lines: Vec<String> = regex::Regex::new(r"\n|\r\n") let lines: Vec<String> = regex::Regex::new(r"\n|\r\n")
.unwrap() .unwrap()
.split(&contents) .split(&contents)
.map(|l| l.to_string()) .map(|l| l.to_string())
.collect(); .collect();
let json_result = hurl_result.to_json(&lines); let json_result = hurl_result.to_json(&lines);
json_results.push(json_result); let serialized = serde_json::to_string(&json_result).unwrap();
unwrap_or_exit(
&log_error_message,
write_output(serialized.into_bytes(), cli_options.output.clone()),
);
} }
} }
let duration = start.elapsed().as_millis();
if let Some(file_path) = cli_options.json_file.clone() {
log_verbose(format!("Writing json report to {}", file_path.display()).as_str());
unwrap_or_exit(
&log_error_message,
json::write_json_report(file_path, json_results),
);
}
if let Some(junit_path) = cli_options.junit_file { if let Some(junit_path) = cli_options.junit_file {
log_verbose(format!("Writing Junit report to {}", junit_path.display()).as_str()); log_verbose(format!("Writing Junit report to {}", junit_path.display()).as_str());
@ -438,8 +430,13 @@ fn main() {
); );
} }
if cli_options.summary { if matches!(cli_options.output_type, OutputType::Summary) {
print_summary(duration, hurl_results.clone()) let duration = start.elapsed().as_millis();
let summary = get_summary(duration, hurl_results.clone());
unwrap_or_exit(
&log_error_message,
write_output(summary.into_bytes(), cli_options.output.clone()),
);
} }
std::process::exit(exit_code(hurl_results)); std::process::exit(exit_code(hurl_results));
@ -558,23 +555,32 @@ fn write_cookies_file(file_path: PathBuf, hurl_results: Vec<HurlResult>) -> Resu
Ok(()) Ok(())
} }
fn print_summary(duration: u128, hurl_results: Vec<HurlResult>) { fn get_summary(duration: u128, hurl_results: Vec<HurlResult>) -> String {
let total = hurl_results.len(); let total = hurl_results.len();
let success = hurl_results.iter().filter(|r| r.success).count(); let success = hurl_results.iter().filter(|r| r.success).count();
let failed = total - success; let failed = total - success;
eprintln!("--------------------------------------------------------------------------------"); let mut s =
eprintln!("Executed: {}", total); "--------------------------------------------------------------------------------\n"
eprintln!( .to_string();
"Succeeded: {} ({:.1}%)", s.push_str(format!("Executed: {}\n", total).as_str());
success, s.push_str(
100.0 * success as f32 / total as f32 format!(
"Succeeded: {} ({:.1}%)\n",
success,
100.0 * success as f32 / total as f32
)
.as_str(),
); );
eprintln!( s.push_str(
"Failed: {} ({:.1}%)", format!(
failed, "Failed: {} ({:.1}%)\n",
100.0 * failed as f32 / total as f32 failed,
100.0 * failed as f32 / total as f32
)
.as_str(),
); );
eprintln!("Duration: {}ms", duration); s.push_str(format!("Duration: {}ms\n", duration).as_str());
s
} }
fn get_version_info() -> String { fn get_version_info() -> String {