blob: 7dac3047f76322dbe876d0d6fc7c2e8773cfb108 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Tests all functionality in the system package
//!
//! Test failure is defined as the function returning via panicking
//! and the result being caught in the test! macro. If a test function
//! returns without panicking, it is assumed to pass.
use mojo::system::core;
use mojo::system::data_pipe;
use mojo::system::message_pipe;
use mojo::system::shared_buffer::{self, SharedBuffer};
use mojo::system::trap::{
ArmResult, Trap, TrapEvent, TriggerCondition, UnsafeTrap, UnsafeTrapEvent,
};
use mojo::system::wait_set;
use mojo::system::{self, CastHandle, Handle, HandleSignals, MojoResult, SignalsState};
use std::string::String;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::vec::Vec;
tests! {
fn get_time_ticks_now() {
let x = core::get_time_ticks_now();
assert!(x >= 10);
}
fn handle() {
let sb = SharedBuffer::new(1).unwrap();
let handle = sb.as_untyped();
unsafe {
assert_eq!((handle.get_native_handle() != 0), handle.is_valid());
assert!(handle.get_native_handle() != 0 && handle.is_valid());
let mut h2 = system::acquire(handle.get_native_handle());
assert!(h2.is_valid());
h2.invalidate();
assert!(!h2.is_valid());
}
}
fn shared_buffer() {
let bufsize = 100;
let sb1;
{
let mut buf;
{
let sb_c = SharedBuffer::new(bufsize).unwrap();
// Extract original handle to check against
let sb_h = sb_c.get_native_handle();
// Test casting of handle types
let sb_u = sb_c.as_untyped();
assert_eq!(sb_u.get_native_handle(), sb_h);
let sb = unsafe { SharedBuffer::from_untyped(sb_u) };
assert_eq!(sb.get_native_handle(), sb_h);
// Test map
buf = sb.map(0, bufsize).unwrap();
assert_eq!(buf.len(), bufsize as usize);
// Test get info
let size = sb.get_info().unwrap();
assert_eq!(size, bufsize);
buf.write(50, 34);
// Test duplicate
sb1 = sb.duplicate(shared_buffer::DuplicateFlags::empty()).unwrap();
}
// sb gets closed
buf.write(51, 35);
}
// buf just got closed
// remap to buf1 from sb1
let buf1 = sb1.map(50, 50).unwrap();
assert_eq!(buf1.len(), 50);
// verify buffer contents
assert_eq!(buf1.read(0), 34);
assert_eq!(buf1.read(1), 35);
}
fn message_pipe() {
let (endpt, endpt1) = message_pipe::create().unwrap();
// Extract original handle to check against
let endpt_h = endpt.get_native_handle();
// Test casting of handle types
let endpt_u = endpt.as_untyped();
assert_eq!(endpt_u.get_native_handle(), endpt_h);
{
let endpt0 = unsafe { message_pipe::MessageEndpoint::from_untyped(endpt_u) };
assert_eq!(endpt0.get_native_handle(), endpt_h);
{
let s: SignalsState = endpt0.wait(HandleSignals::WRITABLE).satisfied().unwrap();
assert!(s.satisfied().is_writable());
assert!(s.satisfiable().is_readable());
assert!(s.satisfiable().is_writable());
assert!(s.satisfiable().is_peer_closed());
}
match endpt0.read() {
Ok((_msg, _handles)) => panic!("Read should not have succeeded."),
Err(r) => assert_eq!(r, mojo::MojoResult::ShouldWait),
}
let hello = "hello".to_string().into_bytes();
let write_result = endpt1.write(&hello, Vec::new());
assert_eq!(write_result, mojo::MojoResult::Okay);
{
let s: SignalsState = endpt0.wait(HandleSignals::READABLE).satisfied().unwrap();
assert!(s.satisfied().is_readable());
assert!(s.satisfied().is_writable());
assert!(s.satisfiable().is_readable());
assert!(s.satisfiable().is_writable());
assert!(s.satisfiable().is_peer_closed());
}
let hello_data;
match endpt0.read() {
Ok((msg, _handles)) => hello_data = msg,
Err(r) => panic!("Failed to read message on endpt0, error: {}", r),
}
assert_eq!(String::from_utf8(hello_data).unwrap(), "hello".to_string());
}
let s: SignalsState = endpt1.wait(HandleSignals::READABLE | HandleSignals::WRITABLE).unsatisfiable().unwrap();
assert!(s.satisfied().is_peer_closed());
// For some reason QuotaExceeded is also set. TOOD(collinbaker): investigate.
assert!(s.satisfiable().is_peer_closed());
}
fn data_pipe() {
let (cons0, prod0) = data_pipe::create_default().unwrap();
// Extract original handle to check against
let cons_h = cons0.get_native_handle();
let prod_h = prod0.get_native_handle();
// Test casting of handle types
let cons_u = cons0.as_untyped();
let prod_u = prod0.as_untyped();
assert_eq!(cons_u.get_native_handle(), cons_h);
assert_eq!(prod_u.get_native_handle(), prod_h);
let cons = unsafe { data_pipe::Consumer::<u8>::from_untyped(cons_u) };
let prod = unsafe { data_pipe::Producer::<u8>::from_untyped(prod_u) };
assert_eq!(cons.get_native_handle(), cons_h);
assert_eq!(prod.get_native_handle(), prod_h);
// Test waiting on producer
prod.wait(HandleSignals::WRITABLE).satisfied().unwrap();
// Test one-phase read/write.
// Writing.
let hello = "hello".to_string().into_bytes();
let bytes_written = prod.write(&hello, data_pipe::WriteFlags::empty()).unwrap();
assert_eq!(bytes_written, hello.len());
// Reading.
cons.wait(HandleSignals::READABLE).satisfied().unwrap();
let data_string = String::from_utf8(cons.read(data_pipe::ReadFlags::empty()).unwrap()).unwrap();
assert_eq!(data_string, "hello".to_string());
{
// Test two-phase read/write.
// Writing.
let goodbye = "goodbye".to_string().into_bytes();
let mut write_buf = match prod.begin() {
Ok(buf) => buf,
Err(err) => panic!("Error on write begin: {}", err),
};
assert!(write_buf.len() >= goodbye.len());
for i in 0..goodbye.len() {
write_buf[i].write(goodbye[i]);
}
// SAFETY: we wrote `goodbye.len()` valid elements to `write_buf`,
// so they are initialized.
unsafe {
write_buf.commit(goodbye.len());
}
// Reading.
cons.wait(HandleSignals::READABLE).satisfied().unwrap();
let mut data_goodbye: Vec<u8> = Vec::with_capacity(goodbye.len());
{
let read_buf = match cons.begin() {
Ok(buf) => buf,
Err(err) => panic!("Error on read begin: {}", err),
};
for i in 0..read_buf.len() {
data_goodbye.push(read_buf[i]);
}
match cons.read(data_pipe::ReadFlags::empty()) {
Ok(_bytes) => assert!(false),
Err(r) => assert_eq!(r, mojo::MojoResult::Busy),
}
read_buf.commit(data_goodbye.len())
}
assert_eq!(data_goodbye.len(), goodbye.len());
assert_eq!(String::from_utf8(data_goodbye).unwrap(), "goodbye".to_string());
}
}
fn wait_set() {
let mut set = wait_set::WaitSet::new().unwrap();
let (endpt0, endpt1) = message_pipe::create().unwrap();
let cookie1 = wait_set::WaitSetCookie(245);
let cookie2 = wait_set::WaitSetCookie(123);
let signals = HandleSignals::READABLE;
assert_eq!(set.add(&endpt0, signals, cookie1), mojo::MojoResult::Okay);
assert_eq!(set.add(&endpt0, signals, cookie1), mojo::MojoResult::AlreadyExists);
assert_eq!(set.remove(cookie1), mojo::MojoResult::Okay);
assert_eq!(set.remove(cookie1), mojo::MojoResult::NotFound);
assert_eq!(set.add(&endpt0, signals, cookie2), mojo::MojoResult::Okay);
thread::spawn(move || {
let hello = "hello".to_string().into_bytes();
let write_result = endpt1.write(&hello, Vec::new());
assert_eq!(write_result, mojo::MojoResult::Okay);
});
let mut output = Vec::with_capacity(2);
let result = set.wait_on_set(&mut output);
assert_eq!(result, mojo::MojoResult::Okay);
assert_eq!(output.len(), 1);
assert_eq!(output[0].cookie, cookie2);
assert_eq!(output[0].wait_result, mojo::MojoResult::Okay);
assert!(output[0].signals_state.satisfied().is_readable());
}
fn trap_signals_on_readable_unwritable() {
// These tests unfortunately need global state, so we have to ensure
// exclusive access (generally Rust tests run on multiple threads).
let _test_lock = TRAP_TEST_LOCK.lock().unwrap();
let trap = UnsafeTrap::new(test_trap_event_handler).unwrap();
let (cons, prod) = data_pipe::create_default().unwrap();
assert_eq!(MojoResult::Okay,
trap.add_trigger(cons.get_native_handle(),
HandleSignals::READABLE,
TriggerCondition::SignalsSatisfied,
1));
assert_eq!(MojoResult::Okay,
trap.add_trigger(prod.get_native_handle(),
HandleSignals::WRITABLE,
TriggerCondition::SignalsUnsatisfied,
2));
let mut blocking_events_buf = [std::mem::MaybeUninit::uninit(); 16];
// The trap should arm with no blocking events since nothing should be
// triggered yet.
match trap.arm(Some(&mut blocking_events_buf)) {
ArmResult::Armed => (),
ArmResult::Blocked(events) => panic!("unexpected blocking events {:?}", events),
ArmResult::Failed(e) => panic!("unexpected mojo error {:?}", e),
}
// Check that there are no events in the list (though of course this
// check is uncertain if a race condition bug exists).
assert_eq!(TRAP_EVENT_LIST.lock().unwrap().len(), 0);
// Write to `prod` making `cons` readable.
assert_eq!(prod.write(&[128u8], data_pipe::WriteFlags::empty()).unwrap(), 1);
{
let list = wait_for_trap_events(TRAP_EVENT_LIST.lock().unwrap(), 1);
assert_eq!(list.len(), 1);
let event = list[0];
assert_eq!(event.trigger_context(), 1);
assert_eq!(event.result(), MojoResult::Okay);
assert!(event.signals_state().satisfiable().is_readable(),
"{:?}", event.signals_state());
assert!(event.signals_state().satisfied().is_readable(),
"{:?}", event.signals_state());
}
// Once the above event has fired, `trap` is disarmed.
// Re-arming should block and return the event above.
match trap.arm(Some(&mut blocking_events_buf)) {
ArmResult::Blocked(events) => {
let event: &UnsafeTrapEvent = events.get(0).unwrap();
assert_eq!(event.trigger_context(), 1);
assert_eq!(event.result(), MojoResult::Okay);
}
ArmResult::Armed => panic!("expected event did not arrive"),
ArmResult::Failed(e) => panic!("unexpected Mojo error {:?}", e),
}
clear_trap_events(1);
// Read the data so we don't receive the same event again.
cons.read(data_pipe::ReadFlags::DISCARD).unwrap();
match trap.arm(Some(&mut blocking_events_buf)) {
ArmResult::Armed => (),
ArmResult::Blocked(events) => panic!("unexpected blocking events {:?}", events),
ArmResult::Failed(e) => panic!("unexpected Mojo error {:?}", e),
}
// Close `cons` making `prod` unwritable.
drop(cons);
// Now we should have two events indicating `prod` is not writable.
{
let list = wait_for_trap_events(TRAP_EVENT_LIST.lock().unwrap(), 1);
assert_eq!(list.len(), 2);
let (event1, event2) = (list[0], list[1]);
// Sort the events since the ordering isn't deterministic.
let (cons_event, prod_event) = if event1.trigger_context() == 1 {
(event1, event2)
} else {
(event2, event1)
};
// 1. `cons` was closed, yielding a `Cancelled` event.
assert_eq!(cons_event.trigger_context(), 1);
assert_eq!(cons_event.result(), MojoResult::Cancelled);
// 2. `prod`'s trigger condition (being unwritable) was met,
// yielding a normal event.
assert_eq!(prod_event.trigger_context(), 2);
assert_eq!(prod_event.result(), MojoResult::Okay);
assert!(!prod_event.signals_state().satisfiable().is_writable())
};
drop(trap);
// We should have 3 events: the two we saw above, plus one Cancelled
// event for `prod` corresponding to removing `prod` from `trap` (which
// happens automatically on `Trap` closure).
clear_trap_events(3);
}
fn trap_handle_closed_before_arm() {
let _test_lock = TRAP_TEST_LOCK.lock().unwrap();
let trap = UnsafeTrap::new(test_trap_event_handler).unwrap();
let (cons, _prod) = data_pipe::create_default().unwrap();
assert_eq!(MojoResult::Okay,
trap.add_trigger(cons.get_native_handle(),
HandleSignals::READABLE,
TriggerCondition::SignalsSatisfied, 1));
drop(cons);
// A cancelled event will be reported even without arming.
{
let events = wait_for_trap_events(TRAP_EVENT_LIST.lock().unwrap(), 1);
assert_eq!(events.len(), 1, "unexpected events {:?}", *events);
let event = events[0];
assert_eq!(event.trigger_context(), 1);
assert_eq!(event.result(), MojoResult::Cancelled);
}
drop(trap);
clear_trap_events(1);
}
fn safe_trap() {
struct SharedContext {
events: Mutex<Vec<TrapEvent>>,
cond: Condvar,
}
let handler = |event: &TrapEvent, context: &Arc<SharedContext>| {
if let Ok(mut events) = context.events.lock() {
events.push(*event);
context.cond.notify_all();
}
};
let context = Arc::new(SharedContext {
events: Mutex::new(Vec::new()),
cond: Condvar::new(),
});
let trap = Trap::new(handler).unwrap();
let (cons, prod) = data_pipe::create_default().unwrap();
let _cons_token = trap.add_trigger(
cons.get_native_handle(),
HandleSignals::READABLE,
TriggerCondition::SignalsSatisfied,
context.clone());
let _prod_token = trap.add_trigger(
prod.get_native_handle(),
HandleSignals::WRITABLE,
TriggerCondition::SignalsUnsatisfied,
context.clone());
assert_eq!(trap.arm(), MojoResult::Okay);
// Make `cons` readable.
assert_eq!(prod.write(&[128u8], data_pipe::WriteFlags::empty()), Ok(1));
{
let mut events =
context.cond.wait_while(context.events.lock().unwrap(), |e| e.is_empty()).unwrap();
assert_eq!(events.len(), 1, "unexpected events {:?}", events);
let event = events[0];
assert_eq!(event.handle(), cons.get_native_handle());
assert_eq!(event.result(), MojoResult::Okay);
assert!(event.signals_state().satisfied().is_readable(), "{:?}", event.signals_state());
events.clear();
}
// Close `cons` to get two events: unreadable on `prod`, and Cancelled on `cons`.
let cons_native = cons.get_native_handle();
drop(cons);
{
// We get the Cancelled event while unarmed.
let mut events =
context.cond.wait_while(context.events.lock().unwrap(), |e| e.is_empty()).unwrap();
assert_eq!(events.len(), 1, "unexpected events {:?}", events);
let event = events[0];
assert_eq!(event.handle(), cons_native);
assert_eq!(event.result(), MojoResult::Cancelled);
events.clear();
}
// When we try to arm, we'll get the `prod` event.
assert_eq!(trap.arm(), MojoResult::FailedPrecondition);
{
let mut events =
context.cond.wait_while(context.events.lock().unwrap(), |e| e.is_empty()).unwrap();
assert_eq!(events.len(), 1, "unexpected events {:?}", events);
let event = events[0];
assert_eq!(event.handle(), prod.get_native_handle());
assert_eq!(event.result(), MojoResult::Okay);
assert!(!event.signals_state().satisfied().is_writable(),
"{:?}", event.signals_state());
events.clear();
}
}
}
fn clear_trap_events(expected_len: usize) {
let mut list = TRAP_EVENT_LIST.lock().unwrap();
assert_eq!(list.len(), expected_len, "unexpected events {:?}", *list);
list.clear();
}
fn wait_for_trap_events(
guard: std::sync::MutexGuard<'static, Vec<UnsafeTrapEvent>>,
expected_len: usize,
) -> std::sync::MutexGuard<'static, Vec<UnsafeTrapEvent>> {
TRAP_EVENT_COND.wait_while(guard, |l| l.len() < expected_len).unwrap()
}
extern "C" fn test_trap_event_handler(event: &UnsafeTrapEvent) {
// If locking fails, it means another thread panicked. In this case we can
// simply do nothing. Note that we cannot panic here since this is called
// from C code.
if let Ok(mut list) = TRAP_EVENT_LIST.lock() {
list.push(*event);
TRAP_EVENT_COND.notify_all();
}
}
lazy_static::lazy_static! {
// We need globals for trap tests so we need mutual exclusion.
static ref TRAP_TEST_LOCK: Mutex<()> = Mutex::new(());
// The TrapEvents received by `test_trap_event_handler`.
static ref TRAP_EVENT_LIST: Mutex<Vec<UnsafeTrapEvent>> = Mutex::new(Vec::new());
static ref TRAP_EVENT_COND: Condvar = Condvar::new();
}