Skip to content

Commit

Permalink
semaphore: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cbiffle committed Apr 26, 2024
1 parent de9c84e commit 6798f7d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ rust-version = "1.69"
lilos = { path = "os", version = "1.0.2-pre.0", default-features = false }
lilos-testsuite = { path = "testsuite" }
lilos-handoff = { path = "handoff" }
lilos-semaphore = { path = "semaphore" }

# External
cfg-if = "1.0.0"
Expand Down
9 changes: 9 additions & 0 deletions semaphore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ impl Semaphore {
Some(Permit { semaphore: self })
}

/// Returns the number of permits available in the semaphore.
///
/// Note that this is a _snapshot._ If this returns 4, for instance, it
/// doesn't mean you can successfully call `acquire` 4 times without
/// blocking, because another acquirer may be racing you.
pub fn permits_available(&self) -> usize {
self.available.load(Ordering::Relaxed)
}

/// Stuffs one permit back into the semaphore.
///
/// Use this if you have called [`core::mem::forget`] on a [`Permit`], when
Expand Down
4 changes: 3 additions & 1 deletion testsuite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ repository.workspace = true
rust-version.workspace = true

[features]
default = ["handoff"]
default = ["handoff", "semaphore"]
handoff = ["dep:lilos-handoff"]
semaphore = ["dep:lilos-semaphore"]

[package.metadata.docs.rs]
default-target = "thumbv7em-none-eabihf"
Expand All @@ -24,6 +25,7 @@ cortex-m-semihosting.workspace = true
futures.workspace = true
lilos = {workspace = true, features = ["spsc", "systick", "mutex"]}
lilos-handoff = { workspace = true, optional = true }
lilos-semaphore = { workspace = true, optional = true }
panic-semihosting.workspace = true

[lib]
Expand Down
13 changes: 13 additions & 0 deletions testsuite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod spsc;
mod mutex;
#[cfg(feature = "handoff")]
mod handoff;
#[cfg(feature = "semaphore")]
mod semaphore;

use core::convert::Infallible;
use core::pin::pin;
Expand Down Expand Up @@ -123,6 +125,17 @@ async fn task_coordinator() -> Infallible {
handoff::test_pop_cancel_after_block,
#[cfg(feature = "handoff")]
handoff::test_pop_cancel_after_success,

#[cfg(feature = "semaphore")]
semaphore::test_create_drop,
#[cfg(feature = "semaphore")]
semaphore::test_acquire_release,
#[cfg(feature = "semaphore")]
semaphore::test_exhaustion,
#[cfg(feature = "semaphore")]
semaphore::test_fairness,
#[cfg(feature = "semaphore")]
semaphore::test_cancellation,
}
};

Expand Down
105 changes: 105 additions & 0 deletions testsuite/src/semaphore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use core::{pin::pin, task::Poll};

use lilos_semaphore::create_semaphore;

pub async fn test_create_drop() {
create_semaphore!(_a_semaphore, 10);
}

pub async fn test_acquire_release() {
create_semaphore!(a_semaphore, 10);

let permit = a_semaphore.acquire().await;
drop(permit);
}

pub async fn test_exhaustion() {
// Start out with a permit.
create_semaphore!(a_semaphore, 1);

// Take it.
let permit = a_semaphore.acquire().await;

// Ensure that we can't get a second one.
{
let acq = pin!(a_semaphore.acquire());
if let Poll::Ready(_) = futures::poll!(acq) {
panic!("acquire resolved and shouldn't've");
};
}
// But not forever.
drop(permit);
let _permit = a_semaphore.acquire().await;
}

pub async fn test_fairness() {
// Start out with a permit.
create_semaphore!(a_semaphore, 1);

// Take it.
let permit = a_semaphore.acquire().await;

// Try and take it twice more.
let mut first_acq = pin!(a_semaphore.acquire());
let mut second_acq = pin!(a_semaphore.acquire());

// Neither of these futures should complete yet.
assert!(futures::poll!(first_acq.as_mut()).is_pending());
assert!(futures::poll!(second_acq.as_mut()).is_pending());

// Yield a permit.
drop(permit);

// A third challenger appears!
let mut third_acq = pin!(a_semaphore.acquire());
assert!(futures::poll!(third_acq.as_mut()).is_pending());

// The _second_ waiter should still not complete, even if called first.
assert!(futures::poll!(second_acq.as_mut()).is_pending());
// Nor the third.
assert!(futures::poll!(third_acq.as_mut()).is_pending());
// But the first should.
let Poll::Ready(permit) = futures::poll!(first_acq.as_mut()) else {
panic!("first taker should have completed now");
};
// This should cause no changes to the others.
assert!(futures::poll!(second_acq.as_mut()).is_pending());
assert!(futures::poll!(third_acq.as_mut()).is_pending());

// Alright, now let's unwind the other two.
drop(permit);
assert!(futures::poll!(third_acq.as_mut()).is_pending());
let Poll::Ready(permit) = futures::poll!(second_acq.as_mut()) else {
panic!("second taker should have completed now");
};
assert!(futures::poll!(third_acq.as_mut()).is_pending());
drop(permit);
let Poll::Ready(_) = futures::poll!(third_acq.as_mut()) else {
panic!("third taker should have completed now");
};
}

pub async fn test_cancellation() {
// Start out with a permit.
create_semaphore!(a_semaphore, 1);

// Take it.
let permit = a_semaphore.acquire().await;

assert_eq!(a_semaphore.permits_available(), 0);

{
let mut acq = pin!(a_semaphore.acquire());
// poll it to join the waitlist.
assert!(futures::poll!(acq.as_mut()).is_pending());

assert_eq!(a_semaphore.permits_available(), 0);

drop(permit);

// Still zero because the permit was directly transferred.
assert_eq!(a_semaphore.permits_available(), 0);
}

assert_eq!(a_semaphore.permits_available(), 1);
}

0 comments on commit 6798f7d

Please sign in to comment.