diff --git a/Cargo.lock b/Cargo.lock index 46290ab97..671ba4f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -423,9 +423,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi", ] @@ -787,7 +787,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -896,9 +896,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -988,14 +988,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "windows-sys 0.48.0", ] [[package]] @@ -1586,6 +1586,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1699,24 +1709,21 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" +version = "1.34.0" +source = "git+https://github.com/tokio-rs/tokio.git?branch=master#7a30504fd423aa782239ead92f3afa3474f8037c" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", "tokio-macros", "tracing", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1761,13 +1768,12 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +version = "2.2.0" +source = "git+https://github.com/tokio-rs/tokio.git?branch=master#7a30504fd423aa782239ead92f3afa3474f8037c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.90", + "syn 2.0.33", ] [[package]] @@ -2122,9 +2128,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] @@ -2148,19 +2154,6 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -2197,12 +2190,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2215,12 +2202,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2233,12 +2214,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2251,12 +2226,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2275,12 +2244,6 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 153d99d70..34423d474 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,6 @@ members = [ "xtask" ] resolver = "2" + +[patch.crates-io] +tokio = { git = "https://github.com/tokio-rs/tokio.git", branch = "master" } diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index 113a4ceb3..5d9d19ded 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -32,7 +32,7 @@ env-filter = ["tracing-subscriber/env-filter"] [dependencies] crossbeam-utils = "0.8.7" -tokio = { version = "^1.21", features = ["sync", "time", "macros", "tracing"] } +tokio = { version = "1.34", features = ["sync", "time", "macros", "tracing"] } tokio-stream = { version = "0.1", features = ["net"] } thread_local = "1.1.3" console-api = { version = "0.6.0", path = "../console-api", features = ["transport"] } @@ -54,7 +54,7 @@ serde_json = "1" crossbeam-channel = "0.5" [dev-dependencies] -tokio = { version = "^1.21", features = ["full", "rt-multi-thread"] } +tokio = { version = "1.34", features = ["full", "rt-multi-thread"] } tower = { version = "0.4", default-features = false } futures = "0.3" diff --git a/console-subscriber/tests/framework.rs b/console-subscriber/tests/framework.rs index 777d49c06..c5e10c3f7 100644 --- a/console-subscriber/tests/framework.rs +++ b/console-subscriber/tests/framework.rs @@ -11,7 +11,7 @@ use futures::future; use tokio::{task, time::sleep}; mod support; -use support::{assert_task, assert_tasks, ExpectedTask}; +use support::{assert_task, assert_tasks, ExpectedTask, TaskState}; #[test] fn expect_present() { @@ -198,6 +198,54 @@ fn fail_polls() { assert_task(expected_task, future); } +#[test] +fn main_task_completes() { + let expected_task = ExpectedTask::default() + .match_default_name() + .expect_state(TaskState::Completed); + + let future = async {}; + + assert_task(expected_task, future); +} + +#[test] +#[should_panic(expected = "Test failed: Task validation failed: + - Task { name=task }: expected `state` to be Idle, but actual was Completed")] +fn fail_completed_task_is_idle() { + let expected_task = ExpectedTask::default() + .match_name("task".into()) + .expect_state(TaskState::Idle); + + let future = async { + _ = task::Builder::new() + .name("task") + .spawn(futures::future::ready(())) + .unwrap() + .await; + }; + + assert_task(expected_task, future); +} + +#[test] +#[should_panic(expected = "Test failed: Task validation failed: + - Task { name=task }: expected `state` to be Completed, but actual was Idle")] +fn fail_idle_task_is_completed() { + let expected_task = ExpectedTask::default() + .match_name("task".into()) + .expect_state(TaskState::Completed); + + let future = async { + _ = task::Builder::new() + .name("task") + .spawn(futures::future::pending::<()>()) + .unwrap(); + }; + + assert_task(expected_task, future); +} + async fn yield_to_runtime() { // There is a race condition that can occur when tests are run in parallel, // caused by tokio-rs/tracing#2743. It tends to cause test failures only diff --git a/console-subscriber/tests/spawn.rs b/console-subscriber/tests/spawn.rs index 21ecaad41..30ecb861e 100644 --- a/console-subscriber/tests/spawn.rs +++ b/console-subscriber/tests/spawn.rs @@ -3,7 +3,7 @@ use std::time::Duration; use tokio::time::sleep; mod support; -use support::{assert_tasks, spawn_named, ExpectedTask}; +use support::{assert_tasks, spawn_named, ExpectedTask, TaskState}; /// This test asserts the behavior that was fixed in #440. Before that fix, /// the polls of a child were also counted towards the parent (the task which @@ -34,3 +34,28 @@ fn child_polls_dont_count_towards_parent_polls() { assert_tasks(expected_tasks, future); } + +/// This test asserts that the lifetime of a task is not affected by the +/// lifetimes of tasks that it spawns. The test will pass when #345 is +/// fixed. +#[test] +fn spawner_task_with_running_children_completes() { + let expected_tasks = vec![ + ExpectedTask::default() + .match_name("parent".into()) + .expect_state(TaskState::Completed), + ExpectedTask::default() + .match_name("child".into()) + .expect_state(TaskState::Idle), + ]; + + let future = async { + spawn_named("parent", async { + spawn_named("child", futures::future::pending::<()>()); + }) + .await + .expect("joining parent failed"); + }; + + assert_tasks(expected_tasks, future); +} diff --git a/console-subscriber/tests/support/mod.rs b/console-subscriber/tests/support/mod.rs index 3a42583a2..090dd824f 100644 --- a/console-subscriber/tests/support/mod.rs +++ b/console-subscriber/tests/support/mod.rs @@ -8,6 +8,8 @@ use subscriber::run_test; pub(crate) use subscriber::MAIN_TASK_NAME; pub(crate) use task::ExpectedTask; +#[allow(unused_imports)] +pub(crate) use task::TaskState; use tokio::task::JoinHandle; /// Assert that an `expected_task` is recorded by a console-subscriber diff --git a/console-subscriber/tests/support/task.rs b/console-subscriber/tests/support/task.rs index 593d45f97..24adef7c2 100644 --- a/console-subscriber/tests/support/task.rs +++ b/console-subscriber/tests/support/task.rs @@ -1,6 +1,7 @@ -use std::{error, fmt}; +use std::{error, fmt, time::SystemTime}; use console_api::tasks; +use prost_types::Timestamp; use super::MAIN_TASK_NAME; @@ -13,6 +14,7 @@ use super::MAIN_TASK_NAME; pub(super) struct ActualTask { pub(super) id: u64, pub(super) name: Option, + pub(super) state: Option, pub(super) wakes: u64, pub(super) self_wakes: u64, pub(super) polls: u64, @@ -23,6 +25,7 @@ impl ActualTask { Self { id, name: None, + state: None, wakes: 0, self_wakes: 0, polls: 0, @@ -35,6 +38,59 @@ impl ActualTask { if let Some(poll_stats) = &stats.poll_stats { self.polls = poll_stats.polls; } + + self.state = calculate_task_state(stats); + } +} + +/// The state of a task. +/// +/// The task state is an amalgamation of a various fields. It is presented in +/// this way to make testing more straight forward. +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) enum TaskState { + /// Task has completed. + /// + /// Indicates that [`dropped_at`] has some value. + /// + /// [`dropped_at`]: fn@tasks::Stats::dropped_at + Completed, + /// Task is being polled. + /// + /// Indicates that the task is not [`Completed`] and the + /// [`last_poll_started`] time is later than [`last_poll_ended`] (or + /// [`last_poll_ended`] has not been set). + Running, + /// Task has been scheduled. + /// + /// Indicates that the task is not [`Completed`] and the [`last_wake`] time + /// is later than [`last_poll_started`]. + Scheduled, + /// Task is idle. + /// + /// Indicates that the task is between polls. + Idle, +} + +fn calculate_task_state(stats: &tasks::Stats) -> Option { + if stats.dropped_at.is_some() { + return Some(TaskState::Completed); + } + + fn convert(ts: &Option) -> Option { + ts.as_ref().map(|v| v.clone().try_into().unwrap()) + } + let poll_stats = stats.poll_stats.as_ref()?; + let last_poll_started = convert(&poll_stats.last_poll_started); + let last_poll_ended = convert(&poll_stats.last_poll_ended); + let last_wake = convert(&stats.last_wake); + + if last_poll_started > last_poll_ended { + Some(TaskState::Running) + } else if last_wake > last_poll_started { + Some(TaskState::Scheduled) + } else { + Some(TaskState::Idle) } } @@ -88,6 +144,7 @@ impl fmt::Debug for TaskValidationFailure { pub(crate) struct ExpectedTask { match_name: Option, expect_present: Option, + expect_state: Option, expect_wakes: Option, expect_self_wakes: Option, expect_polls: Option, @@ -98,6 +155,7 @@ impl Default for ExpectedTask { Self { match_name: None, expect_present: None, + expect_state: None, expect_wakes: None, expect_self_wakes: None, expect_polls: None, @@ -147,6 +205,28 @@ impl ExpectedTask { no_expectations = false; } + if let Some(expected_state) = &self.expect_state { + no_expectations = false; + if let Some(actual_state) = &actual_task.state { + if expected_state != actual_state { + return Err(TaskValidationFailure { + expected: self.clone(), + actual: Some(actual_task.clone()), + failure: format!( + "{self}: expected `state` to be \ + {expected_state:?}, but actual was \ + {actual_state}", + actual_state = actual_task + .state + .as_ref() + .map(|s| format!("{:?}", s)) + .unwrap_or("None".into()), + ), + }); + } + } + } + if let Some(expected_wakes) = self.expect_wakes { no_expectations = false; if expected_wakes != actual_task.wakes { @@ -239,6 +319,16 @@ impl ExpectedTask { self } + /// Expects that a task has a specific [`TaskState`]. + /// + /// To validate, the actual task must be in this state at the time + /// the test ends and the validation is performed. + #[allow(dead_code)] + pub(crate) fn expect_state(mut self, state: TaskState) -> Self { + self.expect_state = Some(state); + self + } + /// Expects that a task has a specific value for `wakes`. /// /// To validate, the actual task matching this expected task must have