Skip to content

Commit 414eb48

Browse files
committed
add only modified for compiletest
1 parent a94b9fd commit 414eb48

File tree

9 files changed

+137
-29
lines changed

9 files changed

+137
-29
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ dependencies = [
887887
name = "compiletest"
888888
version = "0.0.0"
889889
dependencies = [
890+
"build_helper",
890891
"colored",
891892
"diff",
892893
"getopts",

src/bootstrap/builder/tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ mod dist {
557557
rustfix_coverage: false,
558558
pass: None,
559559
run: None,
560+
only_modified: false,
560561
};
561562

562563
let build = Build::new(config);
@@ -627,6 +628,7 @@ mod dist {
627628
rustfix_coverage: false,
628629
pass: None,
629630
run: None,
631+
only_modified: false,
630632
};
631633
// Make sure rustfmt binary not being found isn't an error.
632634
config.channel = "beta".to_string();

src/bootstrap/flags.rs

+10
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub enum Subcommand {
124124
fail_fast: bool,
125125
doc_tests: DocTests,
126126
rustfix_coverage: bool,
127+
only_modified: bool,
127128
},
128129
Bench {
129130
paths: Vec<PathBuf>,
@@ -301,6 +302,7 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
301302
opts.optflag("", "doc", "only run doc tests");
302303
opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
303304
opts.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged");
305+
opts.optflag("", "only-modified", "only run tests that result has been changed");
304306
opts.optopt(
305307
"",
306308
"compare-mode",
@@ -598,6 +600,7 @@ Arguments:
598600
rustc_args: matches.opt_strs("rustc-args"),
599601
fail_fast: !matches.opt_present("no-fail-fast"),
600602
rustfix_coverage: matches.opt_present("rustfix-coverage"),
603+
only_modified: matches.opt_present("only-modified"),
601604
doc_tests: if matches.opt_present("doc") {
602605
DocTests::Only
603606
} else if matches.opt_present("no-doc") {
@@ -777,6 +780,13 @@ impl Subcommand {
777780
}
778781
}
779782

783+
pub fn only_modified(&self) -> bool {
784+
match *self {
785+
Subcommand::Test { only_modified, .. } => only_modified,
786+
_ => false,
787+
}
788+
}
789+
780790
pub fn force_rerun(&self) -> bool {
781791
match *self {
782792
Subcommand::Test { force_rerun, .. } => force_rerun,

src/bootstrap/format.rs

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Runs rustfmt on the repository.
22
33
use crate::builder::Builder;
4-
use crate::util::{output, output_result, program_out_of_date, t};
5-
use build_helper::git::updated_master_branch;
4+
use crate::util::{output, program_out_of_date, t};
5+
use build_helper::git::get_git_modified_files;
66
use ignore::WalkBuilder;
77
use std::collections::VecDeque;
88
use std::path::{Path, PathBuf};
@@ -80,23 +80,11 @@ fn update_rustfmt_version(build: &Builder<'_>) {
8080
///
8181
/// Returns `None` if all files should be formatted.
8282
fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, String> {
83-
let Ok(updated_master) = updated_master_branch(Some(&build.config.src)) else { return Ok(None); };
84-
8583
if !verify_rustfmt_version(build) {
8684
return Ok(None);
8785
}
8886

89-
let merge_base =
90-
output_result(build.config.git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
91-
Ok(Some(
92-
output_result(
93-
build.config.git().arg("diff-index").arg("--name-only").arg(merge_base.trim()),
94-
)?
95-
.lines()
96-
.map(|s| s.trim().to_owned())
97-
.filter(|f| Path::new(f).extension().map_or(false, |ext| ext == "rs"))
98-
.collect(),
99-
))
87+
get_git_modified_files(Some(&build.config.src), &vec!["rs"])
10088
}
10189

10290
#[derive(serde::Deserialize)]
@@ -169,7 +157,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
169157
ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
170158
}
171159
if !check && paths.is_empty() {
172-
match get_modified_rs_files(build) {
160+
match get_modified_rs_files(&build) {
173161
Ok(Some(files)) => {
174162
for file in files {
175163
println!("formatting modified file {file}");

src/bootstrap/test.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,10 @@ note: if you're sure you want to do this, please open an issue as to why. In the
15081508
if builder.config.rust_optimize_tests {
15091509
cmd.arg("--optimize-tests");
15101510
}
1511+
if builder.config.cmd.only_modified() {
1512+
cmd.arg("--only-modified");
1513+
}
1514+
15111515
let mut flags = if is_rustdoc { Vec::new() } else { vec!["-Crpath".to_string()] };
15121516
flags.push(format!("-Cdebuginfo={}", builder.config.rust_debuginfo_level_tests));
15131517
flags.extend(builder.config.cmd.rustc_args().iter().map(|s| s.to_string()));

src/tools/build_helper/src/git.rs

+65-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
use std::process::Stdio;
12
use std::{path::Path, process::Command};
23

4+
/// Runs a command and returns the output
5+
fn output_result(cmd: &mut Command) -> Result<String, String> {
6+
let output = match cmd.stderr(Stdio::inherit()).output() {
7+
Ok(status) => status,
8+
Err(e) => return Err(format!("failed to run command: {:?}: {}", cmd, e)),
9+
};
10+
if !output.status.success() {
11+
return Err(format!(
12+
"command did not execute successfully: {:?}\n\
13+
expected success, got: {}\n{}",
14+
cmd,
15+
output.status,
16+
String::from_utf8(output.stderr).map_err(|err| format!("{err:?}"))?
17+
));
18+
}
19+
Ok(String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?)
20+
}
21+
322
/// Finds the remote for rust-lang/rust.
423
/// For example for these remotes it will return `upstream`.
524
/// ```text
@@ -14,13 +33,7 @@ pub fn get_rust_lang_rust_remote(git_dir: Option<&Path>) -> Result<String, Strin
1433
git.current_dir(git_dir);
1534
}
1635
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
17-
18-
let output = git.output().map_err(|err| format!("{err:?}"))?;
19-
if !output.status.success() {
20-
return Err("failed to execute git config command".to_owned());
21-
}
22-
23-
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
36+
let stdout = output_result(&mut git)?;
2437

2538
let rust_lang_remote = stdout
2639
.lines()
@@ -73,3 +86,48 @@ pub fn updated_master_branch(git_dir: Option<&Path>) -> Result<String, String> {
7386
// We could implement smarter logic here in the future.
7487
Ok("origin/master".into())
7588
}
89+
90+
/// Returns the files that have been modified in the current branch compared to the master branch.
91+
/// The `extensions` parameter can be used to filter the files by their extension.
92+
/// If `extensions` is empty, all files will be returned.
93+
pub fn get_git_modified_files(
94+
git_dir: Option<&Path>,
95+
extensions: &Vec<&str>,
96+
) -> Result<Option<Vec<String>>, String> {
97+
let Ok(updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
98+
99+
let git = || {
100+
let mut git = Command::new("git");
101+
if let Some(git_dir) = git_dir {
102+
git.current_dir(git_dir);
103+
}
104+
git
105+
};
106+
107+
let merge_base = output_result(git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
108+
let files = output_result(git().arg("diff-index").arg("--name-only").arg(merge_base.trim()))?
109+
.lines()
110+
.map(|s| s.trim().to_owned())
111+
.filter(|f| {
112+
Path::new(f).extension().map_or(false, |ext| {
113+
extensions.is_empty() || extensions.contains(&ext.to_str().unwrap())
114+
})
115+
})
116+
.collect();
117+
Ok(Some(files))
118+
}
119+
120+
/// Returns the files that haven't been added to git yet.
121+
pub fn get_git_untracked_files(git_dir: Option<&Path>) -> Result<Option<Vec<String>>, String> {
122+
let Ok(_updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
123+
let mut git = Command::new("git");
124+
if let Some(git_dir) = git_dir {
125+
git.current_dir(git_dir);
126+
}
127+
128+
let files = output_result(git.arg("ls-files").arg("--others").arg("--exclude-standard"))?
129+
.lines()
130+
.map(|s| s.trim().to_owned())
131+
.collect();
132+
Ok(Some(files))
133+
}

src/tools/compiletest/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ diff = "0.1.10"
99
unified-diff = "0.2.1"
1010
getopts = "0.2"
1111
miropt-test-tools = { path = "../miropt-test-tools" }
12+
build_helper = { path = "../build_helper" }
1213
tracing = "0.1"
1314
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
1415
regex = "1.0"

src/tools/compiletest/src/common.rs

+3
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@ pub struct Config {
380380
/// Whether to rerun tests even if the inputs are unchanged.
381381
pub force_rerun: bool,
382382

383+
/// Only rerun the tests that result has been modified accoring to Git status
384+
pub only_modified: bool,
385+
383386
pub target_cfg: LazyCell<TargetCfg>,
384387
}
385388

src/tools/compiletest/src/main.rs

+47-6
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ extern crate test;
88
use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
99
use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
1010
use crate::util::logv;
11+
use build_helper::git::{get_git_modified_files, get_git_untracked_files};
12+
use core::panic;
1113
use getopts::Options;
1214
use lazycell::LazyCell;
13-
use std::env;
1415
use std::ffi::OsString;
1516
use std::fs;
1617
use std::io::{self, ErrorKind};
1718
use std::path::{Path, PathBuf};
1819
use std::process::{Command, Stdio};
1920
use std::time::SystemTime;
21+
use std::{env, vec};
2022
use test::ColorConfig;
2123
use tracing::*;
2224
use walkdir::WalkDir;
@@ -145,9 +147,10 @@ pub fn parse_config(args: Vec<String>) -> Config {
145147
"",
146148
"rustfix-coverage",
147149
"enable this to generate a Rustfix coverage file, which is saved in \
148-
`./<build_base>/rustfix_missing_coverage.txt`",
150+
`./<build_base>/rustfix_missing_coverage.txt`",
149151
)
150152
.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
153+
.optflag("", "only-modified", "only run tests that result been modified")
151154
.optflag("h", "help", "show this message")
152155
.reqopt("", "channel", "current Rust channel", "CHANNEL")
153156
.optopt("", "edition", "default Rust edition", "EDITION");
@@ -279,6 +282,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
279282
lldb_python_dir: matches.opt_str("lldb-python-dir"),
280283
verbose: matches.opt_present("verbose"),
281284
quiet: matches.opt_present("quiet"),
285+
only_modified: matches.opt_present("only-modified"),
282286
color,
283287
remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
284288
compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
@@ -521,8 +525,16 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
521525
pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
522526
debug!("making tests from {:?}", config.src_base.display());
523527
let inputs = common_inputs_stamp(config);
524-
collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
525-
.unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
528+
let modified_tests = modified_tests(config, &config.src_base);
529+
collect_tests_from_dir(
530+
config,
531+
&config.src_base,
532+
&PathBuf::new(),
533+
&inputs,
534+
tests,
535+
&modified_tests,
536+
)
537+
.unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
526538
}
527539

528540
/// Returns a stamp constructed from input files common to all test cases.
@@ -561,12 +573,34 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
561573
stamp
562574
}
563575

576+
fn modified_tests(config: &Config, dir: &Path) -> Vec<PathBuf> {
577+
if !config.only_modified {
578+
return vec![];
579+
}
580+
let Ok(Some(files)) = get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"]) else { return vec![]; };
581+
// Add new test cases to the list, it will be convenient in daily development.
582+
let Ok(Some(untracked_files)) = get_git_untracked_files(None) else { return vec![]; };
583+
584+
let all_paths = [&files[..], &untracked_files[..]].concat();
585+
let full_paths = {
586+
let mut full_paths: Vec<PathBuf> = all_paths
587+
.into_iter()
588+
.map(|f| fs::canonicalize(&f).unwrap().with_extension("").with_extension("rs"))
589+
.collect();
590+
full_paths.dedup();
591+
full_paths.sort_unstable();
592+
full_paths
593+
};
594+
full_paths
595+
}
596+
564597
fn collect_tests_from_dir(
565598
config: &Config,
566599
dir: &Path,
567600
relative_dir_path: &Path,
568601
inputs: &Stamp,
569602
tests: &mut Vec<test::TestDescAndFn>,
603+
only_modified: &Vec<PathBuf>,
570604
) -> io::Result<()> {
571605
// Ignore directories that contain a file named `compiletest-ignore-dir`.
572606
if dir.join("compiletest-ignore-dir").exists() {
@@ -597,7 +631,7 @@ fn collect_tests_from_dir(
597631
let file = file?;
598632
let file_path = file.path();
599633
let file_name = file.file_name();
600-
if is_test(&file_name) {
634+
if is_test(&file_name) && (!config.only_modified || only_modified.contains(&file_path)) {
601635
debug!("found test file: {:?}", file_path.display());
602636
let paths =
603637
TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
@@ -607,7 +641,14 @@ fn collect_tests_from_dir(
607641
let relative_file_path = relative_dir_path.join(file.file_name());
608642
if &file_name != "auxiliary" {
609643
debug!("found directory: {:?}", file_path.display());
610-
collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
644+
collect_tests_from_dir(
645+
config,
646+
&file_path,
647+
&relative_file_path,
648+
inputs,
649+
tests,
650+
only_modified,
651+
)?;
611652
}
612653
} else {
613654
debug!("found other file/directory: {:?}", file_path.display());

0 commit comments

Comments
 (0)