mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Consider exported_name="main"
functions in test modules as tests
This commit is contained in:
parent
2b62d4b2ba
commit
5957835cdf
6 changed files with 94 additions and 22 deletions
|
@ -141,6 +141,10 @@ impl Attrs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cfgs(&self) -> impl Iterator<Item = CfgExpr> + '_ {
|
||||||
|
self.by_key("cfg").tt_values().map(CfgExpr::parse)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
|
pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
|
||||||
match self.cfg() {
|
match self.cfg() {
|
||||||
None => true,
|
None => true,
|
||||||
|
|
|
@ -2006,12 +2006,15 @@ impl Function {
|
||||||
|
|
||||||
/// is this a `fn main` or a function with an `export_name` of `main`?
|
/// is this a `fn main` or a function with an `export_name` of `main`?
|
||||||
pub fn is_main(self, db: &dyn HirDatabase) -> bool {
|
pub fn is_main(self, db: &dyn HirDatabase) -> bool {
|
||||||
if !self.module(db).is_crate_root() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let data = db.function_data(self.id);
|
let data = db.function_data(self.id);
|
||||||
|
data.attrs.export_name() == Some("main")
|
||||||
|
|| self.module(db).is_crate_root() && data.name.to_smol_str() == "main"
|
||||||
|
}
|
||||||
|
|
||||||
data.name.to_smol_str() == "main" || data.attrs.export_name() == Some("main")
|
/// Is this a function with an `export_name` of `main`?
|
||||||
|
pub fn exported_main(self, db: &dyn HirDatabase) -> bool {
|
||||||
|
let data = db.function_data(self.id);
|
||||||
|
data.attrs.export_name() == Some("main")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this function have the ignore attribute?
|
/// Does this function have the ignore attribute?
|
||||||
|
|
|
@ -3,7 +3,7 @@ use syntax::{
|
||||||
AstNode, AstToken,
|
AstNode, AstToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
|
use crate::{utils::test_related_attribute_syn, AssistContext, AssistId, AssistKind, Assists};
|
||||||
|
|
||||||
// Assist: toggle_ignore
|
// Assist: toggle_ignore
|
||||||
//
|
//
|
||||||
|
@ -26,7 +26,7 @@ use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind,
|
||||||
pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||||
let attr: ast::Attr = ctx.find_node_at_offset()?;
|
let attr: ast::Attr = ctx.find_node_at_offset()?;
|
||||||
let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
|
let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
|
||||||
let attr = test_related_attribute(&func)?;
|
let attr = test_related_attribute_syn(&func)?;
|
||||||
|
|
||||||
match has_ignore_attribute(&func) {
|
match has_ignore_attribute(&func) {
|
||||||
None => acc.add(
|
None => acc.add(
|
||||||
|
|
|
@ -71,7 +71,7 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Ex
|
||||||
///
|
///
|
||||||
/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
|
/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
|
||||||
/// but it's better than not to have the runnables for the tests at all.
|
/// but it's better than not to have the runnables for the tests at all.
|
||||||
pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
|
pub fn test_related_attribute_syn(fn_def: &ast::Fn) -> Option<ast::Attr> {
|
||||||
fn_def.attrs().find_map(|attr| {
|
fn_def.attrs().find_map(|attr| {
|
||||||
let path = attr.path()?;
|
let path = attr.path()?;
|
||||||
let text = path.syntax().text().to_string();
|
let text = path.syntax().text().to_string();
|
||||||
|
@ -83,6 +83,19 @@ pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_test_related_attribute(attrs: &hir::AttrsWithOwner) -> bool {
|
||||||
|
attrs.iter().any(|attr| {
|
||||||
|
let path = attr.path();
|
||||||
|
(|| {
|
||||||
|
Some(
|
||||||
|
path.segments().first()?.as_text()?.starts_with("test")
|
||||||
|
|| path.segments().last()?.as_text()?.ends_with("test"),
|
||||||
|
)
|
||||||
|
})()
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum IgnoreAssocItems {
|
pub enum IgnoreAssocItems {
|
||||||
DocHiddenAttrPresent,
|
DocHiddenAttrPresent,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! We have to skip tests, so cannot reuse file_structure module.
|
//! We have to skip tests, so cannot reuse file_structure module.
|
||||||
|
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ide_assists::utils::test_related_attribute;
|
use ide_assists::utils::test_related_attribute_syn;
|
||||||
use ide_db::RootDatabase;
|
use ide_db::RootDatabase;
|
||||||
use syntax::{ast, ast::HasName, AstNode, SyntaxNode, TextRange};
|
use syntax::{ast, ast::HasName, AstNode, SyntaxNode, TextRange};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ pub(super) fn find_all_methods(
|
||||||
|
|
||||||
fn method_range(item: SyntaxNode) -> Option<(TextRange, Option<TextRange>)> {
|
fn method_range(item: SyntaxNode) -> Option<(TextRange, Option<TextRange>)> {
|
||||||
ast::Fn::cast(item).and_then(|fn_def| {
|
ast::Fn::cast(item).and_then(|fn_def| {
|
||||||
if test_related_attribute(&fn_def).is_some() {
|
if test_related_attribute_syn(&fn_def).is_some() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some((
|
Some((
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use ast::HasName;
|
use ast::HasName;
|
||||||
use cfg::CfgExpr;
|
use cfg::{CfgAtom, CfgExpr};
|
||||||
use hir::{db::HirDatabase, AsAssocItem, HasAttrs, HasSource, HirFileIdExt, Semantics};
|
use hir::{
|
||||||
use ide_assists::utils::test_related_attribute;
|
db::HirDatabase, AsAssocItem, AttrsWithOwner, HasAttrs, HasSource, HirFileIdExt, Semantics,
|
||||||
|
};
|
||||||
|
use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{FilePosition, FileRange},
|
base_db::{FilePosition, FileRange},
|
||||||
defs::Definition,
|
defs::Definition,
|
||||||
|
@ -280,7 +282,7 @@ fn find_related_tests_in_module(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
|
fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
|
||||||
if test_related_attribute(fn_def).is_some() {
|
if test_related_attribute_syn(fn_def).is_some() {
|
||||||
let function = sema.to_def(fn_def)?;
|
let function = sema.to_def(fn_def)?;
|
||||||
runnable_fn(sema, function)
|
runnable_fn(sema, function)
|
||||||
} else {
|
} else {
|
||||||
|
@ -293,7 +295,7 @@ fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> O
|
||||||
let module = ast::Module::cast(node)?;
|
let module = ast::Module::cast(node)?;
|
||||||
let module = sema.to_def(&module)?;
|
let module = sema.to_def(&module)?;
|
||||||
|
|
||||||
if has_test_function_or_multiple_test_submodules(sema, &module) {
|
if has_test_function_or_multiple_test_submodules(sema, &module, false) {
|
||||||
Some(module)
|
Some(module)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -305,7 +307,8 @@ pub(crate) fn runnable_fn(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
def: hir::Function,
|
def: hir::Function,
|
||||||
) -> Option<Runnable> {
|
) -> Option<Runnable> {
|
||||||
let kind = if def.is_main(sema.db) {
|
let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db));
|
||||||
|
let kind = if !under_cfg_test && def.is_main(sema.db) {
|
||||||
RunnableKind::Bin
|
RunnableKind::Bin
|
||||||
} else {
|
} else {
|
||||||
let test_id = || {
|
let test_id = || {
|
||||||
|
@ -342,7 +345,8 @@ pub(crate) fn runnable_mod(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
def: hir::Module,
|
def: hir::Module,
|
||||||
) -> Option<Runnable> {
|
) -> Option<Runnable> {
|
||||||
if !has_test_function_or_multiple_test_submodules(sema, &def) {
|
if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let path = def
|
let path = def
|
||||||
|
@ -384,12 +388,17 @@ pub(crate) fn runnable_impl(
|
||||||
Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
|
Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_cfg_test(attrs: AttrsWithOwner) -> bool {
|
||||||
|
attrs.cfgs().any(|cfg| matches!(cfg, CfgExpr::Atom(CfgAtom::Flag(s)) if s == "test"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a test mod runnable for outline modules at the top of their definition.
|
/// Creates a test mod runnable for outline modules at the top of their definition.
|
||||||
fn runnable_mod_outline_definition(
|
fn runnable_mod_outline_definition(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
def: hir::Module,
|
def: hir::Module,
|
||||||
) -> Option<Runnable> {
|
) -> Option<Runnable> {
|
||||||
if !has_test_function_or_multiple_test_submodules(sema, &def) {
|
if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let path = def
|
let path = def
|
||||||
|
@ -522,20 +531,28 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
|
||||||
fn has_test_function_or_multiple_test_submodules(
|
fn has_test_function_or_multiple_test_submodules(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
module: &hir::Module,
|
module: &hir::Module,
|
||||||
|
consider_exported_main: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut number_of_test_submodules = 0;
|
let mut number_of_test_submodules = 0;
|
||||||
|
|
||||||
for item in module.declarations(sema.db) {
|
for item in module.declarations(sema.db) {
|
||||||
match item {
|
match item {
|
||||||
hir::ModuleDef::Function(f) => {
|
hir::ModuleDef::Function(f) => {
|
||||||
if let Some(it) = f.source(sema.db) {
|
if has_test_related_attribute(&f.attrs(sema.db)) {
|
||||||
if test_related_attribute(&it.value).is_some() {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
if consider_exported_main && f.exported_main(sema.db) {
|
||||||
|
// an exported main in a test module can be considered a test wrt to custom test
|
||||||
|
// runners
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hir::ModuleDef::Module(submodule) => {
|
hir::ModuleDef::Module(submodule) => {
|
||||||
if has_test_function_or_multiple_test_submodules(sema, &submodule) {
|
if has_test_function_or_multiple_test_submodules(
|
||||||
|
sema,
|
||||||
|
&submodule,
|
||||||
|
consider_exported_main,
|
||||||
|
) {
|
||||||
number_of_test_submodules += 1;
|
number_of_test_submodules += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1484,4 +1501,39 @@ mod r#mod {
|
||||||
"#]],
|
"#]],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exported_main_is_test_in_cfg_test_mod() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:foo cfg:test
|
||||||
|
$0
|
||||||
|
mod not_a_test_module_inline {
|
||||||
|
#[export_name = "main"]
|
||||||
|
fn exp_main() {}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_mod_inline {
|
||||||
|
#[export_name = "main"]
|
||||||
|
fn exp_main() {}
|
||||||
|
}
|
||||||
|
mod not_a_test_module;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_mod;
|
||||||
|
//- /not_a_test_module.rs
|
||||||
|
#[export_name = "main"]
|
||||||
|
fn exp_main() {}
|
||||||
|
//- /test_mod.rs
|
||||||
|
#[export_name = "main"]
|
||||||
|
fn exp_main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
[
|
||||||
|
"(Bin, NavigationTarget { file_id: FileId(0), full_range: 36..80, focus_range: 67..75, name: \"exp_main\", kind: Function })",
|
||||||
|
"(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\")))",
|
||||||
|
"(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\")))",
|
||||||
|
]
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue