Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/attr.rs12
-rw-r--r--crates/hir/src/lib.rs15
-rw-r--r--crates/ide/src/runnables.rs21
-rw-r--r--crates/rust-analyzer/src/bin/main.rs1
-rw-r--r--crates/rust-analyzer/src/cli.rs16
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs22
-rw-r--r--crates/rust-analyzer/src/cli/flags.rs23
-rw-r--r--crates/rust-analyzer/src/cli/run_tests.rs92
8 files changed, 166 insertions, 36 deletions
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index 09891f4452..659ca2e958 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -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()
}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1699c3dba7..352fa48150 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -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)
}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 27ad63d820..1f331cd932 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -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) }
}
}
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 118aad585c..2fa14fc7e2 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -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(())
}
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index 34cd595634..893eadf3fa 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -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("::")
+}
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index e3b9f215be..826b89926b 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -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;
diff --git a/crates/rust-analyzer/src/cli/flags.rs b/crates/rust-analyzer/src/cli/flags.rs
index 208a4e6ecd..9848739504 100644
--- a/crates/rust-analyzer/src/cli/flags.rs
+++ b/crates/rust-analyzer/src/cli/flags.rs
@@ -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>,
}
diff --git a/crates/rust-analyzer/src/cli/run_tests.rs b/crates/rust-analyzer/src/cli/run_tests.rs
new file mode 100644
index 0000000000..bebbf26b54
--- /dev/null
+++ b/crates/rust-analyzer/src/cli/run_tests.rs
@@ -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
+}