use backtrace::Backtrace;
use io_utils::open_for_write_new;
use lazy_static::lazy_static;
use libc::SIGINT;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
use nix::Error;
use pprof::protos::Message;
use pprof::ProfilerGuard;
use stats_utils::percent_ratio;
use std::{
collections::HashMap,
env,
fs::File,
io::{BufWriter, Write},
ops::Deref,
os::unix::io::FromRawFd,
panic,
str::from_utf8,
sync::atomic::AtomicBool,
sync::atomic::Ordering::SeqCst,
sync::{Mutex, RwLock},
thread,
thread::ThreadId,
time,
time::Instant,
};
use string_utils::{stringme, strme, TextUtils};
use tables::print_tabular_vbox;
use vector_utils::{contains_at, erase_if, make_freq};
static mut GUARD: Option<ProfilerGuard<'static>> = None;
static mut REPORT: Option<pprof::Report> = None;
static mut BLACKLIST: Vec<String> = Vec::new();
pub fn start_profiling(blacklist: &[String]) {
let frequency = 1000;
unsafe {
BLACKLIST = blacklist.to_vec();
GUARD = Some(pprof::ProfilerGuard::new(frequency).unwrap());
}
}
pub fn stop_profiling() {
unsafe {
if REPORT.is_none() {
let report = GUARD.as_ref().unwrap().report().build();
if report.is_err() {
panic!("Failed to build profiling report.");
}
REPORT = Some(report.unwrap());
}
let report = REPORT.as_ref().unwrap();
let mut traces = Vec::<String>::new();
let blacklist = &BLACKLIST;
let mut n = 0;
for (frames, count) in report.data.iter() {
let m = &frames.frames;
n += count;
let mut symv = Vec::<Vec<String>>::new();
for i in 0..m.len() {
for j in 0..m[i].len() {
let s = &m[i][j];
let mut name = s.name();
if name.ends_with("::{{closure}}") {
name = name.rev_before("::{{closure}}").to_string();
}
if name.contains("::") {
name = name.rev_after("::").to_string();
}
let filename;
if s.filename.is_some() {
filename = s.filename.as_ref().unwrap().to_str().unwrap().to_string();
} else {
filename = "unknown".to_string();
}
let mut cratex; let mut version = String::new(); let file;
if filename.contains("/cargo/git/checkouts/") {
let post = filename.after("/cargo/git/checkouts/");
if post.contains("/src/") && post.rev_before("/src/").contains('/') {
let mid = post.rev_before("/src/");
file = post.after("/src/").to_string();
if mid.after("/").contains('/') {
version = mid.between("/", "/").to_string();
cratex = mid.rev_after("/").to_string();
} else {
version = mid.rev_after("/").to_string();
cratex = post.before("/").to_string();
if cratex.contains('-') {
cratex = cratex.rev_before("-").to_string();
}
}
} else {
cratex = "unknown".to_string();
file = "unknown".to_string();
}
} else if filename.contains("/src/")
&& filename.rev_before("/src/").contains('/')
{
cratex = filename.rev_before("/src/").rev_after("/").to_string();
file = filename.rev_after("/src/").to_string();
} else {
cratex = "unknown".to_string();
file = "unknown".to_string();
}
let lineno;
if s.lineno.is_some() {
lineno = format!("{}", s.lineno.unwrap());
} else {
lineno = "?".to_string();
}
if cratex.contains('-') && version.is_empty() {
let c = cratex.rev_before("-");
let d = cratex.rev_after("-").to_string();
if d.contains('.')
&& d.after(".").contains('.')
&& d.before(".").parse::<usize>().is_ok()
&& d.between(".", ".").parse::<usize>().is_ok()
&& d.rev_after(".").parse::<usize>().is_ok()
{
cratex = c.to_string();
version = d.to_string();
}
}
let mut blacklisted = false;
for b in blacklist.iter() {
if *b == cratex {
blacklisted = true;
}
}
if !blacklisted && file.ends_with(".rs") {
symv.push(vec![name, cratex, version, file, lineno]);
}
}
}
if !symv.is_empty() {
let mut log = String::new();
print_tabular_vbox(&mut log, &symv, 0, &b"l|l|l|l|l".to_vec(), false, false);
for _ in 0..*count {
let x = log.to_string();
traces.push(x);
}
}
}
traces.sort();
let mut freq = Vec::<(u32, String)>::new();
make_freq(&traces, &mut freq);
let mut report = String::new();
let traced = 100.0 * traces.len() as f64 / n as f64;
report += &format!(
"\nPRETTY TRACE PROFILE\n\nTRACED = {:.1}%\n\nTOTAL = {}\n\n",
traced,
traces.len()
);
let mut total = 0;
for (i, x) in freq.iter().enumerate() {
total += x.0 as usize;
report += &format!(
"[{}] COUNT = {} = {:.2}% ⮕ {:.2}%\n{}\n",
i + 1,
x.0,
percent_ratio(x.0 as usize, traces.len()),
percent_ratio(total, traces.len()),
x.1
);
}
print!("{}", report);
};
}
pub fn dump_profiling_as_proto(f: &str) {
unsafe {
if REPORT.is_none() {
let report = GUARD.as_ref().unwrap().report().build();
REPORT = Some(report.expect("Failed to build profiling report."));
}
let report = REPORT.as_ref().unwrap();
let profile = report.pprof().unwrap();
let mut y = Vec::new();
profile.encode(&mut y).unwrap();
let mut out = open_for_write_new![&f];
out.write_all(&y).unwrap();
}
}
#[derive(Default)]
pub struct PrettyTrace {
pub full_file: Option<String>,
pub fd: Option<i32>,
pub exit_message: Option<String>,
pub message: Option<&'static CHashMap<ThreadId, String>>,
pub profile: bool,
pub count: Option<usize>,
pub sep: f32,
pub whitelist: Option<Vec<String>>,
pub ctrlc: bool,
pub ctrlc_debug: bool,
pub noexit: bool,
pub function_to_run: Option<fn(&str) -> ()>,
}
impl PrettyTrace {
pub fn new() -> PrettyTrace {
PrettyTrace::default()
}
pub fn on(&mut self) {
let fd = if self.fd.is_some() {
self.fd.unwrap() as i32
} else {
-1_i32
};
let mut haps = Happening::new();
if self.profile {
if self.whitelist.is_none() {
self.whitelist = Some(Vec::<String>::new());
}
haps.initialize(
&self.whitelist.clone().unwrap(),
self.count.unwrap(),
self.sep,
);
}
let full_file = if self.full_file.is_some() {
self.full_file.clone().unwrap()
} else {
String::new()
};
if self.message.is_some() {
force_pretty_trace_fancy(
full_file,
fd,
self.exit_message.clone(),
self.message.unwrap(),
&haps,
self.ctrlc,
self.ctrlc_debug,
self.noexit,
self.function_to_run,
);
} else {
let tm = new_thread_message();
force_pretty_trace_fancy(
full_file,
fd,
self.exit_message.clone(),
tm,
&haps,
self.ctrlc,
self.ctrlc_debug,
self.noexit,
self.function_to_run,
);
}
}
pub fn ctrlc(&mut self) -> &mut PrettyTrace {
self.ctrlc = true;
self
}
pub fn ctrlc_debug(&mut self) -> &mut PrettyTrace {
self.ctrlc = true;
self.ctrlc_debug = true;
self
}
pub fn noexit(&mut self) -> &mut PrettyTrace {
self.noexit = true;
self
}
pub fn run_this(&mut self, f: fn(&str) -> ()) -> &mut PrettyTrace {
self.function_to_run = Some(f);
self
}
pub fn full_file(&mut self, full_file: &str) -> &mut PrettyTrace {
self.full_file = Some(full_file.to_string());
if thread::current().name().unwrap() != "main" {
panic!(
"PrettyTrace::full_file was called from a non-main thread. This is not\n\
allowed because PrettyTrace works by setting the panic hook, which is global.\n\
A value set by one thread might not be valid for another."
);
}
self
}
pub fn fd(&mut self, fd: i32) -> &mut PrettyTrace {
self.fd = Some(fd);
self
}
pub fn exit_message(&mut self, message: &str) -> &mut PrettyTrace {
self.exit_message = Some(message.to_string());
self
}
pub fn message(&mut self, message: &'static CHashMap<ThreadId, String>) -> &mut PrettyTrace {
self.message = Some(message);
if thread::current().name().unwrap() != "main" {
panic!(
"PrettyTrace::message was called from a non-main thread. This is not\n\
allowed because PrettyTrace works by setting the panic hook, which is global.\n\
A value set by one thread might not be valid for another."
);
}
self
}
}
struct Happening {
pub on: bool, pub whitelist: Vec<String>, pub hcount: usize, pub sep: f32, }
impl Happening {
pub fn new() -> Happening {
Happening {
on: false,
whitelist: Vec::<String>::new(),
hcount: 0,
sep: 1.0,
}
}
pub fn initialize(&mut self, whitelist: &[String], hcount: usize, sep: f32) {
self.on = true;
self.whitelist = whitelist.to_owned();
self.hcount = hcount;
self.sep = sep;
}
}
static CTRLC_DEBUG: AtomicBool = AtomicBool::new(false);
lazy_static! {
static ref HAPPENING: Mutex<Happening> = Mutex::new(Happening::new());
}
fn install_signal_handler(happening: bool, ctrlc: bool) -> Result<(), Error> {
if happening {
let handler = SigHandler::Handler(handler);
let action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
unsafe {
sigaction(Signal::SIGUSR1, &action)?;
}
}
if ctrlc {
let handler = SigHandler::Handler(handler);
let action = SigAction::new(handler, SaFlags::SA_RESTART, SigSet::empty());
unsafe {
sigaction(Signal::SIGINT, &action)?;
}
}
Ok(())
}
static mut HEARD_CTRLC: usize = 0;
extern "C" fn handler(sig: i32) {
let sep = HAPPENING.lock().unwrap().sep;
let sleep_time = (sep * 1000.0).round() as u64;
if sig == SIGINT {
if CTRLC_DEBUG.load(SeqCst) {
unsafe {
eprint!("\ncaught Ctrl-C");
eprintln!(" #{}", HEARD_CTRLC + 1);
}
}
unsafe {
if HEARD_CTRLC > 0 {
HEARD_CTRLC += 1;
std::process::exit(0);
}
HEARD_CTRLC += 1;
thread::sleep(time::Duration::from_millis(sleep_time));
if CTRLC_DEBUG.load(SeqCst) {
eprintln!("done sleeping");
}
if HEARD_CTRLC > 1 {
std::process::exit(0);
}
}
eprintln!();
panic!(
"Ctrl-C (SIGINT) interrupt detected\n\nThe traceback below only \
shows the master thread. If your code includes\n\
multithreading, you may need to turn that off to obtain \
a meaningful traceback."
);
}
}
pub struct CHashMap<K, V> {
map: RwLock<HashMap<K, V>>,
}
impl<K, V> CHashMap<K, V>
where
K: std::hash::Hash + std::cmp::Eq,
{
pub fn new() -> CHashMap<K, V> {
CHashMap {
map: RwLock::new(HashMap::new()),
}
}
pub fn insert(&self, k: K, v: V) {
self.map.write().unwrap().insert(k, v);
}
}
impl<K, V> Default for CHashMap<K, V>
where
K: std::hash::Hash + std::cmp::Eq,
{
fn default() -> Self {
Self::new()
}
}
pub fn new_thread_message() -> &'static CHashMap<ThreadId, String> {
let hashmap = CHashMap::new();
let box_thread_message = Box::new(hashmap);
let thread_message: &'static CHashMap<ThreadId, String> = Box::leak(box_thread_message);
thread_message
}
static TRACING: AtomicBool = AtomicBool::new(false);
fn force_pretty_trace_fancy(
log_file_name: String,
fd: i32,
exit_message: Option<String>,
thread_message: &'static CHashMap<ThreadId, String>,
happening: &Happening,
ctrlc: bool,
ctrlc_debug: bool,
noexit: bool,
function_to_run: Option<fn(&str) -> ()>,
) {
let _ = install_signal_handler(happening.on, ctrlc);
if ctrlc_debug {
CTRLC_DEBUG.store(true, SeqCst);
}
let t = Instant::now();
let _ = panic::take_hook();
panic::set_hook(Box::new(move |info| {
if TRACING.load(SeqCst) {
return;
}
TRACING.store(true, SeqCst);
let backtrace = Backtrace::new();
let mut tm = String::new();
let this_thread = thread::current().id();
let tmx = thread_message.map.read();
if tmx.is_err() {
eprintln!("\nProblem processing thread message in PrettyTrace.\n");
std::process::exit(1);
}
if tmx.as_ref().unwrap().contains_key(&this_thread) {
tm = format!("{}\n\n", tmx.unwrap().get(&this_thread).unwrap().deref());
}
let mut _verbose = false;
for (key, _value) in env::vars() {
if key == "RUST_FULL_TRACE" {
let bt: Vec<u8> = format!("{:?}", backtrace).into_bytes();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
let msg2 = match info.location() {
Some(location) => format!(
"thread '{}' panicked at {}:{}",
thread,
location.file(),
location.line()
),
None => format!("thread '{}' panicked ", thread),
};
eprintln!(
"\nRUST PROGRAM PANIC\n\n(Full traceback. \
Rerun with env var RUST_FULL_TRACE unset to \
see short traceback.)\n\n{}{}\n\n{}\n\n{}\n",
tm,
&msg,
&msg2,
from_utf8(&bt).unwrap()
);
std::process::exit(101);
}
}
let bt: Vec<u8> = format!("{:?}", backtrace).into_bytes();
let all_out = prettify_traceback(&bt, &Vec::<String>::new(), false);
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
let mut em = String::new();
if exit_message.is_some() {
em = format!("{}\n\n", exit_message.as_ref().unwrap());
}
let msg = match info.location() {
Some(location) => {
let loc = &(*location.file());
let mut x2 = loc.to_owned();
let x2_orig = x2.clone();
if loc.contains("/rustc/") && loc.after("/rustc/").contains("/src/") {
let y = loc.between("/rustc/", "/src/");
if y.len() > 10 {
x2 = x2.replace(y, "<stuff>");
}
}
if loc.contains("/checkouts/") && loc.after("/checkouts/").contains("/src/") {
let y = loc.between("/checkouts/", "/src/");
if y.len() > 10 {
x2 = x2.replace(y, "<stuff>");
}
}
let pre = format!("{}:{}", x2, location.line());
let prex = if all_out.contains(&pre) || x2_orig.contains("pretty_trace") {
"".to_string()
} else {
format!("\n\n0: ◼ {}", pre)
};
let long_msg = if log_file_name.is_empty() {
"Rerun with env var RUST_FULL_TRACE set to see full traceback.".to_string()
} else {
format!("Full traceback is at {}.", log_file_name)
};
format!(
"RUST PROGRAM PANIC AFTER {} SECONDS\n\n(Shortened traceback. \
{})\n\n{}{}{}",
t.elapsed().as_secs(),
long_msg,
tm,
msg,
prex
)
}
None => format!("RUST PROGRAM PANIC\n\n{}", msg),
};
if msg.contains("Broken pipe") {
std::process::exit(101);
}
let mut out = format!("\n{}\n\n", &msg);
let ex = std::env::current_exe();
match ex {
Err(_) => {
out += "█ WARNING. It was not possible to get the path of your executable.\n\
█ This may result in a defective traceback.\n\n";
}
Ok(ex) => {
let ex = ex.to_str();
match ex {
None => {
out +=
"█ WARNING. The path of your executable could not be converted into\n\
█ a string. This is weird and might result in a defective traceback.\n\n";
}
Some(ex) => {
let f = File::open(&ex);
if f.is_err() {
out +=
"█ WARNING. Your executable file could not be opened for reading.\n\
█ This might be because it does not have read permission set for you.\n\
█ This may result in a defective traceback.\n\n";
}
}
};
}
};
out += &all_out;
out += &em;
eprint!("{}", out);
let mut failed = false;
if fd >= 0 {
unsafe {
let mut err_file = File::from_raw_fd(fd);
let x = err_file.write(out.as_bytes());
if x.is_err() {
eprintln!(
"\nProblem in PrettyTrace writing to file descriptor {}.\n",
fd
);
failed = true;
}
}
}
if !log_file_name.is_empty() {
let f = File::create(&log_file_name);
if f.is_err() {
eprintln!(
"\nDuring panic, attempt to create full log file \
named {} failed, giving up.\n",
log_file_name
);
std::process::exit(101);
}
let mut log_file_writer = BufWriter::new(f.unwrap());
let bt: Vec<u8> = format!("{:?}", backtrace).into_bytes();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
let msg = match info.location() {
Some(location) => format!(
"thread '{}' panicked at '{}': {}:{}",
thread,
msg,
location.file(),
location.line()
),
None => format!("thread '{}' panicked at '{}'", thread, msg),
};
log_file_writer
.write_fmt(format_args!(
"\nRUST PROGRAM PANIC\n\n(Full traceback.)\n\n{}{}\n\n{}\n{}",
tm,
&msg,
from_utf8(&bt).unwrap(),
em
))
.unwrap();
}
if let Some(function_to_run) = function_to_run {
function_to_run(&out);
}
if !noexit || failed {
std::process::exit(101);
}
}));
}
#[allow(clippy::cognitive_complexity)]
fn prettify_traceback(bt: &[u8], whitelist: &[String], pack: bool) -> String {
let mut btlines = Vec::<Vec<u8>>::new();
let mut line = Vec::<u8>::new();
for z in bt {
if *z == b'\n' {
let x = stringme(&line);
let mut x2 = x.clone();
if x.contains("/rustc/") && x.after("/rustc/").contains("/src/") {
let y = x.between("/rustc/", "/src/");
if y.len() > 10 {
x2 = x2.replace(y, "<stuff>");
}
}
if x.contains("/checkouts/") && x.after("/checkouts/").contains("/src/") {
let y = x.between("/checkouts/", "/src/");
if y.len() > 10 {
x2 = x2.replace(y, "<stuff>");
}
}
let srcgit = "/src/github.com-";
if x.contains(srcgit) && x.after(srcgit).contains('/') {
let y = x.between(srcgit, "/");
if y.len() > 10 {
x2 = x2.replace(&format!("{}{}", srcgit, y), "/<stuff>");
}
}
if x2.contains("/src/") && x2.before("/src/").contains('/') && x2.contains(' ') {
x2 = format!(
"{} {}/src/{}",
x2.rev_before(" "),
x2.before("/src/").rev_after("/"),
x2.after("/src/")
);
}
btlines.push(x2.as_bytes().to_vec());
line.clear();
} else {
line.push(*z);
}
}
let mut blocks = Vec::<Vec<Vec<Vec<u8>>>>::new();
let mut block = Vec::<Vec<Vec<u8>>>::new();
let mut blocklet = Vec::<Vec<u8>>::new();
for x in btlines {
if x.is_empty() {
continue;
}
let mut s = x.as_slice();
let mut j = 0;
while j < s.len() {
if s[j] != b' ' {
break;
}
j += 1;
}
while j < s.len() {
if !(s[j] as char).is_digit(10) {
break;
}
j += 1;
}
if j < s.len() && s[j] == b':' && !block.is_empty() {
if !blocklet.is_empty() {
block.push(blocklet.clone());
blocklet.clear();
}
blocks.push(block.clone());
block.clear();
s = &s[j + 1..s.len()];
}
let mut j = 0;
while j < s.len() {
if s[j] != b' ' {
break;
}
j += 1;
}
s = &s[j..s.len()];
blocklet.push(s.to_vec());
if s.starts_with(b"at ") {
block.push(blocklet.clone());
blocklet.clear();
}
}
if !blocklet.is_empty() {
block.push(blocklet.clone());
}
if !block.is_empty() {
blocks.push(block.clone());
}
let blacklist = vec![
"pretty_trace",
"::libunwind::",
"::Backtrace::",
"::backtrace::",
"::panicking::",
"::lang_start",
"rust_maybe_catch_panic",
"rust_panic",
"libc_start_main",
"::force_pretty_trace::",
"::thread::",
"- rayon",
"rayon::iter",
"rayon_core::",
"- start_thread",
"<alloc::",
"rust_begin_unwind",
"start_thread",
"__clone",
"call_once",
"/panic.rs",
"/panicking.rs",
"catch_unwind",
"lang_start_internal",
"libstd/rt.rs",
"tokio",
"future",
"tonic::server",
"hyper::proto",
];
for x in blocks.iter_mut() {
let mut to_delete = vec![false; x.len()];
'block: for j in 0..x.len() {
for k in 0..x[j].len() {
let s = strme(&x[j][k]);
if s.contains("pretty_trace::tests") {
continue 'block;
}
}
'outer1: for k in 0..x[j].len() {
let s = strme(&x[j][k]);
for b in blacklist.iter() {
if s.contains(b) {
to_delete[j] = true;
break 'outer1;
}
}
}
if !to_delete[j] && !whitelist.is_empty() {
let mut good = false;
'outer2: for k in 0..x[j].len() {
let s = strme(&x[j][k]);
for b in whitelist.iter() {
if s.contains(b) {
good = true;
break 'outer2;
}
}
}
if !good {
to_delete[j] = true;
}
}
let s = strme(&x[j][0]);
if !to_delete[j] && x[j].len() == 1 && s.ends_with("- main") {
to_delete[j] = true;
}
let m = " main (";
if s.contains(&m) && s.after(m).contains(')') && !s.between(m, ")").contains('(') {
to_delete[j] = true;
}
}
erase_if(x, &to_delete);
}
let mut to_delete = vec![false; blocks.len()];
for i in 0..blocks.len() {
if blocks[i].is_empty() {
to_delete[i] = true;
}
}
erase_if(&mut blocks, &to_delete);
let src = b"/src/".to_vec();
for z in blocks.iter_mut() {
for w in z.iter_mut() {
if w.len() == 2 {
let mut x = Vec::<u8>::new();
let y = w[1].clone();
'outer: for j in 0..y.len() {
if contains_at(&y, &src, j) {
for k in (0..j).rev() {
if y[k] != b'/' {
continue;
}
for l in (0..k).rev() {
if y[l] == b' ' {
for u in y.iter().take(l + 1) {
x.push(*u);
}
for u in y.iter().skip(k + 1) {
x.push(*u);
}
break 'outer;
}
}
}
}
}
if !x.is_empty() {
w[1] = y;
}
}
}
}
let mut all_out = String::new();
for (i, x) in blocks.iter().enumerate() {
let num = format!("{}: ", i + 1);
let sub = stringme(vec![b' '; num.len()].as_slice());
for (j, y) in x.iter().enumerate() {
for (k, z) in y.iter().enumerate() {
if j == 0 && k == 0 {
all_out += #
} else {
all_out += ⊂
}
if k > 0 {
all_out += "◼ ";
}
let mut s = stringme(z);
if k == 0 && s.contains("::") {
let cc = s.rfind("::").unwrap();
s.truncate(cc);
}
if s.ends_with("::{{closure}}") {
s = s.rev_before("::{{closure}}").to_string();
}
all_out += &s;
all_out += "\n";
}
}
if !pack {
all_out += "\n";
}
}
all_out
}
#[cfg(test)]
mod tests {
#[inline(never)]
fn looper(results: &mut Vec<(usize, usize)>) {
use rayon::prelude::*;
results.par_iter_mut().for_each(|r| {
for _ in 0..10_000 {
r.1 = r.1.wrapping_add(1).wrapping_add(r.0 * r.1);
}
});
}
use super::*;
#[test]
fn test_ctrlc() {
use libc::{kill, SIGINT};
use nix::unistd::{fork, pipe, ForkResult};
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::FromRawFd;
use std::{thread, time};
use string_utils::strme;
let pipefd = pipe().unwrap();
let message = "Dang it, you found a bug! Please call us at (999) 123-4567.";
PrettyTrace::new()
.exit_message(message)
.ctrlc()
.fd(pipefd.1)
.on();
let mut results = vec![(1_usize, 0_usize); 100_000_000];
let bar = "▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓";
println!("\n{}", bar);
println!("DELIBERATELY PROVOKING A PANIC USING A CTRL-C");
print!("{}", bar);
std::io::stdout().flush().unwrap();
unsafe {
match fork() {
Ok(ForkResult::Parent { child: _, .. }) => {
thread::sleep(time::Duration::from_millis(2000));
let mut buffer = [0; 2000];
let mut err_file = File::from_raw_fd(pipefd.0);
let _ = err_file.read(&mut buffer).unwrap();
println!("{}", bar);
println!("TESTING THE PANIC FOR CORRECTNESS");
println!("{}", bar);
let s = strme(&buffer);
let mut have_main = false;
let lines: Vec<&str> = s.split_terminator('\n').collect();
for i in 0..lines.len() {
if lines[i].contains("pretty_trace::tests") {
have_main = true;
}
}
if have_main {
println!("\ngood: found inner loop\n");
} else {
panic!("FAIL: DID NOT FIND INNER LOOP");
}
}
Ok(ForkResult::Child) => {
thread::spawn(|| {
thread::sleep(time::Duration::from_millis(100));
let pid = std::process::id() as i32;
kill(pid, SIGINT);
});
looper(&mut results);
}
Err(_) => println!("Fork failed"),
}
}
}
}