aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-10 13:47:38 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-10 13:47:38 +0000
commit3990f93921537c4ab4db3a89b76ecfd20504d93f (patch)
tree367522218a1dd58880fa187419c22d37bd035324 /crates
parentaca14c591fea40b2f803bbf5f02c1571732348fb (diff)
parent32fa084c07375c7a596e0bfceddbef1830ae23e7 (diff)
Merge #481
481: introduce marking infrastructure for maintainable tests r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/lib.rs2
-rw-r--r--crates/ra_hir/src/marks.rs82
-rw-r--r--crates/ra_hir/src/module_tree.rs6
-rw-r--r--crates/ra_hir/src/nameres/tests.rs29
4 files changed, 118 insertions, 1 deletions
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 1b6b72c98..b8246a7d1 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -17,6 +17,8 @@ macro_rules! ctry {
17pub mod db; 17pub mod db;
18#[cfg(test)] 18#[cfg(test)]
19mod mock; 19mod mock;
20#[macro_use]
21mod marks;
20mod query_definitions; 22mod query_definitions;
21mod path; 23mod path;
22pub mod source_binder; 24pub mod source_binder;
diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs
new file mode 100644
index 000000000..05430b975
--- /dev/null
+++ b/crates/ra_hir/src/marks.rs
@@ -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]
29macro_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]
39macro_rules! covers {
40 ($ident:ident) => {
41 let _checker = crate::marks::marks::MarkChecker::new(&crate::marks::marks::$ident);
42 };
43}
44
45#[cfg(test)]
46pub(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}
diff --git a/crates/ra_hir/src/module_tree.rs b/crates/ra_hir/src/module_tree.rs
index d2c92f150..50383c6d8 100644
--- a/crates/ra_hir/src/module_tree.rs
+++ b/crates/ra_hir/src/module_tree.rs
@@ -14,7 +14,7 @@ use ra_arena::{Arena, RawId, impl_arena_id};
14use crate::{Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource}; 14use crate::{Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource};
15 15
16impl ModuleSource { 16impl ModuleSource {
17 pub fn from_source_item_id( 17 pub(crate) fn from_source_item_id(
18 db: &impl HirDatabase, 18 db: &impl HirDatabase,
19 source_item_id: SourceItemId, 19 source_item_id: SourceItemId,
20 ) -> ModuleSource { 20 ) -> ModuleSource {
@@ -217,6 +217,10 @@ fn modules(root: &impl ast::ModuleItemOwner) -> impl Iterator<Item = (Name, &ast
217 }) 217 })
218 .filter_map(|module| { 218 .filter_map(|module| {
219 let name = module.name()?.as_name(); 219 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 }
220 Some((name, module)) 224 Some((name, module))
221 }) 225 })
222} 226}
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs
index 17de54b44..ba9fcb3d1 100644
--- a/crates/ra_hir/src/nameres/tests.rs
+++ b/crates/ra_hir/src/nameres/tests.rs
@@ -137,6 +137,35 @@ fn re_exports() {
137} 137}
138 138
139#[test] 139#[test]
140fn 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
168#[test]
140fn item_map_contains_items_from_expansions() { 169fn item_map_contains_items_from_expansions() {
141 let (item_map, module_id) = item_map( 170 let (item_map, module_id) = item_map(
142 " 171 "