diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs index a0363bc78b..26acf81f28 100644 --- a/crates/ra_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs @@ -1,11 +1,11 @@ use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass}; use ra_syntax::{ast, AstNode, SyntaxKind, T}; +use test_utils::mark; use crate::{ assist_context::{AssistContext, Assists}, AssistId, }; -use test_utils::tested_by; // Assist: add_turbo_fish // @@ -28,7 +28,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; let next_token = ident.next_token()?; if next_token.kind() == T![::] { - tested_by!(add_turbo_fish_one_fish_is_enough); + mark::hit!(add_turbo_fish_one_fish_is_enough); return None; } let name_ref = ast::NameRef::cast(ident.parent())?; @@ -42,7 +42,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( }; let generics = hir::GenericDef::Function(fun).params(ctx.sema.db); if generics.is_empty() { - tested_by!(add_turbo_fish_non_generic); + mark::hit!(add_turbo_fish_non_generic); return None; } acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { @@ -58,7 +58,7 @@ mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; - use test_utils::covers; + use test_utils::mark; #[test] fn add_turbo_fish_function() { @@ -106,7 +106,7 @@ fn main() { #[test] fn add_turbo_fish_one_fish_is_enough() { - covers!(add_turbo_fish_one_fish_is_enough); + mark::check!(add_turbo_fish_one_fish_is_enough); check_assist_not_applicable( add_turbo_fish, r#" @@ -120,7 +120,7 @@ fn main() { #[test] fn add_turbo_fish_non_generic() { - covers!(add_turbo_fish_non_generic); + mark::check!(add_turbo_fish_non_generic); check_assist_not_applicable( add_turbo_fish, r#" diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs index 722f3c6a4b..525ec4abc3 100644 --- a/crates/ra_assists/src/marks.rs +++ b/crates/ra_assists/src/marks.rs @@ -10,6 +10,4 @@ test_utils::marks![ test_not_applicable_if_variable_unused change_visibility_field_false_positive test_add_from_impl_already_exists - add_turbo_fish_one_fish_is_enough - add_turbo_fish_non_generic ]; diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b1e3c328f3..4e05d464f0 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -8,6 +8,8 @@ #[macro_use] pub mod marks; +#[macro_use] +pub mod mark; use std::{ fs, diff --git a/crates/test_utils/src/mark.rs b/crates/test_utils/src/mark.rs new file mode 100644 index 0000000000..7c309a8945 --- /dev/null +++ b/crates/test_utils/src/mark.rs @@ -0,0 +1,78 @@ +//! This module implements manually tracked test coverage, which is useful for +//! quickly finding a test responsible for testing a particular bit of code. +//! +//! See +//! for details, but the TL;DR is that you write your test as +//! +//! ``` +//! #[test] +//! fn test_foo() { +//! mark::check!(test_foo); +//! } +//! ``` +//! +//! and in the code under test you write +//! +//! ``` +//! # use test_utils::mark; +//! # fn some_condition() -> bool { true } +//! fn foo() { +//! if some_condition() { +//! mark::hit!(test_foo); +//! } +//! } +//! ``` +//! +//! This module then checks that executing the test indeed covers the specified +//! function. This is useful if you come back to the `foo` function ten years +//! later and wonder where the test are: now you can grep for `test_foo`. +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[macro_export] +macro_rules! _hit { + ($ident:ident) => {{ + #[cfg(test)] + { + extern "C" { + #[no_mangle] + static $ident: std::sync::atomic::AtomicUsize; + } + unsafe { + $ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + } + }}; +} +pub use _hit as hit; + +#[macro_export] +macro_rules! _check { + ($ident:ident) => { + #[no_mangle] + static $ident: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + let _checker = $crate::mark::MarkChecker::new(&$ident); + }; +} +pub use _check as check; + +pub struct MarkChecker { + mark: &'static AtomicUsize, + value_on_entry: usize, +} + +impl MarkChecker { + pub fn new(mark: &'static AtomicUsize) -> MarkChecker { + let value_on_entry = mark.load(Ordering::SeqCst); + MarkChecker { mark, value_on_entry } + } +} + +impl Drop for MarkChecker { + fn drop(&mut self) { + if std::thread::panicking() { + return; + } + let value_on_exit = self.mark.load(Ordering::SeqCst); + assert!(value_on_exit > self.value_on_entry, "mark was not hit") + } +}