Compare commits
4 commits
608a9f68f4
...
ec65b6d03f
Author | SHA1 | Date | |
---|---|---|---|
ec65b6d03f | |||
06c09b2831 | |||
8405af8fb3 | |||
93758e3ab2 |
20 changed files with 14426 additions and 81 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/dist
|
/dist
|
||||||
/.env
|
/.env
|
||||||
|
*.snap.new
|
||||||
|
|
80
Cargo.lock
generated
80
Cargo.lock
generated
|
@ -156,6 +156,7 @@ dependencies = [
|
||||||
"hex",
|
"hex",
|
||||||
"http",
|
"http",
|
||||||
"humansize",
|
"humansize",
|
||||||
|
"junit-parser",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -163,6 +164,7 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"proptest",
|
"proptest",
|
||||||
|
"quick-xml",
|
||||||
"quick_cache",
|
"quick_cache",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -497,6 +499,18 @@ dependencies = [
|
||||||
"unicode_categories",
|
"unicode_categories",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -620,6 +634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -695,6 +710,12 @@ version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
|
checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "entities"
|
name = "entities"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1217,6 +1238,19 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "insta"
|
||||||
|
version = "1.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"lazy_static",
|
||||||
|
"linked-hash-map",
|
||||||
|
"serde",
|
||||||
|
"similar",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
|
@ -1253,6 +1287,19 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "junit-parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"insta",
|
||||||
|
"once_cell",
|
||||||
|
"path_macro",
|
||||||
|
"quick-xml",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1271,6 +1318,12 @@ version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -1705,6 +1758,15 @@ version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick_cache"
|
name = "quick_cache"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -2182,6 +2244,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "similar"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -2356,10 +2424,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"itoa",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2368,6 +2438,16 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
|
@ -39,12 +39,14 @@ headers = "0.4.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
http = "1.1.0"
|
http = "1.1.0"
|
||||||
humansize = "2.1.3"
|
humansize = "2.1.3"
|
||||||
|
junit-parser = { path = "crates/junit-parser" }
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
path_macro = "1.0.0"
|
path_macro = "1.0.0"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
pin-project = "1.1.5"
|
pin-project = "1.1.5"
|
||||||
|
quick-xml = { version = "0.31.0", features = ["escape-html"] }
|
||||||
quick_cache = "0.5.1"
|
quick_cache = "0.5.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
|
|
|
@ -89,6 +89,7 @@ Artifactview is configured using environment variables.
|
||||||
| `REPO_BLACKLIST` | - | List of sites/users/repos that can NOT be accessed. The blacklist takes precedence over the whitelist (repos included in both lists cannot be accessed)<br />Example: `github.com/evil-corp/world-destruction;codeberg.org/blackhat;example.org` |
|
| `REPO_BLACKLIST` | - | List of sites/users/repos that can NOT be accessed. The blacklist takes precedence over the whitelist (repos included in both lists cannot be accessed)<br />Example: `github.com/evil-corp/world-destruction;codeberg.org/blackhat;example.org` |
|
||||||
| `REPO_WHITELIST` | - | List of sites/users/repos that can ONLY be accessed. If the whitelist is empty, it will be ignored and any repository can be accessed. Uses the same syntax as `REPO_BLACLIST`. |
|
| `REPO_WHITELIST` | - | List of sites/users/repos that can ONLY be accessed. If the whitelist is empty, it will be ignored and any repository can be accessed. Uses the same syntax as `REPO_BLACLIST`. |
|
||||||
| `SITE_ALIASES` | - | Aliases for sites to make URLs shorter<br />Example: `gh => github.com;cb => codeberg.org` |
|
| `SITE_ALIASES` | - | Aliases for sites to make URLs shorter<br />Example: `gh => github.com;cb => codeberg.org` |
|
||||||
|
| `VIEWER_MAX_SIZE` | 500000 | Maximum file size to be displayed using the viewer |
|
||||||
|
|
||||||
## Technical details
|
## Technical details
|
||||||
|
|
||||||
|
|
18
crates/junit-parser/Cargo.toml
Normal file
18
crates/junit-parser/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "junit-parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Boris Faure <boris@fau.re>"]
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
repository = "https://github.com/borisfaure/junit-parser"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quick-xml = { version = "0.31.0", features = ["escape-html"] }
|
||||||
|
thiserror = "1.0.61"
|
||||||
|
time = { version = "0.3.36", features = ["parsing", "serde-well-known"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = { version = "1.39.0", features = ["json"] }
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
path_macro = "1.0.0"
|
40
crates/junit-parser/src/errors.rs
Normal file
40
crates/junit-parser/src/errors.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Error enumerates all possible errors returned by this library.
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Error while parsing XML
|
||||||
|
#[error("Error while parsing XML: {0}")]
|
||||||
|
Xml(#[from] ::quick_xml::Error),
|
||||||
|
/// Error while converting f64 attribute
|
||||||
|
#[error("Error while converting f64 attribute: {0}")]
|
||||||
|
ParseFloat(#[from] std::num::ParseFloatError),
|
||||||
|
/// Error while converting u64 attribute
|
||||||
|
#[error("Error while converting u64 attribute: {0}")]
|
||||||
|
ParseInt(#[from] std::num::ParseIntError),
|
||||||
|
/// Error while converting bytes to Utf8
|
||||||
|
#[error("Error while converting bytes to Utf8: {0}")]
|
||||||
|
ParseUt8(#[from] std::str::Utf8Error),
|
||||||
|
#[error("Error while parsing timestamp: {0}")]
|
||||||
|
ParseTimestamp(#[from] time::error::Parse),
|
||||||
|
/// Error parsing the `property` element: missing `name`
|
||||||
|
#[error("Missing `name` attribute in property")]
|
||||||
|
MissingPropertyName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::quick_xml::events::attributes::AttrError> for Error {
|
||||||
|
#[inline]
|
||||||
|
/// Convert [`::quick_xml::events::attributes`] into [`Error::XMLError`]
|
||||||
|
fn from(err: ::quick_xml::events::attributes::AttrError) -> Error {
|
||||||
|
Error::Xml(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<::quick_xml::escape::EscapeError> for Error {
|
||||||
|
#[inline]
|
||||||
|
/// Convert [`::quick_xml::escape::EscapeError`] into [`Error::XMLError`]
|
||||||
|
fn from(err: ::quick_xml::escape::EscapeError) -> Error {
|
||||||
|
Error::Xml(err.into())
|
||||||
|
}
|
||||||
|
}
|
564
crates/junit-parser/src/lib.rs
Normal file
564
crates/junit-parser/src/lib.rs
Normal file
|
@ -0,0 +1,564 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
use quick_xml::escape::unescape;
|
||||||
|
use quick_xml::events::BytesStart as XMLBytesStart;
|
||||||
|
use quick_xml::events::Event as XMLEvent;
|
||||||
|
use quick_xml::name::QName;
|
||||||
|
use quick_xml::Error as XMLError;
|
||||||
|
use quick_xml::Reader as XMLReader;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
|
||||||
|
use errors::Error;
|
||||||
|
|
||||||
|
/// Struct representing a JUnit report, containing test suites
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct TestSuites {
|
||||||
|
/// Name of the test suites, from the `name` attribute
|
||||||
|
pub name: String,
|
||||||
|
/// List of tests suites represented by [`TestSuite`]
|
||||||
|
pub suites: Vec<TestSuite>,
|
||||||
|
/// How long the test suites took to run, from the `time` attribute
|
||||||
|
pub time: f64,
|
||||||
|
/// Number of tests in the test suites, from the `tests` attribute
|
||||||
|
pub tests: u64,
|
||||||
|
/// Number of tests in error in the test suites, from the `errors` attribute
|
||||||
|
pub errors: u64,
|
||||||
|
/// Number of tests in failure in the test suites, from the `failures` attribute
|
||||||
|
pub failures: u64,
|
||||||
|
/// Number of tests skipped in the test suites, from the `skipped` attribute
|
||||||
|
pub skipped: u64,
|
||||||
|
/// Number of tests that passed after failed attempts
|
||||||
|
pub flaky: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A test suite, containing test cases [`TestCase`]
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct TestSuite {
|
||||||
|
/// Name of the test suite, from the `name` attribute
|
||||||
|
pub name: String,
|
||||||
|
/// Timestamp when the test suite was run, from the `timestamp` attribute
|
||||||
|
#[serde(with = "time::serde::rfc3339::option")]
|
||||||
|
pub timestamp: Option<OffsetDateTime>,
|
||||||
|
/// List of status of tests represented by [`TestCase`]
|
||||||
|
pub cases: Vec<TestCase>,
|
||||||
|
/// How long the test suite took to run, from the `time` attribute
|
||||||
|
pub time: f64,
|
||||||
|
/// Number of tests in the test suite, from the `tests` attribute
|
||||||
|
pub tests: u64,
|
||||||
|
/// Number of tests in error in the test suite, from the `errors` attribute
|
||||||
|
pub errors: u64,
|
||||||
|
/// Number of tests in failure in the test suite, from the `failures` attribute
|
||||||
|
pub failures: u64,
|
||||||
|
/// Number of tests skipped in the test suites, from the `skipped` attribute
|
||||||
|
pub skipped: u64,
|
||||||
|
/// Number of tests that passed after failed attempts
|
||||||
|
pub flaky: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A test case
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct TestCase {
|
||||||
|
/// Name of the test case, from the `name` attribute
|
||||||
|
pub name: String,
|
||||||
|
/// Original name, from the `name` attribute
|
||||||
|
pub original_name: String,
|
||||||
|
/// Class name, from the `classname` attribute
|
||||||
|
pub classname: Option<String>,
|
||||||
|
/// Timestamp when the test case was run
|
||||||
|
#[serde(with = "time::serde::rfc3339::option")]
|
||||||
|
pub timestamp: Option<OffsetDateTime>,
|
||||||
|
/// Run time in seconds
|
||||||
|
pub time: f64,
|
||||||
|
/// Status of the test case
|
||||||
|
pub status: TestStatus,
|
||||||
|
/// stdout output from the `system-out` element
|
||||||
|
pub system_out: Option<String>,
|
||||||
|
/// stderr output from the `system-err` element
|
||||||
|
pub system_err: Option<String>,
|
||||||
|
/// Previous test attempts, from `rerunFailure` and `flakyFailure` element
|
||||||
|
pub retries: Vec<Retry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub enum TestStatus {
|
||||||
|
#[default]
|
||||||
|
Success,
|
||||||
|
Error(Message),
|
||||||
|
Failure(Message),
|
||||||
|
Flaky,
|
||||||
|
Skipped,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct Message {
|
||||||
|
pub message: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct Retry {
|
||||||
|
/// Timestamp when the retry was run
|
||||||
|
#[serde(with = "time::serde::rfc3339::option")]
|
||||||
|
pub timestamp: Option<OffsetDateTime>,
|
||||||
|
/// Run time in seconds
|
||||||
|
pub time: f64,
|
||||||
|
/// Status of the retry
|
||||||
|
pub status: TestStatus,
|
||||||
|
/// stdout output from the `system-out` element
|
||||||
|
pub system_out: Option<String>,
|
||||||
|
/// stderr output from the `system-err` element
|
||||||
|
pub system_err: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestSuites {
|
||||||
|
/// Fill up `self` with attributes from the XML tag
|
||||||
|
fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
|
||||||
|
for a in e.attributes() {
|
||||||
|
let a = a?;
|
||||||
|
match a.key {
|
||||||
|
QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
|
||||||
|
QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"name") => self.name = try_from_attribute_value_string(a.value)?,
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestSuites`] from empty XML tag
|
||||||
|
fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
|
||||||
|
let mut ts = Self::default();
|
||||||
|
ts.parse_attributes(e)?;
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestSuites`] from XML tree
|
||||||
|
fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
|
||||||
|
let mut ts = Self::default();
|
||||||
|
ts.parse_attributes(e)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuites") => break,
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testrun") => break,
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => {
|
||||||
|
ts.suites.push(TestSuite::from_reader(e, r)?);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => {
|
||||||
|
ts.suites.push(TestSuite::new_empty(e)?);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof("testsuites".to_string()).into())
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.flaky = ts.suites.iter().map(|s| s.flaky).sum();
|
||||||
|
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestSuite {
|
||||||
|
/// Fill up `self` with attributes from the XML tag
|
||||||
|
fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
|
||||||
|
for a in e.attributes() {
|
||||||
|
let a = a?;
|
||||||
|
match a.key {
|
||||||
|
QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
|
||||||
|
QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?,
|
||||||
|
QName(b"name") => self.name = try_from_attribute_value_string(a.value)?,
|
||||||
|
QName(b"timestamp") => {
|
||||||
|
self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestSuite`] from empty XML tag
|
||||||
|
fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
|
||||||
|
let mut ts = Self::default();
|
||||||
|
ts.parse_attributes(e)?;
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestSuite`] from XML tree
|
||||||
|
fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
|
||||||
|
let mut ts = Self::default();
|
||||||
|
ts.parse_attributes(e)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuite") => break,
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testcase") => {
|
||||||
|
ts.cases.push(TestCase::from_reader(e, r)?);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testcase") => {
|
||||||
|
ts.cases.push(TestCase::new_empty(e)?);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof("testsuite".to_string()).into())
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.flaky = ts
|
||||||
|
.cases
|
||||||
|
.iter()
|
||||||
|
.filter(|c| matches!(c.status, TestStatus::Flaky))
|
||||||
|
.count() as u64;
|
||||||
|
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestCase {
|
||||||
|
/// Fill up `self` with attributes from the XML tag
|
||||||
|
fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
|
||||||
|
for a in e.attributes() {
|
||||||
|
let a = a?;
|
||||||
|
match a.key {
|
||||||
|
QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
|
||||||
|
QName(b"timestamp") => {
|
||||||
|
self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?)
|
||||||
|
}
|
||||||
|
QName(b"name") => self.original_name = try_from_attribute_value_string(a.value)?,
|
||||||
|
QName(b"classname") => {
|
||||||
|
self.classname = Some(try_from_attribute_value_string(a.value)?)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(cn) = self.classname.as_ref() {
|
||||||
|
self.name = format!("{}::{}", cn, self.original_name);
|
||||||
|
} else {
|
||||||
|
self.name.clone_from(&self.original_name);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestCase`] from empty XML tag
|
||||||
|
fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
|
||||||
|
let mut tc = Self::default();
|
||||||
|
tc.parse_attributes(e)?;
|
||||||
|
Ok(tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`TestCase`] from XML tree
|
||||||
|
fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
|
||||||
|
let mut tc = Self::default();
|
||||||
|
tc.parse_attributes(e)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf)? {
|
||||||
|
XMLEvent::End(ref e) if e.name() == QName(b"testcase") => break,
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"skipped") => {
|
||||||
|
tc.status = TestStatus::Skipped;
|
||||||
|
}
|
||||||
|
XMLEvent::Empty(ref e) if e.name() == QName(b"skipped") => {
|
||||||
|
tc.status = TestStatus::Skipped;
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"failure") => {
|
||||||
|
let msg = Message::from_reader(e, r)?;
|
||||||
|
tc.status = TestStatus::Failure(msg);
|
||||||
|
}
|
||||||
|
XMLEvent::Empty(ref e) if e.name() == QName(b"failure") => {
|
||||||
|
let msg = Message::new_empty(e)?;
|
||||||
|
tc.status = TestStatus::Failure(msg);
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"error") => {
|
||||||
|
let msg = Message::from_reader(e, r)?;
|
||||||
|
tc.status = TestStatus::Error(msg);
|
||||||
|
}
|
||||||
|
XMLEvent::Empty(ref e) if e.name() == QName(b"error") => {
|
||||||
|
let msg = Message::new_empty(e)?;
|
||||||
|
tc.status = TestStatus::Error(msg);
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"system-out") => {
|
||||||
|
tc.system_out = parse_system(e, r)?;
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"system-err") => {
|
||||||
|
tc.system_err = parse_system(e, r)?;
|
||||||
|
}
|
||||||
|
XMLEvent::Empty(ref e) if e.name() == QName(b"rerunFailure") => {
|
||||||
|
tc.retries.push(Retry::new_empty(e)?);
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"rerunFailure") => {
|
||||||
|
tc.retries.push(Retry::from_reader(e, r)?);
|
||||||
|
}
|
||||||
|
XMLEvent::Empty(ref e) if e.name() == QName(b"flakyFailure") => {
|
||||||
|
tc.status = TestStatus::Flaky;
|
||||||
|
tc.retries.push(Retry::new_empty(e)?);
|
||||||
|
}
|
||||||
|
XMLEvent::Start(ref e) if e.name() == QName(b"flakyFailure") => {
|
||||||
|
tc.status = TestStatus::Flaky;
|
||||||
|
tc.retries.push(Retry::from_reader(e, r)?);
|
||||||
|
}
|
||||||
|
XMLEvent::Eof => return Err(XMLError::UnexpectedEof("testcase".to_string()).into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message {
|
||||||
|
/// Fill up `self` with attributes from the XML tag
|
||||||
|
fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
|
||||||
|
for a in e.attributes() {
|
||||||
|
let a = a?;
|
||||||
|
if let QName(b"message") = a.key {
|
||||||
|
self.message = try_from_attribute_value_string(a.value)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`Message`] from empty XML tag
|
||||||
|
fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
|
||||||
|
let mut tf = Self::default();
|
||||||
|
tf.parse_attributes(e)?;
|
||||||
|
Ok(tf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New [`Message`] from XML tree
|
||||||
|
fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
|
||||||
|
let name = e.name();
|
||||||
|
let mut msg = Self::default();
|
||||||
|
msg.parse_attributes(e)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == name => break,
|
||||||
|
Ok(XMLEvent::Text(e)) => {
|
||||||
|
msg.text += e.unescape()?.trim();
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof("failure".to_string()).into())
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Retry {
|
||||||
|
/// Fill up `self` with attributes from the XML tag
|
||||||
|
fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> {
|
||||||
|
for a in e.attributes() {
|
||||||
|
let a = a?;
|
||||||
|
match a.key {
|
||||||
|
QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?,
|
||||||
|
QName(b"timestamp") => {
|
||||||
|
self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_empty(e: &XMLBytesStart) -> Result<Self, Error> {
|
||||||
|
let mut rt = Self::default();
|
||||||
|
rt.parse_attributes(e)?;
|
||||||
|
Ok(rt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_reader<B: BufRead>(e: &XMLBytesStart, r: &mut XMLReader<B>) -> Result<Self, Error> {
|
||||||
|
let name = e.name();
|
||||||
|
let mut rt = Self::default();
|
||||||
|
rt.parse_attributes(e)?;
|
||||||
|
|
||||||
|
let mut msg = Message::new_empty(e)?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == name => break,
|
||||||
|
Ok(XMLEvent::Text(e)) => {
|
||||||
|
msg.text += e.unescape()?.trim();
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-out") => {
|
||||||
|
rt.system_out = parse_system(e, r)?;
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-err") => {
|
||||||
|
rt.system_err = parse_system(e, r)?;
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof("failure".to_string()).into())
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.status = TestStatus::Failure(msg);
|
||||||
|
Ok(rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decode attribute value as [`f64`]
|
||||||
|
fn try_from_attribute_value_f64(value: Cow<[u8]>) -> Result<f64, Error> {
|
||||||
|
match str::from_utf8(&value)? {
|
||||||
|
"" => Ok(f64::default()),
|
||||||
|
s => Ok(s.parse::<f64>()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decode attribute value as [`u64`]
|
||||||
|
fn try_from_attribute_value_u64(value: Cow<[u8]>) -> Result<u64, Error> {
|
||||||
|
match str::from_utf8(&value)? {
|
||||||
|
"" => Ok(u64::default()),
|
||||||
|
s => Ok(s.parse::<u64>()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decode and unescape attribute value as [`String`]
|
||||||
|
fn try_from_attribute_value_string(value: Cow<[u8]>) -> Result<String, Error> {
|
||||||
|
let s = str::from_utf8(&value)?;
|
||||||
|
let u = unescape(s)?;
|
||||||
|
Ok(u.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decode and unescape attribute value as [`String`]
|
||||||
|
fn try_from_attribute_value_timestamp(value: Cow<[u8]>) -> Result<OffsetDateTime, Error> {
|
||||||
|
let s = str::from_utf8(&value)?;
|
||||||
|
let t = OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)?;
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a chunk of xml as system-out or system-err
|
||||||
|
fn parse_system<B: BufRead>(
|
||||||
|
orig: &XMLBytesStart,
|
||||||
|
r: &mut XMLReader<B>,
|
||||||
|
) -> Result<Option<String>, Error> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut res = None;
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::End(ref e)) if e.name() == orig.name() => break,
|
||||||
|
Ok(XMLEvent::Text(e)) => {
|
||||||
|
res = Some(e.unescape()?.to_string());
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof(format!("{:?}", orig.name())).into());
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`TestSuites`](struct.TestSuites.html) structure from a JUnit XML data read from `reader`
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use std::io::Cursor;
|
||||||
|
/// let xml = r#"
|
||||||
|
/// <testsuite tests="3" failures="1">
|
||||||
|
/// <testcase classname="foo1" name="ASuccessfulTest"/>
|
||||||
|
/// <testcase classname="foo2" name="AnotherSuccessfulTest"/>
|
||||||
|
/// <testcase classname="foo3" name="AFailingTest">
|
||||||
|
/// <failure type="NotEnoughFoo"> details about failure </failure>
|
||||||
|
/// </testcase>
|
||||||
|
/// </testsuite>
|
||||||
|
/// "#;
|
||||||
|
/// let cursor = Cursor::new(xml);
|
||||||
|
/// let r = junit_parser::from_reader(cursor);
|
||||||
|
/// assert!(r.is_ok());
|
||||||
|
/// ```
|
||||||
|
pub fn from_reader<B: BufRead>(reader: B) -> Result<TestSuites, Error> {
|
||||||
|
let mut r = XMLReader::from_reader(reader);
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
loop {
|
||||||
|
match r.read_event_into(&mut buf) {
|
||||||
|
Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuites") => {
|
||||||
|
return TestSuites::new_empty(e);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testrun") => {
|
||||||
|
return TestSuites::new_empty(e);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuites") => {
|
||||||
|
return TestSuites::from_reader(e, &mut r);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testrun") => {
|
||||||
|
return TestSuites::from_reader(e, &mut r);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => {
|
||||||
|
let ts = TestSuite::new_empty(e)?;
|
||||||
|
let mut suites = TestSuites::default();
|
||||||
|
suites.suites.push(ts);
|
||||||
|
return Ok(suites);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => {
|
||||||
|
let ts = TestSuite::from_reader(e, &mut r)?;
|
||||||
|
let mut suites = TestSuites::default();
|
||||||
|
suites.suites.push(ts);
|
||||||
|
return Ok(suites);
|
||||||
|
}
|
||||||
|
Ok(XMLEvent::Eof) => {
|
||||||
|
return Err(XMLError::UnexpectedEof("testsuites".to_string()).into())
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a [`TestSuites`](struct.TestSuites.html) structure from a JUnit XML data read from a string
|
||||||
|
pub fn from_str(s: &str) -> Result<TestSuites, Error> {
|
||||||
|
from_reader(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use path_macro::path;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub static TESTFILES: Lazy<PathBuf> =
|
||||||
|
Lazy::new(|| path!(env!("CARGO_MANIFEST_DIR") / "testfiles"));
|
||||||
|
|
||||||
|
fn parse_test(n: &str) {
|
||||||
|
let file = File::open(path!(*TESTFILES / format!("{n}.junit.xml"))).unwrap();
|
||||||
|
let suites = from_reader(BufReader::new(file)).unwrap();
|
||||||
|
insta::assert_json_snapshot!(format!("parse_{n}"), suites);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_simple() {
|
||||||
|
parse_test("simple")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_vite() {
|
||||||
|
parse_test("vite")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_retry() {
|
||||||
|
parse_test("retry")
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,824 @@
|
||||||
|
---
|
||||||
|
source: crates/junit-parser/src/lib.rs
|
||||||
|
expression: suites
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"name": "vitest tests",
|
||||||
|
"suites": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/server/query/util.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.788Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/server/query/util.test.ts::query builder",
|
||||||
|
"original_name": "query builder",
|
||||||
|
"classname": "src/lib/server/query/util.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.002,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/server/query/util.test.ts::parse search query",
|
||||||
|
"original_name": "parse search query",
|
||||||
|
"classname": "src/lib/server/query/util.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/server/query/util.test.ts::mapSortFields",
|
||||||
|
"original_name": "mapSortFields",
|
||||||
|
"classname": "src/lib/server/query/util.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.006,
|
||||||
|
"tests": 3,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/model/validation.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.789Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/model/validation.test.ts::date string",
|
||||||
|
"original_name": "date string",
|
||||||
|
"classname": "src/lib/shared/model/validation.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.002,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/model/validation.test.ts::filter data",
|
||||||
|
"original_name": "filter data",
|
||||||
|
"classname": "src/lib/shared/model/validation.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.002,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.004,
|
||||||
|
"tests": 2,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.79Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex",
|
||||||
|
"original_name": "color conversion > colorToHex",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor",
|
||||||
|
"original_name": "color conversion > hexToColor",
|
||||||
|
"classname": "src/lib/shared/util/colors.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.005,
|
||||||
|
"tests": 20,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.792Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::formatDate",
|
||||||
|
"original_name": "formatDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.014,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::dateFromYMD",
|
||||||
|
"original_name": "dateFromYMD",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::dateFromYMD",
|
||||||
|
"original_name": "dateFromYMD",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::utcDateToYMD",
|
||||||
|
"original_name": "utcDateToYMD",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::dateToYMD",
|
||||||
|
"original_name": "dateToYMD",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.002,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::humanDate",
|
||||||
|
"original_name": "humanDate",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange ''",
|
||||||
|
"original_name": "parse daterange ''",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange '..'",
|
||||||
|
"original_name": "parse daterange '..'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange 'foo..bar'",
|
||||||
|
"original_name": "parse daterange 'foo..bar'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-15'",
|
||||||
|
"original_name": "parse daterange '2024-04-15'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-13..2024-04-20'",
|
||||||
|
"original_name": "parse daterange '2024-04-13..2024-04-20'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-13..'",
|
||||||
|
"original_name": "parse daterange '2024-04-13..'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::parse daterange '..2024-04-20'",
|
||||||
|
"original_name": "parse daterange '..2024-04-20'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-16'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-13..2024-04-16'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-16'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-13..2024-04-16'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-13'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-13..2024-04-13'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-13'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-13..2024-04-13'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '2024-04-08..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'",
|
||||||
|
"original_name": "shiftDateRange '..2024-04-14'",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.001,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/date.test.ts::dateFromHuman",
|
||||||
|
"original_name": "dateFromHuman",
|
||||||
|
"classname": "src/lib/shared/util/date.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.027,
|
||||||
|
"tests": 39,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/diff.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.795Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/diff.test.ts::versions diff",
|
||||||
|
"original_name": "versions diff",
|
||||||
|
"classname": "src/lib/shared/util/diff.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.003,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.003,
|
||||||
|
"tests": 1,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/util.test.ts",
|
||||||
|
"timestamp": "2024-06-04T11:43:17.795Z",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/util.test.ts::getQueryUrl",
|
||||||
|
"original_name": "getQueryUrl",
|
||||||
|
"classname": "src/lib/shared/util/util.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.005,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "src/lib/shared/util/util.test.ts::normalizeLineEndings",
|
||||||
|
"original_name": "normalizeLineEndings",
|
||||||
|
"classname": "src/lib/shared/util/util.test.ts",
|
||||||
|
"timestamp": null,
|
||||||
|
"time": 0.0,
|
||||||
|
"status": "Success",
|
||||||
|
"system_out": null,
|
||||||
|
"system_err": null,
|
||||||
|
"retries": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 0.005,
|
||||||
|
"tests": 2,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": 1.371,
|
||||||
|
"tests": 67,
|
||||||
|
"errors": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"skipped": 0,
|
||||||
|
"flaky": 0
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
.viewer > pre {
|
.viewer > pre {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre, code {
|
pre, code {
|
||||||
|
@ -9,7 +10,7 @@ pre, code {
|
||||||
background-color: #1c1c1c;
|
background-color: #1c1c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup {
|
.prose {
|
||||||
margin: 20px 20px 0 20px;
|
margin: 20px 20px 0 20px;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
@ -17,169 +18,170 @@ pre, code {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.5 !important;
|
line-height: 1.5 !important;
|
||||||
}
|
}
|
||||||
.markup > :first-child {
|
.prose > :first-child {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
.markup > :last-child {
|
.prose > :last-child {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
.markup h1,
|
.prose h1,
|
||||||
.markup h2,
|
.prose h2,
|
||||||
.markup h3,
|
.prose h3,
|
||||||
.markup h4,
|
.prose h4,
|
||||||
.markup h5,
|
.prose h5,
|
||||||
.markup h6 {
|
.prose h6 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
.markup h1 tt,
|
.prose h1 tt,
|
||||||
.markup h1 code,
|
.prose h1 code,
|
||||||
.markup h2 tt,
|
.prose h2 tt,
|
||||||
.markup h2 code,
|
.prose h2 code,
|
||||||
.markup h3 tt,
|
.prose h3 tt,
|
||||||
.markup h3 code,
|
.prose h3 code,
|
||||||
.markup h4 tt,
|
.prose h4 tt,
|
||||||
.markup h4 code,
|
.prose h4 code,
|
||||||
.markup h5 tt,
|
.prose h5 tt,
|
||||||
.markup h5 code,
|
.prose h5 code,
|
||||||
.markup h6 tt,
|
.prose h6 tt,
|
||||||
.markup h6 code {
|
.prose h6 code {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
.markup h1 {
|
.prose h1 {
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
border-bottom: 1px solid var(--color-secondary);
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.3em;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
.markup h2 {
|
.prose h2 {
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
border-bottom: 1px solid var(--color-secondary);
|
||||||
padding-bottom: 0.3em;
|
padding-bottom: 0.3em;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
.markup h3 {
|
.prose h3 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
.markup h4 {
|
.prose h4 {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
.markup h5 {
|
.prose h5 {
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
.markup h6 {
|
.prose h6 {
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
.markup p,
|
.prose p,
|
||||||
.markup blockquote,
|
.prose blockquote,
|
||||||
.markup details,
|
.prose details,
|
||||||
.markup ul,
|
.prose ul,
|
||||||
.markup ol,
|
.prose ol,
|
||||||
.markup dl,
|
.prose dl,
|
||||||
.markup table,
|
.prose table,
|
||||||
.markup pre {
|
.prose pre {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.markup hr {
|
.prose hr {
|
||||||
background-color: var(--color-secondary);
|
background-color: var(--color-secondary);
|
||||||
border: 0;
|
border: 0;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.markup ul,
|
.prose ul,
|
||||||
.markup ol {
|
.prose ol {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
.markup ul ul,
|
.prose ul ul,
|
||||||
.markup ul ol,
|
.prose ul ol,
|
||||||
.markup ol ol,
|
.prose ol ol,
|
||||||
.markup ol ul {
|
.prose ol ul {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.markup ol ol,
|
.prose ol ol,
|
||||||
.markup ul ol {
|
.prose ul ol {
|
||||||
list-style-type: lower-roman;
|
list-style-type: lower-roman;
|
||||||
}
|
}
|
||||||
.markup li > p {
|
.prose li > p {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.markup li + li {
|
.prose li + li {
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
}
|
}
|
||||||
.markup dl {
|
.prose dl {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.markup dl dt {
|
.prose dl dt {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.markup dl dd {
|
.prose dl dd {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
.markup blockquote {
|
.prose blockquote {
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
border-left: 4px solid var(--color-secondary);
|
border-left: 4px solid var(--color-secondary);
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
.markup blockquote > :first-child {
|
.prose blockquote > :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.markup blockquote > :last-child {
|
.prose blockquote > :last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.markup table {
|
.prose table {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
.markup table th {
|
.prose table th {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.markup table th,
|
.prose table th,
|
||||||
.markup table td {
|
.prose table td {
|
||||||
border: 1px solid var(--color-secondary) !important;
|
border: 1px solid var(--color-secondary) !important;
|
||||||
padding: 6px 13px !important;
|
padding: 6px 13px !important;
|
||||||
}
|
}
|
||||||
.markup table tr {
|
.prose table tr {
|
||||||
border-top: 1px solid var(--color-secondary);
|
border-top: 1px solid var(--color-secondary);
|
||||||
}
|
}
|
||||||
.markup table tr:nth-child(2n) {
|
.prose table tr:nth-child(2n) {
|
||||||
background-color: var(--color-secondary);
|
background-color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
.markup img,
|
.prose img,
|
||||||
.markup video {
|
.prose video {
|
||||||
box-sizing: initial;
|
box-sizing: initial;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
.markup img[align="right"],
|
.prose img[align="right"],
|
||||||
.markup video[align="right"] {
|
.prose video[align="right"] {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
.markup img[align="left"],
|
.prose img[align="left"],
|
||||||
.markup video[align="left"] {
|
.prose video[align="left"] {
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
}
|
}
|
||||||
.markup code {
|
.prose code {
|
||||||
white-space: break-spaces;
|
white-space: break-spaces;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
.markup code br {
|
.prose code br {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.markup pre {
|
.prose pre {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
|
@ -187,13 +189,16 @@ pre, code {
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
}
|
}
|
||||||
.markup pre code:before,
|
.prose pre code {
|
||||||
.markup pre code:after {
|
padding: 0;
|
||||||
|
}
|
||||||
|
.prose pre code:before,
|
||||||
|
.prose pre code:after {
|
||||||
content: normal;
|
content: normal;
|
||||||
}
|
}
|
||||||
.markup .ui.list .list,
|
.prose .ui.list .list,
|
||||||
.markup ol.ui.list ol,
|
.prose ol.ui.list ol,
|
||||||
.markup ul.ui.list ul {
|
.prose ul.ui.list ul {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub(crate) const STYLE_CONTENT_PATH: &str = "/content1.css";
|
||||||
|
|
||||||
const FAVICON_BYTES: &[u8; 268] = include_bytes!("../resources/favicon.ico");
|
const FAVICON_BYTES: &[u8; 268] = include_bytes!("../resources/favicon.ico");
|
||||||
const STYLE_MAIN_BYTES: &[u8; 4057] = include_bytes!("../resources/style.css");
|
const STYLE_MAIN_BYTES: &[u8; 4057] = include_bytes!("../resources/style.css");
|
||||||
const STYLE_CONTENT_BYTES: &[u8; 10063] = include_bytes!("../resources/content.css");
|
const STYLE_CONTENT_BYTES: &[u8; 10079] = include_bytes!("../resources/content.css");
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
10
src/cache.rs
10
src/cache.rs
|
@ -249,8 +249,12 @@ impl CacheEntry {
|
||||||
.entries()
|
.entries()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
|
let name = entry.filename().as_str().ok()?;
|
||||||
|
if name.ends_with('/') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
Some((
|
Some((
|
||||||
entry.filename().as_str().ok()?.to_owned(),
|
name.to_owned(),
|
||||||
FileEntry {
|
FileEntry {
|
||||||
header_offset: entry.header_offset().try_into().ok()?,
|
header_offset: entry.header_offset().try_into().ok()?,
|
||||||
uncompressed_size: entry.uncompressed_size().try_into().ok()?,
|
uncompressed_size: entry.uncompressed_size().try_into().ok()?,
|
||||||
|
@ -269,7 +273,7 @@ impl CacheEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_file(&self, path: &str, url_query: &str) -> Result<GetFileResult> {
|
pub fn get_file(&self, path: &str, url_query: &str) -> Result<GetFileResult> {
|
||||||
let path = path.trim_start_matches('/');
|
let path = path.trim_matches('/');
|
||||||
let mut index_path: Option<Cow<str>> = None;
|
let mut index_path: Option<Cow<str>> = None;
|
||||||
|
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
|
@ -304,7 +308,7 @@ impl CacheEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directory listing
|
// Directory listing
|
||||||
let path_as_dir: Cow<str> = if path.is_empty() || path.ends_with('/') {
|
let path_as_dir: Cow<str> = if path.is_empty() {
|
||||||
path.into()
|
path.into()
|
||||||
} else {
|
} else {
|
||||||
format!("{path}/").into()
|
format!("{path}/").into()
|
||||||
|
|
|
@ -90,7 +90,7 @@ impl Default for ConfigData {
|
||||||
repo_blacklist: QueryFilterList::default(),
|
repo_blacklist: QueryFilterList::default(),
|
||||||
repo_whitelist: QueryFilterList::default(),
|
repo_whitelist: QueryFilterList::default(),
|
||||||
site_aliases: HashMap::new(),
|
site_aliases: HashMap::new(),
|
||||||
viewer_max_size: Some(NonZeroU32::new(100_000).unwrap()),
|
viewer_max_size: Some(NonZeroU32::new(500_000).unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,10 +269,17 @@ impl IntoResponse for ErrorJson {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use http::{header, HeaderMap};
|
use http::{header, HeaderMap};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use path_macro::path;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
|
pub static TESTFILES: Lazy<PathBuf> =
|
||||||
|
Lazy::new(|| path!(env!("CARGO_MANIFEST_DIR") / "tests" / "testfiles"));
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("", false)]
|
#[case("", false)]
|
||||||
#[case("br", false)]
|
#[case("br", false)]
|
||||||
|
|
46
src/viewer/junit.rs
Normal file
46
src/viewer/junit.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
use super::Viewer;
|
||||||
|
|
||||||
|
/// JUnit format documentation: https://llg.cubic.org/docs/junit/
|
||||||
|
pub struct JunitViewer;
|
||||||
|
|
||||||
|
impl Viewer for JunitViewer {
|
||||||
|
fn id(&self) -> &'static str {
|
||||||
|
"junit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"JUnit"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_applicable(&self, filename: &str, ext: &str) -> bool {
|
||||||
|
ext == "xml" && filename.contains("junit")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_render(&self, filename: &str, _ext: &str, data: &str) -> Result<String, Error> {
|
||||||
|
let suites = junit_parser::from_str(data).map_err(|e| {
|
||||||
|
tracing::error!("could not parse junit report {filename}: {e}");
|
||||||
|
Error::ViewerNotApplicable
|
||||||
|
})?;
|
||||||
|
dbg!(&suites);
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use path_macro::path;
|
||||||
|
|
||||||
|
use crate::{util::tests::TESTFILES, viewer::Viewer};
|
||||||
|
|
||||||
|
use super::JunitViewer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn t1() {
|
||||||
|
let data =
|
||||||
|
std::fs::read_to_string(path!(*TESTFILES / "junit" / "simple.junit.xml")).unwrap();
|
||||||
|
let html = JunitViewer.try_render("", "", &data).unwrap();
|
||||||
|
println!("{html}");
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,13 +37,20 @@ impl Viewer for MarkdownViewer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_render(&self, _filename: &str, _ext: &str, data: &str) -> Result<String, Error> {
|
fn try_render(&self, _filename: &str, _ext: &str, data: &str) -> Result<String, Error> {
|
||||||
let options = comrak::Options::default();
|
let mut options = comrak::Options::default();
|
||||||
|
options.extension.autolink = true;
|
||||||
|
options.extension.table = true;
|
||||||
|
options.extension.tasklist = true;
|
||||||
|
options.extension.strikethrough = true;
|
||||||
|
options.extension.multiline_block_quotes = true;
|
||||||
|
options.extension.superscript = true;
|
||||||
|
|
||||||
let mut plugins = comrak::Plugins::default();
|
let mut plugins = comrak::Plugins::default();
|
||||||
plugins.render.codefence_syntax_highlighter = Some(&self.adapter);
|
plugins.render.codefence_syntax_highlighter = Some(&self.adapter);
|
||||||
|
|
||||||
let html = comrak::markdown_to_html_with_plugins(data, &options, &plugins);
|
let html = comrak::markdown_to_html_with_plugins(data, &options, &plugins);
|
||||||
|
|
||||||
Ok(format!("<div class=\"markup\">{html}</div>"))
|
Ok(format!("<div class=\"prose\">{html}</div>"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use syntect::parsing::SyntaxSet;
|
||||||
use crate::{error::Error, templates::ViewerLink};
|
use crate::{error::Error, templates::ViewerLink};
|
||||||
|
|
||||||
mod code;
|
mod code;
|
||||||
|
mod junit;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
|
|
||||||
pub trait Viewer: Sync + Send {
|
pub trait Viewer: Sync + Send {
|
||||||
|
@ -16,7 +17,7 @@ pub trait Viewer: Sync + Send {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Viewers {
|
pub struct Viewers {
|
||||||
viewers: [Box<dyn Viewer>; 2],
|
viewers: [Box<dyn Viewer>; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderRes {
|
pub struct RenderRes {
|
||||||
|
@ -29,6 +30,7 @@ impl Viewers {
|
||||||
let ss = Arc::new(SyntaxSet::load_defaults_newlines());
|
let ss = Arc::new(SyntaxSet::load_defaults_newlines());
|
||||||
Self {
|
Self {
|
||||||
viewers: [
|
viewers: [
|
||||||
|
Box::new(junit::JunitViewer),
|
||||||
Box::new(markdown::MarkdownViewer::new(ss.clone())),
|
Box::new(markdown::MarkdownViewer::new(ss.clone())),
|
||||||
Box::new(code::CodeViewer::new(ss)),
|
Box::new(code::CodeViewer::new(ss)),
|
||||||
],
|
],
|
||||||
|
|
1859
tests/testfiles/junit/retry.junit.xml
Normal file
1859
tests/testfiles/junit/retry.junit.xml
Normal file
File diff suppressed because it is too large
Load diff
1447
tests/testfiles/junit/simple.junit.xml
Normal file
1447
tests/testfiles/junit/simple.junit.xml
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue