use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Condvar, Mutex, mpsc};
use std::thread;
use std::time::{Duration, Instant};
use crate::config::Suite;
use crate::discovery::{Fixture, FixtureKind};
use crate::error::Error;
use crate::freshness::{self, FreshnessFailure, FreshnessSnapshot};
use crate::normalize::{self, NormalizationContext};
use crate::snapshot;
use crate::verdict::{CleanupFailure, FixtureResult, FixtureWarning, MalformedSource, Verdict};
#[derive(Debug, Clone)]
pub struct WorkerContext {
pub crate_root: PathBuf,
pub managed_dylib: PathBuf,
pub deps_dir: PathBuf,
pub dylib_crate: String,
pub extra_extern_crates: Vec<String>,
pub dev_deps: Vec<String>,
pub features: Vec<String>,
pub allow_lints: Vec<String>,
pub edition: String,
pub timeout_secs: u32,
pub memory_mb_ceiling: u32,
pub bless: bool,
pub verbose: bool,
pub keep_output: bool,
pub session_temp: PathBuf,
pub extern_paths: HashMap<String, PathBuf>,
pub norm_ctx: NormalizationContext,
pub sysroot_lib_dir: PathBuf,
pub freshness_snapshot: FreshnessSnapshot,
}
impl WorkerContext {
#[allow(clippy::too_many_arguments)]
pub fn new(
crate_root: PathBuf,
managed_dylib: PathBuf,
deps_dir: PathBuf,
dylib_crate: &str,
suite: &Suite,
bless: bool,
verbose: bool,
keep_output: bool,
session_temp: PathBuf,
norm_ctx: NormalizationContext,
sysroot: &Path,
freshness_snapshot: FreshnessSnapshot,
) -> Self {
let extra_extern_crates = suite
.extern_crates
.iter()
.skip(1)
.cloned()
.collect::<Vec<_>>();
let norm_ctx = norm_ctx
.with_extra_substitutions(suite.extra_substitutions.clone())
.with_strip_lines(
suite
.strip_lines
.iter()
.map(|p| p.as_str().to_owned())
.collect(),
)
.with_strip_line_prefixes(
suite
.strip_line_prefixes
.iter()
.map(|p| p.as_str().to_owned())
.collect(),
);
Self {
crate_root,
managed_dylib,
deps_dir: deps_dir.clone(),
dylib_crate: dylib_crate.to_string(),
extra_extern_crates,
dev_deps: suite.dev_deps.clone(),
features: suite.features.clone(),
allow_lints: suite.allow_lints.clone(),
edition: suite.edition.clone(),
timeout_secs: suite.fixture_timeout_secs,
memory_mb_ceiling: suite.per_fixture_memory_mb,
bless,
verbose,
keep_output,
session_temp,
extern_paths: HashMap::new(),
norm_ctx,
sysroot_lib_dir: sysroot.join("lib"),
freshness_snapshot,
}
}
}
#[derive(Debug)]
pub struct DispatchOutcome {
pub results: Vec<FixtureResult>,
pub freshness_failure: Option<FreshnessFailure>,
}
#[derive(Debug)]
struct ParallelismGate {
inner: Mutex<GateInner>,
cv: Condvar,
}
#[derive(Debug)]
struct GateInner {
available: usize,
cap: usize,
closed: bool,
}
impl ParallelismGate {
fn new(n: usize) -> Self {
let n = n.max(1);
Self {
inner: Mutex::new(GateInner {
available: n,
cap: n,
closed: false,
}),
cv: Condvar::new(),
}
}
fn acquire(&self) -> bool {
let mut g = self.inner.lock().unwrap();
loop {
if g.closed {
return false;
}
if g.available > 0 {
g.available -= 1;
return true;
}
g = self.cv.wait(g).unwrap();
}
}
fn release(&self) {
let mut g = self.inner.lock().unwrap();
if g.available < g.cap {
g.available += 1;
self.cv.notify_one();
}
}
fn reduce(&self) -> usize {
let mut g = self.inner.lock().unwrap();
if g.cap > 1 {
g.cap -= 1;
if g.available > g.cap {
g.available = g.cap;
}
self.cv.notify_all();
}
g.cap
}
fn close(&self) {
let mut g = self.inner.lock().unwrap();
g.closed = true;
self.cv.notify_all();
}
#[cfg(test)]
fn current_cap(&self) -> usize {
self.inner.lock().unwrap().cap
}
}
pub fn resolve_extern_paths(
deps_dir: &Path,
crate_names: &[String],
) -> Result<HashMap<String, PathBuf>, Error> {
let mut out = HashMap::new();
if crate_names.is_empty() {
return Ok(out);
}
let entries = std::fs::read_dir(deps_dir).map_err(|e| {
Error::io(
e,
"reading deps dir for extern resolution",
Some(deps_dir.to_path_buf()),
)
})?;
let mut all_files: Vec<PathBuf> = Vec::new();
for entry in entries {
let entry =
entry.map_err(|e| Error::io(e, "iterating deps dir", Some(deps_dir.to_path_buf())))?;
let p = entry.path();
if p.is_file() {
all_files.push(p);
}
}
for name in crate_names {
let normalized = name.replace('-', "_");
let mut candidates: Vec<PathBuf> = Vec::new();
for f in &all_files {
let stem = match f.file_stem().and_then(|s| s.to_str()) {
Some(s) => s,
None => continue,
};
let ext = f.extension().and_then(|s| s.to_str()).unwrap_or("");
if ext != "rlib" && ext != "so" && ext != "dylib" && ext != "dll" {
continue;
}
let with_lib = format!("lib{normalized}-");
let with_lib_no_hash = format!("lib{normalized}");
if stem.starts_with(&with_lib) || stem == with_lib_no_hash {
candidates.push(f.clone());
continue;
}
if stem.starts_with(&format!("{normalized}-")) || stem == normalized {
candidates.push(f.clone());
}
}
candidates.sort_by(|a, b| {
std::fs::metadata(b)
.and_then(|m| m.modified())
.ok()
.cmp(&std::fs::metadata(a).and_then(|m| m.modified()).ok())
});
if let Some(best) = candidates.into_iter().next() {
out.insert(name.clone(), best);
}
}
Ok(out)
}
pub fn dispatch_serial(
fixtures: &[Fixture],
ctx: &WorkerContext,
progress: impl Fn(&FixtureResult),
) -> DispatchOutcome {
let mut results: Vec<FixtureResult> = Vec::with_capacity(fixtures.len());
for fx in fixtures {
if let Err(failure) = freshness::check(&ctx.freshness_snapshot) {
return DispatchOutcome {
results,
freshness_failure: Some(failure),
};
}
let outcome = run_one(fx, ctx);
progress(&outcome.result);
results.push(outcome.result);
}
DispatchOutcome {
results,
freshness_failure: None,
}
}
pub fn dispatch_pool(
fixtures: &[Fixture],
ctx: &WorkerContext,
parallelism: usize,
progress: impl Fn(&FixtureResult) + Send + Sync + 'static,
) -> DispatchOutcome {
if parallelism <= 1 {
return dispatch_serial(fixtures, ctx, |r| progress(r));
}
let (tx, rx) = mpsc::channel::<FixtureResult>();
let queue: Arc<Mutex<std::collections::VecDeque<Fixture>>> =
Arc::new(Mutex::new(fixtures.iter().cloned().collect()));
let freshness_failure: Arc<Mutex<Option<FreshnessFailure>>> = Arc::new(Mutex::new(None));
let gate = Arc::new(ParallelismGate::new(parallelism));
let ctx = Arc::new(ctx.clone());
let mut handles = Vec::with_capacity(parallelism);
for _ in 0..parallelism {
let q = Arc::clone(&queue);
let c = Arc::clone(&ctx);
let t = tx.clone();
let ff = Arc::clone(&freshness_failure);
let g = Arc::clone(&gate);
let h = thread::spawn(move || {
loop {
if !g.acquire() {
break;
}
if ff.lock().unwrap().is_some() {
g.release();
break;
}
if let Err(failure) = freshness::check(&c.freshness_snapshot) {
let mut slot = ff.lock().unwrap();
if slot.is_none() {
*slot = Some(failure);
}
q.lock().unwrap().clear();
g.release();
break;
}
let next = {
let mut g_q = q.lock().unwrap();
g_q.pop_front()
};
match next {
Some(fx) => {
let outcome = run_one(&fx, &c);
if outcome.harness_oom_observed {
g.reduce();
}
if t.send(outcome.result).is_err() {
g.release();
break;
}
g.release();
}
None => {
g.release();
break;
}
}
}
});
handles.push(h);
}
drop(tx);
let mut results: Vec<FixtureResult> = Vec::with_capacity(fixtures.len());
for r in rx {
progress(&r);
results.push(r);
}
gate.close();
for h in handles {
let _ = h.join();
}
results.sort_by(|a, b| a.relative_path.cmp(&b.relative_path));
let failure = freshness_failure.lock().unwrap().take();
DispatchOutcome {
results,
freshness_failure: failure,
}
}
struct RunOneOutcome {
result: FixtureResult,
harness_oom_observed: bool,
}
fn run_one(fx: &Fixture, ctx: &WorkerContext) -> RunOneOutcome {
let started = Instant::now();
let workdir = ctx.session_temp.join(fixture_workdir_name(fx));
if let Err(e) = std::fs::create_dir_all(&workdir) {
return RunOneOutcome {
result: FixtureResult {
relative_path: fx.relative_path.clone(),
verdict: Verdict::WorkerCrashed {
cause: format!("could not create workdir: {e}"),
},
cleanup_failure: None,
wall_ms: 0,
warning: None,
},
harness_oom_observed: false,
};
}
let outcome = spawn_and_monitor(fx, ctx, &workdir, false);
let mut wall_ms = started.elapsed().as_millis() as u64;
let mut harness_oom_observed = false;
let (mut verdict, mut warning) = match outcome.kind {
MonitorKind::Exited { ok, stderr } => classify_exit(fx, ctx, ok, &stderr),
MonitorKind::HarnessKilledMemory => {
harness_oom_observed = true;
let _ = std::fs::remove_dir_all(&workdir);
let _ = std::fs::create_dir_all(&workdir);
let retry = spawn_and_monitor(fx, ctx, &workdir, true);
wall_ms = started.elapsed().as_millis() as u64;
match retry.kind {
MonitorKind::HarnessKilledMemory => (Verdict::MemoryExhausted, None),
MonitorKind::Exited { ok, stderr } => classify_exit(fx, ctx, ok, &stderr),
MonitorKind::Timeout => (Verdict::Timeout, None),
MonitorKind::ExternalKill { cause } => (Verdict::WorkerCrashed { cause }, None),
}
}
MonitorKind::Timeout => (Verdict::Timeout, None),
MonitorKind::ExternalKill { cause } => (Verdict::WorkerCrashed { cause }, None),
};
if ctx.bless {
if let Verdict::SnapshotDiff { .. } = &verdict
&& let Some(actual) = compute_actual_normalized(fx, ctx)
&& let Ok(p) = snapshot::write(&fx.path, &actual)
{
verdict = Verdict::Blessed { snapshot_path: p };
}
if let Verdict::SnapshotMissing { actual } = &verdict
&& let Ok(p) = snapshot::write(&fx.path, actual)
{
verdict = Verdict::Blessed { snapshot_path: p };
warning = None;
}
}
let cleanup_failure = if ctx.keep_output {
None
} else {
match std::fs::remove_dir_all(&workdir) {
Ok(()) => None,
Err(e) => Some(CleanupFailure {
path: workdir.clone(),
message: e.to_string(),
}),
}
};
RunOneOutcome {
result: FixtureResult {
relative_path: fx.relative_path.clone(),
verdict,
cleanup_failure,
wall_ms,
warning,
},
harness_oom_observed,
}
}
fn compute_actual_normalized(fx: &Fixture, ctx: &WorkerContext) -> Option<String> {
let workdir = ctx.session_temp.join(fixture_workdir_name(fx));
let _ = std::fs::create_dir_all(&workdir);
let outcome = spawn_and_monitor(fx, ctx, &workdir, false);
if let MonitorKind::Exited { stderr, .. } = outcome.kind {
let s = std::str::from_utf8(&stderr).ok()?;
Some(normalize_stderr(s, fx, ctx))
} else {
None
}
}
fn fixture_workdir_name(fx: &Fixture) -> String {
let mut s = String::with_capacity(fx.relative_path.len());
for ch in fx.relative_path.chars() {
match ch {
'/' | '\\' | ':' => s.push('_'),
_ => s.push(ch),
}
}
s
}
fn classify_exit(
fx: &Fixture,
ctx: &WorkerContext,
ok: bool,
stderr_bytes: &[u8],
) -> (Verdict, Option<FixtureWarning>) {
let stderr = match std::str::from_utf8(stderr_bytes) {
Ok(s) => s,
Err(e) => {
return (
Verdict::MalformedDiagnostic {
byte_offset: e.valid_up_to(),
source: MalformedSource::RustcRendered,
},
None,
);
}
};
let normalized = normalize_stderr(stderr, fx, ctx);
match (fx.kind, ok) {
(FixtureKind::CompilePass, true) => (Verdict::Ok, None),
(FixtureKind::CompilePass, false) => {
(Verdict::ExpectedPassButFailed { stderr: normalized }, None)
}
(FixtureKind::CompileFail, true) => (Verdict::ExpectedFailButPassed, None),
(FixtureKind::CompileFail, false) => {
match snapshot::try_read(&fx.path) {
Ok(snapshot::ReadOutcome::Found(expected)) => {
let expected_lines = expected.lines().count();
let actual_lines = normalized.lines().count();
let result = crate::diff::unified_diff(&expected, &normalized);
let warning = match &result {
crate::diff::DiffResult::Diff { warn: true, .. } => {
Some(FixtureWarning::LargeSnapshot {
expected_lines,
actual_lines,
})
}
_ => None,
};
let verdict = match crate::diff::diff_to_verdict(result) {
Some(v) => v,
None => Verdict::Ok,
};
(verdict, warning)
}
Ok(snapshot::ReadOutcome::Missing) => {
(Verdict::SnapshotMissing { actual: normalized }, None)
}
Ok(snapshot::ReadOutcome::Malformed { byte_offset, .. }) => (
Verdict::MalformedDiagnostic {
byte_offset,
source: MalformedSource::Snapshot,
},
None,
),
Err(_) => (
Verdict::MalformedDiagnostic {
byte_offset: 0,
source: MalformedSource::Snapshot,
},
None,
),
}
}
}
}
fn normalize_stderr(json_stderr: &str, fx: &Fixture, ctx: &WorkerContext) -> String {
let rendered = render_json_diagnostics(json_stderr);
let fixture_dir = fx.path.parent().unwrap_or(Path::new("."));
normalize::normalize(&rendered, &ctx.norm_ctx, fixture_dir)
}
fn render_json_diagnostics(stderr: &str) -> String {
let mut out = String::with_capacity(stderr.len());
for line in stderr.lines() {
if !line.starts_with('{') {
out.push_str(line);
out.push('\n');
continue;
}
if let Some(rendered) = extract_rendered(line) {
out.push_str(&rendered);
if !rendered.ends_with('\n') {
out.push('\n');
}
}
}
out
}
fn extract_rendered(line: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_str(line).ok()?;
if let Some(s) = v
.get("message")
.and_then(|m| m.get("rendered"))
.and_then(serde_json::Value::as_str)
{
return Some(s.to_owned());
}
v.get("rendered")
.and_then(serde_json::Value::as_str)
.map(str::to_owned)
}
struct MonitorOutcome {
kind: MonitorKind,
}
enum MonitorKind {
Exited {
ok: bool,
stderr: Vec<u8>,
},
HarnessKilledMemory,
Timeout,
ExternalKill {
cause: String,
},
}
fn apply_rustc_env(cmd: &mut Command, ctx: &WorkerContext) {
let host_lib = ctx
.sysroot_lib_dir
.join(format!("rustlib/{}/lib", host_triple_or_default()));
let mut ld_paths = std::env::var_os("LD_LIBRARY_PATH")
.map(|s| std::env::split_paths(&s).collect::<Vec<_>>())
.unwrap_or_default();
ld_paths.insert(0, host_lib);
ld_paths.insert(0, ctx.deps_dir.clone());
if let Some(parent) = ctx.managed_dylib.parent() {
ld_paths.insert(0, parent.to_path_buf());
}
if let Ok(joined) = std::env::join_paths(ld_paths) {
cmd.env("LD_LIBRARY_PATH", joined);
}
cmd.env("CARGO_MANIFEST_DIR", &ctx.crate_root);
}
fn apply_feature_cfgs(cmd: &mut Command, features: &[String]) {
for feat in features {
cmd.arg("--cfg").arg(format!("feature=\"{feat}\""));
}
}
fn apply_allow_lints(cmd: &mut Command, lints: &[String]) {
for lint in lints {
cmd.arg("-A").arg(lint);
}
}
fn spawn_and_monitor(
fx: &Fixture,
ctx: &WorkerContext,
workdir: &Path,
_is_retry: bool,
) -> MonitorOutcome {
let bin_path = workdir.join(&fx.stem);
let mut cmd = Command::new("rustc");
cmd.arg("--edition")
.arg(&ctx.edition)
.arg("--crate-type=bin")
.arg("--error-format=json")
.arg("-C")
.arg("prefer-dynamic")
.arg("-o")
.arg(&bin_path)
.arg("-L")
.arg(format!("dependency={}", ctx.deps_dir.display()))
.arg("-L")
.arg(format!("native={}", ctx.sysroot_lib_dir.display()))
.arg("--extern")
.arg(format!(
"{}={}",
ctx.dylib_crate.replace('-', "_"),
ctx.managed_dylib.display()
));
for name in &ctx.extra_extern_crates {
if let Some(path) = ctx.extern_paths.get(name) {
cmd.arg("--extern")
.arg(format!("{}={}", name.replace('-', "_"), path.display()));
}
}
for name in &ctx.dev_deps {
if let Some(path) = ctx.extern_paths.get(name) {
cmd.arg("--extern")
.arg(format!("{}={}", name.replace('-', "_"), path.display()));
}
}
apply_feature_cfgs(&mut cmd, &ctx.features);
apply_allow_lints(&mut cmd, &ctx.allow_lints);
cmd.arg(&fx.path);
cmd.stderr(Stdio::piped());
cmd.stdout(Stdio::null());
apply_rustc_env(&mut cmd, ctx);
if ctx.verbose {
eprintln!("lihaaf: rustc invocation:\n {cmd:?}");
}
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
return MonitorOutcome {
kind: MonitorKind::ExternalKill {
cause: format!("could not spawn rustc: {e}"),
},
};
}
};
let pid = child.id();
let stderr_handle = child.stderr.take();
let stderr_join: thread::JoinHandle<Vec<u8>> = thread::spawn(move || {
if let Some(mut h) = stderr_handle {
let mut buf = Vec::new();
let _ = std::io::Read::read_to_end(&mut h, &mut buf);
buf
} else {
Vec::new()
}
});
let timeout = Duration::from_secs(ctx.timeout_secs as u64);
let ceiling_kib = (ctx.memory_mb_ceiling as u64) * 1024;
let start = Instant::now();
let mut harness_killed_memory = false;
loop {
match child.try_wait() {
Ok(Some(status)) => {
let stderr = stderr_join.join().unwrap_or_default();
if harness_killed_memory {
return MonitorOutcome {
kind: MonitorKind::HarnessKilledMemory,
};
}
if status.success() {
return MonitorOutcome {
kind: MonitorKind::Exited { ok: true, stderr },
};
}
#[cfg(unix)]
let signal = std::os::unix::process::ExitStatusExt::signal(&status);
#[cfg(not(unix))]
let signal: Option<i32> = None;
match (status.code(), signal) {
(Some(1), _) => {
return MonitorOutcome {
kind: MonitorKind::Exited { ok: false, stderr },
};
}
(Some(0), _) => {
return MonitorOutcome {
kind: MonitorKind::Exited { ok: true, stderr },
};
}
(Some(code), _) => {
return MonitorOutcome {
kind: MonitorKind::ExternalKill {
cause: format!("exit code: {code}"),
},
};
}
(None, Some(sig)) => {
return MonitorOutcome {
kind: MonitorKind::ExternalKill {
cause: format!("signal: {sig}"),
},
};
}
(None, None) => {
return MonitorOutcome {
kind: MonitorKind::ExternalKill {
cause: "process exited without code or signal".into(),
},
};
}
}
}
Ok(None) => {
if start.elapsed() >= timeout {
terminate(&mut child);
let _ = stderr_join.join();
return MonitorOutcome {
kind: MonitorKind::Timeout,
};
}
if let Some(rss_kib) = sample_rss_kib(pid)
&& rss_kib > ceiling_kib
{
harness_killed_memory = true;
terminate(&mut child);
}
thread::sleep(Duration::from_millis(100));
}
Err(e) => {
return MonitorOutcome {
kind: MonitorKind::ExternalKill {
cause: format!("wait failed: {e}"),
},
};
}
}
}
}
fn host_triple_or_default() -> &'static str {
if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
"x86_64-unknown-linux-gnu"
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
"aarch64-unknown-linux-gnu"
} else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
"x86_64-apple-darwin"
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
"aarch64-apple-darwin"
} else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
"x86_64-pc-windows-msvc"
} else {
"x86_64-unknown-linux-gnu"
}
}
fn terminate(child: &mut Child) {
#[cfg(unix)]
{
let pid = child.id() as i32;
unsafe {
libc_kill(pid, 15);
}
let deadline = Instant::now() + Duration::from_secs(2);
while Instant::now() < deadline {
match child.try_wait() {
Ok(Some(_)) => return,
_ => thread::sleep(Duration::from_millis(50)),
}
}
let _ = child.kill();
}
#[cfg(not(unix))]
{
let _ = child.kill();
}
}
#[cfg(unix)]
unsafe fn libc_kill(pid: i32, sig: i32) {
unsafe { libc::kill(pid, sig) };
}
fn sample_rss_kib(pid: u32) -> Option<u64> {
#[cfg(target_os = "linux")]
{
sample_rss_kib_linux(pid)
}
#[cfg(target_os = "macos")]
{
sample_rss_kib_macos(pid)
}
#[cfg(windows)]
{
sample_rss_kib_windows(pid)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
{
let _ = pid;
None
}
}
#[cfg(target_os = "linux")]
fn sample_rss_kib_linux(pid: u32) -> Option<u64> {
let path = format!("/proc/{pid}/statm");
let text = std::fs::read_to_string(&path).ok()?;
let mut tokens = text.split_whitespace();
tokens.next()?; let resident_pages: u64 = tokens.next()?.parse().ok()?;
let page_kib = page_size_kib();
Some(resident_pages * page_kib)
}
#[cfg(target_os = "linux")]
fn page_size_kib() -> u64 {
let raw = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if raw <= 0 { 4 } else { (raw as u64) / 1024 }
}
#[cfg(target_os = "macos")]
fn sample_rss_kib_macos(pid: u32) -> Option<u64> {
use std::mem::{MaybeUninit, size_of};
let mut info: MaybeUninit<libc::proc_taskinfo> = MaybeUninit::zeroed();
let size = size_of::<libc::proc_taskinfo>() as i32;
let rc = unsafe {
libc::proc_pidinfo(
pid as i32,
libc::PROC_PIDTASKINFO,
0,
info.as_mut_ptr().cast(),
size,
)
};
if rc != size {
return None;
}
let info = unsafe { info.assume_init() };
Some(info.pti_resident_size / 1024)
}
#[cfg(windows)]
fn sample_rss_kib_windows(pid: u32) -> Option<u64> {
use std::mem::{MaybeUninit, size_of};
use windows_sys::Win32::Foundation::{CloseHandle, FALSE};
use windows_sys::Win32::System::ProcessStatus::{
GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS,
};
use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION};
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid) };
if handle.is_null() {
return None;
}
let mut counters: MaybeUninit<PROCESS_MEMORY_COUNTERS> = MaybeUninit::zeroed();
let size = size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
unsafe {
std::ptr::addr_of_mut!((*counters.as_mut_ptr()).cb).write(size);
}
let rc = unsafe { GetProcessMemoryInfo(handle, counters.as_mut_ptr(), size) };
let _ = unsafe { CloseHandle(handle) };
if rc == 0 {
return None;
}
let counters = unsafe { counters.assume_init() };
Some((counters.WorkingSetSize as u64) / 1024)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_rendered_basic_message() {
let line = r#"{"reason":"compiler-message","message":{"rendered":"error: oops\n --> foo.rs:1:1\n"}}"#;
let r = extract_rendered(line).unwrap();
assert!(r.starts_with("error: oops"));
}
#[test]
fn extract_rendered_handles_escape_sequences() {
let line = r#"{"rendered":"a\\nb\nc"}"#;
let r = extract_rendered(line).unwrap();
assert_eq!(r, "a\\nb\nc");
}
#[test]
fn extract_rendered_returns_none_on_null() {
let line = r#"{"rendered":null}"#;
assert!(extract_rendered(line).is_none());
}
#[test]
fn extract_rendered_returns_none_when_field_absent() {
let line = r#"{"message":"no rendered key"}"#;
assert!(extract_rendered(line).is_none());
}
#[test]
fn extract_rendered_preserves_parent_when_child_rendered_is_null_bare_rustc() {
let line = concat!(
r#"{"$message_type":"diagnostic",""#,
r#"message":"not all trait items implemented","#,
r#""code":{"code":"E0046","explanation":null},"#,
r#""level":"error","#,
r#""spans":[],"#,
r#""children":[{"message":"`bar` from trait","code":null,"#,
r#""level":"help","spans":[],"children":[],"rendered":null}],"#,
r#""rendered":"error[E0046]: not all trait items implemented\n --> a.rs:1:1\n"}"#,
);
let r = extract_rendered(line)
.expect("parent rendered must survive a null child rendered field");
assert!(
r.contains("E0046"),
"expected parent E0046 rendered text, got: {r:?}",
);
assert!(
r.contains("not all trait items implemented"),
"expected parent diagnostic body, got: {r:?}",
);
}
#[test]
fn extract_rendered_preserves_parent_when_child_rendered_is_null_cargo_wrap() {
let line = concat!(
r#"{"reason":"compiler-message","message":{"#,
r#""message":"no method named `foo` found","#,
r#""children":[{"message":"consider using ...","level":"help","rendered":null}],"#,
r#""rendered":"error[E0599]: no method named `foo` found\n"}}"#,
);
let r = extract_rendered(line)
.expect("cargo-wrapped parent rendered must survive a null child rendered field");
assert!(
r.contains("E0599"),
"expected parent E0599 rendered text, got: {r:?}",
);
}
#[test]
fn render_json_diagnostics_does_not_reduce_primary_to_abort_only_footer() {
let stderr = concat!(
r#"{"$message_type":"diagnostic","message":"primary","#,
r#""code":{"code":"E0046","explanation":null},"#,
r#""level":"error","children":[{"#,
r#""message":"help","code":null,"level":"help",""#,
r#"spans":[],"children":[],"rendered":null}],"#,
r#""rendered":"error[E0046]: not all trait items implemented\n --> a.rs:1:1\n"}"#,
"\n",
r#"{"$message_type":"diagnostic","message":"aborting due to 1 previous error","#,
r#""level":"error","rendered":"error: aborting due to 1 previous error\n"}"#,
"\n",
r#"{"$message_type":"diagnostic","message":"For more information about this error, try `rustc --explain E0046`.","#,
r#""level":"failure-note","rendered":"For more information about this error, try `rustc --explain E0046`.\n"}"#,
"\n",
);
let out = render_json_diagnostics(stderr);
assert!(
out.contains("E0046"),
"primary E0046 diagnostic must be preserved, got: {out:?}",
);
assert!(
out.contains("not all trait items implemented"),
"primary rendered body must be preserved, got: {out:?}",
);
let abort_only_footer = "error: aborting due to 1 previous error\nFor more information about this error, try `rustc --explain E0046`.\n";
assert_ne!(
out, abort_only_footer,
"rendered output must not be reduced to the abort+explain footer",
);
}
#[test]
fn render_json_diagnostics_concatenates_in_order() {
let stderr = "\
{\"rendered\":\"error: alpha\\n\"}
{\"rendered\":\"error: beta\\n\"}
plain text line
";
let out = render_json_diagnostics(stderr);
assert!(out.contains("error: alpha"));
assert!(out.contains("error: beta"));
assert!(out.contains("plain text line"));
}
#[test]
fn parallelism_gate_starts_at_cap() {
let g = ParallelismGate::new(4);
assert_eq!(g.current_cap(), 4);
}
#[test]
fn parallelism_gate_reduce_drops_cap_with_floor_one() {
let g = ParallelismGate::new(4);
assert_eq!(g.reduce(), 3);
assert_eq!(g.reduce(), 2);
assert_eq!(g.reduce(), 1);
assert_eq!(g.reduce(), 1);
assert_eq!(g.current_cap(), 1);
}
#[test]
fn parallelism_gate_acquire_release_round_trips() {
let g = Arc::new(ParallelismGate::new(2));
assert!(g.acquire());
assert!(g.acquire());
g.release();
g.release();
assert!(g.acquire());
assert!(g.acquire());
}
#[test]
fn parallelism_gate_close_unblocks_waiters() {
let g = Arc::new(ParallelismGate::new(1));
assert!(g.acquire()); let g2 = Arc::clone(&g);
let waiter = thread::spawn(move || g2.acquire());
thread::sleep(Duration::from_millis(50));
g.close();
assert!(!waiter.join().unwrap());
}
#[test]
fn parallelism_gate_reduce_burns_in_flight_permit() {
let g = ParallelismGate::new(2);
assert!(g.acquire());
assert!(g.acquire());
let new_cap = g.reduce();
assert_eq!(new_cap, 1);
g.release();
g.release();
assert!(g.acquire());
assert_eq!(g.current_cap(), 1);
}
fn unit_test_ctx() -> WorkerContext {
WorkerContext {
crate_root: PathBuf::from("/p"),
managed_dylib: PathBuf::from("/p/target/lihaaf/lib.so"),
deps_dir: PathBuf::from("/p/target/release/deps"),
dylib_crate: "consumer".into(),
extra_extern_crates: vec![],
dev_deps: vec![],
features: vec![],
allow_lints: vec![],
edition: "2021".into(),
timeout_secs: 90,
memory_mb_ceiling: 1024,
bless: false,
verbose: false,
keep_output: false,
session_temp: PathBuf::from("/tmp/lihaaf-session"),
extern_paths: HashMap::new(),
norm_ctx: NormalizationContext {
workspace_root: PathBuf::from("/p"),
sysroot: PathBuf::from("/r"),
cargo_registry: None,
compat_short_cargo: false,
extra_substitutions: Vec::new(),
strip_lines: Vec::new(),
strip_line_prefixes: Vec::new(),
},
sysroot_lib_dir: PathBuf::from("/r/lib"),
freshness_snapshot: FreshnessSnapshot {
managed_dylib_path: PathBuf::from("/p/target/lihaaf/lib.so"),
original_mtime_unix_secs: 0,
original_sha256: "0".repeat(64),
original_toolchain: crate::toolchain::Toolchain {
release_line: "rustc 1.95.0 (test 2026-01-01)".into(),
release: "1.95.0".into(),
host: "x86_64-unknown-linux-gnu".into(),
commit_hash: "0000000000000000000000000000000000000000".into(),
sysroot: PathBuf::from("/r"),
},
},
}
}
#[test]
fn classify_exit_emits_malformed_diagnostic_with_correct_offset() {
let ctx = unit_test_ctx();
let fx = Fixture {
path: PathBuf::from("/p/tests/lihaaf/compile_fail/foo.rs"),
relative_path: "tests/lihaaf/compile_fail/foo.rs".into(),
stem: "foo".into(),
kind: FixtureKind::CompileFail,
};
let mut bytes: Vec<u8> = b"abc".to_vec();
bytes.push(0xFE);
bytes.extend_from_slice(b"def");
let (verdict, warning) = classify_exit(&fx, &ctx, false, &bytes);
assert!(warning.is_none(), "malformed input has no warning");
match verdict {
Verdict::MalformedDiagnostic {
byte_offset,
source,
} => {
assert_eq!(byte_offset, 3, "first invalid byte is at offset 3");
assert!(matches!(source, MalformedSource::RustcRendered));
}
other => panic!("expected MalformedDiagnostic, got {other:?}"),
}
}
#[test]
fn classify_exit_malformed_at_offset_zero_when_first_byte_invalid() {
let ctx = unit_test_ctx();
let fx = Fixture {
path: PathBuf::from("/p/tests/lihaaf/compile_fail/x.rs"),
relative_path: "tests/lihaaf/compile_fail/x.rs".into(),
stem: "x".into(),
kind: FixtureKind::CompileFail,
};
let bytes = vec![0xFE];
let (verdict, _) = classify_exit(&fx, &ctx, false, &bytes);
match verdict {
Verdict::MalformedDiagnostic { byte_offset, .. } => assert_eq!(byte_offset, 0),
other => panic!("expected MalformedDiagnostic, got {other:?}"),
}
}
#[test]
fn fixture_workdir_name_replaces_separators() {
let fx = Fixture {
path: PathBuf::from("/p/tests/lihaaf/compile_fail/foo.rs"),
relative_path: "tests/lihaaf/compile_fail/foo.rs".into(),
stem: "foo".into(),
kind: FixtureKind::CompileFail,
};
let n = fixture_workdir_name(&fx);
assert_eq!(n, "tests_lihaaf_compile_fail_foo.rs");
}
#[test]
fn sample_rss_returns_some_for_self() {
let pid = std::process::id();
let kib = sample_rss_kib(pid);
#[cfg(any(target_os = "linux", target_os = "macos", windows))]
{
let rss = kib.expect("expected Some(rss) on supported platforms (Linux/macOS/Windows)");
assert!(rss > 0, "self RSS must be > 0; got {rss}");
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
assert!(kib.is_none(), "unsupported platforms still return None");
}
#[test]
fn sample_rss_returns_none_for_invalid_pid() {
assert_eq!(sample_rss_kib(u32::MAX), None);
}
#[test]
fn fixture_rustc_cmd_carries_cargo_manifest_dir() {
let ctx = unit_test_ctx();
let mut cmd = Command::new("rustc");
apply_rustc_env(&mut cmd, &ctx);
let envs: Vec<(std::ffi::OsString, Option<std::ffi::OsString>)> = cmd
.get_envs()
.map(|(k, v)| (k.to_os_string(), v.map(|s| s.to_os_string())))
.collect();
let manifest_dir = envs
.iter()
.find(|(k, _)| k == "CARGO_MANIFEST_DIR")
.and_then(|(_, v)| v.as_ref())
.expect("CARGO_MANIFEST_DIR must be set on every per-fixture rustc command");
assert_eq!(
std::path::Path::new(manifest_dir),
ctx.crate_root.as_path(),
"CARGO_MANIFEST_DIR must equal the consumer crate root"
);
let ld_present = envs
.iter()
.any(|(k, v)| k == "LD_LIBRARY_PATH" && v.is_some());
assert!(
ld_present,
"LD_LIBRARY_PATH must still be set (regression guard)"
);
}
#[test]
fn apply_rustc_env_overwrites_inherited_cargo_manifest_dir() {
let ctx = unit_test_ctx();
let mut cmd = Command::new("rustc");
cmd.env("CARGO_MANIFEST_DIR", "/the/wrong/crate");
apply_rustc_env(&mut cmd, &ctx);
let manifest_dir = cmd
.get_envs()
.find_map(|(k, v)| (k == "CARGO_MANIFEST_DIR").then_some(v))
.flatten()
.expect("CARGO_MANIFEST_DIR must remain set after apply_rustc_env");
assert_eq!(
std::path::Path::new(manifest_dir),
ctx.crate_root.as_path(),
"apply_rustc_env must overwrite a stale CARGO_MANIFEST_DIR with the consumer crate root"
);
}
#[test]
fn apply_rustc_env_does_not_lock_out_later_overrides() {
let ctx = unit_test_ctx();
let mut cmd = Command::new("rustc");
apply_rustc_env(&mut cmd, &ctx);
let override_path = std::path::PathBuf::from("/tmp/lihaaf-override");
cmd.env("CARGO_MANIFEST_DIR", &override_path);
let manifest_dir = cmd
.get_envs()
.find_map(|(k, v)| (k == "CARGO_MANIFEST_DIR").then_some(v))
.flatten()
.expect("CARGO_MANIFEST_DIR still present after override");
assert_eq!(std::path::Path::new(manifest_dir), override_path.as_path());
}
#[test]
fn apply_feature_cfgs_emits_quoted_feature_cfgs() {
let mut cmd = Command::new("rustc");
apply_feature_cfgs(&mut cmd, &["suite_demo".to_string(), "spatial".to_string()]);
let args: Vec<_> = cmd
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(
args,
vec![
"--cfg",
"feature=\"suite_demo\"",
"--cfg",
"feature=\"spatial\""
]
);
}
#[test]
fn apply_feature_cfgs_is_noop_for_empty_feature_set() {
let mut cmd = Command::new("rustc");
apply_feature_cfgs(&mut cmd, &[]);
assert_eq!(cmd.get_args().count(), 0);
}
#[test]
fn apply_allow_lints_emits_dash_a_per_lint() {
let mut cmd = Command::new("rustc");
apply_allow_lints(
&mut cmd,
&["unexpected_cfgs".to_string(), "dead_code".to_string()],
);
let args: Vec<_> = cmd
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(
args,
vec!["-A", "unexpected_cfgs", "-A", "dead_code"],
"each lint must produce exactly one `-A <lint>` pair on the argv"
);
}
#[test]
fn apply_allow_lints_is_noop_for_empty_slice() {
let mut cmd = Command::new("rustc");
apply_allow_lints(&mut cmd, &[]);
assert_eq!(
cmd.get_args().count(),
0,
"empty allow_lints must not append any flags"
);
}
#[test]
fn apply_allow_lints_handles_namespaced_lint() {
let mut cmd = Command::new("rustc");
apply_allow_lints(&mut cmd, &["clippy::needless_collect".to_string()]);
let args: Vec<_> = cmd
.get_args()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
assert_eq!(
args,
vec!["-A", "clippy::needless_collect"],
"namespaced lint must be forwarded verbatim as a single argv token"
);
}
}