/* 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 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 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 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, ) -> LoopAction { result.expect("failed to write minidump"); LoopAction::Exit } fn on_message(&self, _kind: u32, _buffer: Vec) {} } 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()); } }