diff options
| author | bors <bors@rust-lang.org> | 2023-04-14 15:06:51 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-04-14 15:06:51 +0000 |
| commit | 660c966ff941ddf995d3251df32508b383cd4cee (patch) | |
| tree | a3fe0d1d5a9da6629ff5a3a4f18af694eb4d5d32 /src | |
| parent | b0884a3528c45a5d575e182f407c759d243fdcba (diff) | |
| parent | 425913367b79680068d7cb358b7609ea2be0da14 (diff) | |
| download | rust-660c966ff941ddf995d3251df32508b383cd4cee.tar.gz rust-660c966ff941ddf995d3251df32508b383cd4cee.zip | |
Auto merge of #110324 - JohnTitor:rollup-jq31pd1, r=JohnTitor
Rollup of 7 pull requests Successful merges: - #103682 (Stabilize rustdoc `--test-run-directory`) - #106249 (Create "suggested tests" tool in `rustbuild`) - #110047 (Add link to `collections` docs to `extend` trait) - #110269 (Add `tidy-alphabetical` to features in `core`) - #110292 (Add `tidy-alphabetical` to features in `alloc` & `std`) - #110305 (rustdoc-search: use ES6 `Map` and `Set` where they make sense) - #110315 (Add a stable MIR way to get the main function) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
Diffstat (limited to 'src')
| -rw-r--r-- | src/bootstrap/builder.rs | 22 | ||||
| -rw-r--r-- | src/bootstrap/config.rs | 24 | ||||
| -rw-r--r-- | src/bootstrap/flags.rs | 14 | ||||
| -rw-r--r-- | src/bootstrap/lib.rs | 19 | ||||
| -rw-r--r-- | src/bootstrap/suggest.rs | 80 | ||||
| -rw-r--r-- | src/bootstrap/test.rs | 36 | ||||
| -rw-r--r-- | src/bootstrap/tool.rs | 1 | ||||
| -rw-r--r-- | src/doc/rustdoc/src/command-line-arguments.md | 15 | ||||
| -rw-r--r-- | src/doc/rustdoc/src/write-documentation/documentation-tests.md | 12 | ||||
| -rw-r--r-- | src/librustdoc/html/static/js/externs.js | 7 | ||||
| -rw-r--r-- | src/librustdoc/html/static/js/search.js | 124 | ||||
| -rw-r--r-- | src/librustdoc/lib.rs | 2 | ||||
| -rw-r--r-- | src/tools/suggest-tests/Cargo.toml | 9 | ||||
| -rw-r--r-- | src/tools/suggest-tests/src/dynamic_suggestions.rs | 23 | ||||
| -rw-r--r-- | src/tools/suggest-tests/src/lib.rs | 96 | ||||
| -rw-r--r-- | src/tools/suggest-tests/src/main.rs | 27 | ||||
| -rw-r--r-- | src/tools/suggest-tests/src/static_suggestions.rs | 24 | ||||
| -rw-r--r-- | src/tools/suggest-tests/src/tests.rs | 21 |
18 files changed, 473 insertions, 83 deletions
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index ade8fa4c74d..e959ea06f8b 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -591,6 +591,7 @@ pub enum Kind { Install, Run, Setup, + Suggest, } impl Kind { @@ -610,6 +611,7 @@ impl Kind { "install" => Kind::Install, "run" | "r" => Kind::Run, "setup" => Kind::Setup, + "suggest" => Kind::Suggest, _ => return None, }) } @@ -629,6 +631,7 @@ impl Kind { Kind::Install => "install", Kind::Run => "run", Kind::Setup => "setup", + Kind::Suggest => "suggest", } } } @@ -709,6 +712,7 @@ impl<'a> Builder<'a> { test::CrateRustdoc, test::CrateRustdocJsonTypes, test::CrateJsonDocLint, + test::SuggestTestsCrate, test::Linkcheck, test::TierCheck, test::ReplacePlaceholderTest, @@ -827,7 +831,7 @@ impl<'a> Builder<'a> { Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode), Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std), // 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::Clean { ref paths, .. } => (Kind::Clean, &paths[..]), Subcommand::Format { .. } => (Kind::Format, &[][..]), + Subcommand::Suggest { .. } => (Kind::Suggest, &[][..]), Subcommand::Setup { profile: ref path } => ( Kind::Setup, 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()) } + /// 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) { self.run_step_descriptions(&Builder::get_step_descriptions(self.kind), &self.paths); } diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index dd65dc91c0c..cc3b3bc25f3 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -56,8 +56,7 @@ pub enum DryRun { /// filled out from the decoded forms of the structs below. For documentation /// each field, see the corresponding fields in /// `config.example.toml`. -#[derive(Default)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Clone)] pub struct Config { pub changelog_seen: Option<usize>, pub ccache: Option<String>, @@ -240,23 +239,20 @@ pub struct Config { pub initial_rustfmt: RefCell<RustfmtState>, } -#[derive(Default, Deserialize)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Deserialize, Clone)] pub struct Stage0Metadata { pub compiler: CompilerMetadata, pub config: Stage0Config, pub checksums_sha256: HashMap<String, String>, pub rustfmt: Option<RustfmtMetadata>, } -#[derive(Default, Deserialize)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Deserialize, Clone)] pub struct CompilerMetadata { pub date: String, pub version: String, } -#[derive(Default, Deserialize)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Deserialize, Clone)] pub struct Stage0Config { pub dist_server: String, pub artifacts_server: String, @@ -264,8 +260,7 @@ pub struct Stage0Config { pub git_merge_commit_email: String, pub nightly_branch: String, } -#[derive(Default, Deserialize)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Deserialize, Clone)] pub struct RustfmtMetadata { pub date: String, pub version: String, @@ -443,8 +438,7 @@ impl PartialEq<&str> for TargetSelection { } /// Per-target configuration stored in the global configuration structure. -#[derive(Default)] -#[cfg_attr(test, derive(Clone))] +#[derive(Default, Clone)] pub struct Target { /// Some(path to llvm-config) if using an external LLVM. pub llvm_config: Option<PathBuf>, @@ -1396,7 +1390,8 @@ impl Config { | Subcommand::Fix { .. } | Subcommand::Run { .. } | 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 @@ -1421,7 +1416,8 @@ impl Config { | Subcommand::Fix { .. } | Subcommand::Run { .. } | Subcommand::Setup { .. } - | Subcommand::Format { .. } => {} + | Subcommand::Format { .. } + | Subcommand::Suggest { .. } => {} } } diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs index 2b0b772a618..b6f5f310398 100644 --- a/src/bootstrap/flags.rs +++ b/src/bootstrap/flags.rs @@ -84,8 +84,7 @@ pub struct Flags { pub free_args: Option<Vec<String>>, } -#[derive(Debug)] -#[cfg_attr(test, derive(Clone))] +#[derive(Debug, Clone)] pub enum Subcommand { Build { paths: Vec<PathBuf>, @@ -149,6 +148,9 @@ pub enum Subcommand { Setup { profile: Option<PathBuf>, }, + Suggest { + run: bool, + }, } impl Default for Subcommand { @@ -183,6 +185,7 @@ Subcommands: install Install distribution artifacts run, r Run tools contained in this repository 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`", ); @@ -349,6 +352,9 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`", Kind::Run => { 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() )); } - 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 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::Dist => Subcommand::Dist { paths }, Kind::Install => Subcommand::Install { paths }, + Kind::Suggest => Subcommand::Suggest { run: matches.opt_present("run") }, Kind::Run => { if paths.is_empty() { println!("\nrun requires at least a path!\n"); @@ -734,6 +741,7 @@ impl Subcommand { Subcommand::Install { .. } => Kind::Install, Subcommand::Run { .. } => Kind::Run, Subcommand::Setup { .. } => Kind::Setup, + Subcommand::Suggest { .. } => Kind::Suggest, } } diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index eaa3afa4b7b..1ecb52e75f1 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -58,6 +58,7 @@ mod render_tests; mod run; mod sanity; mod setup; +mod suggest; mod tarball; mod test; mod tool; @@ -190,6 +191,7 @@ pub enum GitRepo { /// although most functions are implemented as free functions rather than /// methods specifically on this structure itself (to make it easier to /// organize). +#[cfg_attr(not(feature = "build-metrics"), derive(Clone))] pub struct Build { /// User-specified configuration from `config.toml`. config: Config, @@ -243,7 +245,7 @@ pub struct Build { metrics: metrics::BuildMetrics, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct Crate { name: Interned<String>, deps: HashSet<Interned<String>>, @@ -657,13 +659,20 @@ impl Build { 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. 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); if let Some(path) = builder.paths.get(0) { diff --git a/src/bootstrap/suggest.rs b/src/bootstrap/suggest.rs new file mode 100644 index 00000000000..ff20ebec267 --- /dev/null +++ b/src/bootstrap/suggest.rs @@ -0,0 +1,80 @@ +#![cfg_attr(feature = "build-metrics", allow(unused))] + +use std::str::FromStr; + +use std::path::PathBuf; + +use crate::{ + builder::{Builder, Kind}, + tool::Tool, +}; + +#[cfg(feature = "build-metrics")] +pub fn suggest(builder: &Builder<'_>, run: bool) { + panic!("`x suggest` is not supported with `build-metrics`") +} + +/// Suggests a list of possible `x.py` commands to run based on modified files in branch. +#[cfg(not(feature = "build-metrics"))] +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"); + } +} diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 2bf28674d36..aedf1ecab13 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -129,6 +129,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)] pub struct Linkcheck { host: TargetSelection, } diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 6a687a7903e..d1fd2e8c42c 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -433,6 +433,7 @@ bootstrap_tool!( ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder"; CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata"; GenerateCopyright, "src/tools/generate-copyright", "generate-copyright"; + SuggestTests, "src/tools/suggest-tests", "suggest-tests"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md index dfc80426372..b46d80eb362 100644 --- a/src/doc/rustdoc/src/command-line-arguments.md +++ b/src/doc/rustdoc/src/command-line-arguments.md @@ -179,7 +179,7 @@ $ rustdoc src/lib.rs --test This flag will run your code examples as tests. For more, see [the chapter on documentation tests](write-documentation/documentation-tests.md). -See also `--test-args`. +See also `--test-args` and `--test-run-directory`. ## `--test-args`: pass options to test runner @@ -194,6 +194,19 @@ For more, see [the chapter on documentation tests](write-documentation/documenta See also `--test`. +## `--test-run-directory`: run code examples in a specific directory + +Using this flag looks like this: + +```bash +$ rustdoc src/lib.rs --test --test-run-directory=/path/to/working/directory +``` + +This flag will run your code examples in the specified working directory. +For more, see [the chapter on documentation tests](write-documentation/documentation-tests.md). + +See also `--test`. + ## `--target`: generate documentation for the specified target triple Using this flag looks like this: diff --git a/src/doc/rustdoc/src/write-documentation/documentation-tests.md b/src/doc/rustdoc/src/write-documentation/documentation-tests.md index 1cb5b049df4..a7d3186fb78 100644 --- a/src/doc/rustdoc/src/write-documentation/documentation-tests.md +++ b/src/doc/rustdoc/src/write-documentation/documentation-tests.md @@ -443,3 +443,15 @@ pub struct ReadmeDoctests; This will include your README as documentation on the hidden struct `ReadmeDoctests`, which will then be tested alongside the rest of your doctests. + +## Controlling the compilation and run directories + +By default, `rustdoc --test` will compile and run documentation test examples +from the same working directory. +The compilation directory is being used for compiler diagnostics, the `file!()` macro and +the output of `rustdoc` test runner itself, whereas the run directory has an influence on file-system +operations within documentation test examples, such as `std::fs::read_to_string`. + +The `--test-run-directory` flag allows controlling the run directory separately from the compilation directory. +This is particularly useful in workspaces, where compiler invocations and thus diagnostics should be +relative to the workspace directory, but documentation test examples should run relative to the crate directory. diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index ecbe15a59da..4c81a0979c1 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -66,6 +66,11 @@ let Row; let ResultsTable; /** + * @typedef {Map<String, ResultObject>} + */ +let Results; + +/** * @typedef {{ * desc: string, * displayPath: string, @@ -80,7 +85,7 @@ let ResultsTable; * ty: number, * }} */ -let Results; +let ResultObject; /** * A pair of [inputs, outputs], or 0 for null. This is stored in the search index. diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index c081578b8d4..40cdc55bbc3 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -191,7 +191,7 @@ function initSearch(rawSearchIndex) { */ let searchIndex; let currentResults; - const ALIASES = Object.create(null); + const ALIASES = new Map(); function isWhitespace(c) { return " \t\n\r".indexOf(c) !== -1; @@ -903,10 +903,18 @@ function initSearch(rawSearchIndex) { * @return {ResultsTable} */ function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) { - const results_others = {}, results_in_args = {}, results_returned = {}; + const results_others = new Map(), results_in_args = new Map(), + results_returned = new Map(); + /** + * Add extra data to result objects, and filter items that have been + * marked for removal. + * + * @param {[ResultObject]} results + * @returns {[ResultObject]} + */ function transformResults(results) { - const duplicates = {}; + const duplicates = new Set(); const out = []; for (const result of results) { @@ -919,10 +927,10 @@ function initSearch(rawSearchIndex) { // To be sure than it some items aren't considered as duplicate. obj.fullPath += "|" + obj.ty; - if (duplicates[obj.fullPath]) { + if (duplicates.has(obj.fullPath)) { continue; } - duplicates[obj.fullPath] = true; + duplicates.add(obj.fullPath); obj.href = res[1]; out.push(obj); @@ -934,24 +942,30 @@ function initSearch(rawSearchIndex) { return out; } + /** + * This function takes a result map, and sorts it by various criteria, including edit + * distance, substring match, and the crate it comes from. + * + * @param {Results} results + * @param {boolean} isType + * @param {string} preferredCrate + * @returns {[ResultObject]} + */ function sortResults(results, isType, preferredCrate) { - const userQuery = parsedQuery.userQuery; - const ar = []; - for (const entry in results) { - if (hasOwnPropertyRustdoc(results, entry)) { - const result = results[entry]; - result.word = searchWords[result.id]; - result.item = searchIndex[result.id] || {}; - ar.push(result); - } - } - results = ar; // if there are no results then return to default and fail - if (results.length === 0) { + if (results.size === 0) { return []; } - results.sort((aaa, bbb) => { + const userQuery = parsedQuery.userQuery; + const result_list = []; + for (const result of results.values()) { + result.word = searchWords[result.id]; + result.item = searchIndex[result.id] || {}; + result_list.push(result); + } + + result_list.sort((aaa, bbb) => { let a, b; // sort by exact match with regard to the last word (mismatch goes later) @@ -1060,7 +1074,7 @@ function initSearch(rawSearchIndex) { nameSplit = hasPath ? null : parsedQuery.elems[0].path; } - for (const result of results) { + for (const result of result_list) { // this validation does not make sense when searching by types if (result.dontValidate) { continue; @@ -1073,7 +1087,7 @@ function initSearch(rawSearchIndex) { result.id = -1; } } - return transformResults(results); + return transformResults(result_list); } /** @@ -1096,7 +1110,7 @@ function initSearch(rawSearchIndex) { // The names match, but we need to be sure that all generics kinda // match as well. if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) { - const elems = Object.create(null); + const elems = new Map(); for (const entry of row.generics) { if (entry.name === "") { // Pure generic, needs to check into it. @@ -1106,39 +1120,30 @@ function initSearch(rawSearchIndex) { } continue; } - if (elems[entry.name] === undefined) { - elems[entry.name] = []; + let currentEntryElems; + if (elems.has(entry.name)) { + currentEntryElems = elems.get(entry.name); + } else { + currentEntryElems = []; + elems.set(entry.name, currentEntryElems); } - elems[entry.name].push(entry.ty); + currentEntryElems.push(entry.ty); } // We need to find the type that matches the most to remove it in order // to move forward. const handleGeneric = generic => { - let match = null; - if (elems[generic.name]) { - match = generic.name; - } else { - for (const elem_name in elems) { - if (!hasOwnPropertyRustdoc(elems, elem_name)) { - continue; - } - if (elem_name === generic) { - match = elem_name; - break; - } - } - } - if (match === null) { + if (!elems.has(generic.name)) { return false; } - const matchIdx = elems[match].findIndex(tmp_elem => + const matchElems = elems.get(generic.name); + const matchIdx = matchElems.findIndex(tmp_elem => typePassesFilter(generic.typeFilter, tmp_elem)); if (matchIdx === -1) { return false; } - elems[match].splice(matchIdx, 1); - if (elems[match].length === 0) { - delete elems[match]; + matchElems.splice(matchIdx, 1); + if (matchElems.length === 0) { + elems.delete(generic.name); } return true; }; @@ -1424,22 +1429,22 @@ function initSearch(rawSearchIndex) { const aliases = []; const crateAliases = []; if (filterCrates !== null) { - if (ALIASES[filterCrates] && ALIASES[filterCrates][lowerQuery]) { - const query_aliases = ALIASES[filterCrates][lowerQuery]; + if (ALIASES.has(filterCrates) && ALIASES.get(filterCrates).has(lowerQuery)) { + const query_aliases = ALIASES.get(filterCrates).get(lowerQuery); for (const alias of query_aliases) { aliases.push(createAliasFromItem(searchIndex[alias])); } } } else { - Object.keys(ALIASES).forEach(crate => { - if (ALIASES[crate][lowerQuery]) { + for (const [crate, crateAliasesIndex] of ALIASES) { + if (crateAliasesIndex.has(lowerQuery)) { const pushTo = crate === currentCrate ? crateAliases : aliases; - const query_aliases = ALIASES[crate][lowerQuery]; + const query_aliases = crateAliasesIndex.get(lowerQuery); for (const alias of query_aliases) { pushTo.push(createAliasFromItem(searchIndex[alias])); } } - }); + } } const sortFunc = (aaa, bbb) => { @@ -1496,19 +1501,19 @@ function initSearch(rawSearchIndex) { function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) { const inBounds = dist <= maxEditDistance || index !== -1; if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) { - if (results[fullId] !== undefined) { - const result = results[fullId]; + if (results.has(fullId)) { + const result = results.get(fullId); if (result.dontValidate || result.dist <= dist) { return; } } - results[fullId] = { + results.set(fullId, { id: id, index: index, dontValidate: parsedQuery.literalSearch, dist: dist, path_dist: path_dist, - }; + }); } } @@ -2345,17 +2350,22 @@ function initSearch(rawSearchIndex) { } if (aliases) { - ALIASES[crate] = Object.create(null); + const currentCrateAliases = new Map(); + ALIASES.set(crate, currentCrateAliases); for (const alias_name in aliases) { if (!hasOwnPropertyRustdoc(aliases, alias_name)) { continue; } - if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) { - ALIASES[crate][alias_name] = []; + let currentNameAliases; + if (currentCrateAliases.has(alias_name)) { + currentNameAliases = currentCrateAliases.get(alias_name); + } else { + currentNameAliases = []; + currentCrateAliases.set(alias_name, currentNameAliases); } for (const local_alias of aliases[alias_name]) { - ALIASES[crate][alias_name].push(local_alias + currentIndex); + currentNameAliases.push(local_alias + currentIndex); } } } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index b3640eab953..60c98cc3831 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -284,7 +284,7 @@ fn opts() -> Vec<RustcOptGroup> { stable("test-args", |o| { o.optmulti("", "test-args", "arguments to pass to the test runner", "ARGS") }), - unstable("test-run-directory", |o| { + stable("test-run-directory", |o| { o.optopt( "", "test-run-directory", diff --git a/src/tools/suggest-tests/Cargo.toml b/src/tools/suggest-tests/Cargo.toml new file mode 100644 index 00000000000..f4f4d548bb7 --- /dev/null +++ b/src/tools/suggest-tests/Cargo.toml @@ -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" diff --git a/src/tools/suggest-tests/src/dynamic_suggestions.rs b/src/tools/suggest-tests/src/dynamic_suggestions.rs new file mode 100644 index 00000000000..2b0213cdc22 --- /dev/null +++ b/src/tools/suggest-tests/src/dynamic_suggestions.rs @@ -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() + } +}]; diff --git a/src/tools/suggest-tests/src/lib.rs b/src/tools/suggest-tests/src/lib.rs new file mode 100644 index 00000000000..44cd3c7f6a8 --- /dev/null +++ b/src/tools/suggest-tests/src/lib.rs @@ -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(()) + } +} diff --git a/src/tools/suggest-tests/src/main.rs b/src/tools/suggest-tests/src/main.rs new file mode 100644 index 00000000000..0b541b60cba --- /dev/null +++ b/src/tools/suggest-tests/src/main.rs @@ -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 +} diff --git a/src/tools/suggest-tests/src/static_suggestions.rs b/src/tools/suggest-tests/src/static_suggestions.rs new file mode 100644 index 00000000000..d8166ead8c4 --- /dev/null +++ b/src/tools/suggest-tests/src/static_suggestions.rs @@ -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"]) + ] +} diff --git a/src/tools/suggest-tests/src/tests.rs b/src/tools/suggest-tests/src/tests.rs new file mode 100644 index 00000000000..5bc1a7df7ca --- /dev/null +++ b/src/tools/suggest-tests/src/tests.rs @@ -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"] +} |
