Skip to content

Commit 3a4f9a1

Browse files
committed
Auto merge of rust-lang#15110 - HKalbasi:run-test-command, r=HKalbasi
internal: Add run-tests command This command is similar to `cargo test` except that it uses r-a to run tests instead of compiling and running them with rustc. This is slower than `cargo test` and it is only useful for me to see a bird view of what needs to be fixed. The current output is: ``` 48 passed, 5028 failed, 2 ignored All tests 174.74s, 648ginstr ``` 48 is very low, but higher than what I originally thought. Now that there is some passing tests, I can show the plan: https://github.com/rust-lang/rust-analyzer/assets/45197576/76d7d777-1843-4ca4-b7fe-e463bdade6cb That is, at the end, I want to be able to immediately re run every test after every change. (0.5s is not really immediate, but it's not finished yet, and it is way better than 8s that running a typical test in r-a will take on my system)
2 parents 6c73f67 + 674cd5a commit 3a4f9a1

File tree

8 files changed

+166
-36
lines changed

8 files changed

+166
-36
lines changed

crates/hir-def/src/attr.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,18 @@ impl Attrs {
272272
self.by_key("proc_macro_derive").exists()
273273
}
274274

275+
pub fn is_test(&self) -> bool {
276+
self.by_key("test").exists()
277+
}
278+
279+
pub fn is_ignore(&self) -> bool {
280+
self.by_key("ignore").exists()
281+
}
282+
283+
pub fn is_bench(&self) -> bool {
284+
self.by_key("bench").exists()
285+
}
286+
275287
pub fn is_unstable(&self) -> bool {
276288
self.by_key("unstable").exists()
277289
}

crates/hir/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,21 @@ impl Function {
19271927
db.function_data(self.id).has_async_kw()
19281928
}
19291929

1930+
/// Does this function have `#[test]` attribute?
1931+
pub fn is_test(self, db: &dyn HirDatabase) -> bool {
1932+
db.function_data(self.id).attrs.is_test()
1933+
}
1934+
1935+
/// Does this function have the ignore attribute?
1936+
pub fn is_ignore(self, db: &dyn HirDatabase) -> bool {
1937+
db.function_data(self.id).attrs.is_ignore()
1938+
}
1939+
1940+
/// Does this function have `#[bench]` attribute?
1941+
pub fn is_bench(self, db: &dyn HirDatabase) -> bool {
1942+
db.function_data(self.id).attrs.is_bench()
1943+
}
1944+
19301945
pub fn is_unsafe_to_call(self, db: &dyn HirDatabase) -> bool {
19311946
hir_ty::is_fn_unsafe_to_call(db, self.id)
19321947
}

crates/ide/src/runnables.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fmt;
22

33
use ast::HasName;
44
use cfg::CfgExpr;
5-
use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
5+
use hir::{db::HirDatabase, AsAssocItem, HasAttrs, HasSource, Semantics};
66
use ide_assists::utils::test_related_attribute;
77
use ide_db::{
88
base_db::{FilePosition, FileRange},
@@ -14,7 +14,7 @@ use ide_db::{
1414
use itertools::Itertools;
1515
use stdx::{always, format_to};
1616
use syntax::{
17-
ast::{self, AstNode, HasAttrs as _},
17+
ast::{self, AstNode},
1818
SmolStr, SyntaxNode,
1919
};
2020

@@ -307,7 +307,6 @@ pub(crate) fn runnable_fn(
307307
sema: &Semantics<'_, RootDatabase>,
308308
def: hir::Function,
309309
) -> Option<Runnable> {
310-
let func = def.source(sema.db)?;
311310
let name = def.name(sema.db).to_smol_str();
312311

313312
let root = def.module(sema.db).krate().root_module(sema.db);
@@ -323,10 +322,10 @@ pub(crate) fn runnable_fn(
323322
canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name))
324323
};
325324

326-
if test_related_attribute(&func.value).is_some() {
327-
let attr = TestAttr::from_fn(&func.value);
325+
if def.is_test(sema.db) {
326+
let attr = TestAttr::from_fn(sema.db, def);
328327
RunnableKind::Test { test_id: test_id(), attr }
329-
} else if func.value.has_atom_attr("bench") {
328+
} else if def.is_bench(sema.db) {
330329
RunnableKind::Bench { test_id: test_id() }
331330
} else {
332331
return None;
@@ -335,7 +334,7 @@ pub(crate) fn runnable_fn(
335334

336335
let nav = NavigationTarget::from_named(
337336
sema.db,
338-
func.as_ref().map(|it| it as &dyn ast::HasName),
337+
def.source(sema.db)?.as_ref().map(|it| it as &dyn ast::HasName),
339338
SymbolKind::Function,
340339
);
341340
let cfg = def.attrs(sema.db).cfg();
@@ -487,12 +486,8 @@ pub struct TestAttr {
487486
}
488487

489488
impl TestAttr {
490-
fn from_fn(fn_def: &ast::Fn) -> TestAttr {
491-
let ignore = fn_def
492-
.attrs()
493-
.filter_map(|attr| attr.simple_name())
494-
.any(|attribute_text| attribute_text == "ignore");
495-
TestAttr { ignore }
489+
fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr {
490+
TestAttr { ignore: fn_def.is_ignore(db) }
496491
}
497492
}
498493

crates/rust-analyzer/src/bin/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ fn main() -> anyhow::Result<()> {
8282
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
8383
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
8484
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
85+
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
8586
}
8687
Ok(())
8788
}

crates/rust-analyzer/src/cli.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ mod diagnostics;
1010
mod ssr;
1111
mod lsif;
1212
mod scip;
13+
mod run_tests;
1314

1415
mod progress_report;
1516

1617
use std::io::Read;
1718

19+
use anyhow::Result;
20+
use hir::{Module, Name};
21+
use hir_ty::db::HirDatabase;
1822
use ide::AnalysisHost;
23+
use itertools::Itertools;
1924
use vfs::Vfs;
2025

2126
#[derive(Clone, Copy)]
@@ -70,3 +75,14 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
7075

7176
eprintln!("{remaining:>8} Remaining");
7277
}
78+
79+
fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String {
80+
module
81+
.path_to_root(db)
82+
.into_iter()
83+
.rev()
84+
.filter_map(|it| it.name(db))
85+
.chain(Some(name))
86+
.map(|it| it.display(db.upcast()).to_string())
87+
.join("::")
88+
}

crates/rust-analyzer/src/cli/analysis_stats.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use vfs::{AbsPathBuf, Vfs, VfsPath};
3434

3535
use crate::cli::{
3636
flags::{self, OutputFormat},
37+
full_name_of_item,
3738
load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
3839
print_memory_usage,
3940
progress_report::ProgressReport,
@@ -274,15 +275,7 @@ impl flags::AnalysisStats {
274275
continue
275276
};
276277
if verbosity.is_spammy() {
277-
let full_name = a
278-
.module(db)
279-
.path_to_root(db)
280-
.into_iter()
281-
.rev()
282-
.filter_map(|it| it.name(db))
283-
.chain(Some(a.name(db)))
284-
.map(|it| it.display(db).to_string())
285-
.join("::");
278+
let full_name = full_name_of_item(db, a.module(db), a.name(db));
286279
println!("Data layout for {full_name} failed due {e:?}");
287280
}
288281
fail += 1;
@@ -304,15 +297,8 @@ impl flags::AnalysisStats {
304297
continue;
305298
};
306299
if verbosity.is_spammy() {
307-
let full_name = c
308-
.module(db)
309-
.path_to_root(db)
310-
.into_iter()
311-
.rev()
312-
.filter_map(|it| it.name(db))
313-
.chain(c.name(db))
314-
.map(|it| it.display(db).to_string())
315-
.join("::");
300+
let full_name =
301+
full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
316302
println!("Const eval for {full_name} failed due {e:?}");
317303
}
318304
fail += 1;

crates/rust-analyzer/src/cli/flags.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ xflags::xflags! {
9090
optional --skip-const-eval
9191
}
9292

93+
/// Run unit tests of the project using mir interpreter
94+
cmd run-tests {
95+
/// Directory with Cargo.toml.
96+
required path: PathBuf
97+
}
98+
9399
cmd diagnostics {
94100
/// Directory with Cargo.toml.
95101
required path: PathBuf
@@ -147,6 +153,7 @@ pub enum RustAnalyzerCmd {
147153
Symbols(Symbols),
148154
Highlight(Highlight),
149155
AnalysisStats(AnalysisStats),
156+
RunTests(RunTests),
150157
Diagnostics(Diagnostics),
151158
Ssr(Ssr),
152159
Search(Search),
@@ -182,16 +189,21 @@ pub struct AnalysisStats {
182189
pub parallel: bool,
183190
pub memory_usage: bool,
184191
pub source_stats: bool,
185-
pub skip_lowering: bool,
186-
pub skip_inference: bool,
187-
pub skip_mir_stats: bool,
188-
pub skip_data_layout: bool,
189-
pub skip_const_eval: bool,
190192
pub only: Option<String>,
191193
pub with_deps: bool,
192194
pub no_sysroot: bool,
193195
pub disable_build_scripts: bool,
194196
pub disable_proc_macros: bool,
197+
pub skip_lowering: bool,
198+
pub skip_inference: bool,
199+
pub skip_mir_stats: bool,
200+
pub skip_data_layout: bool,
201+
pub skip_const_eval: bool,
202+
}
203+
204+
#[derive(Debug)]
205+
pub struct RunTests {
206+
pub path: PathBuf,
195207
}
196208

197209
#[derive(Debug)]
@@ -223,6 +235,7 @@ pub struct Lsif {
223235
#[derive(Debug)]
224236
pub struct Scip {
225237
pub path: PathBuf,
238+
226239
pub output: Option<PathBuf>,
227240
}
228241

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
2+
3+
use hir::{Crate, Module};
4+
use hir_ty::db::HirDatabase;
5+
use ide_db::{base_db::SourceDatabaseExt, LineIndexDatabase};
6+
use profile::StopWatch;
7+
use project_model::{CargoConfig, RustLibSource};
8+
use syntax::TextRange;
9+
10+
use crate::cli::{
11+
flags, full_name_of_item,
12+
load_cargo::load_workspace_at,
13+
load_cargo::{LoadCargoConfig, ProcMacroServerChoice},
14+
Result,
15+
};
16+
17+
impl flags::RunTests {
18+
pub fn run(self) -> Result<()> {
19+
let mut cargo_config = CargoConfig::default();
20+
cargo_config.sysroot = Some(RustLibSource::Discover);
21+
let load_cargo_config = LoadCargoConfig {
22+
load_out_dirs_from_check: true,
23+
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
24+
prefill_caches: false,
25+
};
26+
let (host, _vfs, _proc_macro) =
27+
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
28+
let db = host.raw_database();
29+
30+
let tests = all_modules(db)
31+
.into_iter()
32+
.flat_map(|x| x.declarations(db))
33+
.filter_map(|x| match x {
34+
hir::ModuleDef::Function(f) => Some(f),
35+
_ => None,
36+
})
37+
.filter(|x| x.is_test(db));
38+
let span_formatter = |file_id, text_range: TextRange| {
39+
let line_col = match db.line_index(file_id).try_line_col(text_range.start()) {
40+
None => " (unknown line col)".to_string(),
41+
Some(x) => format!("#{}:{}", x.line + 1, x.col),
42+
};
43+
let path = &db
44+
.source_root(db.file_source_root(file_id))
45+
.path_for_file(&file_id)
46+
.map(|x| x.to_string());
47+
let path = path.as_deref().unwrap_or("<unknown file>");
48+
format!("file://{path}{line_col}")
49+
};
50+
let mut pass_count = 0;
51+
let mut ignore_count = 0;
52+
let mut fail_count = 0;
53+
let mut sw_all = StopWatch::start();
54+
for test in tests {
55+
let full_name = full_name_of_item(db, test.module(db), test.name(db));
56+
println!("test {}", full_name);
57+
if test.is_ignore(db) {
58+
println!("ignored");
59+
ignore_count += 1;
60+
continue;
61+
}
62+
let mut sw_one = StopWatch::start();
63+
let result = test.eval(db, span_formatter);
64+
if result.trim() == "pass" {
65+
pass_count += 1;
66+
} else {
67+
fail_count += 1;
68+
}
69+
println!("{}", result);
70+
eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
71+
}
72+
println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
73+
eprintln!("{:<20} {}", "All tests", sw_all.elapsed());
74+
Ok(())
75+
}
76+
}
77+
78+
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
79+
let mut worklist: Vec<_> = Crate::all(db)
80+
.into_iter()
81+
.filter(|x| x.origin(db).is_local())
82+
.map(|krate| krate.root_module(db))
83+
.collect();
84+
let mut modules = Vec::new();
85+
86+
while let Some(module) = worklist.pop() {
87+
modules.push(module);
88+
worklist.extend(module.children(db));
89+
}
90+
91+
modules
92+
}

0 commit comments

Comments
 (0)