#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use crate::ported::crt::{ColorElements, ColorScheme, A_BOLD, KEY_CTRL, KEY_F};
use crate::ported::functionbar::{FunctionBar_new, FunctionBar_setLabel};
use crate::ported::object::{Object, ObjectClass, Object_class};
use crate::ported::panel::{
HandlerResult, Panel, PanelClass, Panel_add, Panel_delete, Panel_get, Panel_new, Panel_prune,
Panel_setHeader, Panel_size,
};
use crate::ported::process::{
Process, Process_getPid, Process_isThread, CMDLINE_HIGHLIGHT_FLAG_BASENAME,
};
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendnAscii, RichString_appendnWide,
RichString_setAttrn,
};
use crate::ported::settings::Settings;
pub const BACKTRACE_PANEL_ROW_DATA_FRAME: i32 = 0;
pub const BACKTRACE_PANEL_ROW_ERROR: i32 = 1;
pub const BACKTRACE_PANEL_ROW_PROCESS_INFORMATION: i32 = 2;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BacktraceFrameData {
pub address: usize,
pub offset: usize,
pub functionName: Option<String>,
pub demangleFunctionName: Option<String>,
pub objectPath: Option<String>,
pub index: u32,
pub isSignalFrame: bool,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BacktracePanelPrintingHelper {
pub maxAddrLen: usize,
pub maxFrameNumLen: usize,
pub maxObjPathLen: usize,
pub maxObjNameLen: usize,
pub hasDemangledNames: bool,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct BacktracePanelRow {
pub type_: i32,
pub frame: Option<BacktraceFrameData>,
pub error: Option<String>,
pub process: *const Process,
pub panel: *const BacktracePanel,
}
const DEMANGLE_NAME_FUNCTION: i32 = 1 << 0;
const SHOW_FULL_PATH_OBJECT: i32 = 1 << 1;
const BacktraceScreenFunctions: [&str; 3] = ["Full Path", "Refresh", "Done "];
const BacktraceScreenKeys: [&str; 3] = ["F3", "F5", "Esc"];
const BacktraceScreenEvents: [i32; 3] = [KEY_F(3), KEY_F(5), 27];
static BacktracePanelRow_class: ObjectClass = ObjectClass {
extends: Some(&Object_class),
};
impl Object for BacktracePanelRow {
fn klass(&self) -> &'static ObjectClass {
&BacktracePanelRow_class
}
fn display(&self, out: &mut RichString) {
BacktracePanelRow_display(self, out);
}
}
const KEY_F3: i32 = KEY_F(3);
const KEY_F5: i32 = KEY_F(5);
const KEY_CTRL_L: i32 = KEY_CTRL(b'L' as i32);
const KEY_LOWER_P: i32 = b'p' as i32;
pub struct BacktracePanel {
pub super_: Panel,
pub processes: Vec<*const Process>,
pub printingHelper: BacktracePanelPrintingHelper,
pub settings: *const Settings,
pub displayOptions: i32,
}
impl PanelClass for BacktracePanel {
fn as_panel(&self) -> &Panel {
&self.super_
}
fn as_panel_mut(&mut self) -> &mut Panel {
&mut self.super_
}
fn event_handler(&mut self, ev: i32) -> HandlerResult {
BacktracePanel_eventHandler(self, ev)
}
}
pub fn BacktraceFrameData_new() -> BacktraceFrameData {
BacktraceFrameData {
address: 0,
offset: 0,
functionName: None,
demangleFunctionName: None,
objectPath: None,
index: 0,
isSignalFrame: false,
}
}
pub fn getBasename(path: &str) -> &str {
match path.rfind('/') {
Some(idx) => &path[idx + 1..],
None => path,
}
}
pub fn BacktracePanel_makePrintingHelper(
rows: &[BacktracePanelRow],
printingHelper: &mut BacktracePanelPrintingHelper,
) {
use crate::ported::xutils::countDigits;
let mut maxFrameNum: u32 = 0;
let mut longestAddress: usize = 0;
printingHelper.hasDemangledNames = false;
for row in rows {
if row.type_ != BACKTRACE_PANEL_ROW_DATA_FRAME {
continue;
}
let frame = row
.frame
.as_ref()
.expect("DATA_FRAME row must carry a frame");
if frame.demangleFunctionName.is_some() {
printingHelper.hasDemangledNames = true;
}
if let Some(objectPath) = frame.objectPath.as_deref() {
let objectName = getBasename(objectPath);
let objectNameLength = objectName.len();
let objectPathLength = (objectPath.len() - objectNameLength) + objectNameLength;
printingHelper.maxObjNameLen = objectNameLength.max(printingHelper.maxObjNameLen);
printingHelper.maxObjPathLen = objectPathLength.max(printingHelper.maxObjPathLen);
}
maxFrameNum = frame.index.max(maxFrameNum);
longestAddress = frame.address.max(longestAddress);
}
printingHelper.maxFrameNumLen =
countDigits(maxFrameNum as usize, 10).max(printingHelper.maxFrameNumLen);
printingHelper.maxAddrLen = countDigits(longestAddress, 16).max(printingHelper.maxAddrLen);
}
pub fn BacktraceFrameData_delete(this: BacktraceFrameData) {
let _ = this;
}
pub fn BacktracePanel_displayHeader(this: &mut BacktracePanel) {
let displayOptions = this.displayOptions;
let showDemangledNames =
(displayOptions & DEMANGLE_NAME_FUNCTION) != 0 && this.printingHelper.hasDemangledNames;
let showFullPathObject = (displayOptions & SHOW_FULL_PATH_OBJECT) != 0;
let maxObjLen = if showFullPathObject {
this.printingHelper.maxObjPathLen
} else {
this.printingHelper.maxObjNameLen
};
let maxFrameNumLen = this.printingHelper.maxFrameNumLen;
let maxAddrLen = this.printingHelper.maxAddrLen;
debug_assert!(maxFrameNumLen <= i32::MAX as usize);
debug_assert!(maxAddrLen <= i32::MAX as usize - "0x".len());
debug_assert!(maxObjLen <= i32::MAX as usize);
let name = if showDemangledNames {
"NAME (demangled)"
} else {
"NAME"
};
let line = format!(
"{:>fnw$} {:<addrw$} {:<objw$} {}",
"#",
"ADDRESS",
"FILE",
name,
fnw = maxFrameNumLen,
addrw = maxAddrLen + "0x".len(),
objw = maxObjLen,
);
Panel_setHeader(&mut this.super_, &line);
}
pub fn BacktracePanel_makeBacktrace(
frames: &mut Vec<BacktraceFrameData>,
pid: i32,
error: &mut Option<String>,
) {
let _ = frames;
let _ = pid;
*error = Some("The backtrace screen is not implemented".to_string());
}
pub fn BacktracePanel_populateFrames(this: &mut BacktracePanel) {
let mut error: Option<String> = None;
let mut data: Vec<BacktraceFrameData> = Vec::new();
let self_ptr: *const BacktracePanel = this as *const BacktracePanel;
let nproc = this.processes.len();
for i in 0..nproc {
let process: *const Process = this.processes[i];
let pid = Process_getPid(unsafe { &*process });
BacktracePanel_makeBacktrace(&mut data, pid, &mut error);
let mut header = BacktracePanelRow_new(self_ptr);
header.process = process;
header.type_ = BACKTRACE_PANEL_ROW_PROCESS_INFORMATION;
Panel_add(&mut this.super_, Box::new(header));
if error.is_none() {
for j in 0..data.len() {
let mut row = BacktracePanelRow_new(self_ptr);
row.process = process;
row.type_ = BACKTRACE_PANEL_ROW_DATA_FRAME;
row.frame = Some(data[j].clone());
Panel_add(&mut this.super_, Box::new(row));
}
} else {
let mut errorRow = BacktracePanelRow_new(self_ptr);
errorRow.process = process;
errorRow.type_ = BACKTRACE_PANEL_ROW_ERROR;
errorRow.error = error.take();
Panel_add(&mut this.super_, Box::new(errorRow));
}
data.clear();
}
let rows: Vec<BacktracePanelRow> = (0..Panel_size(&this.super_))
.map(|k| {
let obj: &dyn core::any::Any = Panel_get(&this.super_, k);
obj.downcast_ref::<BacktracePanelRow>()
.expect("BacktracePanel items are BacktracePanelRow")
.clone()
})
.collect();
BacktracePanel_makePrintingHelper(&rows, &mut this.printingHelper);
BacktracePanel_displayHeader(this);
}
pub fn BacktracePanel_eventHandler(this: &mut BacktracePanel, ch: i32) -> HandlerResult {
let result = HandlerResult::IGNORED;
match ch {
KEY_LOWER_P | KEY_F3 => {
this.displayOptions ^= SHOW_FULL_PATH_OBJECT;
let showFullPathObject = (this.displayOptions & SHOW_FULL_PATH_OBJECT) != 0;
if let Some(bar) = this.super_.defaultBar.as_mut() {
FunctionBar_setLabel(
bar,
KEY_F(3),
if showFullPathObject {
"Basename "
} else {
"Full Path"
},
);
}
this.super_.needsRedraw = true;
BacktracePanel_displayHeader(this);
}
KEY_CTRL_L | KEY_F5 => {
Panel_prune(&mut this.super_);
BacktracePanel_populateFrames(this);
}
_ => {}
}
result
}
pub fn BacktracePanel_new(
processes: Vec<*const Process>,
settings: *const Settings,
) -> Box<BacktracePanel> {
let showProgramPath = unsafe { &*settings }.showProgramPath;
let displayOptions = DEMANGLE_NAME_FUNCTION
| if showProgramPath {
SHOW_FULL_PATH_OBJECT
} else {
0
};
let bar = FunctionBar_new(
Some(&BacktraceScreenFunctions[..]),
Some(&BacktraceScreenKeys[..]),
Some(&BacktraceScreenEvents[..]),
);
let mut this = Box::new(BacktracePanel {
super_: Panel_new(1, 1, 0, 1, Some(bar)),
processes,
printingHelper: BacktracePanelPrintingHelper {
maxAddrLen: "ADDRESS".len() - "0x".len(),
maxFrameNumLen: "#".len(),
maxObjNameLen: "FILE".len(),
maxObjPathLen: "FILE".len(),
hasDemangledNames: false,
},
settings,
displayOptions,
});
let showFullPathObject = (this.displayOptions & SHOW_FULL_PATH_OBJECT) != 0;
if let Some(bar) = this.super_.defaultBar.as_mut() {
FunctionBar_setLabel(
bar,
KEY_F(3),
if showFullPathObject {
"Basename "
} else {
"Full Path"
},
);
}
BacktracePanel_populateFrames(&mut this);
this
}
pub fn BacktracePanel_delete(this: BacktracePanel) {
let BacktracePanel {
super_, processes, ..
} = this;
let _ = processes;
Panel_delete(super_);
}
pub fn BacktracePanelRow_highlightBasename(
row: &BacktracePanelRow,
out: &mut RichString,
line: &str,
objectPathStart: usize,
) {
debug_assert_eq!(row.type_, BACKTRACE_PANEL_ROW_DATA_FRAME);
let process: &Process = unsafe { &*row.process };
let procExe: &[u8] = match process.procExe.as_deref() {
Some(s) => &s.as_bytes()[process.procExeBasenameOffset..],
None => return,
};
let line_b = line.as_bytes();
let mut endBasenameIndex = objectPathStart;
let mut lastSlashBasenameIndex = objectPathStart;
while endBasenameIndex < line_b.len() && line_b[endBasenameIndex] != b' ' {
if line_b[endBasenameIndex] == b'/' {
lastSlashBasenameIndex = endBasenameIndex + 1;
}
endBasenameIndex += 1;
}
let sizeBasename = endBasenameIndex - lastSlashBasenameIndex;
let lineSlice = &line_b[lastSlashBasenameIndex..lastSlashBasenameIndex + sizeBasename];
if procExe.len() >= sizeBasename && &procExe[..sizeBasename] == lineSlice {
RichString_setAttrn(
out,
ColorElements::PROCESS_BASENAME.packed(ColorScheme::active()),
lastSlashBasenameIndex,
sizeBasename,
);
}
}
pub fn BacktracePanelRow_displayInformation(row: &BacktracePanelRow, out: &mut RichString) {
debug_assert_eq!(row.type_, BACKTRACE_PANEL_ROW_PROCESS_INFORMATION);
let process: &Process = unsafe { &*row.process };
let colorBasename;
let mut highlightLen: usize = 0;
let mut highlightOffset: usize = 0;
let processName: &str = if let Some(s) = process.mergedCommand.str.as_deref() {
for _i in 0..process.mergedCommand.highlightCount {
let highlight = &process.mergedCommand.highlights[0];
if highlight.flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME != 0 {
highlightLen = highlight.length;
highlightOffset = highlight.offset;
break;
}
}
s
} else {
process.cmdline.as_deref().unwrap_or_default()
};
if highlightLen == 0 {
highlightLen = processName.len();
}
let pid = Process_getPid(process);
let verb = if Process_isThread(process) {
colorBasename = ColorElements::PROCESS_THREAD_BASENAME;
"Thread"
} else {
colorBasename = ColorElements::PROCESS_BASENAME;
"Process"
};
let prefix = format!("{} {}: ", verb, pid);
let indexProcessComm = prefix.len(); let information = format!("{}{}", prefix, processName);
let len = information.len();
let scheme = ColorScheme::active();
RichString_appendnWide(
out,
ColorElements::DEFAULT_COLOR.packed(scheme) | A_BOLD,
information.as_bytes(),
len,
);
RichString_setAttrn(
out,
colorBasename.packed(scheme) | A_BOLD,
indexProcessComm + highlightOffset,
highlightLen,
);
}
pub fn BacktracePanelRow_displayFrame(row: &BacktracePanelRow, out: &mut RichString) {
debug_assert_eq!(row.type_, BACKTRACE_PANEL_ROW_DATA_FRAME);
let panel: &BacktracePanel = unsafe { &*row.panel };
let printingHelper = &panel.printingHelper;
let displayOptions = panel.displayOptions;
let frame = row
.frame
.as_ref()
.expect("DATA_FRAME row must carry a frame");
let functionName: &str =
if (displayOptions & DEMANGLE_NAME_FUNCTION) != 0 && frame.demangleFunctionName.is_some() {
frame.demangleFunctionName.as_deref().unwrap()
} else if let Some(f) = frame.functionName.as_deref() {
f
} else {
"???"
};
let completeFunctionName = format!("{}+0x{:x}", functionName, frame.offset);
let mut objectLength = printingHelper.maxObjPathLen;
let mut objectDisplayed: Option<&str> = frame.objectPath.as_deref();
if (displayOptions & SHOW_FULL_PATH_OBJECT) == 0 {
objectLength = printingHelper.maxObjNameLen;
if let Some(op) = frame.objectPath.as_deref() {
objectDisplayed = Some(getBasename(op));
}
}
let maxAddrLen = printingHelper.maxAddrLen;
debug_assert!(printingHelper.maxFrameNumLen <= i32::MAX as usize);
debug_assert!(maxAddrLen <= i32::MAX as usize);
debug_assert!(objectLength <= i32::MAX as usize);
let prefix = format!(
"{:>fnw$} 0x{:0aw$x} ",
frame.index,
frame.address,
fnw = printingHelper.maxFrameNumLen,
aw = maxAddrLen,
);
let objectPathStart = prefix.len();
let line = format!(
"{}{:<objw$} {}",
prefix,
objectDisplayed.unwrap_or("-"),
completeFunctionName,
objw = objectLength,
);
let len = line.len();
let scheme = ColorScheme::active();
let colors = if frame.functionName.is_some() {
ColorElements::DEFAULT_COLOR.packed(scheme)
} else {
ColorElements::PROCESS_SHADOW.packed(scheme)
};
RichString_appendnAscii(out, colors, line.as_bytes(), len);
let settings: &Settings = unsafe { &*panel.settings };
if settings.highlightBaseName {
BacktracePanelRow_highlightBasename(row, out, &line, objectPathStart);
}
}
pub fn BacktracePanelRow_displayError(row: &BacktracePanelRow, out: &mut RichString) {
debug_assert_eq!(row.type_, BACKTRACE_PANEL_ROW_ERROR);
let error = row
.error
.as_deref()
.expect("ERROR row must carry an error message");
let color = ColorElements::DEFAULT_COLOR.packed(ColorScheme::active());
RichString_appendAscii(out, color, error.as_bytes());
}
pub fn BacktracePanelRow_display(row: &BacktracePanelRow, out: &mut RichString) {
match row.type_ {
BACKTRACE_PANEL_ROW_DATA_FRAME => BacktracePanelRow_displayFrame(row, out),
BACKTRACE_PANEL_ROW_PROCESS_INFORMATION => BacktracePanelRow_displayInformation(row, out),
BACKTRACE_PANEL_ROW_ERROR => BacktracePanelRow_displayError(row, out),
_ => {}
}
}
pub fn BacktracePanelRow_new(panel: *const BacktracePanel) -> BacktracePanelRow {
BacktracePanelRow {
panel,
..BacktracePanelRow::default()
}
}
pub fn BacktracePanelRow_delete(this: BacktracePanelRow) {
let BacktracePanelRow {
type_,
frame,
error,
process,
panel,
} = this;
let _ = panel;
match type_ {
BACKTRACE_PANEL_ROW_DATA_FRAME => {
if let Some(frame) = frame {
BacktraceFrameData_delete(frame);
}
let _ = error;
}
BACKTRACE_PANEL_ROW_ERROR => {
let _ = error;
let _ = frame;
}
_ => {
let _ = frame;
let _ = error;
}
}
let _ = process;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::panel::Panel_new;
#[test]
fn backtrace_frame_data_new_is_all_default() {
let f = BacktraceFrameData_new();
assert_eq!(f.address, 0);
assert_eq!(f.offset, 0);
assert_eq!(f.functionName, None);
assert_eq!(f.demangleFunctionName, None);
assert_eq!(f.objectPath, None);
assert_eq!(f.index, 0);
assert!(!f.isSignalFrame);
}
#[test]
fn get_basename_matches_strrchr_semantics() {
assert_eq!(getBasename("/usr/lib/libc.so.6"), "libc.so.6");
assert_eq!(getBasename("libc.so"), "libc.so");
assert_eq!(getBasename("/foo/"), "");
assert_eq!(getBasename("/"), "");
assert_eq!(getBasename(""), "");
assert_eq!(getBasename("a/b/c"), "c");
}
fn frame(
index: u32,
address: usize,
obj: Option<&str>,
demangle: Option<&str>,
) -> BacktracePanelRow {
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_DATA_FRAME,
frame: Some(BacktraceFrameData {
address,
offset: 0,
functionName: None,
demangleFunctionName: demangle.map(str::to_string),
objectPath: obj.map(str::to_string),
index,
isSignalFrame: false,
}),
error: None,
process: std::ptr::null(),
panel: std::ptr::null(),
}
}
fn rendered(rs: &RichString) -> String {
(0..rs.chlen as usize).map(|i| rs.chptr[i].chars).collect()
}
fn header_text(p: &BacktracePanel) -> String {
rendered(&p.super_.header)
}
fn empty_backtrace_panel() -> BacktracePanel {
BacktracePanel {
super_: Panel_new(1, 1, 0, 1, None),
processes: Vec::new(),
printingHelper: seeded_helper(),
settings: std::ptr::null(),
displayOptions: 0,
}
}
fn seeded_helper() -> BacktracePanelPrintingHelper {
BacktracePanelPrintingHelper {
maxAddrLen: "ADDRESS".len() - "0x".len(),
maxFrameNumLen: "#".len(),
maxObjPathLen: "FILE".len(),
maxObjNameLen: "FILE".len(),
hasDemangledNames: false,
}
}
#[test]
fn make_printing_helper_computes_widths_and_skips_non_frames() {
let rows = vec![
frame(3, 0xff, Some("/usr/lib/libc.so.6"), Some("demangled")),
frame(150, 0x10000, Some("ld.so"), None),
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_PROCESS_INFORMATION,
frame: None,
error: None,
process: std::ptr::null(),
panel: std::ptr::null(),
},
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_ERROR,
frame: None,
error: None,
process: std::ptr::null(),
panel: std::ptr::null(),
},
];
let mut h = seeded_helper();
BacktracePanel_makePrintingHelper(&rows, &mut h);
assert_eq!(h.maxObjNameLen, 9);
assert_eq!(h.maxObjPathLen, 18);
assert_eq!(h.maxFrameNumLen, 3);
assert_eq!(h.maxAddrLen, 5);
assert!(h.hasDemangledNames);
}
#[test]
fn make_printing_helper_respects_incoming_floors() {
let rows = vec![
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_PROCESS_INFORMATION,
frame: None,
error: None,
process: std::ptr::null(),
panel: std::ptr::null(),
},
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_ERROR,
frame: None,
error: None,
process: std::ptr::null(),
panel: std::ptr::null(),
},
];
let mut h = seeded_helper();
h.hasDemangledNames = true; BacktracePanel_makePrintingHelper(&rows, &mut h);
assert_eq!(h.maxObjNameLen, 4);
assert_eq!(h.maxObjPathLen, 4);
assert_eq!(h.maxFrameNumLen, 1);
assert_eq!(h.maxAddrLen, 5);
assert!(!h.hasDemangledNames);
}
#[test]
fn make_printing_helper_short_names_do_not_shrink_floor() {
let rows = vec![frame(1, 0x1, Some("ab"), None)];
let mut h = seeded_helper();
BacktracePanel_makePrintingHelper(&rows, &mut h);
assert_eq!(h.maxObjNameLen, 4);
assert_eq!(h.maxObjPathLen, 4);
}
#[test]
fn display_header_formats_seeded_columns() {
let mut p = empty_backtrace_panel();
BacktracePanel_displayHeader(&mut p);
assert_eq!(header_text(&p), "# ADDRESS FILE NAME");
}
#[test]
fn display_header_demangled_and_full_path_widen() {
let mut p = empty_backtrace_panel();
p.printingHelper = BacktracePanelPrintingHelper {
maxAddrLen: 12,
maxFrameNumLen: 3,
maxObjPathLen: 18,
maxObjNameLen: 9,
hasDemangledNames: true,
};
p.displayOptions = DEMANGLE_NAME_FUNCTION | SHOW_FULL_PATH_OBJECT;
BacktracePanel_displayHeader(&mut p);
let expected = format!(
"{:>3} {:<14} {:<18} {}",
"#", "ADDRESS", "FILE", "NAME (demangled)"
);
assert_eq!(header_text(&p), expected);
}
#[test]
fn display_header_demangle_option_without_demangled_names_shows_plain() {
let mut p = empty_backtrace_panel();
p.printingHelper.hasDemangledNames = false;
p.displayOptions = DEMANGLE_NAME_FUNCTION;
BacktracePanel_displayHeader(&mut p);
assert!(header_text(&p).ends_with("NAME"));
assert!(!header_text(&p).contains("demangled"));
}
#[test]
fn make_backtrace_sets_not_implemented_error() {
let mut frames: Vec<BacktraceFrameData> = Vec::new();
let mut error: Option<String> = None;
BacktracePanel_makeBacktrace(&mut frames, 1234, &mut error);
assert_eq!(
error.as_deref(),
Some("The backtrace screen is not implemented")
);
assert!(frames.is_empty());
}
#[test]
fn display_error_appends_error_in_default_color() {
let row = BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_ERROR,
frame: None,
error: Some("ptrace failed".to_string()),
process: std::ptr::null(),
panel: std::ptr::null(),
};
let mut out = RichString::new();
BacktracePanelRow_displayError(&row, &mut out);
assert_eq!(rendered(&out), "ptrace failed");
let expect = ColorElements::DEFAULT_COLOR.packed(ColorScheme::active()) & 0xffffff;
for i in 0..out.chlen as usize {
assert_eq!(out.chptr[i].attr, expect, "attr at {i}");
}
}
#[test]
fn display_dispatches_error_arm_to_display_error() {
let row = BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_ERROR,
frame: None,
error: Some("boom".to_string()),
process: std::ptr::null(),
panel: std::ptr::null(),
};
let mut out = RichString::new();
BacktracePanelRow_display(&row, &mut out);
assert_eq!(rendered(&out), "boom");
}
#[test]
fn display_frame_renders_via_self_referential_panel_back_pointer() {
let settings = Settings::default(); let p = BacktracePanel {
super_: Panel_new(1, 1, 0, 1, None),
processes: Vec::new(),
printingHelper: BacktracePanelPrintingHelper {
maxAddrLen: 5,
maxFrameNumLen: 2,
maxObjPathLen: 18,
maxObjNameLen: 9,
hasDemangledNames: false,
},
settings: &settings as *const Settings,
displayOptions: 0, };
let panel_ptr = &p as *const BacktracePanel;
let mut row = frame(3, 0xff, Some("/usr/lib/libc.so.6"), None);
{
let f = row.frame.as_mut().unwrap();
f.functionName = Some("malloc".to_string());
f.offset = 0x10;
}
row.panel = panel_ptr;
let mut out = RichString::new();
BacktracePanelRow_display(&row, &mut out);
let expected = format!(
"{:>2} 0x{:05x} {:<9} {}",
3u32, 0xffusize, "libc.so.6", "malloc+0x10"
);
assert_eq!(rendered(&out), expected);
let expect = ColorElements::DEFAULT_COLOR.packed(ColorScheme::active()) & 0xffffff;
assert_eq!(out.chptr[0].attr, expect);
}
use crate::ported::process::{ProcessCmdlineHighlight, Process_setPid, Process_setThreadGroup};
fn info_row(p: &Process) -> BacktracePanelRow {
BacktracePanelRow {
type_: BACKTRACE_PANEL_ROW_PROCESS_INFORMATION,
frame: None,
error: None,
process: p as *const Process,
panel: std::ptr::null(),
}
}
#[test]
fn display_information_process_uses_cmdline_when_no_merged() {
let mut p = Process::default();
Process_setPid(&mut p, 4321);
p.mergedCommand.str = None;
p.cmdline = Some("/usr/bin/sleep 100".to_string());
let row = info_row(&p);
let mut out = RichString::new();
BacktracePanelRow_displayInformation(&row, &mut out);
assert_eq!(rendered(&out), "Process 4321: /usr/bin/sleep 100");
let scheme = ColorScheme::active();
let prefixLen = "Process 4321: ".len();
let defaultAttr = (ColorElements::DEFAULT_COLOR.packed(scheme) | A_BOLD) & 0xffffff;
let baseAttr = (ColorElements::PROCESS_BASENAME.packed(scheme) | A_BOLD) & 0xffffff;
for i in 0..prefixLen {
assert_eq!(out.chptr[i].attr, defaultAttr, "prefix attr at {i}");
}
for i in prefixLen..out.chlen as usize {
assert_eq!(out.chptr[i].attr, baseAttr, "name attr at {i}");
}
}
#[test]
fn display_information_thread_uses_thread_verb_and_color() {
let mut p = Process::default();
Process_setPid(&mut p, 77);
Process_setThreadGroup(&mut p, 5);
p.isUserlandThread = true; assert!(Process_isThread(&p));
p.mergedCommand.str = None;
p.cmdline = Some("worker".to_string());
let row = info_row(&p);
let mut out = RichString::new();
BacktracePanelRow_displayInformation(&row, &mut out);
assert_eq!(rendered(&out), "Thread 77: worker");
let scheme = ColorScheme::active();
let baseAttr = (ColorElements::PROCESS_THREAD_BASENAME.packed(scheme) | A_BOLD) & 0xffffff;
let prefixLen = "Thread 77: ".len();
assert_eq!(out.chptr[prefixLen].attr, baseAttr);
}
#[test]
fn display_information_merged_basename_highlight_offsets() {
let mut p = Process::default();
Process_setPid(&mut p, 9);
p.mergedCommand.str = Some("/usr/bin/sleep".to_string());
p.mergedCommand.highlightCount = 1;
p.mergedCommand.highlights[0] = ProcessCmdlineHighlight {
offset: "/usr/bin/".len(), length: "sleep".len(), attr: 0,
flags: CMDLINE_HIGHLIGHT_FLAG_BASENAME,
};
let row = info_row(&p);
let mut out = RichString::new();
BacktracePanelRow_displayInformation(&row, &mut out);
assert_eq!(rendered(&out), "Process 9: /usr/bin/sleep");
let scheme = ColorScheme::active();
let prefixLen = "Process 9: ".len();
let defaultAttr = (ColorElements::DEFAULT_COLOR.packed(scheme) | A_BOLD) & 0xffffff;
let baseAttr = (ColorElements::PROCESS_BASENAME.packed(scheme) | A_BOLD) & 0xffffff;
let hlStart = prefixLen + "/usr/bin/".len();
for i in prefixLen..hlStart {
assert_eq!(out.chptr[i].attr, defaultAttr, "pre-basename attr at {i}");
}
for i in hlStart..hlStart + "sleep".len() {
assert_eq!(out.chptr[i].attr, baseAttr, "basename attr at {i}");
}
}
#[test]
fn display_dispatches_information_arm() {
let mut p = Process::default();
Process_setPid(&mut p, 3);
p.mergedCommand.str = None;
p.cmdline = Some("cmd".to_string());
let row = info_row(&p);
let mut out = RichString::new();
BacktracePanelRow_display(&row, &mut out);
assert_eq!(rendered(&out), "Process 3: cmd");
}
#[test]
fn highlight_basename_marks_matching_executable_basename() {
let mut p = Process::default();
Process_setPid(&mut p, 1);
p.procExe = Some("/usr/bin/sleep".to_string());
p.procExeBasenameOffset = "/usr/bin/".len(); let row = frame(0, 0x0, Some("/usr/bin/sleep"), None);
let row = BacktracePanelRow {
process: &p as *const Process,
..row
};
let line = " 0 /usr/bin/sleep func+0x0";
let objectPathStart = 5usize;
let mut out = RichString::new();
RichString_appendAscii(&mut out, 0, line.as_bytes());
BacktracePanelRow_highlightBasename(&row, &mut out, line, objectPathStart);
let lastSlash = line.find("sleep").unwrap();
let baseAttr = ColorElements::PROCESS_BASENAME.packed(ColorScheme::active()) & 0xffffff;
for i in lastSlash..lastSlash + "sleep".len() {
assert_eq!(out.chptr[i].attr, baseAttr, "basename attr at {i}");
}
assert_eq!(out.chptr[lastSlash - 1].attr, 0);
}
#[test]
fn highlight_basename_no_proc_exe_is_noop() {
let mut p = Process::default();
p.procExe = None;
let row = BacktracePanelRow {
process: &p as *const Process,
..frame(0, 0x0, Some("/lib/x"), None)
};
let line = " 0 /lib/x f+0x0";
let mut out = RichString::new();
RichString_appendAscii(&mut out, 0, line.as_bytes());
BacktracePanelRow_highlightBasename(&row, &mut out, line, 5);
for i in 0..out.chlen as usize {
assert_eq!(out.chptr[i].attr, 0, "attr at {i}");
}
}
}