mem_dbg 0.4.2

Traits and associated procedural macros to display recursively the layout and memory usage of a value
Documentation
#![cfg(feature = "std")]
#![cfg(feature = "derive")]

use mem_dbg::*;
use std::cell::{Cell, OnceCell, RefCell, UnsafeCell};
use std::sync::OnceLock;

#[test]
fn test_cell_in_struct() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test {
        cell: Cell<i32>,
    }

    let s = Test {
        cell: Cell::new(42),
    };
    let size = s.mem_size(SizeFlags::default());

    assert_eq!(size, std::mem::size_of::<Cell<i32>>());
}

#[test]
fn test_cell_with_different_types() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test {
        cell_u8: Cell<u8>,
        cell_u64: Cell<u64>,
        cell_bool: Cell<bool>,
    }

    let s = Test {
        cell_u8: Cell::new(1),
        cell_u64: Cell::new(100),
        cell_bool: Cell::new(true),
    };

    let size = s.mem_size(SizeFlags::default());
    assert_eq!(size, std::mem::size_of::<Test>());
}

#[test]
fn test_cell_with_memdbg() {
    #[derive(MemSize, MemDbg)]
    #[mem_size(rec)]
    struct Test {
        value: Cell<i32>,
    }

    let s = Test {
        value: Cell::new(123),
    };

    let size = s.mem_size(SizeFlags::default());
    assert_eq!(size, std::mem::size_of::<Test>());

    // Test that mem_dbg works without stack overflow
    let result = s.mem_dbg(DbgFlags::default());
    assert!(result.is_ok());

    for depth in 0..3 {
        let result = s.mem_dbg_depth(depth, DbgFlags::default());
        assert!(result.is_ok());
    }
}

#[test]
fn test_refcell_in_struct() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test {
        ref_cell: RefCell<i32>,
    }

    let s = Test {
        ref_cell: RefCell::new(42),
    };
    let size = s.mem_size(SizeFlags::default());

    assert_eq!(size, std::mem::size_of::<RefCell<i32>>());
}

#[test]
fn test_refcell_with_vec() {
    #[derive(MemSize)]
    struct Test {
        ref_cell: RefCell<Vec<i32>>,
    }

    let s = Test {
        ref_cell: RefCell::new(vec![1, 2, 3, 4, 5]),
    };

    let size = s.mem_size(SizeFlags::default());
    // RefCell adds overhead around the Vec
    assert!(size > std::mem::size_of::<RefCell<Vec<i32>>>());
}

#[test]
fn test_refcell_with_memdbg() {
    #[derive(MemSize, MemDbg)]
    struct Test {
        data: RefCell<String>,
    }

    let s = Test {
        data: RefCell::new("hello".to_string()),
    };

    let size = s.mem_size(SizeFlags::default());
    assert!(size > 0);

    // Test that mem_dbg works without stack overflow
    let result = s.mem_dbg(DbgFlags::default());
    assert!(result.is_ok());

    for depth in 0..3 {
        let result = s.mem_dbg_depth(depth, DbgFlags::default());
        assert!(result.is_ok());
    }
}

#[test]
fn test_unsafe_cell_in_struct() {
    #[derive(MemSize)]
    #[mem_size(rec)]
    struct Test {
        unsafe_cell: UnsafeCell<i32>,
    }

    let s = Test {
        unsafe_cell: UnsafeCell::new(42),
    };
    let size = s.mem_size(SizeFlags::default());

    assert_eq!(size, std::mem::size_of::<UnsafeCell<i32>>());
}

#[test]
fn test_unsafe_cell_with_vec() {
    #[derive(MemSize)]
    struct Test {
        unsafe_cell: UnsafeCell<Vec<i32>>,
    }

    let s = Test {
        unsafe_cell: UnsafeCell::new(vec![1, 2, 3, 4, 5]),
    };

    let size = s.mem_size(SizeFlags::default());
    // UnsafeCell adds overhead around the Vec
    assert!(size > std::mem::size_of::<UnsafeCell<Vec<i32>>>());
}

#[test]
fn test_unsafe_cell_with_memdbg() {
    #[derive(MemSize, MemDbg)]
    #[mem_size(rec)]
    struct Test {
        value: UnsafeCell<u64>,
    }

    let s = Test {
        value: UnsafeCell::new(999),
    };

    let size = s.mem_size(SizeFlags::default());
    assert_eq!(size, std::mem::size_of::<Test>());

    // Test that mem_dbg works without stack overflow
    let result = s.mem_dbg(DbgFlags::default());
    assert!(result.is_ok());

    for depth in 0..3 {
        let result = s.mem_dbg_depth(depth, DbgFlags::default());
        assert!(result.is_ok());
    }
}

#[test]
fn test_oncecell_empty() {
    #[derive(MemSize)]
    struct Test {
        once_cell: OnceCell<String>,
    }

    let s = Test {
        once_cell: OnceCell::new(),
    };
    let size = s.mem_size(SizeFlags::default());

    // Empty OnceCell adds nothing beyond the wrapper's stack footprint.
    assert_eq!(size, std::mem::size_of::<Test>());
}

