mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-26 11:59:49 +00:00
Add run-tests command
This commit is contained in:
parent
f0e00ed599
commit
674cd5ab57
8 changed files with 166 additions and 36 deletions
|
@ -272,6 +272,18 @@ impl Attrs {
|
||||||
self.by_key("proc_macro_derive").exists()
|
self.by_key("proc_macro_derive").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_test(&self) -> bool {
|
||||||
|
self.by_key("test").exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ignore(&self) -> bool {
|
||||||
|
self.by_key("ignore").exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_bench(&self) -> bool {
|
||||||
|
self.by_key("bench").exists()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_unstable(&self) -> bool {
|
pub fn is_unstable(&self) -> bool {
|
||||||
self.by_key("unstable").exists()
|
self.by_key("unstable").exists()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1927,6 +1927,21 @@ impl Function {
|
||||||
db.function_data(self.id).has_async_kw()
|
db.function_data(self.id).has_async_kw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does this function have `#[test]` attribute?
|
||||||
|
pub fn is_test(self, db: &dyn HirDatabase) -> bool {
|
||||||
|
db.function_data(self.id).attrs.is_test()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this function have the ignore attribute?
|
||||||
|
pub fn is_ignore(self, db: &dyn HirDatabase) -> bool {
|
||||||
|
db.function_data(self.id).attrs.is_ignore()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does this function have `#[bench]` attribute?
|
||||||
|
pub fn is_bench(self, db: &dyn HirDatabase) -> bool {
|
||||||
|
db.function_data(self.id).attrs.is_bench()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
|
pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
|
||||||
hir_ty::is_fn_unsafe_to_call(db, self.id)
|
hir_ty::is_fn_unsafe_to_call(db, self.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt;
|
||||||
|
|
||||||
use ast::HasName;
|
use ast::HasName;
|
||||||
use cfg::CfgExpr;
|
use cfg::CfgExpr;
|
||||||
use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
|
use hir::{db::HirDatabase, AsAssocItem, HasAttrs, HasSource, Semantics};
|
||||||
use ide_assists::utils::test_related_attribute;
|
use ide_assists::utils::test_related_attribute;
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{FilePosition, FileRange},
|
base_db::{FilePosition, FileRange},
|
||||||
|
@ -14,7 +14,7 @@ use ide_db::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use stdx::{always, format_to};
|
use stdx::{always, format_to};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, AstNode, HasAttrs as _},
|
ast::{self, AstNode},
|
||||||
SmolStr, SyntaxNode,
|
SmolStr, SyntaxNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -307,7 +307,6 @@ pub(crate) fn runnable_fn(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
def: hir::Function,
|
def: hir::Function,
|
||||||
) -> Option<Runnable> {
|
) -> Option<Runnable> {
|
||||||
let func = def.source(sema.db)?;
|
|
||||||
let name = def.name(sema.db).to_smol_str();
|
let name = def.name(sema.db).to_smol_str();
|
||||||
|
|
||||||
let root = def.module(sema.db).krate().root_module(sema.db);
|
let root = def.module(sema.db).krate().root_module(sema.db);
|
||||||
|
@ -323,10 +322,10 @@ pub(crate) fn runnable_fn(
|
||||||
canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name))
|
canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name))
|
||||||
};
|
};
|
||||||
|
|
||||||
if test_related_attribute(&func.value).is_some() {
|
if def.is_test(sema.db) {
|
||||||
let attr = TestAttr::from_fn(&func.value);
|
let attr = TestAttr::from_fn(sema.db, def);
|
||||||
RunnableKind::Test { test_id: test_id(), attr }
|
RunnableKind::Test { test_id: test_id(), attr }
|
||||||
} else if func.value.has_atom_attr("bench") {
|
} else if def.is_bench(sema.db) {
|
||||||
RunnableKind::Bench { test_id: test_id() }
|
RunnableKind::Bench { test_id: test_id() }
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
|
@ -335,7 +334,7 @@ pub(crate) fn runnable_fn(
|
||||||
|
|
||||||
let nav = NavigationTarget::from_named(
|
let nav = NavigationTarget::from_named(
|
||||||
sema.db,
|
sema.db,
|
||||||
func.as_ref().map(|it| it as &dyn ast::HasName),
|
def.source(sema.db)?.as_ref().map(|it| it as &dyn ast::HasName),
|
||||||
SymbolKind::Function,
|
SymbolKind::Function,
|
||||||
);
|
);
|
||||||
let cfg = def.attrs(sema.db).cfg();
|
let cfg = def.attrs(sema.db).cfg();
|
||||||
|
@ -487,12 +486,8 @@ pub struct TestAttr {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestAttr {
|
impl TestAttr {
|
||||||
fn from_fn(fn_def: &ast::Fn) -> TestAttr {
|
fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr {
|
||||||
let ignore = fn_def
|
TestAttr { ignore: fn_def.is_ignore(db) }
|
||||||
.attrs()
|
|
||||||
.filter_map(|attr| attr.simple_name())
|
|
||||||
.any(|attribute_text| attribute_text == "ignore");
|
|
||||||
TestAttr { ignore }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
|
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
|
||||||
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
|
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
|
||||||
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
|
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
|
||||||
|
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,17 @@ mod diagnostics;
|
||||||
mod ssr;
|
mod ssr;
|
||||||
mod lsif;
|
mod lsif;
|
||||||
mod scip;
|
mod scip;
|
||||||
|
mod run_tests;
|
||||||
|
|
||||||
mod progress_report;
|
mod progress_report;
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use hir::{Module, Name};
|
||||||
|
use hir_ty::db::HirDatabase;
|
||||||
use ide::AnalysisHost;
|
use ide::AnalysisHost;
|
||||||
|
use itertools::Itertools;
|
||||||
use vfs::Vfs;
|
use vfs::Vfs;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -70,3 +75,14 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
|
||||||
|
|
||||||
eprintln!("{remaining:>8} Remaining");
|
eprintln!("{remaining:>8} Remaining");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String {
|
||||||
|
module
|
||||||
|
.path_to_root(db)
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|it| it.name(db))
|
||||||
|
.chain(Some(name))
|
||||||
|
.map(|it| it.display(db.upcast()).to_string())
|
||||||
|
.join("::")
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ use vfs::{AbsPathBuf, Vfs, VfsPath};
|
||||||
|
|
||||||
use crate::cli::{
|
use crate::cli::{
|
||||||
flags::{self, OutputFormat},
|
flags::{self, OutputFormat},
|
||||||
|
full_name_of_item,
|
||||||
load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
|
load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
|
||||||
print_memory_usage,
|
print_memory_usage,
|
||||||
progress_report::ProgressReport,
|
progress_report::ProgressReport,
|
||||||
|
@ -274,15 +275,7 @@ impl flags::AnalysisStats {
|
||||||
continue
|
continue
|
||||||
};
|
};
|
||||||
if verbosity.is_spammy() {
|
if verbosity.is_spammy() {
|
||||||
let full_name = a
|
let full_name = full_name_of_item(db, a.module(db), a.name(db));
|
||||||
.module(db)
|
|
||||||
.path_to_root(db)
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.filter_map(|it| it.name(db))
|
|
||||||
.chain(Some(a.name(db)))
|
|
||||||
.map(|it| it.display(db).to_string())
|
|
||||||
.join("::");
|
|
||||||
println!("Data layout for {full_name} failed due {e:?}");
|
println!("Data layout for {full_name} failed due {e:?}");
|
||||||
}
|
}
|
||||||
fail += 1;
|
fail += 1;
|
||||||
|
@ -304,15 +297,8 @@ impl flags::AnalysisStats {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if verbosity.is_spammy() {
|
if verbosity.is_spammy() {
|
||||||
let full_name = c
|
let full_name =
|
||||||
.module(db)
|
full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
|
||||||
.path_to_root(db)
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.filter_map(|it| it.name(db))
|
|
||||||
.chain(c.name(db))
|
|
||||||
.map(|it| it.display(db).to_string())
|
|
||||||
.join("::");
|
|
||||||
println!("Const eval for {full_name} failed due {e:?}");
|
println!("Const eval for {full_name} failed due {e:?}");
|
||||||
}
|
}
|
||||||
fail += 1;
|
fail += 1;
|
||||||
|
|
|
@ -90,6 +90,12 @@ xflags::xflags! {
|
||||||
optional --skip-const-eval
|
optional --skip-const-eval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run unit tests of the project using mir interpreter
|
||||||
|
cmd run-tests {
|
||||||
|
/// Directory with Cargo.toml.
|
||||||
|
required path: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
cmd diagnostics {
|
cmd diagnostics {
|
||||||
/// Directory with Cargo.toml.
|
/// Directory with Cargo.toml.
|
||||||
required path: PathBuf
|
required path: PathBuf
|
||||||
|
@ -147,6 +153,7 @@ pub enum RustAnalyzerCmd {
|
||||||
Symbols(Symbols),
|
Symbols(Symbols),
|
||||||
Highlight(Highlight),
|
Highlight(Highlight),
|
||||||
AnalysisStats(AnalysisStats),
|
AnalysisStats(AnalysisStats),
|
||||||
|
RunTests(RunTests),
|
||||||
Diagnostics(Diagnostics),
|
Diagnostics(Diagnostics),
|
||||||
Ssr(Ssr),
|
Ssr(Ssr),
|
||||||
Search(Search),
|
Search(Search),
|
||||||
|
@ -182,16 +189,21 @@ pub struct AnalysisStats {
|
||||||
pub parallel: bool,
|
pub parallel: bool,
|
||||||
pub memory_usage: bool,
|
pub memory_usage: bool,
|
||||||
pub source_stats: bool,
|
pub source_stats: bool,
|
||||||
pub skip_lowering: bool,
|
|
||||||
pub skip_inference: bool,
|
|
||||||
pub skip_mir_stats: bool,
|
|
||||||
pub skip_data_layout: bool,
|
|
||||||
pub skip_const_eval: bool,
|
|
||||||
pub only: Option<String>,
|
pub only: Option<String>,
|
||||||
pub with_deps: bool,
|
pub with_deps: bool,
|
||||||
pub no_sysroot: bool,
|
pub no_sysroot: bool,
|
||||||
pub disable_build_scripts: bool,
|
pub disable_build_scripts: bool,
|
||||||
pub disable_proc_macros: bool,
|
pub disable_proc_macros: bool,
|
||||||
|
pub skip_lowering: bool,
|
||||||
|
pub skip_inference: bool,
|
||||||
|
pub skip_mir_stats: bool,
|
||||||
|
pub skip_data_layout: bool,
|
||||||
|
pub skip_const_eval: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RunTests {
|
||||||
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -223,6 +235,7 @@ pub struct Lsif {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scip {
|
pub struct Scip {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
||||||
pub output: Option<PathBuf>,
|
pub output: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
crates/rust-analyzer/src/cli/run_tests.rs
Normal file
92
crates/rust-analyzer/src/cli/run_tests.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
|
||||||
|
|
||||||
|
use hir::{Crate, Module};
|
||||||
|
use hir_ty::db::HirDatabase;
|
||||||
|
use ide_db::{base_db::SourceDatabaseExt, LineIndexDatabase};
|
||||||
|
use profile::StopWatch;
|
||||||
|
use project_model::{CargoConfig, RustLibSource};
|
||||||
|
use syntax::TextRange;
|
||||||
|
|
||||||
|
use crate::cli::{
|
||||||
|
flags, full_name_of_item,
|
||||||
|
load_cargo::load_workspace_at,
|
||||||
|
load_cargo::{LoadCargoConfig, ProcMacroServerChoice},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl flags::RunTests {
|
||||||
|
pub fn run(self) -> Result<()> {
|
||||||
|
let mut cargo_config = CargoConfig::default();
|
||||||
|
cargo_config.sysroot = Some(RustLibSource::Discover);
|
||||||
|
let load_cargo_config = LoadCargoConfig {
|
||||||
|
load_out_dirs_from_check: true,
|
||||||
|
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||||
|
prefill_caches: false,
|
||||||
|
};
|
||||||
|
let (host, _vfs, _proc_macro) =
|
||||||
|
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
|
||||||
|
let db = host.raw_database();
|
||||||
|
|
||||||
|
let tests = all_modules(db)
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|x| x.declarations(db))
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
hir::ModuleDef::Function(f) => Some(f),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.filter(|x| x.is_test(db));
|
||||||
|
let span_formatter = |file_id, text_range: TextRange| {
|
||||||
|
let line_col = match db.line_index(file_id).try_line_col(text_range.start()) {
|
||||||
|
None => " (unknown line col)".to_string(),
|
||||||
|
Some(x) => format!("#{}:{}", x.line + 1, x.col),
|
||||||
|
};
|
||||||
|
let path = &db
|
||||||
|
.source_root(db.file_source_root(file_id))
|
||||||
|
.path_for_file(&file_id)
|
||||||
|
.map(|x| x.to_string());
|
||||||
|
let path = path.as_deref().unwrap_or("<unknown file>");
|
||||||
|
format!("file://{path}{line_col}")
|
||||||
|
};
|
||||||
|
let mut pass_count = 0;
|
||||||
|
let mut ignore_count = 0;
|
||||||
|
let mut fail_count = 0;
|
||||||
|
let mut sw_all = StopWatch::start();
|
||||||
|
for test in tests {
|
||||||
|
let full_name = full_name_of_item(db, test.module(db), test.name(db));
|
||||||
|
println!("test {}", full_name);
|
||||||
|
if test.is_ignore(db) {
|
||||||
|
println!("ignored");
|
||||||
|
ignore_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut sw_one = StopWatch::start();
|
||||||
|
let result = test.eval(db, span_formatter);
|
||||||
|
if result.trim() == "pass" {
|
||||||
|
pass_count += 1;
|
||||||
|
} else {
|
||||||
|
fail_count += 1;
|
||||||
|
}
|
||||||
|
println!("{}", result);
|
||||||
|
eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
|
||||||
|
}
|
||||||
|
println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
|
||||||
|
eprintln!("{:<20} {}", "All tests", sw_all.elapsed());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
|
||||||
|
let mut worklist: Vec<_> = Crate::all(db)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|x| x.origin(db).is_local())
|
||||||
|
.map(|krate| krate.root_module(db))
|
||||||
|
.collect();
|
||||||
|
let mut modules = Vec::new();
|
||||||
|
|
||||||
|
while let Some(module) = worklist.pop() {
|
||||||
|
modules.push(module);
|
||||||
|
worklist.extend(module.children(db));
|
||||||
|
}
|
||||||
|
|
||||||
|
modules
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue