Skip to content

Commit 1cfbea5

Browse files
committed
first pass test suite
1 parent ac3565d commit 1cfbea5

8 files changed

Lines changed: 1571 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 350 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,12 @@ env_logger = { version = "~0.11.6", default-features = false }
4242
libc = "0.2"
4343
nvml-wrapper = { version = "0.10.0", optional = true }
4444
unicode-width = "0.2.0"
45+
46+
[dev-dependencies]
47+
mockall = "0.12"
48+
tokio-test = "0.4"
49+
proptest = "1.4"
50+
insta = { version = "1.34", features = ["json"] }
51+
4552
[target.'cfg(target_os = "linux")'.dependencies]
4653
linux-taskstats = { version = "0.7.0", default-features = false }

src/main.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,91 @@ struct ZOptions {
395395
#[options(short = "g", long = "graphics-height", default = "17", meta = "INT")]
396396
graphics_height: u16,
397397
}
398+
399+
#[cfg(test)]
400+
mod tests {
401+
use super::*;
402+
403+
#[test]
404+
fn test_validate_refresh_rate_valid() {
405+
assert_eq!(validate_refresh_rate("1000"), Ok(1000));
406+
assert_eq!(validate_refresh_rate("2000"), Ok(2000));
407+
assert_eq!(validate_refresh_rate("5000"), Ok(5000));
408+
}
409+
410+
#[test]
411+
fn test_validate_refresh_rate_too_low() {
412+
let result = validate_refresh_rate("500");
413+
assert!(result.is_err());
414+
assert!(result.unwrap_err().contains("at least 1000"));
415+
}
416+
417+
#[test]
418+
fn test_validate_refresh_rate_invalid_number() {
419+
let result = validate_refresh_rate("not_a_number");
420+
assert!(result.is_err());
421+
}
422+
423+
#[test]
424+
fn test_validate_refresh_rate_zero() {
425+
let result = validate_refresh_rate("0");
426+
assert!(result.is_err());
427+
}
428+
429+
#[test]
430+
fn test_create_geometry_all_sections() {
431+
let geometry = create_geometry(17, 17, 17, 32, 0, 17);
432+
433+
// Should have 5 sections (graphics is included when height > 0)
434+
assert_eq!(geometry.len(), 5);
435+
436+
// Check that all expected sections are present
437+
let sections: Vec<Section> = geometry.iter().map(|(s, _)| *s).collect();
438+
assert!(sections.contains(&Section::Cpu));
439+
assert!(sections.contains(&Section::Network));
440+
assert!(sections.contains(&Section::Disk));
441+
assert!(sections.contains(&Section::Graphics));
442+
assert!(sections.contains(&Section::Process));
443+
}
444+
445+
#[test]
446+
fn test_create_geometry_sum_to_100() {
447+
let geometry = create_geometry(17, 17, 17, 32, 0, 17);
448+
let sum: f64 = geometry.iter().map(|(_, h)| h).sum();
449+
450+
// Sum should be approximately 100%
451+
assert!((99.9..=100.1).contains(&sum));
452+
}
453+
454+
#[test]
455+
fn test_create_geometry_zero_heights_excluded() {
456+
// Only CPU and Process with non-zero heights
457+
let geometry = create_geometry(50, 0, 0, 50, 0, 0);
458+
459+
assert_eq!(geometry.len(), 2);
460+
let sections: Vec<Section> = geometry.iter().map(|(s, _)| *s).collect();
461+
assert!(sections.contains(&Section::Cpu));
462+
assert!(sections.contains(&Section::Process));
463+
assert!(!sections.contains(&Section::Network));
464+
assert!(!sections.contains(&Section::Disk));
465+
}
466+
467+
#[test]
468+
fn test_create_geometry_proportional_distribution() {
469+
// Test that heights are distributed proportionally
470+
let geometry = create_geometry(25, 25, 25, 25, 0, 0);
471+
472+
// Each should be approximately 25%
473+
for (_, height) in &geometry {
474+
assert!((*height - 25.0).abs() < 1.0);
475+
}
476+
}
477+
478+
#[test]
479+
fn test_default_db_path_not_empty() {
480+
let path = default_db_path();
481+
assert!(!path.is_empty());
482+
assert!(path.contains("zenith"));
483+
}
484+
}
485+

src/metrics/zprocess.rs

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use libc::{id_t, setpriority};
1111
#[cfg(target_os = "linux")]
1212
use linux_taskstats::Client;
1313