#[test]
fn test_oncecell_initialized() {
    #[derive(MemSize)]
    struct Test {
        once_cell: OnceCell<String>,
    }

    let once_cell = OnceCell::new();
    once_cell.set("initialized".to_string()).unwrap();

    let s = Test { once_cell };
    let size = s.mem_size(SizeFlags::default());

    // OnceCell with `Some(t)` reports `size_of::<Self>() + (T::mem_size_rec - size_of::<T>())`.
    let expected = std::mem::size_of::<Test>() + "initialized".len();
    assert_eq!(size, expected);
}

#[test]
fn test_oncecell_with_vec() {
    #[derive(MemSize)]
    struct Test {
        once_cell: OnceCell<Vec<i32>>,
    }

    let once_cell = OnceCell::new();
    once_cell.set(vec![1, 2, 3, 4, 5]).unwrap();

    let s = Test { once_cell };
    let size = s.mem_size(SizeFlags::default());

    // OnceCell + populated Vec<i32>: size_of::<Self>() + len * size_of::<i32>().
    let expected = std::mem::size_of::<Test>() + 5 * std::mem::size_of::<i32>();
    assert_eq!(size, expected);
}

#[test]
fn test_oncecell_with_memdbg() {
    #[derive(MemSize, MemDbg)]
    struct Test {
        data: OnceCell<String>,
    }

    let once_cell = OnceCell::new();
    once_cell.set("test data".to_string()).unwrap();

    let s = Test { data: once_cell };

    let size = s.mem_size(SizeFlags::default());
    // Just verify mem_size doesn't panic
    assert!(size >= std::mem::size_of::<Test>());

    // Test that mem_dbg works without stack overflow
    let result = s.mem_dbg(DbgFlags::default());
    assert!(result.is_ok());

    for depth in 0..3 {
        let result = s.mem_dbg_depth(depth, DbgFlags::default());
        assert!(result.is_ok());
    }
}

#[test]
fn test_oncelock_empty() {
    #[derive(MemSize)]
    struct Test {
        once_lock: OnceLock<String>,
    }

    let s = Test {
        once_lock: OnceLock::new(),
    };

    assert_eq!(
        s.mem_size(SizeFlags::default()),
        std::mem::size_of::<Test>()
    );
}

#[test]
fn test_oncelock_initialized() {
    #[derive(MemSize)]
    struct Test {
        once_lock: OnceLock<String>,
    }

    let once_lock = OnceLock::new();
    once_lock.set("initialized".to_string()).unwrap();

    let s = Test { once_lock };
    let expected = std::mem::size_of::<Test>() + "initialized".len();
    assert_eq!(s.mem_size(SizeFlags::default()), expected);
}

#[test]
fn test_oncelock_with_vec() {
    #[derive(MemSize)]
    struct Test {
        once_lock: OnceLock<Vec<i32>>,
    }

    let once_lock = OnceLock::new();
    once_lock.set(vec![1, 2, 3, 4, 5]).unwrap();

    let s = Test { once_lock };
    let expected = std::mem::size_of::<Test>() + 5 * std::mem::size_of::<i32>();
    assert_eq!(s.mem_size(SizeFlags::default()), expected);
}

#[test]
fn test_oncelock_with_memdbg() {
    #[derive(MemSize, MemDbg)]
    struct Test {
        data: OnceLock<String>,
    }

    let once_lock = OnceLock::new();
    once_lock.set("test data".to_string()).unwrap();

    let s = Test { data: once_lock };
    assert!(s.mem_size(SizeFlags::default()) >= std::mem::size_of::<Test>());

    let mut output = String::new();
    s.mem_dbg_depth_on(&mut output, 2, DbgFlags::default())
        .unwrap();
    assert!(output.contains("Test"));
    assert!(output.contains("data"));
}

#[test]
fn test_all_cells_in_struct() {
    #[derive(MemSize, MemDbg)]
    struct Test {
        cell: Cell<i32>,
        ref_cell: RefCell<i32>,
        unsafe_cell: UnsafeCell<i32>,
        once_cell: OnceCell<String>,
    }

    let once_cell = OnceCell::new();
    once_cell.set("initialized".to_string()).unwrap();

    let s = Test {
        cell: Cell::new(100),
        ref_cell: RefCell::new(200),
        unsafe_cell: UnsafeCell::new(300),
        once_cell,
    };

    let size = s.mem_size(SizeFlags::default());
    // Just verify mem_size doesn't panic due to OnceCell issues
    assert!(size >= std::mem::size_of::<Test>());

    // Test that mem_dbg works without stack overflow
    let result = s.mem_dbg(DbgFlags::default());
    assert!(result.is_ok());

    for depth in 0..3 {
        let result = s.mem_dbg_depth(depth, DbgFlags::default());
        assert!(result.is_ok());
    }
}

/// When a `RefCell` is mutably borrowed, `mem_dbg` should print
/// `<mutably borrowed>` instead of inspecting the inner value.
#[test]
#[cfg_attr(miri, ignore)]
fn test_refcell_mem_dbg_during_borrow_mut() {
    #[derive(MemSize, MemDbg)]
    struct Test {
        data: RefCell<Vec<i32>>,
        other: u64,
    }

    let s = Test {
        data: RefCell::new(vec![1, 2, 3]),
        other: 42,
    };

    let _guard = s.data.borrow_mut();

    let mut output = String::new();
    s.mem_dbg_on(&mut output, DbgFlags::default()).unwrap();

    let arch = std::env::consts::ARCH;
    insta::with_settings!({snapshot_suffix => arch}, {
        insta::assert_snapshot!("refcell_mutably_borrowed", output);
    });
}