1use unicode_width::UnicodeWidthStr;
11
12use crate::blocks::{SuffixType, to_magnitude_and_suffix};
13use crate::columns::{Alignment, Column};
14use crate::filesystem::Filesystem;
15use crate::{BlockSize, Options};
16use uucore::fsext::{FsUsage, MountInfo};
17use uucore::translate;
18
19use std::ffi::OsString;
20use std::iter;
21use std::ops::{Add, AddAssign};
22
23pub(crate) struct Row {
28 file: Option<OsString>,
30
31 fs_device: String,
33
34 fs_type: String,
36
37 fs_mount: OsString,
39
40 bytes: BytesCell,
42
43 bytes_used: BytesCell,
45
46 bytes_avail: BytesCell,
48
49 bytes_usage: Option<f64>,
53
54 #[cfg(target_os = "macos")]
60 bytes_capacity: Option<f64>,
61
62 inodes: u128,
64
65 inodes_used: u128,
67
68 inodes_free: u128,
70
71 inodes_usage: Option<f64>,
75}
76
77impl Row {
78 pub(crate) fn new(source: &str) -> Self {
79 Self {
80 file: None,
81 fs_device: source.into(),
82 fs_type: "-".into(),
83 fs_mount: "-".into(),
84 bytes: BytesCell::default(),
85 bytes_used: BytesCell::default(),
86 bytes_avail: BytesCell::default(),
87 bytes_usage: None,
88 #[cfg(target_os = "macos")]
89 bytes_capacity: None,
90 inodes: 0,
91 inodes_used: 0,
92 inodes_free: 0,
93 inodes_usage: None,
94 }
95 }
96}
97
98impl AddAssign for Row {
99 fn add_assign(&mut self, rhs: Self) {
104 let bytes = self.bytes + rhs.bytes;
105 let bytes_used = self.bytes_used + rhs.bytes_used;
106 let bytes_avail = self.bytes_avail + rhs.bytes_avail;
107 let inodes = self.inodes + rhs.inodes;
108 let inodes_used = self.inodes_used + rhs.inodes_used;
109 *self = Self {
110 file: None,
111 fs_device: translate!("df-total"),
112 fs_type: "-".into(),
113 fs_mount: "-".into(),
114 bytes,
115 bytes_used,
116 bytes_avail,
117 bytes_usage: if bytes.bytes == 0 {
118 None
119 } else {
120 Some(bytes_used.bytes as f64 / (bytes_used.bytes + bytes_avail.bytes) as f64)
124 },
125 #[cfg(target_os = "macos")]
127 bytes_capacity: None,
128 inodes,
129 inodes_used,
130 inodes_free: self.inodes_free + rhs.inodes_free,
131 inodes_usage: if inodes == 0 {
132 None
133 } else {
134 Some(inodes_used as f64 / inodes as f64)
135 },
136 }
137 }
138}
139
140impl Row {
141 fn from_filesystem(fs: Filesystem, row_block_size: &BlockSize) -> Self {
142 let MountInfo {
143 dev_name,
144 fs_type,
145 mount_dir,
146 ..
147 } = fs.mount_info;
148 let FsUsage {
149 blocksize,
150 blocks,
151 bfree,
152 bavail,
153 files,
154 ffree,
155 ..
156 } = fs.usage;
157
158 let bused = blocks.saturating_sub(bfree);
160 let fused = files.saturating_sub(ffree);
161 Self {
162 file: fs.file,
163 fs_device: dev_name,
164 fs_type,
165 fs_mount: mount_dir,
166 bytes: BytesCell::new(blocks * blocksize, row_block_size),
167 bytes_used: BytesCell::new(bused * blocksize, row_block_size),
168 bytes_avail: BytesCell::new(bavail * blocksize, row_block_size),
169 bytes_usage: if blocks == 0 {
170 None
171 } else {
172 Some(bused as f64 / (bused + bavail) as f64)
176 },
177 #[cfg(target_os = "macos")]
178 bytes_capacity: if bavail == 0 {
179 None
180 } else {
181 Some(bavail as f64 / ((bused + bavail) as f64))
182 },
183 inodes: files as u128,
184 inodes_used: fused as u128,
185 inodes_free: ffree as u128,
186 inodes_usage: if files == 0 {
187 None
188 } else {
189 Some(fused as f64 / files as f64)
190 },
191 }
192 }
193}
194
195#[derive(Debug, Copy, Clone)]
196struct BytesCell {
197 bytes: u64,
198 scaled: u64,
199}
200
201impl Default for BytesCell {
206 fn default() -> Self {
207 Self {
208 bytes: 0,
209 scaled: 0,
210 }
211 }
212}
213
214impl BytesCell {
215 fn new(bytes: u64, block_size: &BlockSize) -> Self {
216 Self {
217 bytes,
218 scaled: {
219 let BlockSize::Bytes(d) = block_size;
220 (bytes as f64 / *d as f64).ceil() as u64
221 },
222 }
223 }
224}
225
226impl Add for BytesCell {
227 type Output = Self;
228
229 fn add(self, rhs: Self) -> Self {
230 Self {
231 bytes: self.bytes + rhs.bytes,
232 scaled: self.scaled + rhs.scaled,
233 }
234 }
235}
236
237struct Cell {
241 bytes: Vec<u8>,
242 width: usize,
243}
244
245impl Cell {
246 fn from_ascii_string<T: AsRef<str>>(s: T) -> Self {
248 let s = s.as_ref();
249 Self {
250 bytes: s.as_bytes().into(),
251 width: s.len(),
252 }
253 }
254
255 fn from_string<T: AsRef<str>>(s: T) -> Self {
258 let s = s.as_ref();
259 Self {
260 bytes: s.as_bytes().into(),
261 width: UnicodeWidthStr::width(s),
262 }
263 }
264
265 fn from_os_string(os: &OsString) -> Self {
267 Self {
268 bytes: uucore::os_str_as_bytes(os).unwrap().to_vec(),
269 width: UnicodeWidthStr::width(os.to_string_lossy().as_ref()),
270 }
271 }
272}
273
274pub(crate) struct RowFormatter<'a> {
278 row: &'a Row,
280
281 options: &'a Options,
283 is_total_row: bool,
292}
293
294impl<'a> RowFormatter<'a> {
295 pub(crate) fn new(row: &'a Row, options: &'a Options, is_total_row: bool) -> Self {
297 Self {
298 row,
299 options,
300 is_total_row,
301 }
302 }
303
304 fn scaled_bytes(&self, bytes_column: &BytesCell) -> Cell {
308 let size = bytes_column.scaled;
309 let s = if let Some(h) = self.options.human_readable {
310 let size = if self.is_total_row {
311 let BlockSize::Bytes(d) = self.options.block_size;
312 d * size
313 } else {
314 bytes_column.bytes
315 };
316 to_magnitude_and_suffix(size.into(), SuffixType::HumanReadable(h), true)
317 } else {
318 size.to_string()
319 };
320 Cell::from_ascii_string(s)
321 }
322
323 fn scaled_inodes(&self, size: u128) -> Cell {
327 let s = if let Some(h) = self.options.human_readable {
328 to_magnitude_and_suffix(size, SuffixType::HumanReadable(h), true)
329 } else {
330 size.to_string()
331 };
332 Cell::from_ascii_string(s)
333 }
334
335 fn percentage(fraction: Option<f64>) -> Cell {
339 let s = match fraction {
340 None => "-".to_string(),
341 Some(x) => format!("{:.0}%", (100.0 * x).ceil()),
342 };
343 Cell::from_ascii_string(s)
344 }
345
346 fn get_cells(&self) -> Vec<Cell> {
348 let mut cells = Vec::new();
349
350 for column in &self.options.columns {
351 let cell = match column {
352 Column::Source => {
353 if self.is_total_row {
354 Cell::from_string(translate!("df-total"))
355 } else {
356 Cell::from_string(&self.row.fs_device)
357 }
358 }
359 Column::Size => self.scaled_bytes(&self.row.bytes),
360 Column::Used => self.scaled_bytes(&self.row.bytes_used),
361 Column::Avail => self.scaled_bytes(&self.row.bytes_avail),
362 Column::Pcent => Self::percentage(self.row.bytes_usage),
363
364 Column::Target => {
365 if self.is_total_row && !self.options.columns.contains(&Column::Source) {
366 Cell::from_string(translate!("df-total"))
367 } else {
368 Cell::from_os_string(&self.row.fs_mount)
369 }
370 }
371 Column::Itotal => self.scaled_inodes(self.row.inodes),
372 Column::Iused => self.scaled_inodes(self.row.inodes_used),
373 Column::Iavail => self.scaled_inodes(self.row.inodes_free),
374 Column::Ipcent => Self::percentage(self.row.inodes_usage),
375 Column::File => self
376 .row
377 .file
378 .as_ref()
379 .map_or(Cell::from_ascii_string("-"), Cell::from_os_string),
380
381 Column::Fstype => Cell::from_string(&self.row.fs_type),
382 #[cfg(target_os = "macos")]
383 Column::Capacity => Self::percentage(self.row.bytes_capacity),
384 };
385
386 cells.push(cell);
387 }
388
389 cells
390 }
391}
392
393#[derive(Default)]
395pub(crate) enum HeaderMode {
396 #[default]
397 Default,
398 HumanReadable,
400 PosixPortability,
402 Output,
404}
405
406struct Header {}
408
409impl Header {
410 fn get_headers(options: &Options) -> Vec<String> {
414 let mut headers = Vec::new();
415
416 for column in &options.columns {
417 let header = match column {
418 Column::Source => translate!("df-header-filesystem"),
419 Column::Size => match options.header_mode {
420 HeaderMode::HumanReadable => translate!("df-header-size"),
421 HeaderMode::PosixPortability => {
422 format!(
423 "{}{}",
424 options.block_size.as_u64(),
425 translate!("df-blocks-suffix")
426 )
427 }
428 _ => format!(
429 "{}{}",
430 options.block_size.to_header(),
431 translate!("df-blocks-suffix")
432 ),
433 },
434 Column::Used => translate!("df-header-used"),
435 Column::Avail => match options.header_mode {
436 HeaderMode::HumanReadable | HeaderMode::Output => {
437 translate!("df-header-avail")
438 }
439 _ => translate!("df-header-available"),
440 },
441 Column::Pcent => match options.header_mode {
442 HeaderMode::PosixPortability => translate!("df-header-capacity"),
443 _ => translate!("df-header-use-percent"),
444 },
445 Column::Target => translate!("df-header-mounted-on"),
446 Column::Itotal => translate!("df-header-inodes"),
447 Column::Iused => translate!("df-header-iused"),
448 Column::Iavail => translate!("df-header-iavail"),
449 Column::Ipcent => translate!("df-header-iuse-percent"),
450 Column::File => translate!("df-header-file"),
451 Column::Fstype => translate!("df-header-type"),
452 #[cfg(target_os = "macos")]
453 Column::Capacity => translate!("df-header-capacity"),
454 };
455
456 headers.push(header);
457 }
458
459 headers
460 }
461}
462
463pub(crate) struct Table {
465 alignments: Vec<Alignment>,
466 rows: Vec<Vec<Cell>>,
467 widths: Vec<usize>,
468}
469
470impl Table {
471 pub(crate) fn new(options: &Options, filesystems: Vec<Filesystem>) -> Self {
472 let headers = Header::get_headers(options);
473 let mut widths: Vec<_> = options
474 .columns
475 .iter()
476 .enumerate()
477 .map(|(i, col)| col.min_width().max(headers[i].len()))
478 .collect();
479
480 let mut rows = vec![headers.iter().map(Cell::from_string).collect()];
481
482 let mut total = Row::new(&translate!("df-total"));
487
488 for filesystem in filesystems {
489 if options.show_all_fs || filesystem.usage.blocks > 0 {
493 let row = Row::from_filesystem(filesystem, &options.block_size);
494 let fmt = RowFormatter::new(&row, options, false);
495 let values = fmt.get_cells();
496 if options.show_total {
497 total += row;
498 }
499
500 rows.push(values);
501 }
502 }
503
504 if options.show_total {
505 let total_row = RowFormatter::new(&total, options, true);
506 rows.push(total_row.get_cells());
507 }
508
509 for row in &rows {
512 for (i, value) in row.iter().enumerate() {
513 if value.width > widths[i] {
514 widths[i] = value.width;
515 }
516 }
517 }
518
519 Self {
520 rows,
521 widths,
522 alignments: Self::get_alignments(&options.columns),
523 }
524 }
525
526 fn get_alignments(columns: &Vec<Column>) -> Vec<Alignment> {
527 let mut alignments = Vec::new();
528
529 for column in columns {
530 alignments.push(column.alignment());
531 }
532
533 alignments
534 }
535
536 pub(crate) fn write_to(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
537 for row in &self.rows {
538 let mut col_iter = row.iter().enumerate().peekable();
539 while let Some((i, elem)) = col_iter.next() {
540 let is_last_col = col_iter.peek().is_none();
541
542 let pad_width = self.widths[i].saturating_sub(elem.width);
543 match self.alignments.get(i) {
544 Some(Alignment::Left) => {
545 writer.write_all(&elem.bytes)?;
546 if !is_last_col {
548 writer
549 .write_all(&iter::repeat_n(b' ', pad_width).collect::<Vec<_>>())?;
550 }
551 }
552 Some(Alignment::Right) => {
553 writer.write_all(&iter::repeat_n(b' ', pad_width).collect::<Vec<_>>())?;
554 writer.write_all(&elem.bytes)?;
555 }
556 None => break,
557 }
558
559 if !is_last_col {
560 writer.write_all(b" ")?;
562 }
563 }
564
565 writeln!(writer)?;
566 }
567
568 Ok(())
569 }
570}
571
572#[cfg(test)]
573mod tests {
574
575 use std::vec;
576 use uucore::locale::setup_localization;
577
578 use crate::blocks::HumanReadable;
579 use crate::columns::Column;
580 use crate::table::{BytesCell, Cell, Header, HeaderMode, Row, RowFormatter, Table};
581 use crate::{BlockSize, Options};
582
583 fn init() {
584 unsafe {
585 std::env::set_var("LANG", "C");
586 }
587 let _ = setup_localization("df");
588 }
589
590 const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
591 Column::Source,
592 Column::Fstype,
593 Column::Size,
594 Column::Used,
595 Column::Avail,
596 Column::Pcent,
597 Column::Target,
598 ];
599 const COLUMNS_WITH_INODES: [Column; 6] = [
600 Column::Source,
601 Column::Itotal,
602 Column::Iused,
603 Column::Iavail,
604 Column::Ipcent,
605 Column::Target,
606 ];
607
608 impl Default for Row {
609 fn default() -> Self {
610 Self {
611 file: Some("/path/to/file".into()),
612 fs_device: "my_device".to_string(),
613 fs_type: "my_type".to_string(),
614 fs_mount: "my_mount".into(),
615
616 bytes: BytesCell::new(100, &BlockSize::Bytes(1)),
617 bytes_used: BytesCell::new(25, &BlockSize::Bytes(1)),
618 bytes_avail: BytesCell::new(75, &BlockSize::Bytes(1)),
619 bytes_usage: Some(0.25),
620
621 #[cfg(target_os = "macos")]
622 bytes_capacity: Some(0.5),
623
624 inodes: 10,
625 inodes_used: 2,
626 inodes_free: 8,
627 inodes_usage: Some(0.2),
628 }
629 }
630 }
631
632 #[test]
633 fn test_default_header() {
634 init();
635 let options = Options::default();
636
637 assert_eq!(
638 Header::get_headers(&options),
639 vec!(
640 "Filesystem",
641 "1K-blocks",
642 "Used",
643 "Available",
644 "Use%",
645 "Mounted on"
646 )
647 );
648 }
649
650 #[test]
651 fn test_header_with_fs_type() {
652 init();
653 let options = Options {
654 columns: COLUMNS_WITH_FS_TYPE.to_vec(),
655 ..Default::default()
656 };
657 assert_eq!(
658 Header::get_headers(&options),
659 vec!(
660 "Filesystem",
661 "Type",
662 "1K-blocks",
663 "Used",
664 "Available",
665 "Use%",
666 "Mounted on"
667 )
668 );
669 }
670
671 #[test]
672 fn test_header_with_inodes() {
673 init();
674 let options = Options {
675 columns: COLUMNS_WITH_INODES.to_vec(),
676 ..Default::default()
677 };
678 assert_eq!(
679 Header::get_headers(&options),
680 vec!(
681 "Filesystem",
682 "Inodes",
683 "IUsed",
684 "IFree",
685 "IUse%",
686 "Mounted on"
687 )
688 );
689 }
690
691 #[test]
692 fn test_header_with_block_size_1024() {
693 init();
694 let options = Options {
695 block_size: BlockSize::Bytes(3 * 1024),
696 ..Default::default()
697 };
698 assert_eq!(
699 Header::get_headers(&options),
700 vec!(
701 "Filesystem",
702 "3K-blocks",
703 "Used",
704 "Available",
705 "Use%",
706 "Mounted on"
707 )
708 );
709 }
710
711 #[test]
712 fn test_human_readable_header() {
713 init();
714 let options = Options {
715 header_mode: HeaderMode::HumanReadable,
716 ..Default::default()
717 };
718 assert_eq!(
719 Header::get_headers(&options),
720 vec!("Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")
721 );
722 }
723
724 #[test]
725 fn test_posix_portability_header() {
726 init();
727 let options = Options {
728 header_mode: HeaderMode::PosixPortability,
729 ..Default::default()
730 };
731 assert_eq!(
732 Header::get_headers(&options),
733 vec!(
734 "Filesystem",
735 "1024-blocks",
736 "Used",
737 "Available",
738 "Capacity",
739 "Mounted on"
740 )
741 );
742 }
743
744 #[test]
745 fn test_output_header() {
746 init();
747 let options = Options {
748 header_mode: HeaderMode::Output,
749 ..Default::default()
750 };
751 assert_eq!(
752 Header::get_headers(&options),
753 vec!(
754 "Filesystem",
755 "1K-blocks",
756 "Used",
757 "Avail",
758 "Use%",
759 "Mounted on"
760 )
761 );
762 }
763
764 fn compare_cell_content(cells: Vec<Cell>, expected: Vec<&str>) -> bool {
765 cells
766 .into_iter()
767 .zip(expected)
768 .all(|(c, s)| c.bytes == s.as_bytes())
769 }
770
771 #[test]
772 fn test_row_formatter() {
773 init();
774 let options = Options {
775 block_size: BlockSize::Bytes(1),
776 ..Default::default()
777 };
778 let row = Row {
779 fs_device: "my_device".to_string(),
780 fs_mount: "my_mount".into(),
781
782 bytes: BytesCell::new(100, &BlockSize::Bytes(1)),
783 bytes_used: BytesCell::new(25, &BlockSize::Bytes(1)),
784 bytes_avail: BytesCell::new(75, &BlockSize::Bytes(1)),
785 bytes_usage: Some(0.25),
786
787 ..Default::default()
788 };
789 let fmt = RowFormatter::new(&row, &options, false);
790 assert!(compare_cell_content(
791 fmt.get_cells(),
792 vec!("my_device", "100", "25", "75", "25%", "my_mount")
793 ));
794 }
795
796 #[test]
797 fn test_row_formatter_with_fs_type() {
798 init();
799 let options = Options {
800 columns: COLUMNS_WITH_FS_TYPE.to_vec(),
801 block_size: BlockSize::Bytes(1),
802 ..Default::default()
803 };
804 let row = Row {
805 fs_device: "my_device".to_string(),
806 fs_type: "my_type".to_string(),
807 fs_mount: "my_mount".into(),
808
809 bytes: BytesCell::new(100, &BlockSize::Bytes(1)),
810 bytes_used: BytesCell::new(25, &BlockSize::Bytes(1)),
811 bytes_avail: BytesCell::new(75, &BlockSize::Bytes(1)),
812 bytes_usage: Some(0.25),
813
814 ..Default::default()
815 };
816 let fmt = RowFormatter::new(&row, &options, false);
817 assert!(compare_cell_content(
818 fmt.get_cells(),
819 vec!("my_device", "my_type", "100", "25", "75", "25%", "my_mount")
820 ));
821 }
822
823 #[test]
824 fn test_row_formatter_with_inodes() {
825 init();
826 let options = Options {
827 columns: COLUMNS_WITH_INODES.to_vec(),
828 block_size: BlockSize::Bytes(1),
829 ..Default::default()
830 };
831 let row = Row {
832 fs_device: "my_device".to_string(),
833 fs_mount: "my_mount".into(),
834
835 inodes: 10,
836 inodes_used: 2,
837 inodes_free: 8,
838 inodes_usage: Some(0.2),
839
840 ..Default::default()
841 };
842 let fmt = RowFormatter::new(&row, &options, false);
843 assert!(compare_cell_content(
844 fmt.get_cells(),
845 vec!("my_device", "10", "2", "8", "20%", "my_mount")
846 ));
847 }
848
849 #[test]
850 fn test_row_formatter_with_bytes_and_inodes() {
851 init();
852 let options = Options {
853 columns: vec![Column::Size, Column::Itotal],
854 block_size: BlockSize::Bytes(100),
855 ..Default::default()
856 };
857 let row = Row {
858 bytes: BytesCell::new(100, &BlockSize::Bytes(100)),
859 inodes: 10,
860 ..Default::default()
861 };
862 let fmt = RowFormatter::new(&row, &options, false);
863 assert!(compare_cell_content(fmt.get_cells(), vec!("1", "10")));
864 }
865
866 #[test]
867 fn test_row_formatter_with_human_readable_si() {
868 init();
869 let options = Options {
870 human_readable: Some(HumanReadable::Decimal),
871 columns: COLUMNS_WITH_FS_TYPE.to_vec(),
872 ..Default::default()
873 };
874 let row = Row {
875 fs_device: "my_device".to_string(),
876 fs_type: "my_type".to_string(),
877 fs_mount: "my_mount".into(),
878
879 bytes: BytesCell::new(40000, &BlockSize::default()),
880 bytes_used: BytesCell::new(1000, &BlockSize::default()),
881 bytes_avail: BytesCell::new(39000, &BlockSize::default()),
882 bytes_usage: Some(0.025),
883
884 ..Default::default()
885 };
886 let fmt = RowFormatter::new(&row, &options, false);
887 assert!(compare_cell_content(
888 fmt.get_cells(),
889 vec!(
890 "my_device",
891 "my_type",
892 "40k",
893 "1.0k",
894 "39k",
895 "3%",
896 "my_mount"
897 )
898 ));
899 }
900
901 #[test]
902 fn test_row_formatter_with_human_readable_binary() {
903 init();
904 let options = Options {
905 human_readable: Some(HumanReadable::Binary),
906 columns: COLUMNS_WITH_FS_TYPE.to_vec(),
907 ..Default::default()
908 };
909 let row = Row {
910 fs_device: "my_device".to_string(),
911 fs_type: "my_type".to_string(),
912 fs_mount: "my_mount".into(),
913
914 bytes: BytesCell::new(4096, &BlockSize::default()),
915 bytes_used: BytesCell::new(1024, &BlockSize::default()),
916 bytes_avail: BytesCell::new(3072, &BlockSize::default()),
917 bytes_usage: Some(0.25),
918
919 ..Default::default()
920 };
921 let fmt = RowFormatter::new(&row, &options, false);
922 assert!(compare_cell_content(
923 fmt.get_cells(),
924 vec!(
925 "my_device",
926 "my_type",
927 "4.0K",
928 "1.0K",
929 "3.0K",
930 "25%",
931 "my_mount"
932 )
933 ));
934 }
935
936 #[test]
937 fn test_row_formatter_with_round_up_usage() {
938 init();
939 let options = Options {
940 columns: vec![Column::Pcent],
941 ..Default::default()
942 };
943 let row = Row {
944 bytes_usage: Some(0.251),
945 ..Default::default()
946 };
947 let fmt = RowFormatter::new(&row, &options, false);
948 assert!(compare_cell_content(fmt.get_cells(), vec!("26%")));
949 }
950
951 #[test]
952 fn test_row_formatter_with_round_up_byte_values() {
953 init();
954 fn get_formatted_values(bytes: u64, bytes_used: u64, bytes_avail: u64) -> Vec<Cell> {
955 let options = Options {
956 block_size: BlockSize::Bytes(1000),
957 columns: vec![Column::Size, Column::Used, Column::Avail],
958 ..Default::default()
959 };
960
961 let row = Row {
962 bytes: BytesCell::new(bytes, &BlockSize::Bytes(1000)),
963 bytes_used: BytesCell::new(bytes_used, &BlockSize::Bytes(1000)),
964 bytes_avail: BytesCell::new(bytes_avail, &BlockSize::Bytes(1000)),
965 ..Default::default()
966 };
967 RowFormatter::new(&row, &options, false).get_cells()
968 }
969
970 assert!(compare_cell_content(
971 get_formatted_values(100, 100, 0),
972 vec!("1", "1", "0")
973 ));
974 assert!(compare_cell_content(
975 get_formatted_values(100, 99, 1),
976 vec!("1", "1", "1")
977 ));
978 assert!(compare_cell_content(
979 get_formatted_values(1000, 1000, 0),
980 vec!("1", "1", "0")
981 ));
982 assert!(compare_cell_content(
983 get_formatted_values(1001, 1000, 1),
984 vec!("2", "1", "1")
985 ));
986 }
987
988 #[test]
989 fn test_row_converter_with_invalid_numbers() {
990 init();
991 let d = crate::Filesystem {
993 file: None,
994 mount_info: crate::MountInfo {
995 dev_id: "28".to_string(),
996 dev_name: "none".to_string(),
997 fs_type: "9p".to_string(),
998 mount_dir: "/usr/lib/wsl/drivers".into(),
999 mount_option: "ro,nosuid,nodev,noatime".to_string(),
1000 mount_root: "/".into(),
1001 remote: false,
1002 dummy: false,
1003 },
1004 usage: crate::table::FsUsage {
1005 blocksize: 4096,
1006 blocks: 244_029_695,
1007 bfree: 125_085_030,
1008 bavail: 125_085_030,
1009 bavail_top_bit_set: false,
1010 files: 999,
1011 ffree: 1_000_000,
1012 },
1013 };
1014
1015 let row = Row::from_filesystem(d, &BlockSize::default());
1016
1017 assert_eq!(row.inodes_used, 0);
1018 }
1019
1020 #[test]
1021 fn test_table_column_width_computation_include_total_row() {
1022 init();
1023 let d1 = crate::Filesystem {
1024 file: None,
1025 mount_info: crate::MountInfo {
1026 dev_id: "28".to_string(),
1027 dev_name: "none".to_string(),
1028 fs_type: "9p".to_string(),
1029 mount_dir: "/usr/lib/wsl/drivers".into(),
1030 mount_option: "ro,nosuid,nodev,noatime".to_string(),
1031 mount_root: "/".into(),
1032 remote: false,
1033 dummy: false,
1034 },
1035 usage: crate::table::FsUsage {
1036 blocksize: 4096,
1037 blocks: 244_029_695,
1038 bfree: 125_085_030,
1039 bavail: 125_085_030,
1040 bavail_top_bit_set: false,
1041 files: 99_999_999_999,
1042 ffree: 999_999,
1043 },
1044 };
1045
1046 let filesystems = vec![d1.clone(), d1];
1047
1048 let mut options = Options {
1049 show_total: true,
1050 columns: vec![
1051 Column::Source,
1052 Column::Itotal,
1053 Column::Iused,
1054 Column::Iavail,
1055 ],
1056 ..Default::default()
1057 };
1058
1059 let table_w_total = Table::new(&options, filesystems.clone());
1060 let mut data_w_total: Vec<u8> = vec![];
1061 table_w_total
1062 .write_to(&mut data_w_total)
1063 .expect("Write error.");
1064 assert_eq!(
1065 String::from_utf8_lossy(&data_w_total),
1066 "Filesystem Inodes IUsed IFree\n\
1067 none 99999999999 99999000000 999999\n\
1068 none 99999999999 99999000000 999999\n\
1069 total 199999999998 199998000000 1999998\n"
1070 );
1071
1072 options.show_total = false;
1073
1074 let table_w_o_total = Table::new(&options, filesystems);
1075 let mut data_w_o_total: Vec<u8> = vec![];
1076 table_w_o_total
1077 .write_to(&mut data_w_o_total)
1078 .expect("Write error.");
1079 assert_eq!(
1080 String::from_utf8_lossy(&data_w_o_total),
1081 "Filesystem Inodes IUsed IFree\n\
1082 none 99999999999 99999000000 999999\n\
1083 none 99999999999 99999000000 999999\n"
1084 );
1085 }
1086
1087 #[cfg(unix)]
1088 #[test]
1089 fn test_table_column_width_non_unicode() {
1090 init();
1091 let bad_unicode_os_str = uucore::os_str_from_bytes(b"/usr/lib/w\xf3l/drivers")
1092 .expect("Only unix platforms can test non-unicode names")
1093 .to_os_string();
1094 let d1 = crate::Filesystem {
1095 file: None,
1096 mount_info: crate::MountInfo {
1097 dev_id: "28".to_string(),
1098 dev_name: "none".to_string(),
1099 fs_type: "9p".to_string(),
1100 mount_dir: bad_unicode_os_str,
1101 mount_option: "ro,nosuid,nodev,noatime".to_string(),
1102 mount_root: "/".into(),
1103 remote: false,
1104 dummy: false,
1105 },
1106 usage: crate::table::FsUsage {
1107 blocksize: 4096,
1108 blocks: 244_029_695,
1109 bfree: 125_085_030,
1110 bavail: 125_085_030,
1111 bavail_top_bit_set: false,
1112 files: 99_999_999_999,
1113 ffree: 999_999,
1114 },
1115 };
1116
1117 let filesystems = vec![d1];
1118
1119 let options = Options {
1120 show_total: false,
1121 columns: vec![Column::Source, Column::Target, Column::Itotal],
1122 ..Default::default()
1123 };
1124
1125 let table = Table::new(&options, filesystems.clone());
1126 let mut data: Vec<u8> = vec![];
1127 table.write_to(&mut data).expect("Write error.");
1128 assert_eq!(
1129 data,
1130 b"Filesystem Mounted on Inodes\n\
1131 none /usr/lib/w\xf3l/drivers 99999999999\n",
1132 "Comparison failed, lossy data for reference:\n{}\n",
1133 String::from_utf8_lossy(&data)
1134 );
1135 }
1136
1137 #[test]
1138 fn test_row_accumulation_u64_overflow() {
1139 init();
1140 let total = u64::MAX as u128;
1141 let used1 = 3000u128;
1142 let used2 = 50000u128;
1143
1144 let mut row1 = Row {
1145 inodes: total,
1146 inodes_used: used1,
1147 inodes_free: total - used1,
1148 ..Default::default()
1149 };
1150
1151 let row2 = Row {
1152 inodes: total,
1153 inodes_used: used2,
1154 inodes_free: total - used2,
1155 ..Default::default()
1156 };
1157
1158 row1 += row2;
1159
1160 assert_eq!(row1.inodes, total * 2);
1161 assert_eq!(row1.inodes_used, used1 + used2);
1162 assert_eq!(row1.inodes_free, total * 2 - used1 - used2);
1163 }
1164}