Skip to content

Commit f8a4553

Browse files
committed
Auto merge of #17014 - Veykril:runnables-exported-main-test, r=Veykril
fix: Consider `exported_name="main"` functions in test modules as tests Fixesrust-lang/rust-analyzer#17011
2 parents 8e581ac + 5957835 commit f8a4553

6 files changed

Lines changed: 94 additions & 22 deletions

File tree

‎crates/hir-def/src/attr.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ impl Attrs {
141141
}
142142
}
143143

144+
pubfncfgs(&self) -> implIterator<Item = CfgExpr> + '_{
145+
self.by_key("cfg").tt_values().map(CfgExpr::parse)
146+
}
147+
144148
pub(crate)fnis_cfg_enabled(&self,cfg_options:&CfgOptions) -> bool{
145149
matchself.cfg(){
146150
None => true,

‎crates/hir/src/lib.rs‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,12 +2006,15 @@ impl Function {
20062006

20072007
/// is this a `fn main` or a function with an `export_name` of `main`?
20082008
pubfnis_main(self,db:&dynHirDatabase) -> bool{
2009-
if !self.module(db).is_crate_root(){
2010-
returnfalse;
2011-
}
20122009
let data = db.function_data(self.id);
2010+
data.attrs.export_name() == Some("main")
2011+
|| self.module(db).is_crate_root() && data.name.to_smol_str() == "main"
2012+
}
20132013

2014-
data.name.to_smol_str() == "main" || data.attrs.export_name() == Some("main")
2014+
/// Is this a function with an `export_name` of `main`?
2015+
pubfnexported_main(self,db:&dynHirDatabase) -> bool{
2016+
let data = db.function_data(self.id);
2017+
data.attrs.export_name() == Some("main")
20152018
}
20162019

20172020
/// Does this function have the ignore attribute?

‎crates/ide-assists/src/handlers/toggle_ignore.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use syntax::{
33
AstNode,AstToken,
44
};
55

6-
usecrate::{utils::test_related_attribute,AssistContext,AssistId,AssistKind,Assists};
6+
usecrate::{utils::test_related_attribute_syn,AssistContext,AssistId,AssistKind,Assists};
77

88
// Assist: toggle_ignore
99
//
@@ -26,7 +26,7 @@ use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind,
2626
pub(crate)fntoggle_ignore(acc:&mutAssists,ctx:&AssistContext<'_>) -> Option<()>{
2727
let attr: ast::Attr = ctx.find_node_at_offset()?;
2828
let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
29-
let attr = test_related_attribute(&func)?;
29+
let attr = test_related_attribute_syn(&func)?;
3030

3131
matchhas_ignore_attribute(&func){
3232
None => acc.add(

‎crates/ide-assists/src/utils.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Ex
7171
///
7272
/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
7373
/// but it's better than not to have the runnables for the tests at all.
74-
pubfntest_related_attribute(fn_def:&ast::Fn) -> Option<ast::Attr>{
74+
pubfntest_related_attribute_syn(fn_def:&ast::Fn) -> Option<ast::Attr>{
7575
fn_def.attrs().find_map(|attr| {
7676
let path = attr.path()?;
7777
let text = path.syntax().text().to_string();
@@ -83,6 +83,19 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
8383
})
8484
}
8585

86+
pubfnhas_test_related_attribute(attrs:&hir::AttrsWithOwner) -> bool{
87+
attrs.iter().any(|attr| {
88+
let path = attr.path();
89+
(|| {
90+
Some(
91+
path.segments().first()?.as_text()?.starts_with("test")
92+
|| path.segments().last()?.as_text()?.ends_with("test"),
93+
)
94+
})()
95+
.unwrap_or_default()
96+
})
97+
}
98+
8699
#[derive(Clone,Copy,PartialEq)]
87100
pubenumIgnoreAssocItems{
88101
DocHiddenAttrPresent,

‎crates/ide/src/annotations/fn_references.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! We have to skip tests, so cannot reuse file_structure module.
33
44
use hir::Semantics;
5-
use ide_assists::utils::test_related_attribute;
5+
use ide_assists::utils::test_related_attribute_syn;
66
use ide_db::RootDatabase;
77
use syntax::{ast, ast::HasName,AstNode,SyntaxNode,TextRange};
88

@@ -19,7 +19,7 @@ pub(super) fn find_all_methods(
1919

2020
fnmethod_range(item:SyntaxNode) -> Option<(TextRange,Option<TextRange>)>{
2121
ast::Fn::cast(item).and_then(|fn_def| {
22-
iftest_related_attribute(&fn_def).is_some(){
22+
iftest_related_attribute_syn(&fn_def).is_some(){
2323
None
2424
}else{
2525
Some((

‎crates/ide/src/runnables.rs‎

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use std::fmt;
22

33
use ast::HasName;
4-
use cfg::CfgExpr;
5-
use hir::{db::HirDatabase,AsAssocItem,HasAttrs,HasSource,HirFileIdExt,Semantics};
6-
use ide_assists::utils::test_related_attribute;
4+
use cfg::{CfgAtom,CfgExpr};
5+
use hir::{
6+
db::HirDatabase,AsAssocItem,AttrsWithOwner,HasAttrs,HasSource,HirFileIdExt,Semantics,
7+
};
8+
use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
79
use ide_db::{
810
base_db::{FilePosition,FileRange},
911
defs::Definition,
@@ -280,7 +282,7 @@ fn find_related_tests_in_module(
280282
}
281283

282284
fnas_test_runnable(sema:&Semantics<'_,RootDatabase>,fn_def:&ast::Fn) -> Option<Runnable>{
283-
iftest_related_attribute(fn_def).is_some(){
285+
iftest_related_attribute_syn(fn_def).is_some(){
284286
let function = sema.to_def(fn_def)?;
285287
runnable_fn(sema, function)
286288
}else{
@@ -293,7 +295,7 @@ fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> O
293295
let module = ast::Module::cast(node)?;
294296
let module = sema.to_def(&module)?;
295297

296-
ifhas_test_function_or_multiple_test_submodules(sema,&module){
298+
ifhas_test_function_or_multiple_test_submodules(sema,&module,false){
297299
Some(module)
298300
}else{
299301
None
@@ -305,7 +307,8 @@ pub(crate) fn runnable_fn(
305307
sema:&Semantics<'_,RootDatabase>,
306308
def: hir::Function,
307309
) -> Option<Runnable>{
308-
let kind = if def.is_main(sema.db){
310+
let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db));
311+
let kind = if !under_cfg_test && def.is_main(sema.db){
309312
RunnableKind::Bin
310313
}else{
311314
let test_id = || {
@@ -342,7 +345,8 @@ pub(crate) fn runnable_mod(
342345
sema:&Semantics<'_,RootDatabase>,
343346
def: hir::Module,
344347
) -> Option<Runnable>{
345-
if !has_test_function_or_multiple_test_submodules(sema,&def){
348+
if !has_test_function_or_multiple_test_submodules(sema,&def,has_cfg_test(def.attrs(sema.db)))
349+
{
346350
returnNone;
347351
}
348352
let path = def
@@ -384,12 +388,17 @@ pub(crate) fn runnable_impl(
384388
Some(Runnable{use_name_in_title:false, nav,kind:RunnableKind::DocTest{ test_id }, cfg })
385389
}
386390

391+
fnhas_cfg_test(attrs:AttrsWithOwner) -> bool{
392+
attrs.cfgs().any(|cfg| matches!(cfg,CfgExpr::Atom(CfgAtom::Flag(s))if s == "test"))
393+
}
394+
387395
/// Creates a test mod runnable for outline modules at the top of their definition.
388396
fnrunnable_mod_outline_definition(
389397
sema:&Semantics<'_,RootDatabase>,
390398
def: hir::Module,
391399
) -> Option<Runnable>{
392-
if !has_test_function_or_multiple_test_submodules(sema,&def){
400+
if !has_test_function_or_multiple_test_submodules(sema,&def,has_cfg_test(def.attrs(sema.db)))
401+
{
393402
returnNone;
394403
}
395404
let path = def
@@ -522,20 +531,28 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
522531
fnhas_test_function_or_multiple_test_submodules(
523532
sema:&Semantics<'_,RootDatabase>,
524533
module:&hir::Module,
534+
consider_exported_main:bool,
525535
) -> bool{
526536
letmut number_of_test_submodules = 0;
527537

528538
for item in module.declarations(sema.db){
529539
match item {
530540
hir::ModuleDef::Function(f) => {
531-
ifletSome(it) = f.source(sema.db){
532-
iftest_related_attribute(&it.value).is_some(){
533-
returntrue;
534-
}
541+
ifhas_test_related_attribute(&f.attrs(sema.db)){
542+
returntrue;
543+
}
544+
if consider_exported_main && f.exported_main(sema.db){
545+
// an exported main in a test module can be considered a test wrt to custom test
546+
// runners
547+
returntrue;
535548
}
536549
}
537550
hir::ModuleDef::Module(submodule) => {
538-
ifhas_test_function_or_multiple_test_submodules(sema,&submodule){
551+
ifhas_test_function_or_multiple_test_submodules(
552+
sema,
553+
&submodule,
554+
consider_exported_main,
555+
){
539556
number_of_test_submodules += 1;
540557
}
541558
}
@@ -1484,4 +1501,39 @@ mod r#mod {
14841501
"#]],
14851502
)
14861503
}
1504+
1505+
#[test]
1506+
fnexported_main_is_test_in_cfg_test_mod(){
1507+
check(
1508+
r#"
1509+
//- /lib.rs crate:foo cfg:test
1510+
$0
1511+
mod not_a_test_module_inline {
1512+
#[export_name = "main"]
1513+
fn exp_main() {}
1514+
}
1515+
#[cfg(test)]
1516+
mod test_mod_inline {
1517+
#[export_name = "main"]
1518+
fn exp_main() {}
1519+
}
1520+
mod not_a_test_module;
1521+
#[cfg(test)]
1522+
mod test_mod;
1523+
//- /not_a_test_module.rs
1524+
#[export_name = "main"]
1525+
fn exp_main() {}
1526+
//- /test_mod.rs
1527+
#[export_name = "main"]
1528+
fn exp_main() {}
1529+
"#,
1530+
expect![[r#"
1531+
[
1532+
"(Bin, NavigationTarget { file_id: FileId(0), full_range: 36..80, focus_range: 67..75, name: \"exp_main\", kind: Function })",
1533+
"(TestMod, NavigationTarget { file_id: FileId(0), full_range: 83..168, focus_range: 100..115, name: \"test_mod_inline\", kind: Module, description: \"mod test_mod_inline\" }, Atom(Flag(\"test\")))",
1534+
"(TestMod, NavigationTarget { file_id: FileId(0), full_range: 192..218, focus_range: 209..217, name: \"test_mod\", kind: Module, description: \"mod test_mod\" }, Atom(Flag(\"test\")))",
1535+
]
1536+
"#]],
1537+
)
1538+
}
14871539
}

0 commit comments

Comments
 (0)