#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
use crate::ported::crt::ColorElements::*;
use crate::ported::crt::{ColorElements, ColorScheme};
use crate::ported::dynamiccolumn::{
DynamicColumn_lookup, DYNAMIC_DEFAULT_COLUMN_WIDTH, DYNAMIC_MAX_COLUMN_WIDTH,
};
use crate::ported::linux::linuxprocess::{Process_fields, LAST_PROCESSFIELD};
use crate::ported::machine::Machine;
use crate::ported::object::{Object, ObjectClass, Object_class};
use crate::ported::process::ProcessField;
use crate::ported::richstring::{
RichString, RichString_appendAscii, RichString_appendChr, RichString_appendnAscii,
RichString_appendnWideColumns, RichString_setAttr, RichString_size,
};
use crate::ported::settings::{RowField, Settings};
use crate::ported::table::Table;
use crate::ported::xutils::countDigits;
use core::any::Any;
use core::ffi::c_void;
use core::ops::Deref;
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};
macro_rules! spaceship_number {
($a:expr, $b:expr) => {
(($a > $b) as i32 - ($a < $b) as i32)
};
}
pub(crate) use spaceship_number;
const ONE_K: u64 = 1024;
const ONE_M: u64 = ONE_K * ONE_K;
const ONE_G: u64 = ONE_M * ONE_K;
const ONE_T: u64 = ONE_G * ONE_K;
const ONE_P: u64 = ONE_T * ONE_K;
const ONE_DECIMAL_K: u64 = 1000;
const ONE_DECIMAL_M: u64 = ONE_DECIMAL_K * ONE_DECIMAL_K;
const ONE_DECIMAL_G: u64 = ONE_DECIMAL_M * ONE_DECIMAL_K;
const ONE_DECIMAL_T: u64 = ONE_DECIMAL_G * ONE_DECIMAL_K;
const ROW_MIN_PID_DIGITS: i32 = 5;
const ROW_MAX_PID_DIGITS: i32 = 19;
const ROW_MIN_UID_DIGITS: i32 = 5;
const ROW_MAX_UID_DIGITS: i32 = 20;
pub static Row_pidDigits: AtomicI32 = AtomicI32::new(ROW_MIN_PID_DIGITS);
pub static Row_uidDigits: AtomicI32 = AtomicI32::new(ROW_MIN_UID_DIGITS);
pub static Row_fieldWidths: [AtomicU8; LAST_PROCESSFIELD] =
[const { AtomicU8::new(0) }; LAST_PROCESSFIELD];
const unitPrefixes: [u8; 10] = [b'K', b'M', b'G', b'T', b'P', b'E', b'Z', b'Y', b'R', b'Q'];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PercentageAttr {
Shadow,
Megabytes,
Unchanged,
}
#[derive(Debug, Clone)]
pub struct Row {
pub host: *const c_void,
pub id: i32,
pub group: i32,
pub parent: i32,
pub isRoot: bool,
pub tag: bool,
pub show: bool,
pub wasShown: bool,
pub showChildren: bool,
pub updated: bool,
pub indent: i32,
pub tree_depth: u32,
pub seenStampMs: u64,
pub tombStampMs: u64,
}
impl Default for Row {
fn default() -> Self {
Row {
host: core::ptr::null(),
id: 0,
group: 0,
parent: 0,
isRoot: false,
tag: false,
show: false,
wasShown: false,
showChildren: false,
updated: false,
indent: 0,
tree_depth: 0,
seenStampMs: 0,
tombStampMs: 0,
}
}
}
pub type Row_WriteField = fn(&dyn Object, &mut RichString, RowField);
pub type Row_IsHighlighted = fn(&dyn Object) -> bool;
pub type Row_IsVisible = fn(&dyn Object, &Table) -> bool;
pub type Row_MatchesFilter = fn(&dyn Object, &Table) -> bool;
pub type Row_SortKeyString = fn(&dyn Object) -> Option<&[u8]>;
pub type Row_CompareByParent = fn(&dyn Object, &dyn Object) -> i32;
pub struct RowClass {
pub super_: ObjectClass,
pub isHighlighted: Option<Row_IsHighlighted>,
pub isVisible: Option<Row_IsVisible>,
pub writeField: Option<Row_WriteField>,
pub matchesFilter: Option<Row_MatchesFilter>,
pub sortKeyString: Option<Row_SortKeyString>,
pub compareByParent: Option<Row_CompareByParent>,
}
impl Deref for RowClass {
type Target = ObjectClass;
fn deref(&self) -> &ObjectClass {
&self.super_
}
}
pub static Row_class: RowClass = RowClass {
super_: ObjectClass {
extends: Some(&Object_class),
},
isHighlighted: None,
isVisible: None,
writeField: None,
matchesFilter: None,
sortKeyString: None,
compareByParent: None,
};
impl Object for Row {
fn klass(&self) -> &'static ObjectClass {
&Row_class.super_
}
fn row_class(&self) -> Option<&'static RowClass> {
Some(&Row_class)
}
fn as_row(&self) -> Option<&Row> {
Some(self)
}
fn as_row_mut(&mut self) -> Option<&mut Row> {
Some(self)
}
fn compare(&self, other: &dyn Object) -> i32 {
let any: &dyn Any = other;
let o = any
.downcast_ref::<Row>()
.expect("Row_compare called across incompatible classes");
Row_compare(self, o)
}
}
pub fn Row_init(this: &mut Row, host: *const c_void) {
this.host = host;
this.tag = false;
this.showChildren = true;
this.show = true;
this.wasShown = false;
this.updated = false;
}
pub fn Row_getGroupOrParent(this: &Row) -> i32 {
if this.group == this.id {
this.parent
} else {
this.group
}
}
pub fn Row_isChildOf(this: &Row, id: i32) -> bool {
id == Row_getGroupOrParent(this)
}
pub fn Row_done(this: &Row) {
let _ = this;
}
pub fn Row_isNew(this: &Row) -> bool {
let host = unsafe { &*(this.host as *const Machine) };
if host.monotonicMs < this.seenStampMs {
return false;
}
let settings = host
.settings
.as_ref()
.expect("Row_isNew: host->settings is NULL");
host.monotonicMs - this.seenStampMs <= 1000 * settings.highlightDelaySecs as u64
}
pub fn Row_isTomb(this: &Row) -> bool {
this.tombStampMs > 0
}
pub fn Row_display(cast: &dyn Object, out: &mut RichString) {
let this = cast.as_row().expect("Row_display: object is not a Row");
let host = unsafe { &*(this.host as *const Machine) };
let settings = host
.settings
.as_ref()
.expect("Row_display: host->settings is NULL");
let scheme = ColorScheme::active();
let rc = cast
.row_class()
.expect("Row_display: object has no RowClass vtable");
let write_field = rc
.writeField
.expect("Row_display: RowClass has no writeField slot");
let fields = &settings.screens[settings.ssIndex as usize].fields;
for &field in fields {
if field == 0 {
break; }
write_field(cast, out, field);
}
if rc.isHighlighted.is_some_and(|f| f(cast)) {
RichString_setAttr(out, PROCESS_SHADOW.packed(scheme));
}
if this.tag {
RichString_setAttr(out, PROCESS_TAG.packed(scheme));
}
if settings.highlightChanges {
if Row_isTomb(this) {
out.highlightAttr = PROCESS_TOMB.packed(scheme);
} else if Row_isNew(this) {
out.highlightAttr = PROCESS_NEW.packed(scheme);
}
}
debug_assert!(RichString_size(out) > 0);
}
pub fn Row_setPidColumnWidth(maxPid: i32) {
if maxPid < 10_i32.pow(ROW_MIN_PID_DIGITS as u32) {
Row_pidDigits.store(ROW_MIN_PID_DIGITS, Ordering::Relaxed);
return;
}
let digits = countDigits(maxPid as usize, 10) as i32;
Row_pidDigits.store(digits, Ordering::Relaxed);
debug_assert!(digits <= ROW_MAX_PID_DIGITS);
}
pub fn Row_setUidColumnWidth(maxUid: u32) {
if maxUid < 10_u32.pow(ROW_MIN_UID_DIGITS as u32) {
Row_uidDigits.store(ROW_MIN_UID_DIGITS, Ordering::Relaxed);
return;
}
let digits = countDigits(maxUid as usize, 10) as i32;
Row_uidDigits.store(digits, Ordering::Relaxed);
debug_assert!(digits <= ROW_MAX_UID_DIGITS);
}
pub fn Row_resetFieldWidths() {
for i in 0..LAST_PROCESSFIELD {
if !Process_fields[i].autoWidth {
continue;
}
let len = Process_fields[i].title.map_or(0, |t| t.len());
debug_assert!(len <= u8::MAX as usize);
Row_fieldWidths[i].store(len as u8, Ordering::Relaxed);
}
}
pub fn Row_updateFieldWidth(key: RowField, width: usize) {
if width > u8::MAX as usize {
Row_fieldWidths[key as usize].store(u8::MAX, Ordering::Relaxed);
} else if width > Row_fieldWidths[key as usize].load(Ordering::Relaxed) as usize {
Row_fieldWidths[key as usize].store(width as u8, Ordering::Relaxed);
}
}
pub fn alignedTitleDynamicColumn(settings: &Settings, field: RowField) -> String {
let column = settings
.dynamicColumns
.and_then(|dc| DynamicColumn_lookup(unsafe { &*dc }, field as u32));
let column = match column {
Some(c) => c,
None => return "- ".to_string(),
};
let mut width = column.width;
if width == 0 || width.abs() > DYNAMIC_MAX_COLUMN_WIDTH {
width = DYNAMIC_DEFAULT_COLUMN_WIDTH;
}
let heading = column.heading.as_deref().unwrap_or("");
if width >= 0 {
format!("{heading:>w$} ", w = width as usize)
} else {
format!("{heading:<w$} ", w = width.unsigned_abs() as usize)
}
}
pub fn alignedTitleProcessField(field: RowField) -> String {
let fd = &Process_fields[field as usize];
let title = match fd.title {
Some(t) => t,
None => return "- ".to_string(),
};
if fd.pidColumn {
let w = Row_pidDigits.load(Ordering::Relaxed) as usize;
return format!("{title:>w$} ");
}
if field == ProcessField::ST_UID as RowField {
let w = Row_uidDigits.load(Ordering::Relaxed) as usize;
return format!("{title:>w$} ");
}
if fd.autoWidth {
let w = Row_fieldWidths[field as usize].load(Ordering::Relaxed) as usize;
if fd.autoTitleRightAlign {
return format!("{title:>w$} ");
}
return format!("{title:<w$.w$} ");
}
title.to_string()
}
pub fn RowField_alignedTitle(settings: &Settings, field: RowField) -> String {
if (field as usize) < LAST_PROCESSFIELD {
alignedTitleProcessField(field)
} else {
alignedTitleDynamicColumn(settings, field)
}
}
pub fn RowField_keyAt(settings: &Settings, at: i32) -> RowField {
let fields = &settings.screens[settings.ssIndex as usize].fields;
let mut rem = at;
for &field in fields {
if field == 0 {
break;
}
let len = if rem > 0 {
RowField_alignedTitle(settings, field)
.len()
.min(rem as usize) as i32
} else {
0
};
if rem <= len {
return field;
}
rem -= len;
}
ProcessField::COMM as RowField
}
pub fn Row_printKBytes(str: &mut RichString, mut number: u64, coloring: bool) {
let scheme = ColorScheme::active();
let color_of = |e: ColorElements| e.packed(scheme);
let mut color = color_of(PROCESS);
let mut next_unit_color = color_of(PROCESS);
let colors = [
color_of(PROCESS),
color_of(PROCESS_MEGABYTES),
color_of(PROCESS_GIGABYTES),
color_of(LARGE_NUMBER),
];
if number == u64::MAX {
if coloring {
color = color_of(PROCESS_SHADOW);
}
RichString_appendAscii(str, color, b" N/A ");
return;
}
if coloring {
color = colors[0];
next_unit_color = colors[1];
}
if number < 1000 {
let buf = format!("{:5} ", number as u32);
RichString_appendnAscii(str, color, buf.as_bytes(), buf.len());
return;
}
if number < 100000 {
let buf = format!("{:2}", (number / 1000) as u32);
RichString_appendnAscii(str, next_unit_color, buf.as_bytes(), buf.len());
let buf = format!("{:03} ", (number % 1000) as u32);
RichString_appendnAscii(str, color, buf.as_bytes(), buf.len());
return;
}
let max_unit_index: usize = (8 * 8 - 1) / 10 + 1;
let can_overflow = max_unit_index >= unitPrefixes.len();
let mut i: usize = 1;
let mut prev_unit_color: i32;
let mut hundredths: u64 = (number / 256) * 25 + (number % 256) * 25 / 256;
loop {
if can_overflow && i >= unitPrefixes.len() {
if coloring {
color = color_of(PROCESS_SHADOW);
}
RichString_appendAscii(str, color, b" N/A ");
return;
}
prev_unit_color = color;
color = next_unit_color;
if coloring && i + 1 < colors.len() {
next_unit_color = colors[i + 1];
}
if hundredths < 1000000 {
break;
}
hundredths /= ONE_K;
i += 1;
}
number = hundredths / 100;
hundredths %= 100;
let tail: String;
if number < 100 {
let buf = format!("{}", number as u32);
RichString_appendnAscii(str, color, buf.as_bytes(), buf.len());
let buf = if number < 10 {
format!(".{:02}", hundredths as u32)
} else {
format!(".{}", (hundredths as u32) / 10)
};
RichString_appendnAscii(str, prev_unit_color, buf.as_bytes(), buf.len());
tail = format!("{} ", unitPrefixes[i] as char);
} else if number < 1000 {
tail = format!("{:4}{} ", number as u32, unitPrefixes[i] as char);
} else {
debug_assert!(number < 10000);
let buf = format!("{}", (number as u32) / 1000);
RichString_appendnAscii(str, next_unit_color, buf.as_bytes(), buf.len());
tail = format!("{:03}{} ", (number as u32) % 1000, unitPrefixes[i] as char);
}
RichString_appendnAscii(str, color, tail.as_bytes(), tail.len());
}
pub fn Row_printBytes(str: &mut RichString, number: u64, coloring: bool) {
if number == u64::MAX {
Row_printKBytes(str, u64::MAX, coloring);
} else {
Row_printKBytes(str, number / ONE_K, coloring);
}
}
pub fn Row_printCount(str: &mut RichString, number: u64, coloring: bool) {
let scheme = ColorScheme::active();
let color_of = |e: ColorElements| e.packed(scheme);
let large_number_color = if coloring {
color_of(LARGE_NUMBER)
} else {
color_of(PROCESS)
};
let megabytes_color = if coloring {
color_of(PROCESS_MEGABYTES)
} else {
color_of(PROCESS)
};
let shadow_color = if coloring {
color_of(PROCESS_SHADOW)
} else {
color_of(PROCESS)
};
let base_color = color_of(PROCESS);
if number == u64::MAX {
RichString_appendAscii(str, color_of(PROCESS_SHADOW), b" N/A ");
} else if number >= 100000 * ONE_DECIMAL_T {
let buf = format!("{:11} ", number / ONE_DECIMAL_G);
let b = buf.as_bytes();
RichString_appendnAscii(str, large_number_color, b, 12);
} else if number >= 100 * ONE_DECIMAL_T {
let buf = format!("{:11} ", number / ONE_DECIMAL_M);
let b = buf.as_bytes();
RichString_appendnAscii(str, large_number_color, b, 8);
RichString_appendnAscii(str, megabytes_color, &b[8..], 4);
} else if number >= 10 * ONE_DECIMAL_G {
let buf = format!("{:11} ", number / ONE_DECIMAL_K);
let b = buf.as_bytes();
RichString_appendnAscii(str, large_number_color, b, 5);
RichString_appendnAscii(str, megabytes_color, &b[5..], 3);
RichString_appendnAscii(str, base_color, &b[8..], 4);
} else {
let buf = format!("{:11} ", number);
let b = buf.as_bytes();
RichString_appendnAscii(str, large_number_color, b, 2);
RichString_appendnAscii(str, megabytes_color, &b[2..], 3);
RichString_appendnAscii(str, base_color, &b[5..], 3);
RichString_appendnAscii(str, shadow_color, &b[8..], 4);
}
}
pub fn Row_printTime(str: &mut RichString, total_hundredths: u64, coloring: bool) {
let scheme = ColorScheme::active();
let color_of = |e: ColorElements| e.packed(scheme);
if total_hundredths == 0 {
let shadow_color = if coloring {
color_of(PROCESS_SHADOW)
} else {
color_of(PROCESS)
};
RichString_appendAscii(str, shadow_color, b" 0:00.00 ");
return;
}
let year_color = if coloring {
color_of(LARGE_NUMBER)
} else {
color_of(PROCESS)
};
let day_color = if coloring {
color_of(PROCESS_GIGABYTES)
} else {
color_of(PROCESS)
};
let hour_color = if coloring {
color_of(PROCESS_MEGABYTES)
} else {
color_of(PROCESS)
};
let base_color = color_of(PROCESS);
let total_seconds = total_hundredths / 100;
let total_minutes = total_seconds / 60;
let total_hours = total_minutes / 60;
let seconds = (total_seconds % 60) as u32;
let minutes = (total_minutes % 60) as u32;
if total_minutes < 60 {
let hundredths = (total_hundredths % 100) as u32;
let buf = format!(
"{:2}:{:02}.{:02} ",
total_minutes as u32, seconds, hundredths
);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
if total_hours < 24 {
let buf = format!("{:2}h", total_hours as u32);
RichString_appendnAscii(str, hour_color, buf.as_bytes(), buf.len());
let buf = format!("{:02}:{:02} ", minutes, seconds);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
let total_days = total_hours / 24;
let hours = (total_hours % 24) as u32;
if total_days < 10 {
let buf = format!("{}d", total_days as u32);
RichString_appendnAscii(str, day_color, buf.as_bytes(), buf.len());
let buf = format!("{:02}h", hours);
RichString_appendnAscii(str, hour_color, buf.as_bytes(), buf.len());
let buf = format!("{:02}m ", minutes);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
if total_days < 365 {
let buf = format!("{:4}d", total_days as u32);
RichString_appendnAscii(str, day_color, buf.as_bytes(), buf.len());
let buf = format!("{:02}h ", hours);
RichString_appendnAscii(str, hour_color, buf.as_bytes(), buf.len());
return;
}
let years = total_days / 365;
let days = (total_days % 365) as u32;
if years < 1000 {
let buf = format!("{:3}y", years as u32);
RichString_appendnAscii(str, year_color, buf.as_bytes(), buf.len());
let buf = format!("{:03}d ", days);
RichString_appendnAscii(str, day_color, buf.as_bytes(), buf.len());
} else if years < 10000000 {
let buf = format!("{:7}y ", years);
RichString_appendnAscii(str, year_color, buf.as_bytes(), buf.len());
} else {
RichString_appendAscii(str, year_color, b"eternity ");
}
}
pub fn Row_printNanoseconds(str: &mut RichString, total_nanoseconds: u64, coloring: bool) {
let scheme = ColorScheme::active();
let color_of = |e: ColorElements| e.packed(scheme);
if total_nanoseconds == 0 {
let shadow_color = if coloring {
color_of(PROCESS_SHADOW)
} else {
color_of(PROCESS)
};
RichString_appendAscii(str, shadow_color, b" 0ns ");
return;
}
let base_color = color_of(PROCESS);
if total_nanoseconds < 1000000 {
let buf = format!("{:6}ns ", total_nanoseconds);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
if total_nanoseconds < 10000000 {
let mut fraction = (total_nanoseconds as u32) / 100;
let milliseconds = fraction / 10000;
fraction %= 10000;
let buf = format!("{}.{:04}ms ", milliseconds, fraction);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
let total_microseconds = total_nanoseconds / 1000;
let total_seconds = total_microseconds / 1000000;
let microseconds = (total_microseconds % 1000000) as u32;
if total_seconds < 60 {
let mut width: i32 = 6;
let mut fraction = microseconds;
let mut limit: u64 = 1;
while total_seconds >= limit {
width -= 1;
fraction /= 10;
limit *= 10;
}
let secs = if total_seconds == 0 {
String::new()
} else {
format!("{}", total_seconds as u32)
};
let buf = format!("{}.{:0w$}s ", secs, fraction, w = width as usize);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
if total_seconds < 600 {
let minutes = (total_seconds as u32) / 60;
let seconds = (total_seconds as u32) % 60;
let milliseconds = microseconds / 1000;
let buf = format!("{}:{:02}.{:03} ", minutes, seconds, milliseconds);
RichString_appendnAscii(str, base_color, buf.as_bytes(), buf.len());
return;
}
let total_hundredths = total_microseconds / 1000 / 10;
Row_printTime(str, total_hundredths, coloring);
}
pub fn Row_printRate(str: &mut RichString, rate: f64, coloring: bool) {
let scheme = ColorScheme::active();
let color_of = |e: ColorElements| e.packed(scheme);
let mut large_number_color = color_of(LARGE_NUMBER);
let mut megabytes_color = color_of(PROCESS_MEGABYTES);
let shadow_color = color_of(PROCESS_SHADOW);
let base_color = color_of(PROCESS);
if !coloring {
large_number_color = color_of(PROCESS);
megabytes_color = color_of(PROCESS);
}
if !(rate >= 0.0) {
RichString_appendAscii(str, shadow_color, b" N/A ");
return;
}
let mut i: usize = 0;
let mut scaled = rate;
while scaled >= ONE_K as f64 && i < unitPrefixes.len() {
scaled /= ONE_K as f64;
i += 1;
}
let mut color = base_color;
if rate < 0.005 {
color = shadow_color;
} else if i == 2 {
color = megabytes_color;
} else if i >= 3 {
color = large_number_color;
}
let prefix = if i == 0 { b'B' } else { unitPrefixes[i - 1] };
let buf = format!("{:7.2} {}/s ", scaled, prefix as char);
RichString_appendnAscii(str, color, buf.as_bytes(), buf.len());
}
pub fn Row_printLeftAlignedField(str: &mut RichString, attr: i32, content: &[u8], width: u32) {
let mut columns: i32 = width as i32;
RichString_appendnWideColumns(str, attr, content, content.len(), &mut columns);
RichString_appendChr(str, attr, ' ', width as i32 + 1 - columns);
}
pub fn Row_printPercentage(mut val: f32, n: usize, width: u8, attr: &mut PercentageAttr) -> String {
debug_assert!(
n >= 6 && width >= 4,
"Invalid width in Row_printPercentage()"
);
let high = n - 2;
debug_assert!(4 <= high); let w = width as usize;
let width = (if w > high { high } else { w.max(4) }) as u8;
debug_assert!(
(width as usize) < n - 1,
"Insufficient space to print column"
);
if val >= 0.0 {
if val < 0.05_f32 {
*attr = PercentageAttr::Shadow;
} else if val >= 99.9_f32 {
*attr = PercentageAttr::Megabytes;
}
let mut precision: usize = 1;
if width == 4 && val > 99.9_f32 {
precision = 0;
val = 100.0_f32;
}
return format!(
"{:>width$.precision$} ",
val,
width = width as usize,
precision = precision
);
}
*attr = PercentageAttr::Shadow;
let w = width as usize;
format!("{:>width$.precision$} ", "N/A", width = w, precision = w)
}
pub fn Row_toggleTag(this: &mut Row) {
this.tag = !this.tag;
}
pub fn Row_compare(v1: &Row, v2: &Row) -> i32 {
spaceship_number!(v1.id, v2.id)
}
pub fn Row_compareByParent_Base(v1: &Row, v2: &Row) -> i32 {
let result = spaceship_number!(
if v1.isRoot {
0
} else {
Row_getGroupOrParent(v1)
},
if v2.isRoot {
0
} else {
Row_getGroupOrParent(v2)
}
);
if result != 0 {
return result;
}
Row_compare(v1, v2)
}
#[cfg(test)]
mod tests {
use super::*;
fn run(val: f32, n: usize, width: u8) -> (String, PercentageAttr) {
let mut attr = PercentageAttr::Unchanged;
let s = Row_printPercentage(val, n, width, &mut attr);
(s, attr)
}
#[test]
fn zero_is_shadow_and_zero_point_zero() {
assert_eq!(
run(0.0, 7, 5),
(" 0.0 ".to_string(), PercentageAttr::Shadow)
);
}
#[test]
fn below_shadow_threshold_is_shadow() {
assert_eq!(
run(0.04, 7, 5),
(" 0.0 ".to_string(), PercentageAttr::Shadow)
);
}
#[test]
fn mid_range_leaves_attr_unchanged() {
assert_eq!(
run(50.0, 7, 5),
(" 50.0 ".to_string(), PercentageAttr::Unchanged)
);
}
#[test]
fn unchanged_branch_preserves_caller_attr() {
let mut attr = PercentageAttr::Megabytes;
let s = Row_printPercentage(50.0, 7, 5, &mut attr);
assert_eq!(s, " 50.0 ");
assert_eq!(attr, PercentageAttr::Megabytes);
}
#[test]
fn at_ninety_nine_nine_is_megabytes_precision_one() {
assert_eq!(
run(99.9, 6, 4),
("99.9 ".to_string(), PercentageAttr::Megabytes)
);
}
#[test]
fn hundred_at_width_five_keeps_one_decimal() {
assert_eq!(
run(100.0, 7, 5),
("100.0 ".to_string(), PercentageAttr::Megabytes)
);
}
#[test]
fn mem_percent_width_four_collapses_to_integer() {
assert_eq!(
run(100.0, 6, 4),
(" 100 ".to_string(), PercentageAttr::Megabytes)
);
}
#[test]
fn negative_is_na_and_shadow() {
assert_eq!(
run(-1.0, 7, 5),
(" N/A ".to_string(), PercentageAttr::Shadow)
);
}
#[test]
fn nan_is_na_and_shadow() {
assert_eq!(
run(f32::NAN, 7, 5),
(" N/A ".to_string(), PercentageAttr::Shadow)
);
}
#[test]
fn width_clamped_to_n_minus_two() {
assert_eq!(
run(50.0, 6, 200),
("50.0 ".to_string(), PercentageAttr::Unchanged)
);
}
fn text(r: &RichString) -> String {
r.chptr
.iter()
.take(r.chlen as usize)
.map(|c| c.chars)
.collect()
}
fn cell_attrs(r: &RichString) -> Vec<i32> {
r.chptr
.iter()
.take(r.chlen as usize)
.map(|c| c.attr)
.collect()
}
fn col(element: ColorElements) -> i32 {
element.packed(ColorScheme::active()) & 0xffffff
}
fn kbytes(n: u64, coloring: bool) -> RichString {
let mut r = RichString::new();
Row_printKBytes(&mut r, n, coloring);
r
}
#[test]
fn kbytes_zero_plain_process() {
let r = kbytes(0, true);
assert_eq!(text(&r), " 0 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn kbytes_under_1000_plain() {
let r = kbytes(999, true);
assert_eq!(text(&r), " 999 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn kbytes_k_m_split_colors_the_thousands_group() {
let r = kbytes(1500, true);
assert_eq!(text(&r), " 1500 ");
let a = cell_attrs(&r);
assert_eq!(&a[0..2], &[col(PROCESS_MEGABYTES), col(PROCESS_MEGABYTES)]);
assert!(a[2..6].iter().all(|&x| x == col(PROCESS)));
}
#[test]
fn kbytes_k_m_split_no_coloring_all_process() {
let r = kbytes(1500, false);
assert_eq!(text(&r), " 1500 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn kbytes_97_6_mib_two_digit_decimal() {
let r = kbytes(100000, true);
assert_eq!(text(&r), "97.6M ");
let a = cell_attrs(&r);
assert_eq!(&a[0..2], &[col(PROCESS_MEGABYTES); 2]); assert_eq!(&a[2..4], &[col(PROCESS); 2]); assert_eq!(&a[4..6], &[col(PROCESS_MEGABYTES); 2]); }
#[test]
fn kbytes_9_76_gib_one_digit_decimal() {
let r = kbytes(10240000, true);
assert_eq!(text(&r), "9.76G ");
let a = cell_attrs(&r);
assert_eq!(a[0], col(PROCESS_GIGABYTES));
assert_eq!(&a[1..4], &[col(PROCESS_MEGABYTES); 3]);
assert_eq!(&a[4..6], &[col(PROCESS_GIGABYTES); 2]);
}
#[test]
fn kbytes_10_0_gib() {
let r = kbytes(10485760, true);
assert_eq!(text(&r), "10.0G ");
let a = cell_attrs(&r);
assert_eq!(&a[0..2], &[col(PROCESS_GIGABYTES); 2]); assert_eq!(&a[2..4], &[col(PROCESS_MEGABYTES); 2]); assert_eq!(&a[4..6], &[col(PROCESS_GIGABYTES); 2]); }
#[test]
fn kbytes_four_digit_megabytes() {
let r = kbytes(2097152, true);
assert_eq!(text(&r), "2048M ");
let a = cell_attrs(&r);
assert_eq!(a[0], col(PROCESS_GIGABYTES));
assert!(a[1..6].iter().all(|&x| x == col(PROCESS_MEGABYTES)));
}
#[test]
fn kbytes_invalid_na_shadow_when_coloring() {
let r = kbytes(u64::MAX, true);
assert_eq!(text(&r), " N/A ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
#[test]
fn kbytes_invalid_na_process_when_not_coloring() {
let r = kbytes(u64::MAX, false);
assert_eq!(text(&r), " N/A ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn bytes_divides_by_one_k_then_formats() {
let mut r = RichString::new();
Row_printBytes(&mut r, 1500, true);
assert_eq!(text(&r), " 1 ");
let mut r2 = RichString::new();
Row_printBytes(&mut r2, u64::MAX, true);
assert_eq!(text(&r2), " N/A ");
}
fn count(n: u64, coloring: bool) -> RichString {
let mut r = RichString::new();
Row_printCount(&mut r, n, coloring);
r
}
#[test]
fn count_small_four_color_bands() {
let r = count(0, true);
assert_eq!(text(&r), " 0 ");
let a = cell_attrs(&r);
assert!(a[0..2].iter().all(|&x| x == col(LARGE_NUMBER)));
assert!(a[2..5].iter().all(|&x| x == col(PROCESS_MEGABYTES)));
assert!(a[5..8].iter().all(|&x| x == col(PROCESS)));
assert!(a[8..12].iter().all(|&x| x == col(PROCESS_SHADOW)));
}
#[test]
fn count_invalid_na() {
let r = count(u64::MAX, true);
assert_eq!(text(&r), " N/A ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
#[test]
fn count_hundred_peta_decimal_all_large() {
let r = count(100_000 * 1_000_000_000_000, true); assert_eq!(text(&r), " 100000000 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(LARGE_NUMBER)));
}
fn time(h: u64, coloring: bool) -> RichString {
let mut r = RichString::new();
Row_printTime(&mut r, h, coloring);
r
}
#[test]
fn time_zero_shadow() {
let r = time(0, true);
assert_eq!(text(&r), " 0:00.00 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
#[test]
fn time_sub_minute_base_color() {
let r = time(100, true);
assert_eq!(text(&r), " 0:01.00 ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn time_one_minute() {
assert_eq!(text(&time(6000, true)), " 1:00.00 ");
}
#[test]
fn time_hour_layout() {
let r = time(360000, true);
assert_eq!(text(&r), " 1h00:00 ");
let a = cell_attrs(&r);
assert!(a[0..3].iter().all(|&x| x == col(PROCESS_MEGABYTES))); assert!(a[3..9].iter().all(|&x| x == col(PROCESS))); }
#[test]
fn time_day_under_ten() {
let r = time(8640000, true);
assert_eq!(text(&r), "1d00h00m ");
let a = cell_attrs(&r);
assert_eq!(&a[0..2], &[col(PROCESS_GIGABYTES); 2]); assert_eq!(&a[2..5], &[col(PROCESS_MEGABYTES); 3]); assert_eq!(&a[5..9], &[col(PROCESS); 4]); }
#[test]
fn time_day_under_year() {
let r = time(86_400_000, true);
assert_eq!(text(&r), " 10d00h ");
let a = cell_attrs(&r);
assert!(a[0..5].iter().all(|&x| x == col(PROCESS_GIGABYTES))); assert!(a[5..9].iter().all(|&x| x == col(PROCESS_MEGABYTES))); }
#[test]
fn time_year_under_thousand() {
let r = time(3_153_600_000, true);
assert_eq!(text(&r), " 1y000d ");
let a = cell_attrs(&r);
assert!(a[0..4].iter().all(|&x| x == col(LARGE_NUMBER))); assert!(a[4..9].iter().all(|&x| x == col(PROCESS_GIGABYTES))); }
#[test]
fn time_thousand_years_wide() {
let r = time(1000 * 365 * 8_640_000, true);
assert_eq!(text(&r), " 1000y ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(LARGE_NUMBER)));
}
#[test]
fn time_eternity() {
let r = time(u64::MAX, true);
assert_eq!(text(&r), "eternity ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(LARGE_NUMBER)));
}
fn nanos(n: u64) -> RichString {
let mut r = RichString::new();
Row_printNanoseconds(&mut r, n, true);
r
}
#[test]
fn nanoseconds_zero_shadow() {
let r = nanos(0);
assert_eq!(text(&r), " 0ns ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
#[test]
fn nanoseconds_sub_microsecond_range() {
let r = nanos(500);
assert_eq!(text(&r), " 500ns ");
assert!(cell_attrs(&r).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn nanoseconds_millisecond_range() {
assert_eq!(text(&nanos(5_000_000)), "5.0000ms ");
}
#[test]
fn nanoseconds_sub_second_zero_seconds() {
assert_eq!(text(&nanos(100_000_000)), ".100000s ");
}
#[test]
fn nanoseconds_five_seconds_variable_precision() {
assert_eq!(text(&nanos(5_000_000_000)), "5.00000s ");
}
#[test]
fn nanoseconds_minute_range() {
assert_eq!(text(&nanos(60_000_000_000)), "1:00.000 ");
}
#[test]
fn nanoseconds_defers_to_print_time_at_ten_minutes() {
assert_eq!(text(&nanos(600_000_000_000)), "10:00.00 ");
}
fn rate(r: f64, coloring: bool) -> RichString {
let mut s = RichString::new();
Row_printRate(&mut s, r, coloring);
s
}
#[test]
fn rate_negative_and_nan_are_na_shadow() {
for r in [-1.0, f64::NAN] {
let s = rate(r, true);
assert_eq!(text(&s), " N/A ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
}
#[test]
fn rate_tiny_is_shadow_bytes() {
let s = rate(0.0, true);
assert_eq!(text(&s), " 0.00 B/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS_SHADOW)));
}
#[test]
fn rate_bytes_base_color() {
let s = rate(1.0, true);
assert_eq!(text(&s), " 1.00 B/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn rate_kilobytes_base_color() {
let s = rate(2048.0, true);
assert_eq!(text(&s), " 2.00 K/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn rate_megabytes_mega_color() {
let s = rate(ONE_M as f64, true);
assert_eq!(text(&s), " 1.00 M/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS_MEGABYTES)));
}
#[test]
fn rate_gigabytes_large_color() {
let s = rate(ONE_G as f64, true);
assert_eq!(text(&s), " 1.00 G/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(LARGE_NUMBER)));
}
#[test]
fn rate_tera_and_peta() {
assert_eq!(text(&rate(ONE_T as f64, true)), " 1.00 T/s ");
assert_eq!(text(&rate(ONE_P as f64, true)), " 1.00 P/s ");
}
#[test]
fn rate_no_coloring_uses_process_for_scaled_units() {
let s = rate(ONE_G as f64, false);
assert_eq!(text(&s), " 1.00 G/s ");
assert!(cell_attrs(&s).iter().all(|&a| a == col(PROCESS)));
}
#[test]
fn left_aligned_field_pads_to_width_plus_one() {
let mut r = RichString::new();
Row_printLeftAlignedField(&mut r, 0, b"abc", 5);
assert_eq!(text(&r), "abc ");
}
#[test]
fn left_aligned_field_truncates_to_width() {
let mut r = RichString::new();
Row_printLeftAlignedField(&mut r, 0, b"abcdefgh", 3);
assert_eq!(text(&r), "abc ");
}
#[test]
fn left_aligned_field_short_content() {
let mut r = RichString::new();
Row_printLeftAlignedField(&mut r, 0, b"x", 4);
assert_eq!(text(&r), "x ");
}
fn row_id(id: i32) -> Row {
Row {
id,
..Row::default()
}
}
#[test]
fn row_init_sets_display_defaults_and_host() {
let mut r = Row::default();
let host = 0x1234_usize as *const c_void;
Row_init(&mut r, host);
assert_eq!(r.host, host);
assert!(!r.tag);
assert!(r.showChildren); assert!(r.show); assert!(!r.wasShown);
assert!(!r.updated);
assert_eq!(r.id, 0);
assert_eq!(r.tombStampMs, 0);
}
#[test]
fn row_done_is_a_noop() {
let r = row_id(7);
Row_done(&r);
assert_eq!(r.id, 7);
}
#[test]
fn row_toggle_tag_flips() {
let mut r = Row::default();
assert!(!r.tag);
Row_toggleTag(&mut r);
assert!(r.tag);
Row_toggleTag(&mut r);
assert!(!r.tag);
}
#[test]
fn row_is_tomb_tracks_exit_stamp() {
let mut r = Row::default();
assert!(!Row_isTomb(&r)); r.tombStampMs = 1;
assert!(Row_isTomb(&r));
r.tombStampMs = u64::MAX;
assert!(Row_isTomb(&r));
}
#[test]
fn row_compare_orders_by_id() {
assert_eq!(Row_compare(&row_id(1), &row_id(2)), -1);
assert_eq!(Row_compare(&row_id(2), &row_id(1)), 1);
assert_eq!(Row_compare(&row_id(5), &row_id(5)), 0);
assert_eq!(Row_compare(&row_id(-3), &row_id(4)), -1);
}
#[test]
fn row_compare_dispatches_through_object_trait() {
let a = row_id(1);
let b = row_id(2);
assert_eq!(a.compare(&b), -1);
assert_eq!(b.compare(&a), 1);
assert_eq!(a.compare(&row_id(1)), 0);
}
#[test]
fn row_get_group_or_parent_picks_parent_when_own_leader() {
let r = Row {
id: 10,
group: 10,
parent: 3,
..Row::default()
};
assert_eq!(Row_getGroupOrParent(&r), 3);
let r2 = Row {
id: 10,
group: 7,
parent: 3,
..Row::default()
};
assert_eq!(Row_getGroupOrParent(&r2), 7);
}
#[test]
fn row_is_child_of_matches_group_or_parent() {
let r = Row {
id: 10,
group: 7,
parent: 3,
..Row::default()
};
assert!(Row_isChildOf(&r, 7));
assert!(!Row_isChildOf(&r, 3)); assert!(!Row_isChildOf(&r, 10));
}
#[test]
fn row_compare_by_parent_orders_by_group_then_id() {
let a = Row {
id: 99,
group: 1,
parent: 1,
..Row::default()
};
let b = Row {
id: 2,
group: 5,
parent: 5,
..Row::default()
};
assert_eq!(Row_compareByParent_Base(&a, &b), -1); assert_eq!(Row_compareByParent_Base(&b, &a), 1);
}
#[test]
fn row_compare_by_parent_ties_break_on_id() {
let a = Row {
id: 10,
group: 4,
parent: 4,
..Row::default()
};
let b = Row {
id: 20,
group: 4,
parent: 4,
..Row::default()
};
assert_eq!(Row_compareByParent_Base(&a, &b), -1); }
#[test]
fn row_compare_by_parent_roots_sort_as_zero() {
let root = Row {
id: 50,
group: 999,
parent: 999,
isRoot: true,
..Row::default()
};
let child = Row {
id: 3,
group: 7,
parent: 7,
..Row::default()
};
assert_eq!(Row_compareByParent_Base(&root, &child), -1);
}
#[test]
fn set_pid_column_width_min_then_grows() {
Row_setPidColumnWidth(0);
assert_eq!(Row_pidDigits.load(Ordering::Relaxed), ROW_MIN_PID_DIGITS);
Row_setPidColumnWidth(99999);
assert_eq!(Row_pidDigits.load(Ordering::Relaxed), ROW_MIN_PID_DIGITS);
Row_setPidColumnWidth(100000);
assert_eq!(Row_pidDigits.load(Ordering::Relaxed), 6);
Row_setPidColumnWidth(4194304);
assert_eq!(Row_pidDigits.load(Ordering::Relaxed), 7);
Row_setPidColumnWidth(0);
assert_eq!(Row_pidDigits.load(Ordering::Relaxed), ROW_MIN_PID_DIGITS);
}
#[test]
fn set_uid_column_width_min_then_grows() {
Row_setUidColumnWidth(0);
assert_eq!(Row_uidDigits.load(Ordering::Relaxed), ROW_MIN_UID_DIGITS);
Row_setUidColumnWidth(99999);
assert_eq!(Row_uidDigits.load(Ordering::Relaxed), ROW_MIN_UID_DIGITS);
Row_setUidColumnWidth(u32::MAX);
assert_eq!(Row_uidDigits.load(Ordering::Relaxed), 10);
Row_setUidColumnWidth(0);
assert_eq!(Row_uidDigits.load(Ordering::Relaxed), ROW_MIN_UID_DIGITS);
}
#[test]
fn field_widths_reset_grow_and_cap() {
Row_resetFieldWidths();
let cgroup = ProcessField::CGROUP as usize;
assert_eq!(
Row_fieldWidths[cgroup].load(Ordering::Relaxed),
"CGROUP (raw)".len() as u8
);
let key = ProcessField::CGROUP as RowField;
Row_updateFieldWidth(key, 40);
assert_eq!(Row_fieldWidths[cgroup].load(Ordering::Relaxed), 40);
Row_updateFieldWidth(key, 5);
assert_eq!(Row_fieldWidths[cgroup].load(Ordering::Relaxed), 40);
Row_updateFieldWidth(key, 1000);
assert_eq!(Row_fieldWidths[cgroup].load(Ordering::Relaxed), u8::MAX);
}
#[test]
fn aligned_title_reserved_plain_fields() {
assert_eq!(alignedTitleProcessField(9), "- ");
assert_eq!(
alignedTitleProcessField(ProcessField::PRIORITY as RowField),
"PRI "
);
assert_eq!(
alignedTitleProcessField(ProcessField::COMM as RowField),
"Command "
);
}
#[test]
fn rowfield_keyat_walks_active_screen_fields() {
use crate::ported::settings::ScreenSettings;
let mut s = Settings::default();
s.screens = vec![ScreenSettings {
fields: vec![
ProcessField::PRIORITY as RowField,
ProcessField::NICE as RowField,
],
..Default::default()
}];
assert_eq!(RowField_keyAt(&s, 0), ProcessField::PRIORITY as RowField);
assert_eq!(RowField_keyAt(&s, 4), ProcessField::PRIORITY as RowField);
assert_eq!(RowField_keyAt(&s, 5), ProcessField::NICE as RowField);
assert_eq!(RowField_keyAt(&s, 100), ProcessField::COMM as RowField);
}
}