14-
use std::cmp::Ordering::{self, Equal};
14+
use std::cmp::Ordering::{self};
1515
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1616
use sysinfo::Process;
1717
use sysinfo::ProcessExt;
@@ -436,3 +436,243 @@ impl ProcessStatusExt for ProcessStatus {
436436
}
437437
}
438438
}
439+
440+
#[cfg(test)]
441+
mod tests {
442+
use super::*;
443+
use std::time::Duration;
444+
445+
fn create_test_process() -> ZProcess {
446+
ZProcess {
447+
pid: 1234,
448+
uid: 1000,
449+
user_name: "testuser".to_string(),
450+
memory: 1024 * 1024,
451+
cpu_usage: 50.0,
452+
cum_cpu_usage: 100.0,
453+
command: vec!["test".to_string(), "--arg".to_string()],
454+
exe: "/usr/bin/test".to_string(),
455+
status: ProcessStatus::Run,
456+
name: "test_process".to_string(),
457+
priority: 20,
458+
nice: 0,
459+
virtual_memory: 2 * 1024 * 1024,
460+
threads_total: 4,
461+
read_bytes: 1000,
462+
write_bytes: 500,
463+
prev_read_bytes: 500,
464+
prev_write_bytes: 250,
465+
last_updated: SystemTime::now(),
466+
end_time: None,
467+
start_time: SystemTime::now()
468+
.duration_since(UNIX_EPOCH)
469+
.unwrap()
470+
.as_secs()
471+
- 3600, // Started 1 hour ago
472+
gpu_usage: 0,
473+
fb_utilization: 0,
474+
enc_utilization: 0,
475+
dec_utilization: 0,
476+
sm_utilization: 0,
477+
io_delay: Duration::from_nanos(0),
478+
swap_delay: Duration::from_nanos(0),
479+
prev_io_delay: Duration::from_nanos(0),
480+
prev_swap_delay: Duration::from_nanos(0),
481+
}
482+
}
483+
484+
#[test]
485+
fn test_get_read_bytes_sec() {
486+
let process = create_test_process();
487+
let tick_rate = Duration::from_secs(1);
488+
489+
// read_bytes: 1000, prev_read_bytes: 500
490+
// (1000 - 500) / 1.0 = 500.0
491+
let read_rate = process.get_read_bytes_sec(&tick_rate);
492+
assert!((read_rate - 500.0).abs() < 0.01);
493+
}
494+
495+
#[test]
496+
fn test_get_read_bytes_sec_with_different_tick() {
497+
let process = create_test_process();
498+
let tick_rate = Duration::from_secs(2);
499+
500+
// (1000 - 500) / 2.0 = 250.0
501+
let read_rate = process.get_read_bytes_sec(&tick_rate);
502+
assert!((read_rate - 250.0).abs() < 0.01);
503+
}
504+
505+
#[test]
506+
fn test_get_write_bytes_sec() {
507+
let process = create_test_process();
508+
let tick_rate = Duration::from_secs(1);
509+
510+
// write_bytes: 500, prev_write_bytes: 250
511+
// (500 - 250) / 1.0 = 250.0
512+
let write_rate = process.get_write_bytes_sec(&tick_rate);
513+
assert!((write_rate - 250.0).abs() < 0.01);
514+
}
515+
516+
#[test]
517+
fn test_get_write_bytes_sec_with_different_tick() {
518+
let process = create_test_process();
519+
let tick_rate = Duration::from_millis(500);
520+
521+
// (500 - 250) / 0.5 = 500.0
522+
let write_rate = process.get_write_bytes_sec(&tick_rate);
523+
assert!((write_rate - 500.0).abs() < 0.01);
524+
}
525+
526+
#[test]
527+
fn test_get_run_duration_alive_process() {
528+
let process = create_test_process();
529+
let duration = process.get_run_duration();
530+
531+
// Process started 1 hour ago
532+
assert!(duration.num_minutes() >= 59);
533+
assert!(duration.num_minutes() <= 61);
534+
}
535+
536+
#[test]
537+
fn test_get_run_duration_dead_process() {
538+
let mut process = create_test_process();
539+
// Process ended 30 minutes after start
540+
process.end_time = Some(process.start_time + 1800);
541+
542+
let duration = process.get_run_duration();
543+
assert_eq!(duration.num_minutes(), 30);
544+
}
545+
546+
#[test]
547+
fn test_set_end_time() {
548+
let mut process = create_test_process();
549+
assert!(process.end_time.is_none());
550+
551+
process.set_end_time();
552+
assert!(process.end_time.is_some());
553+
554+
// Calling again should not change the end time
555+
let first_end_time = process.end_time;
556+
process.set_end_time();
557+
assert_eq!(process.end_time, first_end_time);
558+
}
559+
560+
#[test]
561+
fn test_field_comparator_cpu() {
562+
let mut p1 = create_test_process();
563+
let mut p2 = create_test_process();
564+
p1.cpu_usage = 10.0;
565+
p2.cpu_usage = 20.0;
566+
567+
let tick = Duration::from_secs(1);
568+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::Cpu);
569+
570+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
571+
assert_eq!(comparator(&p2, &p1, &tick), Ordering::Greater);
572+
assert_eq!(comparator(&p1, &p1, &tick), Ordering::Equal);
573+
}
574+
575+
#[test]
576+
fn test_field_comparator_memory() {
577+
let mut p1 = create_test_process();
578+
let mut p2 = create_test_process();
579+
p1.memory = 1000;
580+
p2.memory = 2000;
581+
582+
let tick = Duration::from_secs(1);
583+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::Mem);
584+
585+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
586+
assert_eq!(comparator(&p2, &p1, &tick), Ordering::Greater);
587+
}
588+
589+
#[test]
590+
fn test_field_comparator_pid() {
591+
let mut p1 = create_test_process();
592+
let mut p2 = create_test_process();
593+
p1.pid = 100;
594+
p2.pid = 200;
595+
596+
let tick = Duration::from_secs(1);
597+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::Pid);
598+
599+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
600+
}
601+
602+
#[test]
603+
fn test_field_comparator_user() {
604+
let mut p1 = create_test_process();
605+
let mut p2 = create_test_process();
606+
p1.user_name = "alice".to_string();
607+
p2.user_name = "bob".to_string();
608+
609+
let tick = Duration::from_secs(1);
610+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::User);
611+
612+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
613+
}
614+
615+
#[test]
616+
fn test_field_comparator_disk_read() {
617+
let mut p1 = create_test_process();
618+
let mut p2 = create_test_process();
619+
p1.read_bytes = 1000;
620+
p1.prev_read_bytes = 0;
621+
p2.read_bytes = 2000;
622+
p2.prev_read_bytes = 0;
623+
624+
let tick = Duration::from_secs(1);
625+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::DiskRead);
626+
627+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
628+
}
629+
630+
#[test]
631+
fn test_field_comparator_cmd() {
632+
let mut p1 = create_test_process();
633+
let mut p2 = create_test_process();
634+
p1.name = "aaa".to_string();
635+
p2.name = "zzz".to_string();
636+
637+
let tick = Duration::from_secs(1);
638+
let comparator = ZProcess::field_comparator(ProcessTableSortBy::Cmd);
639+
640+
assert_eq!(comparator(&p1, &p2, &tick), Ordering::Less);
641+
}
642+
643+
#[test]
644+
fn test_process_status_to_single_char() {
645+
assert_eq!(ProcessStatus::Run.to_single_char(), "R");
646+
assert_eq!(ProcessStatus::Sleep.to_single_char(), "S");
647+
assert_eq!(ProcessStatus::Stop.to_single_char(), "T");
648+
assert_eq!(ProcessStatus::Zombie.to_single_char(), "Z");
649+
assert_eq!(ProcessStatus::Idle.to_single_char(), "I");
650+
}
651+
652+
#[test]
653+
fn test_zero_io_rates() {
654+
let mut process = create_test_process();
655+
process.read_bytes = 0;
656+
process.prev_read_bytes = 0;
657+
process.write_bytes = 0;
658+
process.prev_write_bytes = 0;
659+
660+
let tick_rate = Duration::from_secs(1);
661+
662+
assert_eq!(process.get_read_bytes_sec(&tick_rate), 0.0);
663+
assert_eq!(process.get_write_bytes_sec(&tick_rate), 0.0);
664+
}
665+
666+
#[test]
667+
fn test_large_io_rates() {
668+
let mut process = create_test_process();
669+
process.read_bytes = 1_000_000_000; // 1GB
670+
process.prev_read_bytes = 0;
671+
672+
let tick_rate = Duration::from_secs(1);
673+
let rate = process.get_read_bytes_sec(&tick_rate);
674+
675+
assert!((rate - 1_000_000_000.0).abs() < 1.0);
676+
}
677+
}
678+

0 commit comments

Comments
 (0)