From f360174dbb87c8089e3f7e09203aae4f746c30fc Mon Sep 17 00:00:00 2001 From: TheWastl <36932506+TheWastl@users.noreply.github.com> Date: Sun, 29 May 2022 16:34:26 +0200 Subject: [PATCH] Use strong-xml instead of serde and generalize frontend - use strong-xml instead of serde - display all errors, not only leaks - display s and additional stacks - use the description provided by valgrind instead of providing our own --- Cargo.lock | 115 +++++++++---------- Cargo.toml | 11 +- src/main.rs | 37 +++--- src/valgrind/mod.rs | 30 ++--- src/valgrind/xml/mod.rs | 236 ++++++++++++++++++++++++++++++-------- src/valgrind/xml/tests.rs | 97 +++++++++++++--- 6 files changed, 359 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ed65ea..38900f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,17 +67,11 @@ dependencies = [ "bytesize", "colored", "predicates", - "serde", - "serde-xml-rs", + "strong-xml", + "strum", "textwrap", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "colored" version = "2.0.0" @@ -116,6 +110,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -134,6 +134,12 @@ dependencies = [ "either", ] +[[package]] +name = "jetscii" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492895b76e8c1a78a419f2977a38e42032077ca67d101d26b435ee705119e373" + [[package]] name = "lazy_static" version = "1.4.0" @@ -146,15 +152,6 @@ version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - [[package]] name = "memchr" version = "2.4.1" @@ -248,31 +245,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "serde" -version = "1.0.136" +name = "rustversion" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" -dependencies = [ - "serde_derive", -] +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] -name = "serde-xml-rs" -version = "0.5.1" +name = "smawk" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "strong-xml" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d19fb3a618e2f1039e32317c9f525e6d45c55af704ec7c429aa74412419bebf" dependencies = [ - "log", - "serde", - "thiserror", - "xml-rs", + "jetscii", + "lazy_static", + "memchr", + "strong-xml-derive", + "xmlparser", ] [[package]] -name = "serde_derive" -version = "1.0.136" +name = "strong-xml-derive" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "92c781f499321613b112be5d9338189ef1ed19689a01edd23d923ea57ad5c7e1" dependencies = [ "proc-macro2", "quote", @@ -280,10 +281,26 @@ dependencies = [ ] [[package]] -name = "smawk" -version = "0.3.1" +name = "strum" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] [[package]] name = "syn" @@ -324,26 +341,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "unicode-linebreak" version = "0.1.2" @@ -397,7 +394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "xml-rs" -version = "0.8.4" +name = "xmlparser" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" diff --git a/Cargo.toml b/Cargo.toml index 756d77f..4d420c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,13 +26,12 @@ exclude = [ [features] default = ["textwrap"] -[dependencies.serde] -version = "1" -features = ["derive"] +[dependencies.strong-xml] +version = "0.6" -[dependencies.serde-xml-rs] -version = "0.5" -default-features = false +[dependencies.strum] +version = "0.24" +features = ["derive"] [dependencies.colored] version = "2" diff --git a/src/main.rs b/src/main.rs index 631f854..f2c566e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,24 +24,20 @@ mod valgrind; use colored::Colorize as _; use std::env; use std::process; +use valgrind::xml; /// Nicely format the errors in the valgrind output, if there are any. -fn display_error(errors: &[valgrind::xml::Error]) { +fn display_error(errors: &[xml::Error]) { // format the output in a helpful manner for error in errors { - eprintln!( - "{:>12} leaked {} in {} block{}", - "Error".red().bold(), - bytesize::to_string(error.resources.bytes as _, true), - error.resources.blocks, - if error.resources.blocks == 1 { "" } else { "s" } - ); - let mut info = Some("Info".cyan().bold()); - error - .stack_trace - .frames - .iter() - .for_each(|frame| eprintln!("{:>12} at {}", info.take().unwrap_or_default(), frame)); + eprintln!("{:>12} {}", "Error".red().bold(), error.description); + display_backtrace(&error.stack_trace); + for extra in &error.extra { + match extra { + xml::ErrorExtra::AuxWhat(s) => eprintln!("{:>12} {}", "Info".cyan().bold(), s), + xml::ErrorExtra::StackTrace(stack) => display_backtrace(stack), + } + } } let total: usize = errors.iter().map(|error| error.resources.bytes).sum(); @@ -52,6 +48,14 @@ fn display_error(errors: &[valgrind::xml::Error]) { ); } +/// Display a backtrace provided by valgrind. +fn display_backtrace(backtrace: &xml::Stack) { + backtrace + .frames + .iter() + .for_each(|frame| eprintln!("{:>12} at {}", "", frame)); +} + fn main() { panic::replace_hook(); @@ -90,10 +94,7 @@ fn main() { let command = env::args_os().skip(1); let exit_code = match valgrind::execute(command) { - Ok(valgrind::xml::Output { - errors: Some(errors), - .. - }) => { + Ok(xml::Output { errors, .. }) if !errors.is_empty() => { display_error(&errors); 127 } diff --git a/src/valgrind/mod.rs b/src/valgrind/mod.rs index ecbe7ed..3f87ba2 100644 --- a/src/valgrind/mod.rs +++ b/src/valgrind/mod.rs @@ -2,11 +2,11 @@ pub mod xml; -use serde::Deserialize; use std::net::{SocketAddr, TcpListener}; use std::process::Command; use std::{env, fmt, io::Read}; use std::{ffi::OsStr, process::Stdio}; +use strong_xml::{XmlError, XmlRead}; /// Error type for valgrind-execution-related failures. #[derive(Debug)] @@ -27,7 +27,7 @@ pub enum Error { /// /// This variant contains the inner deserialization error and the output of /// valgrind. - MalformedOutput(serde_xml_rs::Error, Vec), + MalformedOutput(XmlError, Vec), } impl std::error::Error for Error {} @@ -85,26 +85,12 @@ where listener .read_to_end(&mut output) .map_err(|_| Error::SocketConnection)?; - let xml: xml::Output = xml::Output::deserialize( - &mut serde_xml_rs::Deserializer::new_from_reader(&*output) - .non_contiguous_seq_elements(true), - ) - .map(|output_: xml::Output| { - let mut output = output_; - if let Some(err) = output.errors { - let new_err: Vec = err - .into_iter() - .filter(|e| e.resources.bytes > 0 || e.resources.blocks > 0) - .collect(); - if new_err.is_empty() { - output.errors = None; - } else { - output.errors = Some(new_err); - } - } - output - }) - .map_err(|e| Error::MalformedOutput(e, output))?; + let output_str = match std::str::from_utf8(&output) { + Ok(s) => s, + Err(e) => return Err(Error::MalformedOutput(XmlError::Utf8(e), output))?, + }; + let xml: xml::Output = + xml::Output::from_str(output_str).map_err(|e| Error::MalformedOutput(e, output))?; Ok(xml) }); diff --git a/src/valgrind/xml/mod.rs b/src/valgrind/xml/mod.rs index 03e27eb..a56728a 100644 --- a/src/valgrind/xml/mod.rs +++ b/src/valgrind/xml/mod.rs @@ -11,19 +11,20 @@ #[cfg(test)] mod tests; -use serde::{de::Visitor, Deserialize, Deserializer}; use std::fmt::{self, Display, Formatter}; +use strong_xml::{XmlError, XmlRead, XmlReader, XmlResult}; +use strum::EnumString; /// The output of a valgrind run. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] -#[serde(rename = "valgrindoutput")] +#[derive(Debug, Clone, PartialEq, Eq, Hash, XmlRead)] +#[xml(tag = "valgrindoutput")] pub struct Output { - #[serde(rename = "protocolversion")] + #[xml(flatten_text = "protocolversion")] protocol_version: ProtocolVersion, - #[serde(rename = "protocoltool")] + #[xml(flatten_text = "protocoltool")] tool: Tool, - #[serde(rename = "error")] - pub errors: Option>, + #[xml(child = "error")] + pub errors: Vec, } /// The version of the XML format. @@ -31,9 +32,9 @@ pub struct Output { /// Although there are also versions 1-3, there is only a variant for version 4, /// so that all older formats will fail. The other `struct`s in this file assume /// the newest protocol version. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)] enum ProtocolVersion { - #[serde(rename = "4")] + #[strum(serialize = "4")] Version4, // other formats are not supported } @@ -43,34 +44,119 @@ enum ProtocolVersion { /// Although there are other tools available, there is only a variant for the /// so-called `memcheck` tool, so that all other tools will fail. The other /// `struct`s in this file assume the memcheck output. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)] enum Tool { - #[serde(rename = "memcheck")] + #[strum(serialize = "memcheck")] MemCheck, // other tools are not supported } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Error { - #[serde(deserialize_with = "deserialize_hex")] unique: u64, pub kind: Kind, - #[serde(default)] - #[serde(rename = "xwhat")] + pub description: String, pub resources: Resources, - #[serde(rename = "stack")] pub stack_trace: Stack, + pub extra: Vec, +} +impl<'a> XmlRead<'a> for Error { + fn from_reader(reader: &mut XmlReader<'a>) -> XmlResult { + #[derive(XmlRead)] + enum What { + #[xml(tag = "what")] + What(#[xml(text)] String), + #[xml(tag = "xwhat")] + XWhat { + #[xml(flatten_text = "text")] + text: String, + #[xml(flatten_text = "leakedbytes", default)] + bytes: usize, + #[xml(flatten_text = "leakedblocks", default)] + blocks: usize, + }, + } + + #[derive(XmlRead)] + #[xml(tag = "error")] + struct TmpError { + #[xml(flatten_text = "unique")] + unique: Hex64, + #[xml(flatten_text = "kind")] + pub kind: Kind, + #[xml(child = "what", child = "xwhat")] + pub what: What, + #[xml(child = "stack", child = "auxwhat", child = "xauxwhat")] + pub extra: Vec, + } + + let TmpError { + unique: Hex64(unique), + kind, + what, + mut extra, + } = TmpError::from_reader(reader)?; + let first = (!extra.is_empty()).then(|| extra.remove(0)); + let stack_trace = if let Some(ErrorExtra::StackTrace(s)) = first { + s + } else { + return Err(XmlError::MissingField { + name: "Error".to_string(), + field: "stack_trace".to_string(), + }); + }; + let (description, resources) = match what { + What::What(s) => (s, Resources::default()), + What::XWhat { + text, + bytes, + blocks, + } => (text, Resources { bytes, blocks }), + }; + Ok(Self { + unique, + kind, + description, + resources, + stack_trace, + extra, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ErrorExtra { + StackTrace(Stack), + AuxWhat(String), +} +impl<'a> XmlRead<'a> for ErrorExtra { + fn from_reader(reader: &mut XmlReader<'a>) -> XmlResult { + #[derive(XmlRead)] + pub enum TmpErrorExtra { + #[xml(tag = "stack")] + StackTrace(Stack), + #[xml(tag = "auxwhat")] + AuxWhat(#[xml(text)] String), + #[xml(tag = "xauxwhat")] + XAuxWhat(#[xml(flatten_text = "text")] String), + } + + Ok(match TmpErrorExtra::from_reader(reader)? { + TmpErrorExtra::StackTrace(s) => Self::StackTrace(s), + TmpErrorExtra::AuxWhat(s) | TmpErrorExtra::XAuxWhat(s) => Self::AuxWhat(s), + }) + } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)] pub enum Kind { - #[serde(rename = "Leak_DefinitelyLost")] + #[strum(serialize = "Leak_DefinitelyLost")] LeakDefinitelyLost, - #[serde(rename = "Leak_StillReachable")] + #[strum(serialize = "Leak_StillReachable")] LeakStillReachable, - #[serde(rename = "Leak_IndirectlyLost")] + #[strum(serialize = "Leak_IndirectlyLost")] LeakIndirectlyLost, - #[serde(rename = "Leak_PossiblyLost")] + #[strum(serialize = "Leak_PossiblyLost")] LeakPossiblyLost, InvalidFree, MismatchedFree, @@ -106,30 +192,24 @@ impl Display for Kind { } } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub struct Resources { - #[serde(rename = "leakedbytes")] pub bytes: usize, - #[serde(rename = "leakedblocks")] pub blocks: usize, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, XmlRead)] +#[xml(tag = "stack")] pub struct Stack { - #[serde(rename = "frame")] + #[xml(child = "frame")] pub frames: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Frame { - #[serde(rename = "ip")] - #[serde(deserialize_with = "deserialize_hex")] pub instruction_pointer: u64, - #[serde(rename = "obj")] pub object: Option, - #[serde(rename = "dir")] pub directory: Option, - #[serde(rename = "fn")] pub function: Option, pub file: Option, pub line: Option, @@ -148,26 +228,88 @@ impl Display for Frame { Ok(()) } } +impl<'a> XmlRead<'a> for Frame { + fn from_reader(reader: &mut XmlReader<'a>) -> XmlResult { + #[derive(XmlRead)] + #[xml(tag = "frame")] + pub struct TmpFrame { + #[xml(flatten_text = "ip")] + pub instruction_pointer: Hex64, + #[xml(flatten_text = "obj")] + pub object: Option, + #[xml(flatten_text = "dir")] + pub directory: Option, + #[xml(flatten_text = "fn")] + pub function: Option, + #[xml(flatten_text = "file")] + pub file: Option, + #[xml(flatten_text = "line")] + pub line: Option, + } -fn deserialize_hex<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - deserializer.deserialize_str(HexVisitor) + let TmpFrame { + instruction_pointer: Hex64(instruction_pointer), + object, + directory, + function, + file, + line, + } = TmpFrame::from_reader(reader)?; + Ok(Self { + instruction_pointer, + object, + directory, + function, + file, + line, + }) + } } -/// A visitor for parsing a `u64` in the format `0xDEADBEEF`. -struct HexVisitor; -impl<'de> Visitor<'de> for HexVisitor { - type Value = u64; +use hex64::Hex64; +mod hex64 { + use std::error::Error; + use std::fmt::{self, Display, Formatter}; + use std::num::ParseIntError; + use std::str::FromStr; + + #[derive(Debug)] + pub enum ParseHex64Error { + MissingPrefix, + ParseInt(ParseIntError), + } + + impl Display for ParseHex64Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingPrefix => write!(f, "HEX64: missing 0x prefix"), + Self::ParseInt(err) => write!(f, "HEX64: {}", err), + } + } + } - fn expecting(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("hexadecimal number with leading '0x'") + impl Error for ParseHex64Error { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::MissingPrefix => None, + Self::ParseInt(err) => Some(err), + } + } } - fn visit_str(self, value: &str) -> Result { - let value = value.to_ascii_lowercase(); - let value = value - .strip_prefix("0x") - .ok_or_else(|| E::custom("'0x' prefix missing"))?; - Self::Value::from_str_radix(value, 16) - .map_err(|_| E::custom(format!("invalid hex number '{}'", value))) + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct Hex64(pub u64); + impl FromStr for Hex64 { + type Err = ParseHex64Error; + + fn from_str(value: &str) -> Result { + let value = value.to_ascii_lowercase(); + let value = value + .strip_prefix("0x") + .ok_or(ParseHex64Error::MissingPrefix)?; + u64::from_str_radix(value, 16) + .map(Hex64) + .map_err(ParseHex64Error::ParseInt) + } } } diff --git a/src/valgrind/xml/tests.rs b/src/valgrind/xml/tests.rs index bc16d11..c5df295 100644 --- a/src/valgrind/xml/tests.rs +++ b/src/valgrind/xml/tests.rs @@ -1,16 +1,18 @@ -use super::{Error, Frame, Kind, Output, Resources}; -use std::{fs, io::BufReader}; +use super::{Error, ErrorExtra, Frame, Kind, Output, Resources, Stack}; +use std::{fs, io::Read}; -use serde_xml_rs::{from_reader, from_str}; +use strong_xml::XmlRead; #[test] fn sample_output() { - let xml: Output = from_reader(BufReader::new( - fs::File::open("src/valgrind/xml/memory-leaks.xml").expect("Could not open test file"), - )) - .expect("Could not read test file"); + let mut xml_string = String::new(); + fs::File::open("src/valgrind/xml/memory-leaks.xml") + .expect("Could not open test file") + .read_to_string(&mut xml_string) + .expect("Could not read test file"); + let xml = Output::from_str(&xml_string).expect("Could not parse test file"); - let errors = xml.errors.expect("There are errors in the test case"); + let errors = xml.errors; assert_eq!(errors.len(), 8); assert_eq!(errors[0].kind, Kind::LeakDefinitelyLost); assert_eq!(errors[0].unique, 0x0); @@ -58,7 +60,7 @@ fn sample_output() { #[test] fn unique_ids_have_to_be_in_hex_with_prefix() { - let result: Error = from_str( + let result = Error::from_str( r"\ 0xDEAD1234\ 1\ @@ -81,7 +83,7 @@ fn unique_ids_have_to_be_in_hex_with_prefix() { #[test] fn missing_hex_prefix_is_an_error() { - let result: Result = from_str( + let result = Error::from_str( r"\ 0DEADBEEF\ 1\ @@ -100,7 +102,7 @@ fn missing_hex_prefix_is_an_error() { ); assert!(result.is_err()); - let result: Result = from_str( + let result = Error::from_str( r"\ xDEADBEEF\ 1\ @@ -119,7 +121,7 @@ fn missing_hex_prefix_is_an_error() { ); assert!(result.is_err()); - let result: Result = from_str( + let result = Error::from_str( r"\ DEADBEEF\ 1\ @@ -141,7 +143,7 @@ fn missing_hex_prefix_is_an_error() { #[test] fn invalid_hex_digits_are_an_error() { - let result: Result = from_str( + let result = Error::from_str( r"\ 0xhello\ 1\ @@ -163,7 +165,7 @@ fn invalid_hex_digits_are_an_error() { #[test] fn hex_and_prefix_case_is_ignored() { - let result: Error = from_str( + let result = Error::from_str( r"\ 0XdEaDbEeF\ 1\ @@ -186,7 +188,7 @@ fn hex_and_prefix_case_is_ignored() { #[test] fn unique_id_is_64bit() { - let result: Error = from_str( + let result = Error::from_str( r"\ 0x123456789ABCDEF0\ 1\ @@ -206,3 +208,68 @@ fn unique_id_is_64bit() { .expect("Could not parse test XML"); assert_eq!(result.unique, 0x1234_5678_9ABC_DEF0); } + +#[test] +fn auxwhat_and_multiple_stacks() { + let result = Error::from_str( + r"\ + 0x00\ + 1\ + Leak_DefinitelyLost\ + \ + ...\ + 15\ + 1\ + \ + \ + \ + 0x483AD7B\ + \ + \ + Something else happened here\ + \ + \ + 0x1\ + \ + \ + more auxwhatfile\ + ", + ) + .expect("Could not parse test XML"); + assert_eq!( + result, + Error { + unique: 0x00, + kind: Kind::LeakDefinitelyLost, + description: "...".to_string(), + resources: Resources { + bytes: 15, + blocks: 1 + }, + stack_trace: Stack { + frames: vec![Frame { + instruction_pointer: 0x483AD7B, + object: None, + directory: None, + function: None, + file: None, + line: None, + }], + }, + extra: vec![ + ErrorExtra::AuxWhat("Something else happened here".to_string()), + ErrorExtra::StackTrace(Stack { + frames: vec![Frame { + instruction_pointer: 0x1, + object: None, + directory: None, + function: None, + file: None, + line: None, + }] + }), + ErrorExtra::AuxWhat("more auxwhat".to_string()), + ] + } + ); +}