Skip to content

Commit

Permalink
Remove xmltree/indexmap dependency.
Browse files Browse the repository at this point in the history
Due to xmltree re-exposing an older version of indexmap, we couldnt' upgrade to the latest version of indemap.
xmltree is a tree in-memory representation of an XML document that we use for JUnit export. As xmltree is a thin layer above xml-rs, we re-implement a thin tree in-memory XML document using xml-rs directly and remove xmltree/indexmap dependency.
jcamiel committed Nov 1, 2023
1 parent 842a167 commit c72f04f
Showing 9 changed files with 66 additions and 140 deletions.
30 changes: 1 addition & 29 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions bin/update_crates.sh
Original file line number Diff line number Diff line change
@@ -116,9 +116,7 @@ main() {
check_args "${arg}"
updated_count=0

# xmltree-rs crate v0.10.3 doesn't build with the latest indexmap crates
# see <https://github.com/eminence/xmltree-rs/issues/39>
blacklisted="indexmap"
blacklisted=""

# update toml
for package in packages/*; do
2 changes: 1 addition & 1 deletion integration/tests_ok/junit.out.pattern
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?><testsuites><testsuite tests="2" errors="0" failures="1"><testcase id="tests_ok/test.1.hurl" name="tests_ok/test.1.hurl" time="~~~" /><testcase id="tests_ok/test.2.hurl" name="tests_ok/test.2.hurl" time="~~~"><failure>Assert body value
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite tests="2" errors="0" failures="1"><testcase id="tests_ok/test.1.hurl" name="tests_ok/test.1.hurl" time="~~~" /><testcase id="tests_ok/test.2.hurl" name="tests_ok/test.2.hurl" time="~~~"><failure>Assert body value
--> tests_ok/test.2.hurl:8:1
|
8 | `Goodbye World!`
2 changes: 0 additions & 2 deletions packages/hurl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@ glob = "0.3.1"
hex = "0.4.3"
hex-literal = "0.4.1"
hurl_core = { version = "4.2.0-SNAPSHOT", path = "../hurl_core" }
indexmap = "1.9.3"
libflate = "2.0.0"
libxml = "0.3.3"
md5 = "0.7.0"
@@ -40,7 +39,6 @@ serde = "1.0.190"
serde_json = "1.0.108"
sha2 = "0.10.8"
url = "2.4.1"
xmltree = { version = "0.10.3", features = ["attribute-order"] }
xml-rs = { version = "0.8.19"}
lazy_static = "1.4.0"
# uuid features: lets you generate random UUIDs and use a faster (but still sufficiently random) RNG
68 changes: 25 additions & 43 deletions packages/hurl/src/report/junit/mod.rs
Original file line number Diff line number Diff line change
@@ -58,45 +58,33 @@ mod testcase;
mod xml;
use std::fs::File;

use indexmap::IndexMap;
use xmltree::{Element, XMLNode};

use crate::report::junit::xml::{Element, XmlDocument};
use crate::report::Error;
pub use testcase::Testcase;

/// Creates a JUnit from a list of `testcases`.
pub fn write_report(filename: &str, testcases: &[Testcase]) -> Result<(), Error> {
let mut testsuites = vec![];

// If there is an existing JUnit report, we parses it to insert a new testsuite.
let path = std::path::Path::new(&filename);
if path.exists() {
let s = match std::fs::read_to_string(path) {
let mut root = if path.exists() {
let file = match File::open(path) {
Ok(s) => s,
Err(why) => {
return Err(Error {
message: format!("Issue reading {} to string to {:?}", path.display(), why),
});
}
};
let root = Element::parse(s.as_bytes()).unwrap();
for child in root.children {
if let XMLNode::Element(_) = child.clone() {
testsuites.push(child.clone());
}
}
}
let doc = XmlDocument::parse(file).unwrap();
doc.root.unwrap()
} else {
Element::new("testsuites")
};

let testsuite = create_testsuite(testcases);
testsuites.push(XMLNode::Element(testsuite));
let report = Element {
name: "testsuites".to_string(),
prefix: None,
namespace: None,
namespaces: None,
attributes: IndexMap::new(),
children: testsuites,
};
root = root.add_child(testsuite);

let doc = XmlDocument::new(root);
let file = match File::create(filename) {
Ok(f) => f,
Err(e) => {
@@ -105,7 +93,7 @@ pub fn write_report(filename: &str, testcases: &[Testcase]) -> Result<(), Error>
});
}
};
match report.write(file) {
match doc.write(file) {
Ok(_) => Ok(()),
Err(e) => Err(Error {
message: format!("Failed to produce Junit report: {e:?}"),
@@ -115,7 +103,6 @@ pub fn write_report(filename: &str, testcases: &[Testcase]) -> Result<(), Error>

/// Returns a testsuite as a XML object, from a list of `testcases`.
fn create_testsuite(testcases: &[Testcase]) -> Element {
let mut attrs = IndexMap::new();
let mut tests = 0;
let mut errors = 0;
let mut failures = 0;
@@ -126,26 +113,21 @@ fn create_testsuite(testcases: &[Testcase]) -> Element {
failures += cases.get_fail_count();
}

attrs.insert("tests".to_string(), tests.to_string());
attrs.insert("errors".to_string(), errors.to_string());
attrs.insert("failures".to_string(), failures.to_string());
let mut element = Element::new("testsuite")
.attr("tests", &tests.to_string())
.attr("errors", &errors.to_string())
.attr("failures", &failures.to_string());

let children = testcases
.iter()
.map(|t| XMLNode::Element(t.to_xml()))
.collect();
Element {
name: "testsuite".to_string(),
prefix: None,
namespace: None,
namespaces: None,
attributes: attrs,
children,
for testcase in testcases.iter() {
let child = testcase.to_xml();
element = element.add_child(child);
}
element
}

#[cfg(test)]
mod tests {
use crate::report::junit::xml::XmlDocument;
use crate::report::junit::{create_testsuite, Testcase};
use crate::runner::{EntryResult, Error, HurlResult, RunnerError};
use hurl_core::ast::SourceInfo;
@@ -214,11 +196,11 @@ mod tests {
let tc = Testcase::from(&res, content, filename);
testcases.push(tc);

let mut buffer = Vec::new();
create_testsuite(&testcases).write(&mut buffer).unwrap();
let suite = create_testsuite(&testcases);
let doc = XmlDocument::new(suite);
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
doc.to_string().unwrap(),
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\
<testsuite tests=\"3\" errors=\"1\" failures=\"1\">\
<testcase id=\"-\" name=\"-\" time=\"0.230\" />\
<testcase id=\"-\" name=\"-\" time=\"0.230\">\
86 changes: 27 additions & 59 deletions packages/hurl/src/report/junit/testcase.rs
Original file line number Diff line number Diff line change
@@ -15,11 +15,9 @@
* limitations under the License.
*
*/
use super::xml::XmlDocument;
use crate::report::junit::xml::Element;
use crate::runner::HurlResult;
use crate::util::logger;
use indexmap::map::IndexMap;
use xmltree::{Element, XMLNode};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Testcase {
@@ -58,44 +56,21 @@ impl Testcase {

/// Serializes this testcase to XML.
pub fn to_xml(&self) -> Element {
let name = "testcase".to_string();
let mut attributes = IndexMap::new();
attributes.insert("id".to_string(), self.id.clone());
attributes.insert("name".to_string(), self.name.clone());
let time_in_seconds = format!("{:.3}", self.time_in_ms as f64 / 1000.0);
attributes.insert("time".to_string(), time_in_seconds);

let mut children = vec![];
for message in self.failures.clone() {
let element = Element {
prefix: None,
namespace: None,
namespaces: None,
name: "failure".to_string(),
attributes: IndexMap::new(),
children: vec![XMLNode::Text(message)],
};
children.push(XMLNode::Element(element));
}
for message in self.errors.clone() {
let element = Element {
prefix: None,
namespace: None,
namespaces: None,
name: "error".to_string(),
attributes: IndexMap::new(),
children: vec![XMLNode::Text(message)],
};
children.push(XMLNode::Element(element));

let mut element = Element::new("testcase")
.attr("id", &self.id)
.attr("name", &self.name)
.attr("time", &time_in_seconds);

for failure in self.failures.iter() {
element = element.add_child(Element::new("failure").text(failure))
}
Element {
name,
prefix: None,
namespace: None,
namespaces: None,
attributes,
children,

for error in self.errors.iter() {
element = element.add_child(Element::new("error").text(error))
}
element
}

pub fn get_error_count(&self) -> usize {
@@ -112,6 +87,7 @@ mod test {
use hurl_core::ast::SourceInfo;

use crate::report::junit::testcase::Testcase;
use crate::report::junit::xml::XmlDocument;
use crate::runner::{EntryResult, Error, HurlResult, RunnerError};

#[test]
@@ -124,16 +100,13 @@ mod test {
timestamp: 1,
};

let mut buffer = Vec::new();
let content = "";
let filename = "test.hurl";
Testcase::from(&hurl_result, content, filename)
.to_xml()
.write(&mut buffer)
.unwrap();
let element = Testcase::from(&hurl_result, content, filename).to_xml();
let doc = XmlDocument::new(element);
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8"?><testcase id="test.hurl" name="test.hurl" time="0.230" />"#
doc.to_string().unwrap(),
r#"<?xml version="1.0" encoding="utf-8"?><testcase id="test.hurl" name="test.hurl" time="0.230" />"#
);
}

@@ -164,14 +137,12 @@ HTTP/1.0 200
cookies: vec![],
timestamp: 1,
};
let mut buffer = Vec::new();
Testcase::from(&hurl_result, content, filename)
.to_xml()
.write(&mut buffer)
.unwrap();

let element = Testcase::from(&hurl_result, content, filename).to_xml();
let doc = XmlDocument::new(element);
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8"?><testcase id="test.hurl" name="test.hurl" time="0.230"><failure>Assert status code
doc.to_string().unwrap(),
r#"<?xml version="1.0" encoding="utf-8"?><testcase id="test.hurl" name="test.hurl" time="0.230"><failure>Assert status code
--> test.hurl:2:10
|
2 | HTTP/1.0 200
@@ -205,14 +176,11 @@ HTTP/1.0 200
cookies: vec![],
timestamp: 1,
};
let mut buffer = Vec::new();
Testcase::from(&hurl_result, content, filename)
.to_xml()
.write(&mut buffer)
.unwrap();
let element = Testcase::from(&hurl_result, content, filename).to_xml();
let doc = XmlDocument::new(element);
assert_eq!(
std::str::from_utf8(&buffer).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8"?><testcase id="test.hurl" name="test.hurl" time="0.230"><error>HTTP connection
doc.to_string().unwrap(),
r#"<?xml version="1.0" encoding="utf-8"?><testcase id="test.hurl" name="test.hurl" time="0.230"><error>HTTP connection
--> test.hurl:1:5
|
1 | GET http://unknown
6 changes: 6 additions & 0 deletions packages/hurl/src/report/junit/xml/mod.rs
Original file line number Diff line number Diff line change
@@ -31,6 +31,12 @@ pub struct XmlDocument {
pub root: Option<Element>,
}

impl XmlDocument {
pub fn new(root: Element) -> XmlDocument {
XmlDocument { root: Some(root) }
}
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum XmlNode {
/// An XML element.
1 change: 1 addition & 0 deletions packages/hurl/src/report/junit/xml/reader.rs
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ pub enum ParserError {
/// document returned is a in-memory tree representation of the whole document.
impl XmlDocument {
/// Convenient associated method to read and parse a XML string `source`.
#[allow(dead_code)]
pub fn parse_str(source: &str) -> Result<XmlDocument, ParserError> {
let bytes = source.as_bytes();
XmlDocument::parse(bytes)
Loading

0 comments on commit c72f04f

Please sign in to comment.