chore: initial release v0.1.0

Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
swaits committed 2025-10-13 19:36:37 -06:00
commit e8e55b7676
64 files changed
+23966

No files matched your search

+77
View File
@@ -0,0 +1,77 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use announcement::Announcement;
use std::sync::Arc;
fuzz_target!(|data: &[u8]| {
// Test various data types and patterns
// Test with raw bytes
if !data.is_empty() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Convert to Vec for owned data
let vec_data = data.to_vec();
let _ = announcer.announce(vec_data.clone());
// Verify listener receives the data
if let Some(received) = listener.try_listen() {
assert_eq!(received, vec_data);
}
}
// Test with Arc-wrapped data
if data.len() >= 4 {
let (announcer, announcement) = Announcement::new();
let listener1 = announcement.listener();
let listener2 = announcement.listener();
let arc_data = Arc::new(data.to_vec());
let _ = announcer.announce(arc_data.clone());
// Both listeners should get the same Arc
if let (Some(r1), Some(r2)) = (listener1.try_listen(), listener2.try_listen()) {
assert_eq!(r1, r2);
assert_eq!(*r1, data.to_vec());
}
}
// Test with String data (if valid UTF-8)
if let Ok(string_data) = std::str::from_utf8(data) {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let _ = announcer.announce(string_data.to_string());
if let Some(received) = listener.try_listen() {
assert_eq!(received, string_data);
}
}
// Test with numeric data
if data.len() >= 8 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&data[..8]);
let num = u64::from_le_bytes(bytes);
let _ = announcer.announce(num);
if let Some(received) = listener.try_listen() {
assert_eq!(received, num);
}
}
// Test is_announced flag
{
let (announcer, announcement) = Announcement::new();
assert!(!announcement.is_announced());
let _ = announcer.announce(42u32);
assert!(announcement.is_announced());
}
});
+84
View File
@@ -0,0 +1,84 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use announcement::Announcement;
fuzz_target!(|data: &[u8]| {
if data.is_empty() {
return;
}
let action = data[0] % 4;
match action {
0 => {
// Test drop without announce
let (announcer, announcement) = Announcement::<u32>::new();
let listener = announcement.listener();
assert!(!announcement.is_closed());
drop(announcer);
// Should be closed now
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
1 => {
// Test explicit close
let (announcer, announcement) = Announcement::<u32>::new();
let listener = announcement.listener();
assert!(!announcement.is_closed());
announcer.close();
// Should be closed now
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
2 => {
// Test announce then drop (should not be closed)
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let _ = announcer.announce(42u32);
// announcer is consumed by announce(), no need to drop
// Should NOT be closed (announced successfully)
assert!(!announcement.is_closed());
assert!(!listener.is_closed());
// Listener should get value
assert_eq!(listener.try_listen(), Some(42u32));
}
_ => {
// Test listener created after close
let (announcer, announcement) = Announcement::<u32>::new();
announcer.close();
// Create listener after close
let listener = announcement.listener();
// Should immediately know it's closed
assert!(listener.is_closed());
}
}
// Test with multiple listeners
if data.len() >= 2 {
let num_listeners = (data[1] % 10) as usize + 1; // 1-10 listeners
let (announcer, announcement) = Announcement::<u32>::new();
let listeners: Vec<_> = (0..num_listeners)
.map(|_| announcement.listener())
.collect();
// Drop announcer
drop(announcer);
// All should see closed state
for listener in listeners {
assert!(listener.is_closed());
}
}
});
+184
View File
@@ -0,0 +1,184 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use announcement::Announcement;
use std::sync::{Arc, Barrier};
use std::thread;
fuzz_target!(|data: &[u8]| {
if data.len() < 4 {
return;
}
// Extract test parameters from fuzz data
let num_threads = (data[0] % 8) as usize + 2; // 2-9 threads
let announce_delay = data[1] % 3; // 0-2 delay pattern
let pattern = data[2] % 4;
match pattern {
0 => {
// Test concurrent listeners
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(num_threads + 1));
let handles: Vec<_> = (0..num_threads)
.map(|i| {
let listener = announcement.listener();
let barrier = barrier.clone();
thread::spawn(move || {
// Synchronize start
barrier.wait();
// All threads try to listen
let value = listener.try_listen();
// Return thread id and value
(i, value)
})
})
.collect();
// Start all threads together
barrier.wait();
// Announce after threads are started
match announce_delay {
0 => {
// Immediate announce
let _ = announcer.announce(42u32);
}
1 => {
// Short delay
thread::sleep(std::time::Duration::from_micros(10));
let _ = announcer.announce(42u32);
}
_ => {
// Drop without announcing
drop(announcer);
}
}
// Collect results
for handle in handles {
let (id, value) = handle.join().unwrap();
if announce_delay < 2 {
// Should get value if we announced
// (might be None if thread checked before announce)
if let Some(v) = value {
assert_eq!(v, 42u32, "Thread {} got wrong value", id);
}
}
}
}
1 => {
// Test concurrent clone and listener creation
let (announcer, announcement) = Announcement::new();
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let ann_clone = announcement.clone();
thread::spawn(move || {
// Create listener from clone
let listener = ann_clone.listener();
// Try to read
listener.try_listen()
})
})
.collect();
// Announce while threads are creating listeners
let _ = announcer.announce(42u32);
// All should eventually see the value
for handle in handles {
let value = handle.join().unwrap();
// May or may not have seen the value depending on timing
if let Some(v) = value {
assert_eq!(v, 42u32);
}
}
}
2 => {
// Test concurrent is_announced checks
let (announcer, announcement) = Announcement::new();
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let ann_clone = announcement.clone();
thread::spawn(move || {
// Check announced state multiple times
let mut states = vec![];
for _ in 0..10 {
states.push(ann_clone.is_announced());
}
states
})
})
.collect();
// Announce in the middle
thread::sleep(std::time::Duration::from_micros(5));
let _ = announcer.announce(42u32);
// Collect all state observations
for handle in handles {
let states = handle.join().unwrap();
// States should only transition from false to true, never back
let mut seen_true = false;
for state in states {
if seen_true {
assert!(state, "is_announced went from true to false");
}
if state {
seen_true = true;
}
}
}
}
_ => {
// Test concurrent close checks
let (announcer, announcement) = Announcement::<u32>::new();
let handles: Vec<_> = (0..num_threads)
.map(|_| {
let ann_clone = announcement.clone();
thread::spawn(move || {
// Check closed state multiple times
let mut states = vec![];
for _ in 0..10 {
states.push(ann_clone.is_closed());
}
states
})
})
.collect();
// Close in the middle
thread::sleep(std::time::Duration::from_micros(5));
announcer.close();
// Collect all state observations
for handle in handles {
let states = handle.join().unwrap();
// States should only transition from false to true, never back
let mut seen_true = false;
for state in states {
if seen_true {
assert!(state, "is_closed went from true to false");
}
if state {
seen_true = true;
}
}
}
}
}
});
+103
View File
@@ -0,0 +1,103 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use announcement::Announcement;
fuzz_target!(|data: &[u8]| {
if data.is_empty() {
return;
}
let pattern = data[0] % 5;
match pattern {
0 => {
// Test try_listen before announce
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Should return None
assert_eq!(listener.try_listen(), None);
// Now announce
let _ = announcer.announce(42u32);
// Should return Some
assert_eq!(listener.try_listen(), Some(42u32));
}
1 => {
// Test try_listen after announce
let (announcer, announcement) = Announcement::new();
let _ = announcer.announce(42u32);
let listener = announcement.listener();
// Should immediately return Some
assert_eq!(listener.try_listen(), Some(42u32));
}
2 => {
// Test multiple try_listen calls
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let _ = announcer.announce(42u32);
// Multiple calls should return the same value
assert_eq!(listener.try_listen(), Some(42u32));
assert_eq!(listener.try_listen(), Some(42u32));
assert_eq!(listener.try_listen(), Some(42u32));
}
3 => {
// Test multiple listeners
if data.len() >= 2 {
let num_listeners = (data[1] % 10) as usize + 1; // 1-10 listeners
let (announcer, announcement) = Announcement::new();
let listeners: Vec<_> = (0..num_listeners)
.map(|_| announcement.listener())
.collect();
let _ = announcer.announce(42u32);
// All should get the value
for listener in listeners {
assert_eq!(listener.try_listen(), Some(42u32));
}
}
}
_ => {
// Test clone and listener
let (announcer, announcement) = Announcement::new();
let announcement2 = announcement.clone();
let l1 = announcement.listener();
let l2 = announcement2.listener();
let _ = announcer.announce(42u32);
// Both should get the value
assert_eq!(l1.try_listen(), Some(42u32));
assert_eq!(l2.try_listen(), Some(42u32));
}
}
// Test is_announced with various patterns
if data.len() >= 3 {
let test_announced = data[2] % 2 == 0;
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
assert!(!announcement.is_announced());
if test_announced {
let _ = announcer.announce(42u32);
assert!(announcement.is_announced());
assert_eq!(listener.try_listen(), Some(42u32));
} else {
announcer.close();
assert!(!announcement.is_announced());
assert!(announcement.is_closed());
}
}
});