feat: implement basic suggest-tests tool
This commit is contained in:
parent
7cd6f55323
commit
1e95cddc74
15 changed files with 377 additions and 25 deletions
13
Cargo.lock
13
Cargo.lock
|
@ -3459,9 +3459,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opener"
|
name = "opener"
|
||||||
|
@ -6097,6 +6097,15 @@ version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "suggest-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"build_helper",
|
||||||
|
"glob",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
|
|
|
@ -44,6 +44,7 @@ members = [
|
||||||
"src/tools/lld-wrapper",
|
"src/tools/lld-wrapper",
|
||||||
"src/tools/collect-license-metadata",
|
"src/tools/collect-license-metadata",
|
||||||
"src/tools/generate-copyright",
|
"src/tools/generate-copyright",
|
||||||
|
"src/tools/suggest-tests",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|
|
@ -591,6 +591,7 @@ pub enum Kind {
|
||||||
Install,
|
Install,
|
||||||
Run,
|
Run,
|
||||||
Setup,
|
Setup,
|
||||||
|
Suggest,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kind {
|
impl Kind {
|
||||||
|
@ -610,6 +611,7 @@ impl Kind {
|
||||||
"install" => Kind::Install,
|
"install" => Kind::Install,
|
||||||
"run" | "r" => Kind::Run,
|
"run" | "r" => Kind::Run,
|
||||||
"setup" => Kind::Setup,
|
"setup" => Kind::Setup,
|
||||||
|
"suggest" => Kind::Suggest,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -629,6 +631,7 @@ impl Kind {
|
||||||
Kind::Install => "install",
|
Kind::Install => "install",
|
||||||
Kind::Run => "run",
|
Kind::Run => "run",
|
||||||
Kind::Setup => "setup",
|
Kind::Setup => "setup",
|
||||||
|
Kind::Suggest => "suggest",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -709,6 +712,7 @@ impl<'a> Builder<'a> {
|
||||||
test::CrateRustdoc,
|
test::CrateRustdoc,
|
||||||
test::CrateRustdocJsonTypes,
|
test::CrateRustdocJsonTypes,
|
||||||
test::CrateJsonDocLint,
|
test::CrateJsonDocLint,
|
||||||
|
test::SuggestTestsCrate,
|
||||||
test::Linkcheck,
|
test::Linkcheck,
|
||||||
test::TierCheck,
|
test::TierCheck,
|
||||||
test::ReplacePlaceholderTest,
|
test::ReplacePlaceholderTest,
|
||||||
|
@ -827,7 +831,7 @@ impl<'a> Builder<'a> {
|
||||||
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
|
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
|
||||||
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
|
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
|
||||||
// special-cased in Build::build()
|
// special-cased in Build::build()
|
||||||
Kind::Format => vec![],
|
Kind::Format | Kind::Suggest => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,6 +895,7 @@ impl<'a> Builder<'a> {
|
||||||
Subcommand::Run { ref paths, .. } => (Kind::Run, &paths[..]),
|
Subcommand::Run { ref paths, .. } => (Kind::Run, &paths[..]),
|
||||||
Subcommand::Clean { ref paths, .. } => (Kind::Clean, &paths[..]),
|
Subcommand::Clean { ref paths, .. } => (Kind::Clean, &paths[..]),
|
||||||
Subcommand::Format { .. } => (Kind::Format, &[][..]),
|
Subcommand::Format { .. } => (Kind::Format, &[][..]),
|
||||||
|
Subcommand::Suggest { .. } => (Kind::Suggest, &[][..]),
|
||||||
Subcommand::Setup { profile: ref path } => (
|
Subcommand::Setup { profile: ref path } => (
|
||||||
Kind::Setup,
|
Kind::Setup,
|
||||||
path.as_ref().map_or([].as_slice(), |path| std::slice::from_ref(path)),
|
path.as_ref().map_or([].as_slice(), |path| std::slice::from_ref(path)),
|
||||||
|
@ -900,6 +905,21 @@ impl<'a> Builder<'a> {
|
||||||
Self::new_internal(build, kind, paths.to_owned())
|
Self::new_internal(build, kind, paths.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new standalone builder for use outside of the normal process
|
||||||
|
pub fn new_standalone(
|
||||||
|
build: &mut Build,
|
||||||
|
kind: Kind,
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
stage: Option<u32>,
|
||||||
|
) -> Builder<'_> {
|
||||||
|
// FIXME: don't mutate `build`
|
||||||
|
if let Some(stage) = stage {
|
||||||
|
build.config.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new_internal(build, kind, paths.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn execute_cli(&self) {
|
pub fn execute_cli(&self) {
|
||||||
self.run_step_descriptions(&Builder::get_step_descriptions(self.kind), &self.paths);
|
self.run_step_descriptions(&Builder::get_step_descriptions(self.kind), &self.paths);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,7 @@ pub enum DryRun {
|
||||||
/// filled out from the decoded forms of the structs below. For documentation
|
/// filled out from the decoded forms of the structs below. For documentation
|
||||||
/// each field, see the corresponding fields in
|
/// each field, see the corresponding fields in
|
||||||
/// `config.example.toml`.
|
/// `config.example.toml`.
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub changelog_seen: Option<usize>,
|
pub changelog_seen: Option<usize>,
|
||||||
pub ccache: Option<String>,
|
pub ccache: Option<String>,
|
||||||
|
@ -240,23 +239,20 @@ pub struct Config {
|
||||||
pub initial_rustfmt: RefCell<RustfmtState>,
|
pub initial_rustfmt: RefCell<RustfmtState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct Stage0Metadata {
|
pub struct Stage0Metadata {
|
||||||
pub compiler: CompilerMetadata,
|
pub compiler: CompilerMetadata,
|
||||||
pub config: Stage0Config,
|
pub config: Stage0Config,
|
||||||
pub checksums_sha256: HashMap<String, String>,
|
pub checksums_sha256: HashMap<String, String>,
|
||||||
pub rustfmt: Option<RustfmtMetadata>,
|
pub rustfmt: Option<RustfmtMetadata>,
|
||||||
}
|
}
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct CompilerMetadata {
|
pub struct CompilerMetadata {
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct Stage0Config {
|
pub struct Stage0Config {
|
||||||
pub dist_server: String,
|
pub dist_server: String,
|
||||||
pub artifacts_server: String,
|
pub artifacts_server: String,
|
||||||
|
@ -264,8 +260,7 @@ pub struct Stage0Config {
|
||||||
pub git_merge_commit_email: String,
|
pub git_merge_commit_email: String,
|
||||||
pub nightly_branch: String,
|
pub nightly_branch: String,
|
||||||
}
|
}
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct RustfmtMetadata {
|
pub struct RustfmtMetadata {
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
@ -443,8 +438,7 @@ impl PartialEq<&str> for TargetSelection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-target configuration stored in the global configuration structure.
|
/// Per-target configuration stored in the global configuration structure.
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub struct Target {
|
pub struct Target {
|
||||||
/// Some(path to llvm-config) if using an external LLVM.
|
/// Some(path to llvm-config) if using an external LLVM.
|
||||||
pub llvm_config: Option<PathBuf>,
|
pub llvm_config: Option<PathBuf>,
|
||||||
|
@ -1396,7 +1390,8 @@ impl Config {
|
||||||
| Subcommand::Fix { .. }
|
| Subcommand::Fix { .. }
|
||||||
| Subcommand::Run { .. }
|
| Subcommand::Run { .. }
|
||||||
| Subcommand::Setup { .. }
|
| Subcommand::Setup { .. }
|
||||||
| Subcommand::Format { .. } => flags.stage.unwrap_or(0),
|
| Subcommand::Format { .. }
|
||||||
|
| Subcommand::Suggest { .. } => flags.stage.unwrap_or(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
// CI should always run stage 2 builds, unless it specifically states otherwise
|
// CI should always run stage 2 builds, unless it specifically states otherwise
|
||||||
|
@ -1421,7 +1416,8 @@ impl Config {
|
||||||
| Subcommand::Fix { .. }
|
| Subcommand::Fix { .. }
|
||||||
| Subcommand::Run { .. }
|
| Subcommand::Run { .. }
|
||||||
| Subcommand::Setup { .. }
|
| Subcommand::Setup { .. }
|
||||||
| Subcommand::Format { .. } => {}
|
| Subcommand::Format { .. }
|
||||||
|
| Subcommand::Suggest { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,8 +84,7 @@ pub struct Flags {
|
||||||
pub free_args: Option<Vec<String>>,
|
pub free_args: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(test, derive(Clone))]
|
|
||||||
pub enum Subcommand {
|
pub enum Subcommand {
|
||||||
Build {
|
Build {
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
|
@ -149,6 +148,9 @@ pub enum Subcommand {
|
||||||
Setup {
|
Setup {
|
||||||
profile: Option<PathBuf>,
|
profile: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
Suggest {
|
||||||
|
run: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Subcommand {
|
impl Default for Subcommand {
|
||||||
|
@ -183,6 +185,7 @@ Subcommands:
|
||||||
install Install distribution artifacts
|
install Install distribution artifacts
|
||||||
run, r Run tools contained in this repository
|
run, r Run tools contained in this repository
|
||||||
setup Create a config.toml (making it easier to use `x.py` itself)
|
setup Create a config.toml (making it easier to use `x.py` itself)
|
||||||
|
suggest Suggest a subset of tests to run, based on modified files
|
||||||
|
|
||||||
To learn more about a subcommand, run `./x.py <subcommand> -h`",
|
To learn more about a subcommand, run `./x.py <subcommand> -h`",
|
||||||
);
|
);
|
||||||
|
@ -349,6 +352,9 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
|
||||||
Kind::Run => {
|
Kind::Run => {
|
||||||
opts.optmulti("", "args", "arguments for the tool", "ARGS");
|
opts.optmulti("", "args", "arguments for the tool", "ARGS");
|
||||||
}
|
}
|
||||||
|
Kind::Suggest => {
|
||||||
|
opts.optflag("", "run", "run suggested tests");
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -565,7 +571,7 @@ Arguments:
|
||||||
Profile::all_for_help(" ").trim_end()
|
Profile::all_for_help(" ").trim_end()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Kind::Bench | Kind::Clean | Kind::Dist | Kind::Install => {}
|
Kind::Bench | Kind::Clean | Kind::Dist | Kind::Install | Kind::Suggest => {}
|
||||||
};
|
};
|
||||||
// Get any optional paths which occur after the subcommand
|
// Get any optional paths which occur after the subcommand
|
||||||
let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
|
let mut paths = matches.free[1..].iter().map(|p| p.into()).collect::<Vec<PathBuf>>();
|
||||||
|
@ -626,6 +632,7 @@ Arguments:
|
||||||
Kind::Format => Subcommand::Format { check: matches.opt_present("check"), paths },
|
Kind::Format => Subcommand::Format { check: matches.opt_present("check"), paths },
|
||||||
Kind::Dist => Subcommand::Dist { paths },
|
Kind::Dist => Subcommand::Dist { paths },
|
||||||
Kind::Install => Subcommand::Install { paths },
|
Kind::Install => Subcommand::Install { paths },
|
||||||
|
Kind::Suggest => Subcommand::Suggest { run: matches.opt_present("run") },
|
||||||
Kind::Run => {
|
Kind::Run => {
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
println!("\nrun requires at least a path!\n");
|
println!("\nrun requires at least a path!\n");
|
||||||
|
@ -734,6 +741,7 @@ impl Subcommand {
|
||||||
Subcommand::Install { .. } => Kind::Install,
|
Subcommand::Install { .. } => Kind::Install,
|
||||||
Subcommand::Run { .. } => Kind::Run,
|
Subcommand::Run { .. } => Kind::Run,
|
||||||
Subcommand::Setup { .. } => Kind::Setup,
|
Subcommand::Setup { .. } => Kind::Setup,
|
||||||
|
Subcommand::Suggest { .. } => Kind::Suggest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ mod render_tests;
|
||||||
mod run;
|
mod run;
|
||||||
mod sanity;
|
mod sanity;
|
||||||
mod setup;
|
mod setup;
|
||||||
|
mod suggest;
|
||||||
mod tarball;
|
mod tarball;
|
||||||
mod test;
|
mod test;
|
||||||
mod tool;
|
mod tool;
|
||||||
|
@ -189,6 +190,7 @@ pub enum GitRepo {
|
||||||
/// although most functions are implemented as free functions rather than
|
/// although most functions are implemented as free functions rather than
|
||||||
/// methods specifically on this structure itself (to make it easier to
|
/// methods specifically on this structure itself (to make it easier to
|
||||||
/// organize).
|
/// organize).
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Build {
|
pub struct Build {
|
||||||
/// User-specified configuration from `config.toml`.
|
/// User-specified configuration from `config.toml`.
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -242,7 +244,7 @@ pub struct Build {
|
||||||
metrics: metrics::BuildMetrics,
|
metrics: metrics::BuildMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
struct Crate {
|
struct Crate {
|
||||||
name: Interned<String>,
|
name: Interned<String>,
|
||||||
deps: HashSet<Interned<String>>,
|
deps: HashSet<Interned<String>>,
|
||||||
|
@ -656,13 +658,20 @@ impl Build {
|
||||||
job::setup(self);
|
job::setup(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Subcommand::Format { check, paths } = &self.config.cmd {
|
|
||||||
return format::format(&builder::Builder::new(&self), *check, &paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download rustfmt early so that it can be used in rust-analyzer configs.
|
// Download rustfmt early so that it can be used in rust-analyzer configs.
|
||||||
let _ = &builder::Builder::new(&self).initial_rustfmt();
|
let _ = &builder::Builder::new(&self).initial_rustfmt();
|
||||||
|
|
||||||
|
// hardcoded subcommands
|
||||||
|
match &self.config.cmd {
|
||||||
|
Subcommand::Format { check, paths } => {
|
||||||
|
return format::format(&builder::Builder::new(&self), *check, &paths);
|
||||||
|
}
|
||||||
|
Subcommand::Suggest { run } => {
|
||||||
|
return suggest::suggest(&builder::Builder::new(&self), *run);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let builder = builder::Builder::new(&self);
|
let builder = builder::Builder::new(&self);
|
||||||
if let Some(path) = builder.paths.get(0) {
|
if let Some(path) = builder.paths.get(0) {
|
||||||
|
|
72
src/bootstrap/suggest.rs
Normal file
72
src/bootstrap/suggest.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
builder::{Builder, Kind},
|
||||||
|
tool::Tool,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Suggests a list of possible `x.py` commands to run based on modified files in branch.
|
||||||
|
pub fn suggest(builder: &Builder<'_>, run: bool) {
|
||||||
|
let suggestions =
|
||||||
|
builder.tool_cmd(Tool::SuggestTests).output().expect("failed to run `suggest-tests` tool");
|
||||||
|
|
||||||
|
if !suggestions.status.success() {
|
||||||
|
println!("failed to run `suggest-tests` tool ({})", suggestions.status);
|
||||||
|
println!(
|
||||||
|
"`suggest_tests` stdout:\n{}`suggest_tests` stderr:\n{}",
|
||||||
|
String::from_utf8(suggestions.stdout).unwrap(),
|
||||||
|
String::from_utf8(suggestions.stderr).unwrap()
|
||||||
|
);
|
||||||
|
panic!("failed to run `suggest-tests`");
|
||||||
|
}
|
||||||
|
|
||||||
|
let suggestions = String::from_utf8(suggestions.stdout).unwrap();
|
||||||
|
let suggestions = suggestions
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
let mut sections = line.split_ascii_whitespace();
|
||||||
|
|
||||||
|
// this code expects one suggestion per line in the following format:
|
||||||
|
// <x_subcommand> {some number of flags} [optional stage number]
|
||||||
|
let cmd = sections.next().unwrap();
|
||||||
|
let stage = sections.next_back().map(|s| str::parse(s).ok()).flatten();
|
||||||
|
let paths: Vec<PathBuf> = sections.map(|p| PathBuf::from_str(p).unwrap()).collect();
|
||||||
|
|
||||||
|
(cmd, stage, paths)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if !suggestions.is_empty() {
|
||||||
|
println!("==== SUGGESTIONS ====");
|
||||||
|
for sug in &suggestions {
|
||||||
|
print!("x {} ", sug.0);
|
||||||
|
if let Some(stage) = sug.1 {
|
||||||
|
print!("--stage {stage} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for path in &sug.2 {
|
||||||
|
print!("{} ", path.display());
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
println!("=====================");
|
||||||
|
} else {
|
||||||
|
println!("No suggestions found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if run {
|
||||||
|
for sug in suggestions {
|
||||||
|
let mut build = builder.build.clone();
|
||||||
|
|
||||||
|
let builder =
|
||||||
|
Builder::new_standalone(&mut build, Kind::parse(&sug.0).unwrap(), sug.2, sug.1);
|
||||||
|
|
||||||
|
builder.execute_cli()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("help: consider using the `--run` flag to automatically run suggested tests");
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,6 +128,42 @@ impl Step for CrateJsonDocLint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SuggestTestsCrate {
|
||||||
|
host: TargetSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step for SuggestTestsCrate {
|
||||||
|
type Output = ();
|
||||||
|
const ONLY_HOSTS: bool = true;
|
||||||
|
const DEFAULT: bool = true;
|
||||||
|
|
||||||
|
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||||
|
run.path("src/tools/suggest-tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_run(run: RunConfig<'_>) {
|
||||||
|
run.builder.ensure(SuggestTestsCrate { host: run.target });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(self, builder: &Builder<'_>) {
|
||||||
|
let bootstrap_host = builder.config.build;
|
||||||
|
let compiler = builder.compiler(0, bootstrap_host);
|
||||||
|
|
||||||
|
let suggest_tests = tool::prepare_tool_cargo(
|
||||||
|
builder,
|
||||||
|
compiler,
|
||||||
|
Mode::ToolBootstrap,
|
||||||
|
bootstrap_host,
|
||||||
|
"test",
|
||||||
|
"src/tools/suggest-tests",
|
||||||
|
SourceType::InTree,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
add_flags_and_try_run_tests(builder, &mut suggest_tests.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct Linkcheck {
|
pub struct Linkcheck {
|
||||||
host: TargetSelection,
|
host: TargetSelection,
|
||||||
|
|
|
@ -433,6 +433,7 @@ bootstrap_tool!(
|
||||||
ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder";
|
ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder";
|
||||||
CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata";
|
CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata";
|
||||||
GenerateCopyright, "src/tools/generate-copyright", "generate-copyright";
|
GenerateCopyright, "src/tools/generate-copyright", "generate-copyright";
|
||||||
|
SuggestTests, "src/tools/suggest-tests", "suggest-tests";
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
|
|
9
src/tools/suggest-tests/Cargo.toml
Normal file
9
src/tools/suggest-tests/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "suggest-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glob = "0.3.0"
|
||||||
|
build_helper = { version = "0.1.0", path = "../build_helper" }
|
||||||
|
once_cell = "1.17.1"
|
23
src/tools/suggest-tests/src/dynamic_suggestions.rs
Normal file
23
src/tools/suggest-tests/src/dynamic_suggestions.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::Suggestion;
|
||||||
|
|
||||||
|
type DynamicSuggestion = fn(&Path) -> Vec<Suggestion>;
|
||||||
|
|
||||||
|
pub(crate) const DYNAMIC_SUGGESTIONS: &[DynamicSuggestion] = &[|path: &Path| -> Vec<Suggestion> {
|
||||||
|
if path.starts_with("compiler/") || path.starts_with("library/") {
|
||||||
|
let path = path.components().take(2).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
vec![Suggestion::with_single_path(
|
||||||
|
"test",
|
||||||
|
None,
|
||||||
|
&format!(
|
||||||
|
"{}/{}",
|
||||||
|
path[0].as_os_str().to_str().unwrap(),
|
||||||
|
path[1].as_os_str().to_str().unwrap()
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}];
|
96
src/tools/suggest-tests/src/lib.rs
Normal file
96
src/tools/suggest-tests/src/lib.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use dynamic_suggestions::DYNAMIC_SUGGESTIONS;
|
||||||
|
use glob::Pattern;
|
||||||
|
use static_suggestions::STATIC_SUGGESTIONS;
|
||||||
|
|
||||||
|
mod dynamic_suggestions;
|
||||||
|
mod static_suggestions;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
macro_rules! sug {
|
||||||
|
($cmd:expr) => {
|
||||||
|
Suggestion::new($cmd, None, &[])
|
||||||
|
};
|
||||||
|
|
||||||
|
($cmd:expr, $paths:expr) => {
|
||||||
|
Suggestion::new($cmd, None, $paths.as_slice())
|
||||||
|
};
|
||||||
|
|
||||||
|
($cmd:expr, $stage:expr, $paths:expr) => {
|
||||||
|
Suggestion::new($cmd, Some($stage), $paths.as_slice())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use sug;
|
||||||
|
|
||||||
|
pub fn get_suggestions<T: AsRef<str>>(modified_files: &[T]) -> Vec<Suggestion> {
|
||||||
|
let mut suggestions = Vec::new();
|
||||||
|
|
||||||
|
// static suggestions
|
||||||
|
for sug in STATIC_SUGGESTIONS.iter() {
|
||||||
|
let glob = Pattern::new(&sug.0).expect("Found invalid glob pattern!");
|
||||||
|
|
||||||
|
for file in modified_files {
|
||||||
|
if glob.matches(file.as_ref()) {
|
||||||
|
suggestions.extend_from_slice(&sug.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamic suggestions
|
||||||
|
for sug in DYNAMIC_SUGGESTIONS {
|
||||||
|
for file in modified_files {
|
||||||
|
let sugs = sug(Path::new(file.as_ref()));
|
||||||
|
|
||||||
|
suggestions.extend_from_slice(&sugs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions.sort();
|
||||||
|
suggestions.dedup();
|
||||||
|
|
||||||
|
suggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Suggestion {
|
||||||
|
pub cmd: String,
|
||||||
|
pub stage: Option<u32>,
|
||||||
|
pub paths: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Suggestion {
|
||||||
|
pub fn new(cmd: &str, stage: Option<u32>, paths: &[&str]) -> Self {
|
||||||
|
Self { cmd: cmd.to_owned(), stage, paths: paths.iter().map(|p| p.to_string()).collect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_single_path(cmd: &str, stage: Option<u32>, path: &str) -> Self {
|
||||||
|
Self::new(cmd, stage, &[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Suggestion {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "{} ", self.cmd)?;
|
||||||
|
|
||||||
|
for path in &self.paths {
|
||||||
|
write!(f, "{} ", path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stage) = self.stage {
|
||||||
|
write!(f, "{}", stage)?;
|
||||||
|
} else {
|
||||||
|
// write a sentinel value here (in place of a stage) to be consumed
|
||||||
|
// by the shim in bootstrap, it will be read and ignored.
|
||||||
|
write!(f, "N/A")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
27
src/tools/suggest-tests/src/main.rs
Normal file
27
src/tools/suggest-tests/src/main.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use build_helper::git::get_git_modified_files;
|
||||||
|
use suggest_tests::get_suggestions;
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let modified_files = get_git_modified_files(None, &Vec::new());
|
||||||
|
let modified_files = match modified_files {
|
||||||
|
Ok(Some(files)) => files,
|
||||||
|
Ok(None) => {
|
||||||
|
eprintln!("git error");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Could not get modified files from git: \"{err}\"");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let suggestions = get_suggestions(&modified_files);
|
||||||
|
|
||||||
|
for sug in &suggestions {
|
||||||
|
println!("{sug}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
24
src/tools/suggest-tests/src/static_suggestions.rs
Normal file
24
src/tools/suggest-tests/src/static_suggestions.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::{sug, Suggestion};
|
||||||
|
|
||||||
|
// FIXME: perhaps this could use `std::lazy` when it is stablizied
|
||||||
|
macro_rules! static_suggestions {
|
||||||
|
($( $glob:expr => [ $( $suggestion:expr ),* ] ),*) => {
|
||||||
|
pub(crate) const STATIC_SUGGESTIONS: ::once_cell::unsync::Lazy<Vec<(&'static str, Vec<Suggestion>)>>
|
||||||
|
= ::once_cell::unsync::Lazy::new(|| vec![ $( ($glob, vec![ $($suggestion),* ]) ),*]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_suggestions! {
|
||||||
|
"*.md" => [
|
||||||
|
sug!("test", 0, ["linkchecker"])
|
||||||
|
],
|
||||||
|
|
||||||
|
"compiler/*" => [
|
||||||
|
sug!("check"),
|
||||||
|
sug!("test", 1, ["src/test/ui", "src/test/run-make"])
|
||||||
|
],
|
||||||
|
|
||||||
|
"src/librustdoc/*" => [
|
||||||
|
sug!("test", 1, ["rustdoc"])
|
||||||
|
]
|
||||||
|
}
|
21
src/tools/suggest-tests/src/tests.rs
Normal file
21
src/tools/suggest-tests/src/tests.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
macro_rules! sugg_test {
|
||||||
|
( $( $name:ident: $paths:expr => $suggestions:expr ),* ) => {
|
||||||
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let suggestions = crate::get_suggestions(&$paths).into_iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||||
|
assert_eq!(suggestions, $suggestions);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sugg_test! {
|
||||||
|
test_error_code_docs: ["compiler/rustc_error_codes/src/error_codes/E0000.md"] =>
|
||||||
|
["check N/A", "test compiler/rustc_error_codes N/A", "test linkchecker 0", "test src/test/ui src/test/run-make 1"],
|
||||||
|
|
||||||
|
test_rustdoc: ["src/librustdoc/src/lib.rs"] => ["test rustdoc 1"],
|
||||||
|
|
||||||
|
test_rustdoc_and_libstd: ["src/librustdoc/src/lib.rs", "library/std/src/lib.rs"] =>
|
||||||
|
["test library/std N/A", "test rustdoc 1"]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue