Skip to content
/ rust Public
forked from rust-lang/rust

Commit 30c6961

Browse files
authored
Rollup merge of rust-lang#106249 - Ezrashaw:suggest-test-tool, r=jyn514
Create "suggested tests" tool in `rustbuild` Not the claimed person in rust-lang#97339 but: I've done a very rough implementation of this feature in-tree. I'm very new to `rustc` development (outside of docs) so some help would be greatly appreciated. The UI of this new subcommand obviously will change and I need some mentoring with the `--run` flag. r? ``@jyn514``
2 parents 2352af9 + 1e95cdd commit 30c6961

15 files changed

+377
-25
lines changed

Cargo.lock

+11-2
Original file line numberDiff line numberDiff line change
@@ -3452,9 +3452,9 @@ dependencies = [
34523452

34533453
[[package]]
34543454
name = "once_cell"
3455-
version = "1.16.0"
3455+
version = "1.17.1"
34563456
source = "registry+https://github.com/rust-lang/crates.io-index"
3457-
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
3457+
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
34583458

34593459
[[package]]
34603460
name = "opener"
@@ -6101,6 +6101,15 @@ version = "2.4.1"
61016101
source = "registry+https://github.com/rust-lang/crates.io-index"
61026102
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
61036103

6104+
[[package]]
6105+
name = "suggest-tests"
6106+
version = "0.1.0"
6107+
dependencies = [
6108+
"build_helper",
6109+
"glob",
6110+
"once_cell",
6111+
]
6112+
61046113
[[package]]
61056114
name = "syn"
61066115
version = "1.0.102"

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ members = [
4444
"src/tools/lld-wrapper",
4545
"src/tools/collect-license-metadata",
4646
"src/tools/generate-copyright",
47+
"src/tools/suggest-tests",
4748
]
4849

4950
exclude = [

src/bootstrap/builder.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ pub enum Kind {
591591
Install,
592592
Run,
593593
Setup,
594+
Suggest,
594595
}
595596

596597
impl Kind {
@@ -610,6 +611,7 @@ impl Kind {
610611
"install" => Kind::Install,
611612
"run" | "r" => Kind::Run,
612613
"setup" => Kind::Setup,
614+
"suggest" => Kind::Suggest,
613615
_ => return None,
614616
})
615617
}
@@ -629,6 +631,7 @@ impl Kind {
629631
Kind::Install => "install",
630632
Kind::Run => "run",
631633
Kind::Setup => "setup",
634+
Kind::Suggest => "suggest",
632635
}
633636
}
634637
}
@@ -709,6 +712,7 @@ impl<'a> Builder<'a> {
709712
test::CrateRustdoc,
710713
test::CrateRustdocJsonTypes,
711714
test::CrateJsonDocLint,
715+
test::SuggestTestsCrate,
712716
test::Linkcheck,
713717
test::TierCheck,
714718
test::ReplacePlaceholderTest,
@@ -827,7 +831,7 @@ impl<'a> Builder<'a> {
827831
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
828832
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
829833
// special-cased in Build::build()
830-
Kind::Format => vec![],
834+
Kind::Format | Kind::Suggest => vec![],
831835
}
832836
}
833837

