8000 tests: use the json visitor to implement a simple path matcher by schopin-mozilla · Pull Request #207 · rust-minidump/minidump-writer · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::{
result,
};

use serde::Serialize;

#[allow(unused)]
type Error = Box<dyn error::Error + std::marker::Send + std::marker::Sync>;
#[allow(unused)]
Expand Down Expand Up @@ -129,25 +131,84 @@ where
serde_json::from_str(contents).expect("expected json")
}

pub fn visit_json_objects<V>(json: &serde_json::Value, path: &mut Vec<String>, visit: &mut V)
where
V: FnMut(&[String], &serde_json::Value),
{
if let Some(obj) = json.as_object() {
for (key, value) in obj.iter() {
// visiting the key is useful if you're matching against a complex enum member
// but don't really care about its contents
visit(path, &serde_json::Value::String(key.clone()));
path.push(key.clone());
visit_json_objects(value, path, visit);
path.pop();
}
}
if let Some(arr) = json.as_array() {
for value in arr.iter() {
visit_json_objects(value, path, visit);
}
return;
}
visit(path, json);
}

#[allow(unused)]
#[derive(Debug)]
pub struct ErrorPattern {
value: serde_json::Value,
ancestor: Option<String>,
}

#[allow(unused)]
pub fn assert_soft_errors_in_minidump<'a, 'b, T, I>(
impl ErrorPattern {
pub fn value(value: impl Serialize) -> Self {
Self {
value: serde_json::to_value(value).unwrap(),
ancestor: None,
}
}
pub fn with_ancestor(mut self, ancestor: impl ToString) -> Self {
self.ancestor = Some(ancestor.to_string());
self
}

fn matches(&self, value: &serde_json::Value, path: &[String]) -> bool {
&self.value == value
&& match &self.ancestor {
None => true,
Some(ancestor) => path.contains(ancestor),
}
}
}

#[allow(unused)]
pub(crate) fn assert_minidump_soft_errors_match_all<'a, T>(
dump: &minidump::Minidump<'a, T>,
expected_errors: I,
patterns: &[ErrorPattern],
) where
T: std::ops::Deref<Target = [u8]> + 'a,
I: IntoIterator<Item = &'b serde_json::Value>,
{
let actual_json = read_minidump_soft_errors_or_panic(dump);
let actual_errors = actual_json.as_array().unwrap();

// Ensure that every error we expect is in the actual list somewhere
for expected_error in expected_errors {
assert!(
actual_errors
.iter()
.any(|actual_error| actual_error == expected_error),
"soft error list missing expected error `{expected_error:#?}`\nError_list: {actual_errors:#?}"
);

let mut path = vec![];
// We can't use a HashSet because remove() is too greedy in terms of
// lifetime.
let mut expected: Vec<_> = patterns.iter().collect();

visit_json_objects(&actual_json, &mut path, &mut |path, value| {
for i in 0..expected.len() {
let pattern = &expected[i];
if pattern.matches(value, path) {
expected.remove(i);
return;
}
}
});

if !expected.is_empty() {
panic!("Soft errors patterns not found: {:?}&q 8000 uot;, expected);
}
}

Expand Down
6 changes: 2 additions & 4 deletions tests/linux_minidump_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,7 @@ contextual_test! {
contextual_test! {
fn skip_if_requested(context: Context) {
let expected_errors = vec![
json!({
"InitErrors": ["PrincipalMappingNotReferenced"]
}),
ErrorPattern::value("PrincipalMappingNotReferenced").with_ancestor("InitErrors")
];

let num_of_threads = 1;
Expand Down Expand Up @@ -356,7 +354,7 @@ contextual_test! {

// Ensure the MozSoftErrors stream contains the expected errors
let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
assert_soft_errors_in_minidump(&dump, &expected_errors);
assert_minidump_soft_errors_match_all(&dump, &expected_errors);
}
}

Expand Down
76 changes: 12 additions & 64 deletions tests/linux_minidump_writer_soft_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use common::*;
use minidump::Minidump;
use minidump_writer::{FailSpotName, minidump_writer::MinidumpWriterConfig};
use serde_json as json;

mod common;

Expand Down Expand Up @@ -32,27 +31,6 @@ fn soft_error_stream() {
read_minidump_soft_errors_or_panic(&dump);
}

fn visit_json_terminals<V>(json: &json::Value, path: &mut Vec<String>, visit: &mut V)
where
V: FnMut(&[String], &json::Value),
{
if let Some(obj) = json.as_object() {
for (key, value) in obj.iter() {
path.push(key.clone());
visit_json_terminals(value, path, visit);
path.pop();
}
return;
}
if let Some(arr) = json.as_array() {
for value in arr.iter() {
visit_json_terminals(value, path, visit);
}
return;
}
visit(path, json);
}

#[test]
fn soft_error_stream_content() {
let mut child = start_child_and_wait_for_threads(1);
Expand Down Expand Up @@ -81,50 +59,20 @@ fn soft_error_stream_content() {
child.kill().expect("Failed to kill process");
child.wait().expect("Failed to wait on killed process");

// Ensure the MozSoftErrors stream contains the expected errors
let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");

let actual_json = read_minidump_soft_errors_or_panic(&dump);

let mut stop_process_error_found = false;
let mut missing_auxv_error_found = false;
let mut thread_name_error_found = false;
let mut suspend_thread_error_found = false;
let mut cpu_info_error_found = false;

let mut path = Vec::new();

visit_json_terminals(&actual_json, &mut path, &mut |path, value| {
if value.as_i64() == Some(libc::EPERM.into())
&& path.contains(&"StopProcessFailed".to_string())
{
stop_process_error_found = true;
}
if value.as_str() == Some("InvalidFormat")
&& path.contains(&"FillMissingAuxvInfoErrors".to_string())
{
missing_auxv_error_found = true;
}
if path.contains(&"ReadThreadNameFailed".to_string()) {
thread_name_error_found = true;
}
if value.as_i64() == Some(libc::EPERM.into())
&& path.contains(&"SuspendThreadsErrors".to_string())
{
suspend_thread_error_found = true;
}
if value.as_i64() == Some(libc::EPERM.into())
&& path.contains(&"WriteCpuInformationFailed".to_string())
{
cpu_info_error_found = true;
}
});

assert!(stop_process_error_found);
assert!(missing_auxv_error_found);
assert!(thread_name_error_found);
assert!(suspend_thread_error_found);
assert!(cpu_info_error_found);
// Ensure the MozSoftErrors stream contains the expected errors
assert_minidump_soft_errors_match_all(
&dump,
&[
ErrorPattern::value(libc::EPERM).with_ancestor("StopProcessFailed"),
// AuxvError is not reachable so use the string form instead
ErrorPattern::value("InvalidFormat").with_ancestor("FillMissingAuxvInfoErrors"),
ErrorPattern::value("ReadThreadNameFailed"),
ErrorPattern::value(libc::EPERM).with_ancestor("SuspendThreadsErrors"),
ErrorPattern::value(libc::EPERM).with_ancestor("WriteCpuInformationFailed"),
],
);
}

#[test]
Expand Down
Loading
0