#[cfg(feature = "full_profiling")]
use thag_profiler::{
enable_profiling, end, file_stem_from_path_str,
mem_attribution::{find_profile, ProfileReg},
profile, profiled,
profiling::{set_profile_config, Profile, ProfileType},
safe_alloc,
};
#[cfg(feature = "full_profiling")]
use std::sync::{LazyLock, Mutex};
#[cfg(feature = "full_profiling")]
static TEST_ALLOCATIONS: LazyLock<Mutex<Vec<Vec<u8>>>> = LazyLock::new(|| Mutex::new(Vec::new()));
#[cfg(feature = "full_profiling")]
#[profiled(mem_detail)]
fn mem_attribution_whole_function() {
let data = vec![0u8; 1024];
let data2 = vec![0u8; 2048];
assert_eq!(data.len() + data2.len(), 3072);
let profile = profile.as_ref().unwrap();
let file_name = profile.file_name();
let fn_name = profile.fn_name();
assert_eq!(profile.start_line(), None);
assert_eq!(profile.end_line(), None);
assert!(profile.detailed_memory());
let found_profile = find_profile(file_name, fn_name, 0);
assert!(
found_profile.is_some(),
"Profile should be registered and findable"
);
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_with_sections() {
profile!(test_section_1, mem_detail);
let section_profile = test_section_1.as_ref().unwrap();
let start_line = section_profile.start_line().unwrap();
assert!(start_line > 0);
assert!(section_profile.detailed_memory());
let data = vec![0u8; 4096];
assert_eq!(data.len(), 4096);
end!(test_section_1);
profile!(test_section_2, mem_summary);
let section_profile = test_section_2.as_ref().unwrap();
assert!(!section_profile.detailed_memory());
let data = vec![0u8; 8192];
assert_eq!(data.len(), 8192);
end!(test_section_2);
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_nested_sections() {
profile!(outer_section, mem_detail);
let outer_profile = outer_section.as_ref().unwrap();
let outer_start = outer_profile.start_line().unwrap();
let outer_data = vec![0u8; 1024];
assert_eq!(outer_data.len(), 1024);
profile!(inner_section, mem_detail);
let inner_profile = inner_section.as_ref().unwrap();
let inner_start = inner_profile.start_line().unwrap();
assert!(inner_start > outer_start);
let inner_data = vec![0u8; 2048];
assert_eq!(inner_data.len(), 2048);
end!(inner_section);
let more_outer = vec![0u8; 4096];
assert_eq!(more_outer.len(), 4096);
end!(outer_section);
}
#[cfg(feature = "full_profiling")]
#[allow(clippy::cast_possible_truncation)]
fn mem_attribution_persistent_allocations() {
use std::vec::Vec;
profile!(persistent_allocs, mem_detail);
let mut allocations = Vec::new();
for i in 0..5 {
allocations.push(vec![i as u8; 1024 * (i + 1)]);
}
{
let mut stored_allocs = TEST_ALLOCATIONS.lock().unwrap();
*stored_allocs = allocations;
}
let stored_sum = {
let stored_allocs = TEST_ALLOCATIONS.lock().unwrap();
stored_allocs.iter().map(Vec::len).sum::<usize>()
};
assert_eq!(stored_sum, 1024 + 2048 + 3072 + 4096 + 5120);
end!(persistent_allocs);
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_manual_profile() {
safe_alloc! {
let file_name = file_stem_from_path_str(file!());
let fn_name = "manual_profile_test";
let profile = Profile::new(
Some("manual_section"),
Some(fn_name),
ProfileType::Memory,
false,
true, file!(),
Some(100), Some(200), )
.unwrap();
let found = find_profile(&file_name, profile.fn_name(), 150);
assert!(found.is_some(), "Manual profile should be findable");
if let Some(profile_ref) = found {
assert!(
profile_ref.detailed_memory(),
"Profile should have detailed memory enabled"
);
assert_eq!(profile_ref.name(), "manual_section");
}
let data = vec![0u8; 16384];
assert_eq!(data.len(), 16384);
};
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_registry_functions() {
safe_alloc! {
let file_name = file_stem_from_path_str(file!());
let fn_name = "registry_test";
let profile = Profile::new(
Some("registry_section"),
Some(fn_name),
ProfileType::Memory,
false,
true,
file!(),
Some(300),
Some(400),
)
.unwrap();
let file_names = ProfileReg::get().get_file_names();
assert!(
file_names.contains(&file_name),
"Registry should contain our file name"
);
let fn_name = profile.fn_name();
let in_range = find_profile(&file_name, fn_name, 350);
assert!(in_range.is_some(), "Should find profile for line in range");
let before_range = find_profile(&file_name, fn_name, 250);
assert!(
before_range.is_none(),
"Should not find profile for line before range"
);
let after_range = find_profile(&file_name, fn_name, 450);
assert!(
after_range.is_none(),
"Should not find profile for line after range"
);
};
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_overlapping_profiles() {
let file_name = file_stem_from_path_str(file!());
let fn_name = "overlap_test";
let profile1 = Profile::new(
Some("overlap_first"),
Some(fn_name),
ProfileType::Memory,
false,
true,
file!(),
Some(500),
Some(600),
)
.unwrap();
let _profile2 = Profile::new(
Some("overlap_second"),
Some(fn_name),
ProfileType::Memory,
false,
true,
file!(),
Some(550),
Some(650),
)
.unwrap();
let overlap = find_profile(&file_name, profile1.fn_name(), 575);
assert!(
overlap.is_some(),
"Should find a profile in the overlap region"
);
if let Some(profile_ref) = overlap {
assert_eq!(
profile_ref.name(),
"overlap_first",
"Should find the profile that starts first (overlap_first)"
);
}
}
#[cfg(feature = "full_profiling")]
fn mem_attribution_record_allocation() {
let file_name = file_stem_from_path_str(file!());
let fn_name = format!("{file_name}::mem_attribution_record_allocation");
profile!(record_alloc_section, mem_detail);
let profile = record_alloc_section.as_ref().unwrap();
let start_line = profile.start_line().unwrap();
safe_alloc! {
let valid = ProfileReg::get().record_allocation(
&file_name,
&fn_name,
start_line + 1, 1024, );
assert!(
valid,
"Should have successfully recorded allocation within range"
);
let invalid_file =
ProfileReg::get().record_allocation("nonexistent_file", &fn_name, start_line, 1024);
assert!(
!invalid_file,
"Should not have recorded allocation for non-existent file"
);
let invalid_fn = ProfileReg::get().record_allocation(
&file_name,
"nonexistent_function",
start_line,
1024,
);
assert!(
!invalid_fn,
"Should not have recorded allocation for non-existent function"
);
let out_of_range = ProfileReg::get().record_allocation(
&file_name,
&fn_name,
start_line - 10, 1024,
);
assert!(
!out_of_range,
"Should not have recorded allocation outside line range"
);
};
end!(record_alloc_section);
}
#[test]
#[cfg(feature = "full_profiling")]
#[enable_profiling]
fn test_mem_attribution_full_sequence() {
set_profile_config(
ProfileConfiguration::try_from(vec!["both", "", "announce"].as_slice()).unwrap(),
);
eprintln!("Starting memory attribution tests");
eprintln!("Testing whole function profiling...");
mem_attribution_whole_function();
eprintln!("Testing section profiling...");
mem_attribution_with_sections();
eprintln!("Testing nested sections...");
mem_attribution_nested_sections();
eprintln!("Testing persistent allocations...");
mem_attribution_persistent_allocations();
eprintln!("Testing manual profile creation...");
mem_attribution_manual_profile();
eprintln!("Testing registry functions...");
mem_attribution_registry_functions();
eprintln!("Testing overlapping profiles...");
mem_attribution_overlapping_profiles();
eprintln!("Testing record_allocation function...");
mem_attribution_record_allocation();
eprintln!("Verifying persistent allocations...");
let stored = {
let stored_allocs = TEST_ALLOCATIONS.lock().unwrap();
stored_allocs.iter().map(Vec::len).sum::<usize>()
};
assert_eq!(stored, 1024 + 2048 + 3072 + 4096 + 5120);
eprintln!("All memory attribution tests passed!");
}