mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-23 11:02:43 +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",
|
||||
"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"
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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(())
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user