Skip to content

Commit 3990f93

Browse files
bors[bot]matklad
andcommitted
Merge #481
481: introduce marking infrastructure for maintainable tests r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
2 parents aca14c5 + 32fa084 commit 3990f93

File tree

4 files changed

+118
-1
lines changed

4 files changed

+118
-1
lines changed

crates/ra_hir/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ macro_rules! ctry {
1717
pub mod db;
1818
#[cfg(test)]
1919
mod mock;
20+
#[macro_use]
21+
mod marks;
2022
mod query_definitions;
2123
mod path;
2224
pub mod source_binder;

crates/ra_hir/src/marks.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//! This module implements manually tracked test coverage, which useful for
2+
//! quickly finding a test responsible for testing a particular bit of code.
3+
//!
4+
//! See https://matklad.github.io/2018/06/18/a-trick-for-test-maintenance.html
5+
//! for details, but the TL;DR is that you write your test as
6+
//!
7+
//! ```no-run
8+
//! #[test]
9+
//! fn test_foo() {
10+
//! covers!(test_foo);
11+
//! }
12+
//! ```
13+
//!
14+
//! and in the code under test you write
15+
//!
16+
//! ```no-run
17+
//! fn foo() {
18+
//! if some_condition() {
19+
//! tested_by!(test_foo);
20+
//! }
21+
//! }
22+
//! ```
23+
//!
24+
//! This module then checks that executing the test indeed covers the specified
25+
//! function. This is useful if you come back to the `foo` function ten years
26+
//! later and wonder where the test are: now you can grep for `test_foo`.
27+
28+
#[macro_export]
29+
macro_rules! tested_by {
30+
($ident:ident) => {
31+
#[cfg(test)]
32+
{
33+
crate::marks::marks::$ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
34+
}
35+
};
36+
}
37+
38+
#[macro_export]
39+
macro_rules! covers {
40+
($ident:ident) => {
41+
let _checker = crate::marks::marks::MarkChecker::new(&crate::marks::marks::$ident);
42+
};
43+
}
44+
45+
#[cfg(test)]
46+
pub(crate) mod marks {
47+
use std::sync::atomic::{AtomicUsize, Ordering};
48+
49+
pub(crate) struct MarkChecker {
50+
mark: &'static AtomicUsize,
51+
value_on_entry: usize,
52+
}
53+
54+
impl MarkChecker {
55+
pub(crate) fn new(mark: &'static AtomicUsize) -> MarkChecker {
56+
let value_on_entry = mark.load(Ordering::SeqCst);
57+
MarkChecker {
58+
mark,
59+
value_on_entry,
60+
}
61+
}
62+
}
63+
64+
impl Drop for MarkChecker {
65+
fn drop(&mut self) {
66+
if std::thread::panicking() {
67+
return;
68+
}
69+
let value_on_exit = self.mark.load(Ordering::SeqCst);
70+
assert!(value_on_exit > self.value_on_entry, "mark was not hit")
71+
}
72+
}
73+
74+
macro_rules! mark {
75+
($ident:ident) => {
76+
#[allow(bad_style)]
77+
pub(crate) static $ident: AtomicUsize = AtomicUsize::new(0);
78+
};
79+
}
80+
81+
mark!(name_res_works_for_broken_modules);
82+
}

crates/ra_hir/src/module_tree.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use ra_arena::{Arena, RawId, impl_arena_id};
1414
use crate::{Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource};
1515

1616
impl ModuleSource {
17-
pub fn from_source_item_id(
17+
pub(crate) fn from_source_item_id(
1818
db: &impl HirDatabase,
1919
source_item_id: SourceItemId,
2020
) -> ModuleSource {
@@ -217,6 +217,10 @@ fn modules(root: &impl ast::ModuleItemOwner) -> impl Iterator<Item = (Name, &ast
217217
})
218218
.filter_map(|module| {
219219
let name = module.name()?.as_name();
220+
if !module.has_semi() && module.item_list().is_none() {
221+
tested_by!(name_res_works_for_broken_modules);
222+
return None;
223+
}
220224
Some((name, module))
221225
})
222226
}

crates/ra_hir/src/nameres/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,35 @@ fn re_exports() {
136136
);
137137
}
138138

139+
#[test]
140+
fn name_res_works_for_broken_modules() {
141+
covers!(name_res_works_for_broken_modules);
142+
let (item_map, module_id) = item_map(
143+
"
144+
//- /lib.rs
145+
mod foo // no `;`, no body
146+
147+
use self::foo::Baz;
148+
<|>
149+
150+
//- /foo/mod.rs
151+
pub mod bar;
152+
153+
pub use self::bar::Baz;
154+
155+
//- /foo/bar.rs
156+
pub struct Baz;
157+
",
158+
);
159+
check_module_item_map(
160+
&item_map,
161+
module_id,
162+
"
163+
Baz: _
164+
",
165+
);
166+
}
167+
139168
#[test]
140169
fn item_map_contains_items_from_expansions() {
141170
let (item_map, module_id) = item_map(

0 commit comments

Comments
 (0)