mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +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()
|
||||
}
|
||||
|
||||
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 {
|
||||
self.by_key("unstable").exists()
|
||||
}
|
||||
|
|
|
@ -1927,6 +1927,21 @@ impl Function {
|
|||
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 {
|
||||
hir_ty::is_fn_unsafe_to_call(db, self.id)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt;
|
|||
|
||||
use ast::HasName;
|
||||
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_db::{
|
||||
base_db::{FilePosition, FileRange},
|
||||
|
@ -14,7 +14,7 @@ use ide_db::{
|
|||
use itertools::Itertools;
|
||||
use stdx::{always, format_to};
|
||||
use syntax::{
|
||||
ast::{self, AstNode, HasAttrs as _},
|
||||
ast::{self, AstNode},
|
||||
SmolStr, SyntaxNode,
|
||||
};
|
||||
|
||||
|
@ -307,7 +307,6 @@ pub(crate) fn runnable_fn(
|
|||
sema: &Semantics<'_, RootDatabase>,
|
||||
def: hir::Function,
|
||||
) -> Option<Runnable> {
|
||||
let func = def.source(sema.db)?;
|
||||
let name = def.name(sema.db).to_smol_str();
|
||||
|
||||
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))
|
||||
};
|
||||
|
||||
if test_related_attribute(&func.value).is_some() {
|
||||
let attr = TestAttr::from_fn(&func.value);
|
||||
if def.is_test(sema.db) {
|
||||
let attr = TestAttr::from_fn(sema.db, def);
|
||||
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() }
|
||||
} else {
|
||||
return None;
|
||||
|
@ -335,7 +334,7 @@ pub(crate) fn runnable_fn(
|
|||
|
||||
let nav = NavigationTarget::from_named(
|
||||
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,
|
||||
);
|
||||
let cfg = def.attrs(sema.db).cfg();
|
||||
|
@ -487,12 +486,8 @@ pub struct TestAttr {
|
|||
}
|
||||
|
||||
impl TestAttr {
|
||||
fn from_fn(fn_def: &ast::Fn) -> TestAttr {
|
||||
let ignore = fn_def
|
||||
.attrs()
|
||||
.filter_map(|attr| attr.simple_name())
|
||||
.any(|attribute_text| attribute_text == "ignore");
|
||||
TestAttr { ignore }
|
||||
fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr {
|
||||
TestAttr { ignore: fn_def.is_ignore(db) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ fn main() -> anyhow::Result<()> {
|
|||
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,12 +10,17 @@ mod diagnostics;
|
|||
mod ssr;
|
||||
mod lsif;
|
||||
mod scip;
|
||||
mod run_tests;
|
||||
|
||||
mod progress_report;
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use anyhow::Result;
|
||||
use hir::{Module, Name};
|
||||
use hir_ty::db::HirDatabase;
|
||||
use ide::AnalysisHost;
|
||||
use itertools::Itertools;
|
||||
use vfs::Vfs;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -70,3 +75,14 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
|
|||
|
||||
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::{
|
||||
flags::{self, OutputFormat},
|
||||
full_name_of_item,
|
||||
load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
|
||||
print_memory_usage,
|
||||
progress_report::ProgressReport,
|
||||
|
@ -274,15 +275,7 @@ impl flags::AnalysisStats {
|
|||
continue
|
||||
};
|
||||
if verbosity.is_spammy() {
|
||||
let full_name = a
|
||||
.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("::");
|
||||
let full_name = full_name_of_item(db, a.module(db), a.name(db));
|
||||
println!("Data layout for {full_name} failed due {e:?}");
|
||||
}
|
||||
fail += 1;
|
||||
|
@ -304,15 +297,8 @@ impl flags::AnalysisStats {
|
|||
continue;
|
||||
};
|
||||
if verbosity.is_spammy() {
|
||||
let full_name = c
|
||||
.module(db)
|
||||
.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("::");
|
||||
let full_name =
|
||||
full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
|
||||
println!("Const eval for {full_name} failed due {e:?}");
|
||||
}
|
||||
fail += 1;
|
||||
|
|
|
@ -90,6 +90,12 @@ xflags::xflags! {
|
|||
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 {
|
||||
/// Directory with Cargo.toml.
|
||||
required path: PathBuf
|
||||
|
@ -147,6 +153,7 @@ pub enum RustAnalyzerCmd {
|
|||
Symbols(Symbols),
|
||||
Highlight(Highlight),
|
||||
AnalysisStats(AnalysisStats),
|
||||
RunTests(RunTests),
|
||||
Diagnostics(Diagnostics),
|
||||
Ssr(Ssr),
|
||||
Search(Search),
|
||||
|
@ -182,16 +189,21 @@ pub struct AnalysisStats {
|
|||
pub parallel: bool,
|
||||
pub memory_usage: 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 with_deps: bool,
|
||||
pub no_sysroot: bool,
|
||||
pub disable_build_scripts: 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)]
|
||||
|
@ -223,6 +235,7 @@ pub struct Lsif {
|
|||
#[derive(Debug)]
|
||||
pub struct Scip {
|
||||
pub path: 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…
Reference in a new issue