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:
Carl Lerche
2022-12-09 02:13:22 +09:00
committed by GitHub
parent 36039d0bb9
commit c693ccd210
5 changed files with 267 additions and 229 deletions
+23 -7
View File
@@ -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:
+7 -1
View File
@@ -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");
+4 -4
View File
@@ -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
View File
@@ -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);
});
}
}
+50 -45
View File
@@ -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);
}
}