Use an uuid as identifier for the HTML Hurl file run report

This commit is contained in:
jcamiel 2023-02-21 13:38:38 +01:00
parent 2a526d7b0b
commit 5939501adb
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
5 changed files with 55 additions and 42 deletions

11
Cargo.lock generated
View File

@ -522,6 +522,7 @@ dependencies = [
"sha2",
"termion",
"url",
"uuid",
"winres",
"xmltree",
]
@ -1193,6 +1194,16 @@ dependencies = [
"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]]
name = "vcpkg"
version = "0.2.15"

View File

@ -42,6 +42,8 @@ sha2 = "0.10.6"
url = "2.3.1"
xmltree = { version = "0.10.3", features = ["attribute-order"] }
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]
termion = "2.0.1"

View File

@ -21,35 +21,42 @@
mod testcase;
use crate::report::Error;
use crate::util::path;
use chrono::{DateTime, Local};
use std::io::Write;
use std::path::Path;
pub use testcase::Testcase;
/// 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)]
struct HTMLResult {
/// Original filename, as given in the run execution
pub filename: String,
/// The id of the corresponding [`Testcase`]
pub id: String,
pub time_in_ms: u128,
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.
///
/// If the report already exists, results are merged.
pub fn write_report(dir_path: &Path, testcases: &[Testcase]) -> Result<(), Error> {
let index_path = dir_path.join("index.html");
let mut results = parse_html(&index_path)?;
for testcase in testcases {
let html_result = HTMLResult {
filename: path::canonicalize_filename(&testcase.filename),
time_in_ms: testcase.time_in_ms,
success: testcase.success,
};
for testcase in testcases.iter() {
let html_result = HTMLResult::from(testcase);
results.push(html_result);
}
let now: DateTime<Local> = Local::now();
@ -111,19 +118,20 @@ fn parse_html_report(html: &str) -> Vec<HTMLResult> {
data-status="(?P<status>[a-z]+)"
\s+
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();
re.captures_iter(html)
.map(|cap| {
let filename = cap["filename"].to_string();
// The HTML filename is using a relative path relatively in the report
// to make the report portable
// But the original Hurl file is really an absolute file
let id = cap["id"].to_string();
let time_in_ms = cap["time_in_ms"].to_string().parse().unwrap();
let success = &cap["status"] == "success";
HTMLResult {
filename,
id,
time_in_ms,
success,
}
@ -188,10 +196,16 @@ fn create_html_table_row(result: &HTMLResult) -> String {
let duration_in_ms = result.time_in_ms;
let duration_in_s = result.time_in_ms as f64 / 1000.0;
let filename = &result.filename;
let displayed_filename = if filename == "-" {
"(standard input)"
} else {
filename
};
let id = &result.id;
format!(
r#"<tr class="{status}" data-duration="{duration_in_ms}" data-status="{status}" data-filename="{filename}">
<td><a href="{filename}.html">{filename}</a></td>
r#"<tr class="{status}" data-duration="{duration_in_ms}" data-status="{status}" data-filename="{filename}" data-id="{id}">
<td><a href="store/{id}.html">{displayed_filename}</a></td>
<td>{status}</td>
<td>{duration_in_s}</td>
</tr>
@ -218,12 +232,12 @@ mod tests {
<h2>Hurl Report</h2>
<table>
<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>success</td>
<td>0.1s</td>
</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>failure</td>
<td>0.2s</td>
@ -238,11 +252,13 @@ mod tests {
vec![
HTMLResult {
filename: "tests/hello.hurl".to_string(),
id: "08aad14a-8d10-4ecc-892e-a72703c5b494".to_string(),
time_in_ms: 100,
success: true,
},
HTMLResult {
filename: "tests/failure.hurl".to_string(),
id: "a6641ae3-8ce0-4d9f-80c5-3e23e032e055".to_string(),
time_in_ms: 200,
success: false,
}

View File

@ -17,14 +17,14 @@
*/
use super::Error;
use crate::runner::HurlResult;
use crate::util::path;
use hurl_core::parser;
use std::io::Write;
use std::path::Path;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Testcase {
id: String,
pub id: String,
pub filename: String,
pub success: bool,
pub time_in_ms: u128,
@ -33,8 +33,9 @@ pub struct Testcase {
impl Testcase {
/// Creates an HTML testcase.
pub fn from(hurl_result: &HurlResult) -> Testcase {
let id = Uuid::new_v4();
Testcase {
id: "to-be-defined".to_string(),
id: id.to_string(),
filename: hurl_result.filename.to_string(),
time_in_ms: hurl_result.time_in_ms,
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.
pub fn write_html(&self, content: &str, dir_path: &Path) -> Result<(), Error> {
let relative_input_file = path::canonicalize_filename(&self.filename);
let absolute_input_file = dir_path.join(format!("{relative_input_file}.html"));
let output_file = dir_path.join("store").join(format!("{}.html", self.id));
let parent = absolute_input_file.parent().expect("a parent");
let parent = output_file.parent().expect("a parent");
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) => {
return Err(Error {
message: format!(
"Issue writing to {}: {:?}",
absolute_input_file.display(),
why
),
message: format!("Issue writing to {}: {:?}", output_file.display(), why),
});
}
Ok(file) => file,
@ -68,11 +64,7 @@ impl Testcase {
if let Err(why) = file.write_all(s.as_bytes()) {
return Err(Error {
message: format!(
"Issue writing to {}: {:?}",
absolute_input_file.display(),
why
),
message: format!("Issue writing to {}: {:?}", output_file.display(), why),
});
}
Ok(())

View File

@ -17,14 +17,6 @@
*/
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.
pub fn is_descendant(path: &Path, ancestor: &Path) -> bool {
let path = normalize_path(path);