Skip to content

Commit e1b20ac

Browse files
committed
test(memtrack): add spawn wrapper integration test for static allocator discovery
Verify that CODSPEED_MEMTRACK_BINARIES-style static allocator discovery works for spawned child processes. The test runs a wrapper binary that exec's a child with statically linked jemalloc, confirming: - Without discovery: no allocations are tracked - With discovery: all marker-delimited allocations are captured COD-2347
1 parent a15d442 commit e1b20ac

8 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

crates/memtrack/testdata/spawn_wrapper/Cargo.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[workspace]
2+
3+
[package]
4+
name = "spawn_wrapper"
5+
version = "0.1.0"
6+
edition = "2021"
7+
8+
[[bin]]
9+
name = "wrapper"
10+
path = "src/bin/wrapper.rs"
11+
12+
[[bin]]
13+
name = "alloc_child"
14+
path = "src/bin/alloc_child.rs"
15+
16+
[dependencies]
17+
jemallocator = { version = "0.5" }
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::alloc::GlobalAlloc;
2+
3+
#[global_allocator]
4+
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
5+
6+
fn main() {
7+
std::thread::sleep(std::time::Duration::from_secs(1));
8+
9+
let emit_marker = || unsafe {
10+
let layout = std::alloc::Layout::array::<u8>(0xC0D59EED).unwrap();
11+
let ptr = GLOBAL.alloc(layout);
12+
core::hint::black_box(ptr);
13+
GLOBAL.dealloc(ptr, layout);
14+
};
15+
16+
emit_marker();
17+
18+
// malloc
19+
unsafe {
20+
let layout = std::alloc::Layout::array::<u8>(4321).unwrap();
21+
let ptr = GLOBAL.alloc(layout);
22+
core::hint::black_box(ptr);
23+
GLOBAL.dealloc(ptr, layout);
24+
}
25+
26+
// alloc zeroed
27+
unsafe {
28+
let layout = std::alloc::Layout::array::<u8>(1234).unwrap();
29+
let ptr = GLOBAL.alloc_zeroed(layout);
30+
core::hint::black_box(ptr);
31+
GLOBAL.dealloc(ptr, layout);
32+
}
33+
34+
emit_marker();
35+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use std::process::Command;
2+
3+
fn main() {
4+
let args: Vec<String> = std::env::args().skip(1).collect();
5+
if args.is_empty() {
6+
eprintln!("Usage: wrapper <command> [args...]");
7+
std::process::exit(1);
8+
}
9+
10+
let mut child = Command::new(&args[0])
11+
.args(&args[1..])
12+
.spawn()
13+
.expect("Failed to spawn child process");
14+
15+
let status = child.wait().expect("Failed to wait for child");
16+
std::process::exit(status.code().unwrap_or(1));
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: crates/memtrack/tests/spawn_tests.rs
3+
assertion_line: 90
4+
expression: formatted_events
5+
---
6+
[
7+
"Malloc { size: 4321 }",
8+
"Free",
9+
"Calloc { size: 1234 }",
10+
"Free",
11+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
source: crates/memtrack/tests/spawn_tests.rs
3+
assertion_line: 71
4+
expression: formatted_events
5+
---
6+
[]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#[macro_use]
2+
mod shared;
3+
4+
use memtrack::AllocatorLib;
5+
use memtrack::prelude::*;
6+
use std::path::Path;
7+
use std::process::Command;
8+
9+
fn compile_spawn_binary(name: &str) -> anyhow::Result<std::path::PathBuf> {
10+
shared::compile_rust_binary(Path::new("testdata/spawn_wrapper"), name, &[])
11+
}
12+
13+
/// Without discovering the child's static allocator, the spawned child's
14+
/// jemalloc allocations should NOT be tracked — we expect no marker-delimited events.
15+
#[test_with::env(GITHUB_ACTIONS)]
16+
#[test_log::test]
17+
fn test_spawn_without_static_allocator_discovery() -> Result<(), Box<dyn std::error::Error>> {
18+
let wrapper = compile_spawn_binary("wrapper")?;
19+
let child = compile_spawn_binary("alloc_child")?;
20+
21+
// No allocators attached — static jemalloc in alloc_child won't be found
22+
let mut cmd = Command::new(&wrapper);
23+
cmd.arg(&child);
24+
let (events, thread_handle) = shared::track_command(cmd, &[], false)?;
25+
26+
assert_events_with_marker!("spawn_without_discovery", &events);
27+
28+
thread_handle.join().unwrap();
29+
Ok(())
30+
}
31+
32+
/// With the child binary's static allocator discovered (simulating
33+
/// CODSPEED_MEMTRACK_BINARIES), allocations from the spawned child should be tracked.
34+
#[test_with::env(GITHUB_ACTIONS)]
35+
#[test_log::test]
36+
fn test_spawn_with_static_allocator_discovery() -> Result<(), Box<dyn std::error::Error>> {
37+
let wrapper = compile_spawn_binary("wrapper")?;
38+
let child = compile_spawn_binary("alloc_child")?;
39+
40+
// Simulate what CODSPEED_MEMTRACK_BINARIES does: discover the static jemalloc
41+
let allocator = AllocatorLib::from_path_static(&child)?;
42+
43+
let mut cmd = Command::new(&wrapper);
44+
cmd.arg(&child);
45+
let (events, thread_handle) = shared::track_command(cmd, &[allocator], false)?;
46+
47+
assert_events_with_marker!("spawn_with_discovery", &events);
48+
49+
thread_handle.join().unwrap();
50+
Ok(())
51+
}

0 commit comments

Comments
 (0)