Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
use minidump_analyzer::MinidumpAnalyzer;
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use tempfile::tempdir;
/// Child failure types.
///
/// These must correspond to tests defined in `client.rs` (per the `Display` implementation).
#[derive(Debug, Clone, Copy)]
enum FailureType {
RaiseAbort,
}
impl FailureType {
pub fn test_name(&self) -> &str {
match self {
FailureType::RaiseAbort => "raise_abort",
}
}
}
impl AsRef<std::ffi::OsStr> for FailureType {
fn as_ref(&self) -> &std::ffi::OsStr {
self.test_name().as_ref()
}
}
impl std::fmt::Display for FailureType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(self.test_name())
}
}
fn start_child(failure: FailureType) -> Child {
Command::new("cargo")
.args([
"test",
"--package",
env!("CARGO_PKG_NAME"),
"--test",
"client",
"--",
"--include-ignored",
])
.arg(failure)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("failed to execute child")
}
// XXX: minidumper is somewhat slow to establish the connection, making the test slow.
fn write_minidump(minidump_file: &Path, failure: FailureType) {
use minidumper::{LoopAction, MinidumpBinary, Server, ServerHandler};
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
struct Handler {
minidump_file: PathBuf,
}
impl ServerHandler for Handler {
fn create_minidump_file(&self) -> std::io::Result<(File, PathBuf)> {
let f = File::create(&self.minidump_file)?;
let p = self.minidump_file.clone();
Ok((f, p))
}
fn on_minidump_created(
&self,
result: Result<MinidumpBinary, minidumper::Error>,
) -> LoopAction {
result.expect("failed to write minidump");
LoopAction::Exit
}
fn on_message(&self, _kind: u32, _buffer: Vec<u8>) {}
}
let mut server =
Server::with_name(&failure.to_string()).expect("failed to create minidumper server");
let mut child = start_child(failure);
/// Maximum time we want to wait for the child to execute and crash.
const CHILD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
// Run the server.
let shutdown = AtomicBool::default();
std::thread::scope(|s| {
let handle = s.spawn(|| {
std::thread::park_timeout(CHILD_TIMEOUT);
shutdown.store(true, Relaxed);
});
server
.run(
Box::new(Handler {
minidump_file: minidump_file.into(),
}),
&shutdown,
None,
)
.expect("minidumper server failure");
handle.thread().unpark();
});
drop(child.kill());
if !minidump_file.exists() {
// Likely a timeout occurred
panic!("expected child process to crash within {:?}", CHILD_TIMEOUT);
}
}
#[test]
fn analyze_basic_minidump() {
let dir = tempdir().expect("failed to create temporary directory");
let minidump_file = dir.path().join("mini.dump");
let extra_file = dir.path().join("mini.extra");
// Create minidump from test.
write_minidump(&minidump_file, FailureType::RaiseAbort);
// Create empty extra file
{
let mut extra = File::create(&extra_file).expect("failed to create extra json file");
write!(&mut extra, "{{}}").expect("failed to write to extra json file");
}
MinidumpAnalyzer::new(&minidump_file).analyze().unwrap();
// Check the output JSON
// The stack trace will actually be in cargo. It forks and execs the test program; there is no
// clean way to make it just exec one or to directly address the binary (without creating a new
// crate).
{
let mut extra_content = String::new();
File::open(extra_file)
.expect("failed to open extra json file")
.read_to_string(&mut extra_content)
.expect("failed to read extra json file");
let extra = json::parse(&extra_content).expect("failed to parse extra json");
let stack_traces = &extra["StackTraces"];
assert!(stack_traces.is_object());
let modules = &stack_traces["modules"];
assert!(modules.is_array());
for m in modules.members() {
assert!(m.is_object());
if m.has_key("debug_file") {
assert!(!m["debug_file"]
.as_str()
.unwrap()
.contains(std::path::MAIN_SEPARATOR));
}
}
let threads = &stack_traces["threads"];
assert!(threads.is_array() && threads.len() == 1);
assert!(threads[0].is_object());
let frames = &threads[0]["frames"];
assert!(frames.is_array() && !frames.is_empty());
}
}