mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-23 19:12:06 +03:00
Use an uuid as identifier for the HTML Hurl file run report
This commit is contained in:
parent
2a526d7b0b
commit
5939501adb
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -522,6 +522,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"termion",
|
"termion",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
"winres",
|
"winres",
|
||||||
"xmltree",
|
"xmltree",
|
||||||
]
|
]
|
||||||
@ -1193,6 +1194,16 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -42,6 +42,8 @@ sha2 = "0.10.6"
|
|||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
xmltree = { version = "0.10.3", features = ["attribute-order"] }
|
xmltree = { version = "0.10.3", features = ["attribute-order"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
# uuid features: lets you generate random UUIDs and use a faster (but still sufficiently random) RNG
|
||||||
|
uuid = { version = "1.3.0", features = ["v4" , "fast-rng"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
termion = "2.0.1"
|
termion = "2.0.1"
|
||||||
|
@ -21,35 +21,42 @@
|
|||||||
mod testcase;
|
mod testcase;
|
||||||
|
|
||||||
use crate::report::Error;
|
use crate::report::Error;
|
||||||
use crate::util::path;
|
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
pub use testcase::Testcase;
|
pub use testcase::Testcase;
|
||||||
|
|
||||||
/// The test result to be displayed in an HTML page
|
/// The test result to be displayed in an HTML page
|
||||||
///
|
|
||||||
/// The filename has been [canonicalized] (https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.canonicalize)
|
|
||||||
/// and does not need to exist in the filesystem
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct HTMLResult {
|
struct HTMLResult {
|
||||||
|
/// Original filename, as given in the run execution
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
|
/// The id of the corresponding [`Testcase`]
|
||||||
|
pub id: String,
|
||||||
pub time_in_ms: u128,
|
pub time_in_ms: u128,
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HTMLResult {
|
||||||
|
/// Creates a new HTMLResult from a [`Testcase`].
|
||||||
|
fn from(testcase: &Testcase) -> Self {
|
||||||
|
HTMLResult {
|
||||||
|
filename: testcase.filename.clone(),
|
||||||
|
id: testcase.id.clone(),
|
||||||
|
time_in_ms: testcase.time_in_ms,
|
||||||
|
success: testcase.success,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates and HTML report for this list of [`Testcase`] at `dir_path`/index.html.
|
/// Creates and HTML report for this list of [`Testcase`] at `dir_path`/index.html.
|
||||||
///
|
///
|
||||||
/// If the report already exists, results are merged.
|
/// If the report already exists, results are merged.
|
||||||
pub fn write_report(dir_path: &Path, testcases: &[Testcase]) -> Result<(), Error> {
|
pub fn write_report(dir_path: &Path, testcases: &[Testcase]) -> Result<(), Error> {
|
||||||
let index_path = dir_path.join("index.html");
|
let index_path = dir_path.join("index.html");
|
||||||
let mut results = parse_html(&index_path)?;
|
let mut results = parse_html(&index_path)?;
|
||||||
for testcase in testcases {
|
for testcase in testcases.iter() {
|
||||||
let html_result = HTMLResult {
|
let html_result = HTMLResult::from(testcase);
|
||||||
filename: path::canonicalize_filename(&testcase.filename),
|
|
||||||
time_in_ms: testcase.time_in_ms,
|
|
||||||
success: testcase.success,
|
|
||||||
};
|
|
||||||
results.push(html_result);
|
results.push(html_result);
|
||||||
}
|
}
|
||||||
let now: DateTime<Local> = Local::now();
|
let now: DateTime<Local> = Local::now();
|
||||||
@ -111,19 +118,20 @@ fn parse_html_report(html: &str) -> Vec<HTMLResult> {
|
|||||||
data-status="(?P<status>[a-z]+)"
|
data-status="(?P<status>[a-z]+)"
|
||||||
\s+
|
\s+
|
||||||
data-filename="(?P<filename>[A-Za-z0-9_./-]+)"
|
data-filename="(?P<filename>[A-Za-z0-9_./-]+)"
|
||||||
|
\s+
|
||||||
|
data-id="(?P<id>[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})"
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
re.captures_iter(html)
|
re.captures_iter(html)
|
||||||
.map(|cap| {
|
.map(|cap| {
|
||||||
let filename = cap["filename"].to_string();
|
let filename = cap["filename"].to_string();
|
||||||
// The HTML filename is using a relative path relatively in the report
|
let id = cap["id"].to_string();
|
||||||
// to make the report portable
|
|
||||||
// But the original Hurl file is really an absolute file
|
|
||||||
let time_in_ms = cap["time_in_ms"].to_string().parse().unwrap();
|
let time_in_ms = cap["time_in_ms"].to_string().parse().unwrap();
|
||||||
let success = &cap["status"] == "success";
|
let success = &cap["status"] == "success";
|
||||||
HTMLResult {
|
HTMLResult {
|
||||||
filename,
|
filename,
|
||||||
|
id,
|
||||||
time_in_ms,
|
time_in_ms,
|
||||||
success,
|
success,
|
||||||
}
|
}
|
||||||
@ -188,10 +196,16 @@ fn create_html_table_row(result: &HTMLResult) -> String {
|
|||||||
let duration_in_ms = result.time_in_ms;
|
let duration_in_ms = result.time_in_ms;
|
||||||
let duration_in_s = result.time_in_ms as f64 / 1000.0;
|
let duration_in_s = result.time_in_ms as f64 / 1000.0;
|
||||||
let filename = &result.filename;
|
let filename = &result.filename;
|
||||||
|
let displayed_filename = if filename == "-" {
|
||||||
|
"(standard input)"
|
||||||
|
} else {
|
||||||
|
filename
|
||||||
|
};
|
||||||
|
let id = &result.id;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r#"<tr class="{status}" data-duration="{duration_in_ms}" data-status="{status}" data-filename="{filename}">
|
r#"<tr class="{status}" data-duration="{duration_in_ms}" data-status="{status}" data-filename="{filename}" data-id="{id}">
|
||||||
<td><a href="{filename}.html">{filename}</a></td>
|
<td><a href="store/{id}.html">{displayed_filename}</a></td>
|
||||||
<td>{status}</td>
|
<td>{status}</td>
|
||||||
<td>{duration_in_s}</td>
|
<td>{duration_in_s}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -218,12 +232,12 @@ mod tests {
|
|||||||
<h2>Hurl Report</h2>
|
<h2>Hurl Report</h2>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="success" data-duration="100" data-status="success" data-filename="tests/hello.hurl">
|
<tr class="success" data-duration="100" data-status="success" data-filename="tests/hello.hurl" data-id="08aad14a-8d10-4ecc-892e-a72703c5b494">
|
||||||
<td><a href="tests/hello.hurl.html">tests/hello.hurl</a></td>
|
<td><a href="tests/hello.hurl.html">tests/hello.hurl</a></td>
|
||||||
<td>success</td>
|
<td>success</td>
|
||||||
<td>0.1s</td>
|
<td>0.1s</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="failure" data-duration="200" data-status="failure" data-filename="tests/failure.hurl">
|
<tr class="failure" data-duration="200" data-status="failure" data-filename="tests/failure.hurl" data-id="a6641ae3-8ce0-4d9f-80c5-3e23e032e055">
|
||||||
<td><a href="tests/failure.hurl.html">tests/failure.hurl</a></td>
|
<td><a href="tests/failure.hurl.html">tests/failure.hurl</a></td>
|
||||||
<td>failure</td>
|
<td>failure</td>
|
||||||
<td>0.2s</td>
|
<td>0.2s</td>
|
||||||
@ -238,11 +252,13 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
HTMLResult {
|
HTMLResult {
|
||||||
filename: "tests/hello.hurl".to_string(),
|
filename: "tests/hello.hurl".to_string(),
|
||||||
|
id: "08aad14a-8d10-4ecc-892e-a72703c5b494".to_string(),
|
||||||
time_in_ms: 100,
|
time_in_ms: 100,
|
||||||
success: true,
|
success: true,
|
||||||
},
|
},
|
||||||
HTMLResult {
|
HTMLResult {
|
||||||
filename: "tests/failure.hurl".to_string(),
|
filename: "tests/failure.hurl".to_string(),
|
||||||
|
id: "a6641ae3-8ce0-4d9f-80c5-3e23e032e055".to_string(),
|
||||||
time_in_ms: 200,
|
time_in_ms: 200,
|
||||||
success: false,
|
success: false,
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use crate::runner::HurlResult;
|
use crate::runner::HurlResult;
|
||||||
use crate::util::path;
|
|
||||||
use hurl_core::parser;
|
use hurl_core::parser;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Testcase {
|
pub struct Testcase {
|
||||||
id: String,
|
pub id: String,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
pub time_in_ms: u128,
|
pub time_in_ms: u128,
|
||||||
@ -33,8 +33,9 @@ pub struct Testcase {
|
|||||||
impl Testcase {
|
impl Testcase {
|
||||||
/// Creates an HTML testcase.
|
/// Creates an HTML testcase.
|
||||||
pub fn from(hurl_result: &HurlResult) -> Testcase {
|
pub fn from(hurl_result: &HurlResult) -> Testcase {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
Testcase {
|
Testcase {
|
||||||
id: "to-be-defined".to_string(),
|
id: id.to_string(),
|
||||||
filename: hurl_result.filename.to_string(),
|
filename: hurl_result.filename.to_string(),
|
||||||
time_in_ms: hurl_result.time_in_ms,
|
time_in_ms: hurl_result.time_in_ms,
|
||||||
success: hurl_result.success,
|
success: hurl_result.success,
|
||||||
@ -45,19 +46,14 @@ impl Testcase {
|
|||||||
///
|
///
|
||||||
/// For the moment, it's just an export of this HTML file, with syntax colored.
|
/// For the moment, it's just an export of this HTML file, with syntax colored.
|
||||||
pub fn write_html(&self, content: &str, dir_path: &Path) -> Result<(), Error> {
|
pub fn write_html(&self, content: &str, dir_path: &Path) -> Result<(), Error> {
|
||||||
let relative_input_file = path::canonicalize_filename(&self.filename);
|
let output_file = dir_path.join("store").join(format!("{}.html", self.id));
|
||||||
let absolute_input_file = dir_path.join(format!("{relative_input_file}.html"));
|
|
||||||
|
|
||||||
let parent = absolute_input_file.parent().expect("a parent");
|
let parent = output_file.parent().expect("a parent");
|
||||||
std::fs::create_dir_all(parent).unwrap();
|
std::fs::create_dir_all(parent).unwrap();
|
||||||
let mut file = match std::fs::File::create(&absolute_input_file) {
|
let mut file = match std::fs::File::create(&output_file) {
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
return Err(Error {
|
return Err(Error {
|
||||||
message: format!(
|
message: format!("Issue writing to {}: {:?}", output_file.display(), why),
|
||||||
"Issue writing to {}: {:?}",
|
|
||||||
absolute_input_file.display(),
|
|
||||||
why
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
@ -68,11 +64,7 @@ impl Testcase {
|
|||||||
|
|
||||||
if let Err(why) = file.write_all(s.as_bytes()) {
|
if let Err(why) = file.write_all(s.as_bytes()) {
|
||||||
return Err(Error {
|
return Err(Error {
|
||||||
message: format!(
|
message: format!("Issue writing to {}: {:?}", output_file.display(), why),
|
||||||
"Issue writing to {}: {:?}",
|
|
||||||
absolute_input_file.display(),
|
|
||||||
why
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -17,14 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
/// Returns the canonical fullname relative to / (technically a relative path)
|
|
||||||
/// The function will panic if the input file does not exist
|
|
||||||
pub fn canonicalize_filename(input_file: &str) -> String {
|
|
||||||
let relative_input_file = Path::new(input_file).canonicalize().expect("existing file");
|
|
||||||
let relative_input_file = relative_input_file.to_string_lossy();
|
|
||||||
relative_input_file.trim_start_matches('/').to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return true if `path` is a descendant path of `ancestor`, false otherwise.
|
/// Return true if `path` is a descendant path of `ancestor`, false otherwise.
|
||||||
pub fn is_descendant(path: &Path, ancestor: &Path) -> bool {
|
pub fn is_descendant(path: &Path, ancestor: &Path) -> bool {
|
||||||
let path = normalize_path(path);
|
let path = normalize_path(path);
|
||||||
|
Loading…
Reference in New Issue
Block a user