mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-19 00:00:09 +02:00
ci: test no const mutex new (#5257)
This adds CI coverage for a couple of code paths that are not currently hit in CI: * no `const fn Mutex::new` * no `AtomicU64` This is done by adding some new CFG flags used only for tests in order to force those code paths.
This commit is contained in:
@@ -268,26 +268,42 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- i686-unknown-linux-gnu
|
||||
- arm-unknown-linux-gnueabihf
|
||||
- armv7-unknown-linux-gnueabihf
|
||||
- aarch64-unknown-linux-gnu
|
||||
include:
|
||||
- target: i686-unknown-linux-gnu
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
- target: armv7-unknown-linux-gnueabihf
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
|
||||
# Run a platform without AtomicU64 and no const Mutex::new
|
||||
- target: arm-unknown-linux-gnueabihf
|
||||
rustflags: --cfg tokio_no_const_mutex_new
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Rust ${{ env.rust_stable }}
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.rust_stable }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
# First run with all features (including parking_lot)
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: test
|
||||
args: -p tokio --all-features --target ${{ matrix.target }} --tests
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 ${{ matrix.rustflags }}
|
||||
# Now run without parking_lot
|
||||
- name: Remove `parking_lot` from `full` feature
|
||||
run: sed -i '0,/parking_lot/{/parking_lot/d;}' tokio/Cargo.toml
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: test
|
||||
# The `tokio_no_parking_lot` cfg is here to ensure the `sed` above does not silently break.
|
||||
args: -p tokio --features full,test-util --target ${{ matrix.target }} --tests
|
||||
env:
|
||||
RUSTFLAGS: --cfg tokio_unstable -Dwarnings --cfg tokio_no_ipv6 --cfg tokio_no_parking_lot ${{ matrix.rustflags }}
|
||||
|
||||
# See https://github.com/tokio-rs/tokio/issues/5187
|
||||
no-atomic-u64:
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
#![cfg(not(any(feature = "full", tokio_wasm)))]
|
||||
#[cfg(not(any(feature = "full", tokio_wasm)))]
|
||||
compile_error!("run main Tokio tests with `--features full`");
|
||||
|
||||
// CI sets `--cfg tokio_no_parking_lot` when trying to run tests with
|
||||
// `parking_lot` disabled. This check prevents "silent failure" if `parking_lot`
|
||||
// accidentally gets enabled.
|
||||
#[cfg(all(tokio_no_parking_lot, feature = "parking_lot"))]
|
||||
compile_error!("parking_lot feature enabled when it should not be");
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use parking_lot::{const_mutex, Mutex};
|
||||
use std::panic;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<String> {
|
||||
static PANIC_MUTEX: Mutex<()> = const_mutex(());
|
||||
static PANIC_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
{
|
||||
let _guard = PANIC_MUTEX.lock();
|
||||
@@ -16,6 +15,7 @@ pub fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<Stri
|
||||
let panic_location = panic_info.location().unwrap();
|
||||
panic_file
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone_from(&Some(panic_location.file().to_string()));
|
||||
}));
|
||||
}
|
||||
@@ -26,7 +26,7 @@ pub fn test_panic<Func: FnOnce() + panic::UnwindSafe>(func: Func) -> Option<Stri
|
||||
panic::set_hook(prev_hook);
|
||||
|
||||
if result.is_err() {
|
||||
panic_file.lock().clone()
|
||||
panic_file.lock().unwrap().clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
+183
-172
@@ -4,178 +4,7 @@
|
||||
use std::mem;
|
||||
use std::ops::Drop;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::time::Duration;
|
||||
use tokio::runtime;
|
||||
use tokio::sync::{OnceCell, SetError};
|
||||
use tokio::time;
|
||||
|
||||
async fn func1() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
async fn func2() -> u32 {
|
||||
time::sleep(Duration::from_millis(1)).await;
|
||||
10
|
||||
}
|
||||
|
||||
async fn func_err() -> Result<u32, ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
async fn func_ok() -> Result<u32, ()> {
|
||||
Ok(10)
|
||||
}
|
||||
|
||||
async fn func_panic() -> u32 {
|
||||
time::sleep(Duration::from_millis(1)).await;
|
||||
panic!();
|
||||
}
|
||||
|
||||
async fn sleep_and_set() -> u32 {
|
||||
// Simulate sleep by pausing time and waiting for another thread to
|
||||
// resume clock when calling `set`, then finding the cell being initialized
|
||||
// by this call
|
||||
time::sleep(Duration::from_millis(2)).await;
|
||||
5
|
||||
}
|
||||
|
||||
async fn advance_time_and_set(cell: &'static OnceCell<u32>, v: u32) -> Result<(), SetError<u32>> {
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
cell.set(v)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_init() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.start_paused(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(func1).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_init(func2).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
time::resume();
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert_eq!(*result2, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_init_panic() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
time::pause();
|
||||
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(func1).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_init(func_panic).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert_eq!(*result2, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_get() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let _ = rt.spawn(async { ONCE.set(5) }).await;
|
||||
let value = ONCE.get().unwrap();
|
||||
assert_eq!(*value, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_uninit() {
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
let uninit = ONCE.get();
|
||||
assert!(uninit.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_twice() {
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
let first = ONCE.set(5);
|
||||
assert_eq!(first, Ok(()));
|
||||
let second = ONCE.set(6);
|
||||
assert!(second.err().unwrap().is_already_init_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_while_initializing() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
time::pause();
|
||||
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(sleep_and_set).await });
|
||||
let handle2 = rt.spawn(async { advance_time_and_set(&ONCE, 10).await });
|
||||
|
||||
time::advance(Duration::from_millis(2)).await;
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert!(result2.err().unwrap().is_initializing_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_try_init() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.start_paused(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_try_init(func_err).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_try_init(func_ok).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
time::resume();
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
assert!(result1.is_err());
|
||||
|
||||
let result2 = handle2.await.unwrap();
|
||||
assert_eq!(*result2.unwrap(), 10);
|
||||
});
|
||||
}
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
#[test]
|
||||
fn drop_cell() {
|
||||
@@ -272,3 +101,185 @@ fn from() {
|
||||
let cell = OnceCell::from(2);
|
||||
assert_eq!(*cell.get().unwrap(), 2);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parking_lot")]
|
||||
mod parking_lot {
|
||||
use super::*;
|
||||
|
||||
use tokio::runtime;
|
||||
use tokio::sync::SetError;
|
||||
use tokio::time;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
async fn func1() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
async fn func2() -> u32 {
|
||||
time::sleep(Duration::from_millis(1)).await;
|
||||
10
|
||||
}
|
||||
|
||||
async fn func_err() -> Result<u32, ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
async fn func_ok() -> Result<u32, ()> {
|
||||
Ok(10)
|
||||
}
|
||||
|
||||
async fn func_panic() -> u32 {
|
||||
time::sleep(Duration::from_millis(1)).await;
|
||||
panic!();
|
||||
}
|
||||
|
||||
async fn sleep_and_set() -> u32 {
|
||||
// Simulate sleep by pausing time and waiting for another thread to
|
||||
// resume clock when calling `set`, then finding the cell being initialized
|
||||
// by this call
|
||||
time::sleep(Duration::from_millis(2)).await;
|
||||
5
|
||||
}
|
||||
|
||||
async fn advance_time_and_set(
|
||||
cell: &'static OnceCell<u32>,
|
||||
v: u32,
|
||||
) -> Result<(), SetError<u32>> {
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
cell.set(v)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_init() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.start_paused(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(func1).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_init(func2).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
time::resume();
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert_eq!(*result2, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_init_panic() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
time::pause();
|
||||
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(func1).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_init(func_panic).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert_eq!(*result2, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_get() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let _ = rt.spawn(async { ONCE.set(5) }).await;
|
||||
let value = ONCE.get().unwrap();
|
||||
assert_eq!(*value, 5);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_uninit() {
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
let uninit = ONCE.get();
|
||||
assert!(uninit.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_twice() {
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
let first = ONCE.set(5);
|
||||
assert_eq!(first, Ok(()));
|
||||
let second = ONCE.set(6);
|
||||
assert!(second.err().unwrap().is_already_init_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_while_initializing() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
time::pause();
|
||||
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_init(sleep_and_set).await });
|
||||
let handle2 = rt.spawn(async { advance_time_and_set(&ONCE, 10).await });
|
||||
|
||||
time::advance(Duration::from_millis(2)).await;
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
let result2 = handle2.await.unwrap();
|
||||
|
||||
assert_eq!(*result1, 5);
|
||||
assert!(result2.err().unwrap().is_initializing_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_try_init() {
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.start_paused(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
static ONCE: OnceCell<u32> = OnceCell::const_new();
|
||||
|
||||
rt.block_on(async {
|
||||
let handle1 = rt.spawn(async { ONCE.get_or_try_init(func_err).await });
|
||||
let handle2 = rt.spawn(async { ONCE.get_or_try_init(func_ok).await });
|
||||
|
||||
time::advance(Duration::from_millis(1)).await;
|
||||
time::resume();
|
||||
|
||||
let result1 = handle1.await.unwrap();
|
||||
assert!(result1.is_err());
|
||||
|
||||
let result2 = handle2.await.unwrap();
|
||||
assert_eq!(*result2.unwrap(), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use tokio::sync::oneshot;
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::time::Duration;
|
||||
|
||||
use futures::future::FutureExt;
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
@@ -156,49 +154,6 @@ fn runtime_gone() {
|
||||
.is_cancelled());
|
||||
}
|
||||
|
||||
// This ensures that `join_next` works correctly when the coop budget is
|
||||
// exhausted.
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn join_set_coop() {
|
||||
// Large enough to trigger coop.
|
||||
const TASK_NUM: u32 = 1000;
|
||||
|
||||
static SEM: tokio::sync::Semaphore = tokio::sync::Semaphore::const_new(0);
|
||||
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
for _ in 0..TASK_NUM {
|
||||
set.spawn(async {
|
||||
SEM.add_permits(1);
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete.
|
||||
//
|
||||
// Since this is a `current_thread` runtime, there's no race condition
|
||||
// between the last permit being added and the task completing.
|
||||
let _ = SEM.acquire_many(TASK_NUM).await.unwrap();
|
||||
|
||||
let mut count = 0;
|
||||
let mut coop_count = 0;
|
||||
loop {
|
||||
match set.join_next().now_or_never() {
|
||||
Some(Some(Ok(()))) => {}
|
||||
Some(Some(Err(err))) => panic!("failed: {}", err),
|
||||
None => {
|
||||
coop_count += 1;
|
||||
tokio::task::yield_now().await;
|
||||
continue;
|
||||
}
|
||||
Some(None) => break,
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
assert!(coop_count >= 1);
|
||||
assert_eq!(count, TASK_NUM);
|
||||
}
|
||||
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn abort_all() {
|
||||
let mut set: JoinSet<()> = JoinSet::new();
|
||||
@@ -228,3 +183,53 @@ async fn abort_all() {
|
||||
assert_eq!(count, 10);
|
||||
assert_eq!(set.len(), 0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "parking_lot")]
|
||||
mod parking_lot {
|
||||
use super::*;
|
||||
|
||||
use futures::future::FutureExt;
|
||||
|
||||
// This ensures that `join_next` works correctly when the coop budget is
|
||||
// exhausted.
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn join_set_coop() {
|
||||
// Large enough to trigger coop.
|
||||
const TASK_NUM: u32 = 1000;
|
||||
|
||||
static SEM: tokio::sync::Semaphore = tokio::sync::Semaphore::const_new(0);
|
||||
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
for _ in 0..TASK_NUM {
|
||||
set.spawn(async {
|
||||
SEM.add_permits(1);
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete.
|
||||
//
|
||||
// Since this is a `current_thread` runtime, there's no race condition
|
||||
// between the last permit being added and the task completing.
|
||||
let _ = SEM.acquire_many(TASK_NUM).await.unwrap();
|
||||
|
||||
let mut count = 0;
|
||||
let mut coop_count = 0;
|
||||
loop {
|
||||
match set.join_next().now_or_never() {
|
||||
Some(Some(Ok(()))) => {}
|
||||
Some(Some(Err(err))) => panic!("failed: {}", err),
|
||||
None => {
|
||||
coop_count += 1;
|
||||
tokio::task::yield_now().await;
|
||||
continue;
|
||||
}
|
||||
Some(None) => break,
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
assert!(coop_count >= 1);
|
||||
assert_eq!(count, TASK_NUM);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user