@@ -891,6 +895,7 @@ impl<'a> Builder<'a> {
891895
Subcommand::Run { ref paths, .. } => (Kind::Run, &paths[..]),
892896
Subcommand::Clean { ref paths, .. } => (Kind::Clean, &paths[..]),
893897
Subcommand::Format { .. } => (Kind::Format, &[][..]),
898+
Subcommand::Suggest { .. } => (Kind::Suggest, &[][..]),
894899
Subcommand::Setup { profile: ref path } => (
895900
Kind::Setup,
896901
path.as_ref().map_or([].as_slice(), |path| std::slice::from_ref(path)),
@@ -900,6 +905,21 @@ impl<'a> Builder<'a> {
900905
Self::new_internal(build, kind, paths.to_owned())
901906
}
902907

908+
/// Creates a new standalone builder for use outside of the normal process
909+
pub fn new_standalone(
910+
build: &mut Build,
911+
kind: Kind,
912+
paths: Vec<PathBuf>,
913+
stage: Option<u32>,
914+
) -> Builder<'_> {
915+
// FIXME: don't mutate `build`
916+
if let Some(stage) = stage {
917+
build.config.stage = stage;
918+
}
919+
920+
Self::new_internal(build, kind, paths.to_owned())
921+
}
922+
903923
pub fn execute_cli(&self) {
904924
self.run_step_descriptions(&Builder::get_step_descriptions(self.kind), &self.paths);
905925
}

src/bootstrap/config.rs

+10-14
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ pub enum DryRun {
5656
/// filled out from the decoded forms of the structs below. For documentation
5757
/// each field, see the corresponding fields in
5858
/// `config.example.toml`.
59-
#[derive(Default)]
60-
#[cfg_attr(test, derive(Clone))]
59+
#[derive(Default, Clone)]
6160
pub struct Config {
6261
pub changelog_seen: Option<usize>,
6362
pub ccache: Option<String>,
@@ -240,32 +239,28 @@ pub struct Config {
240239
pub initial_rustfmt: RefCell<RustfmtState>,
241240
}
242241

243-
#[derive(Default, Deserialize)]
244-
#[cfg_attr(test, derive(Clone))]
242+
#[derive(Default, Deserialize, Clone)]
245243
pub struct Stage0Metadata {
246244
pub compiler: CompilerMetadata,
247245
pub config: Stage0Config,
248246
pub checksums_sha256: HashMap<String, String>,
249247
pub rustfmt: Option<RustfmtMetadata>,
250248
}
251-
#[derive(Default, Deserialize)]
252-
#[cfg_attr(test, derive(Clone))]
249+
#[derive(Default, Deserialize, Clone)]
253250
pub struct CompilerMetadata {
254251
pub date: String,
255252
pub version: String,
256253
}
257254

258-
#[derive(Default, Deserialize)]
259-
#[cfg_attr(test, derive(Clone))]
255+
#[derive(Default, Deserialize, Clone)]
260256
pub struct Stage0Config {
261257
pub dist_server: String,
262258
pub artifacts_server: String,
263259
pub artifacts_with_llvm_assertions_server: String,
264260
pub git_merge_commit_email: String,
265261
pub nightly_branch: String,
266262
}
267-
#[derive(Default, Deserialize)]
268-
#[cfg_attr(test, derive(Clone))]
263+
#[derive(Default, Deserialize, Clone)]
269264
pub struct RustfmtMetadata {
270265
pub date: String,
271266
pub version: String,
@@ -443,8 +438,7 @@ impl PartialEq<&str> for TargetSelection {
443438
}
444439

445440
/// Per-target configuration stored in the global configuration structure.
446-
#[derive(Default)]
447-
#[cfg_attr(test, derive(Clone))]
441+
#[derive(Default, Clone)]
448442
pub struct Target {
449443
/// Some(path to llvm-config) if using an external LLVM.
450444
pub llvm_config: Option<PathBuf>,
@@ -1396,7 +1390,8 @@ impl Config {
13961390
| Subcommand::Fix { .. }
13971391
| Subcommand::Run { .. }
13981392
| Subcommand::Setup { .. }
1399-
| Subcommand::Format { .. } => flags.stage.unwrap_or(0),
1393+
| Subcommand::Format { .. }
1394+
| Subcommand::Suggest { .. } => flags.stage.unwrap_or(0),
14001395
};
14011396

14021397
// CI should always run stage 2 builds, unless it specifically states otherwise
@@ -1421,7 +1416,8 @@ impl Config {
14211416
| Subcommand::Fix { .. }
14221417
| Subcommand::Run { .. }
14231418
| Subcommand::Setup { .. }
1424-
| Subcommand::Format { .. } => {}
1419+
| Subcommand::Format { .. }
1420+
| Subcommand::Suggest { .. } => {}
14251421
}
14261422
}
14271423

src/bootstrap/flags.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ pub struct Flags {
8484
pub free_args: Option<Vec<String>>,
8585
}
8686

87-
#[derive(Debug)]
88-
#[cfg_attr(test, derive(Clone))]
87+
#[derive(Debug, Clone)]
8988
pub enum Subcommand {
9089
Build {
9190
paths: Vec<PathBuf>,
@@ -149,6 +148,9 @@ pub enum Subcommand {
149148
Setup {
150149
profile: Option<PathBuf>,
151150
},
151+
Suggest {
152+
run: bool,
153+
},
152154
}
153155

154156
impl Default for Subcommand {
@@ -183,6 +185,7 @@ Subcommands:
183185
install Install distribution artifacts
184186
run, r Run tools contained in this repository
185187
setup Create a config.toml (making it easier to use `x.py` itself)
188+
suggest Suggest a subset of tests to run, based on modified files
186189
187190
To learn more about a subcommand, run `./x.py <subcommand> -h`",
188191
);
@@ -349,6 +352,9 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
349352
Kind::Run => {
350353
opts.optmulti("", "args", "arguments for the tool", "ARGS");
351354
}
355+
Kind::Suggest => {
356+
opts.optflag("", "run", "run suggested tests");
357+
}
352358
_ => {}
353359
};
354360

@@ -565,7 +571,7 @@ Arguments:
565571
Profile::all_for_help(" ").trim_end()
566572
));
567573
}
568-
Kind::Bench | Kind::Clean | Kind::Dist | Kind::Install => {}
574+
Kind::Bench | Kind::Clean | Kind::Dist | Kind::Install | Kind::Suggest => {}
569575
};
570576
// Get any optional paths which occur after the subcommand
571577
let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
@@ -626,6 +632,7 @@ Arguments:
626632
Kind::Format => Subcommand::Format { check: matches.opt_present("check"), paths },
627633
Kind::Dist => Subcommand::Dist { paths },
628634
Kind::Install => Subcommand::Install { paths },
635+
Kind::Suggest => Subcommand::Suggest { run: matches.opt_present("run") },
629636
Kind::Run => {
630637
if paths.is_empty() {
631638
println!("\nrun requires at least a path!\n");
@@ -734,6 +741,7 @@ impl Subcommand {
734741
Subcommand::Install { .. } => Kind::Install,
735742
Subcommand::Run { .. } => Kind::Run,
736743
Subcommand::Setup { .. } => Kind::Setup,
744+
Subcommand::Suggest { .. } => Kind::Suggest,
737745
}
738746
}
739747

src/bootstrap/lib.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ mod render_tests;
5858
mod run;
5959
mod sanity;
6060
mod setup;
61+
mod suggest;
6162
mod tarball;
6263
mod test;
6364
mod tool;
@@ -190,6 +191,7 @@ pub enum GitRepo {
190191
/// although most functions are implemented as free functions rather than
191192
/// methods specifically on this structure itself (to make it easier to
192193
/// organize).
194+
#[derive(Clone)]
193195
pub struct Build {
194196
/// User-specified configuration from `config.toml`.
195197
config: Config,
@@ -243,7 +245,7 @@ pub struct Build {
243245
metrics: metrics::BuildMetrics,
244246
}
245247

246-
#[derive(Debug)]
248+
#[derive(Debug, Clone)]
247249
struct Crate {
248250
name: Interned<String>,
249251
deps: HashSet<Interned<String>>,
@@ -657,13 +659,20 @@ impl Build {
657659
job::setup(self);
658660
}
659661

660-
if let Subcommand::Format { check, paths } = &self.config.cmd {
661-
return format::format(&builder::Builder::new(&self), *check, &paths);
662-
}
663-
664662
// Download rustfmt early so that it can be used in rust-analyzer configs.
665663
let _ = &builder::Builder::new(&self).initial_rustfmt();
666664

665+
// hardcoded subcommands
666+
match &self.config.cmd {
667+
Subcommand::Format { check, paths } => {
668+
return format::format(&builder::Builder::new(&self), *check, &paths);
669+
}
670+
Subcommand::Suggest { run } => {
671+
return suggest::suggest(&builder::Builder::new(&self), *run);
672+
}
673+
_ => (),
674+
}
675+
667676
{
668677
let builder = builder::Builder::new(&self);
669678
if let Some(path) = builder.paths.get(0) {

src/bootstrap/suggest.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::str::FromStr;
2+
3+
use std::path::PathBuf;
4+
5+
use crate::{
6+
builder::{Builder, Kind},
7+
tool::Tool,
8+
};
9+
10+
/// Suggests a list of possible `x.py` commands to run based on modified files in branch.
11+
pub fn suggest(builder: &Builder<'_>, run: bool) {
12+
let suggestions =
13+
builder.tool_cmd(Tool::SuggestTests).output().expect("failed to run `suggest-tests` tool");
14+
15+
if !suggestions.status.success() {
16+
println!("failed to run `suggest-tests` tool ({})", suggestions.status);
17+
println!(
18+
"`suggest_tests` stdout:\n{}`suggest_tests` stderr:\n{}",
19+
String::from_utf8(suggestions.stdout).unwrap(),
20+
String::from_utf8(suggestions.stderr).unwrap()
21+
);
22+
panic!("failed to run `suggest-tests`");
23+
}
24+
25+
let suggestions = String::from_utf8(suggestions.stdout).unwrap();
26+
let suggestions = suggestions
27+
.lines()
28+
.map(|line| {
29+
let mut sections = line.split_ascii_whitespace();
30+
31+
// this code expects one suggestion per line in the following format:
32+
// <x_subcommand> {some number of flags} [optional stage number]
33+
let cmd = sections.next().unwrap();
34+
let stage = sections.next_back().map(|s| str::parse(s).ok()).flatten();
35+
let paths: Vec<PathBuf> = sections.map(|p| PathBuf::from_str(p).unwrap()).collect();
36+
37+
(cmd, stage, paths)
38+
})
39+
.collect::<Vec<_>>();
40+
41+
if !suggestions.is_empty() {
42+
println!("==== SUGGESTIONS ====");
43+
for sug in &suggestions {
44+
print!("x {} ", sug.0);
45+
if let Some(stage) = sug.1 {
46+
print!("--stage {stage} ");
47+
}
48+
49+
for path in &sug.2 {
50+
print!("{} ", path.display());
51+
}
52+
println!();
53+
}
54+
println!("=====================");
55+
} else {
56+
println!("No suggestions found!");
57+
return;
58+
}
59+
60+
if run {
61+
for sug in suggestions {
62+
let mut build = builder.build.clone();
63+
64+
let builder =
65+
Builder::new_standalone(&mut build, Kind::parse(&sug.0).unwrap(), sug.2, sug.1);
66+
67+
builder.execute_cli()
68+
}
69+
} else {
70+
println!("help: consider using the `--run` flag to automatically run suggested tests");
71+
}
72+
}

0 commit comments

Comments
 (0)