Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions tests/api_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

use std::{
mem::{MaybeUninit, zeroed},
sync::{Arc, LazyLock, Mutex, MutexGuard},
sync::{
Arc, LazyLock, Mutex, MutexGuard,
atomic::{AtomicUsize, Ordering},
},
thread,
time::Duration,
};

use axcpu::uspace::UserContext;
Expand All @@ -12,14 +17,28 @@ use starry_signal::{
SignalDisposition, SignalInfo, SignalOSAction, SignalSet, Signo,
api::{ProcessSignalManager, SignalActions, ThreadSignalManager},
};
use starry_vm::{VmIo, VmResult};
use starry_vm::{VmError, VmIo, VmResult};

struct TestEnv {
actions: Arc<SpinNoIrq<SignalActions>>,
proc: Arc<ProcessSignalManager>,
thr: Arc<ThreadSignalManager>,
}

fn wait_until<F>(timeout: Duration, mut check: F) -> bool
where
F: FnMut() -> bool,
{
let start = std::time::Instant::now();
while start.elapsed() < timeout {
if check() {
return true;
}
thread::sleep(Duration::from_millis(1));
}
false
}

impl TestEnv {
fn new() -> Self {
let actions = Arc::new(SpinNoIrq::new(SignalActions::default()));
Expand All @@ -30,7 +49,7 @@ impl TestEnv {
}

static POOL: LazyLock<Mutex<Box<[u8]>>> = LazyLock::new(|| {
let size = 0x0100_0000; // 1 MiB
let size = 0x0100_0000;
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment indicating the size unit "1 MiB" has been removed. The value 0x0100_0000 is actually 16 MiB (16,777,216 bytes), not 1 MiB (1,048,576 bytes). While removing the incorrect comment avoids misleading information, consider adding a correct comment such as "16 MiB" for clarity.

Suggested change
let size = 0x0100_0000;
let size = 0x0100_0000; // 16 MiB

Copilot uses AI. Check for mistakes.
Mutex::new(vec![0; size].into_boxed_slice())
});

Expand All @@ -45,15 +64,21 @@ unsafe impl VmIo for Vm {

fn read(&mut self, start: usize, buf: &mut [MaybeUninit<u8>]) -> VmResult {
let base = self.0.as_ptr() as usize;
let offset = start - base;
let offset = start.checked_sub(base).ok_or(VmError::BadAddress)?;
if offset.checked_add(buf.len()).ok_or(VmError::BadAddress)? > self.0.len() {
return Err(VmError::BadAddress);
}
let slice = &self.0[offset..offset + buf.len()];
buf.write_copy_of_slice(slice);
Ok(())
}

fn write(&mut self, start: usize, buf: &[u8]) -> VmResult {
let base = self.0.as_ptr() as usize;
let offset = start - base;
let offset = start.checked_sub(base).ok_or(VmError::BadAddress)?;
if offset.checked_add(buf.len()).ok_or(VmError::BadAddress)? > self.0.len() {
return Err(VmError::BadAddress);
}
let slice = &mut self.0[offset..offset + buf.len()];
slice.copy_from_slice(buf);
Ok(())
Expand Down Expand Up @@ -176,3 +201,130 @@ fn restore() {
assert_eq!(uctx_user.ip(), initial.ip());
assert_eq!(uctx_user.sp(), initial.sp());
}

#[test]
fn thread_send_signal() {
let env = TestEnv::new();
let sig = SignalInfo::new_user(Signo::SIGTERM, 9, 9);

let thr = env.thr.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
let _ = thr.send_signal(sig);
});

let res = wait_until(Duration::from_millis(100), || {
env.thr.pending().has(Signo::SIGTERM) || env.proc.pending().has(Signo::SIGTERM)
});

assert!(res);
}

#[test]
fn thread_blocked() {
let env = TestEnv::new();
let sig = SignalInfo::new_user(Signo::SIGTERM, 9, 9);

let mut blocked = SignalSet::default();
blocked.add(Signo::SIGTERM);
let prev = env.thr.set_blocked(blocked);
assert!(!prev.has(Signo::SIGTERM));
assert!(env.thr.signal_blocked(Signo::SIGTERM));

let thr = env.thr.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
let _ = thr.send_signal(sig);
});

let pending_res = wait_until(Duration::from_millis(100), || {
env.thr.pending().has(Signo::SIGTERM)
});
assert!(pending_res);

env.thr.set_blocked(SignalSet::default());
assert!(!env.thr.signal_blocked(Signo::SIGTERM));

let uctx = Arc::new(SpinNoIrq::new(unsafe { zeroed::<UserContext>() }));
uctx.lock().set_sp(0x8000_0000);
let res = wait_until(Duration::from_millis(100), || {
let mut uctx_ref = uctx.lock().clone();
if let Some((si, _)) = env.thr.check_signals(&mut uctx_ref, None) {
assert_eq!(si.signo(), Signo::SIGTERM);
true
} else {
false
}
});
assert!(res);
}

#[test]
fn thread_handler() {
unsafe extern "C" fn test_handler(_: i32) {}

let env = TestEnv::new();
env.actions.lock()[Signo::SIGTERM].disposition = SignalDisposition::Handler(test_handler);

let uctx = Arc::new(SpinNoIrq::new(unsafe { zeroed::<UserContext>() }));
let initial_sp = {
let pool = POOL.lock().unwrap();
pool.as_ptr() as usize + pool.len()
};
uctx.lock().set_sp(initial_sp);

let first = SignalInfo::new_user(Signo::SIGTERM, 9, 9);
assert!(env.thr.send_signal(first.clone()));
let (si, action) = {
let mut guard = uctx.lock();
env.thr.check_signals(&mut guard, None).unwrap()
};
assert_eq!(si.signo(), Signo::SIGTERM);
assert!(matches!(action, SignalOSAction::Handler));
assert!(env.thr.signal_blocked(Signo::SIGTERM));

let thr = env.thr.clone();
thread::spawn(move || {
let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGINT, 2, 2));
let _ = thr.send_signal(SignalInfo::new_user(Signo::SIGTERM, 3, 3));
});

let pending_res = wait_until(Duration::from_millis(200), || {
env.thr.pending().has(Signo::SIGTERM)
});
assert!(pending_res);

let pending_res = wait_until(Duration::from_millis(200), || {
env.thr.pending().has(Signo::SIGINT)
});
assert!(pending_res);

let frame_sp = uctx.lock().sp() + 8;
{
let mut guard = uctx.lock();
guard.set_sp(frame_sp);
env.thr.restore(&mut guard);
}
assert!(!env.thr.signal_blocked(Signo::SIGTERM));

let delivered = Arc::new(AtomicUsize::new(0));
let delivered_result = wait_until(Duration::from_millis(200), || {
let thr = env.thr.clone();
let delivered_ref = delivered.clone();
let uctx_ref = uctx.clone();
let mut guard = uctx_ref.lock();
let mut ctx = guard.clone();
if let Some((sig, _)) = thr.check_signals(&mut ctx, None) {
let bit = match sig.signo() {
Signo::SIGINT => 0b01,
Signo::SIGTERM => 0b10,
_ => unreachable!(),
};
*guard = ctx;
delivered_ref.fetch_or(bit, Ordering::SeqCst);
}
delivered_ref.load(Ordering::SeqCst) & 0b11 == 0b11
});

assert!(delivered_result);
}
Loading