add only modified for compiletest

This commit is contained in:
yukang 2023-02-04 00:23:11 +08:00
parent a94b9fd0ac
commit 414eb48b66
9 changed files with 137 additions and 29 deletions

View file

@ -1,5 +1,24 @@
use std::process::Stdio;
use std::{path::Path, process::Command};
/// Runs a command and returns the output
fn output_result(cmd: &mut Command) -> Result<String, String> {
let output = match cmd.stderr(Stdio::inherit()).output() {
Ok(status) => status,
Err(e) => return Err(format!("failed to run command: {:?}: {}", cmd, e)),
};
if !output.status.success() {
return Err(format!(
"command did not execute successfully: {:?}\n\
expected success, got: {}\n{}",
cmd,
output.status,
String::from_utf8(output.stderr).map_err(|err| format!("{err:?}"))?
));
}
Ok(String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?)
}
/// Finds the remote for rust-lang/rust.
/// For example for these remotes it will return `upstream`.
/// ```text
@ -14,13 +33,7 @@ pub fn get_rust_lang_rust_remote(git_dir: Option<&Path>) -> Result<String, Strin
git.current_dir(git_dir);
}
git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
let output = git.output().map_err(|err| format!("{err:?}"))?;
if !output.status.success() {
return Err("failed to execute git config command".to_owned());
}
let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
let stdout = output_result(&mut git)?;
let rust_lang_remote = stdout
.lines()
@ -73,3 +86,48 @@ pub fn updated_master_branch(git_dir: Option<&Path>) -> Result<String, String> {
// We could implement smarter logic here in the future.
Ok("origin/master".into())
}
/// Returns the files that have been modified in the current branch compared to the master branch.
/// The `extensions` parameter can be used to filter the files by their extension.
/// If `extensions` is empty, all files will be returned.
pub fn get_git_modified_files(
git_dir: Option<&Path>,
extensions: &Vec<&str>,
) -> Result<Option<Vec<String>>, String> {
let Ok(updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
let git = || {
let mut git = Command::new("git");
if let Some(git_dir) = git_dir {
git.current_dir(git_dir);
}
git
};
let merge_base = output_result(git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
let files = output_result(git().arg("diff-index").arg("--name-only").arg(merge_base.trim()))?
.lines()
.map(|s| s.trim().to_owned())
.filter(|f| {
Path::new(f).extension().map_or(false, |ext| {
extensions.is_empty() || extensions.contains(&ext.to_str().unwrap())
})
})
.collect();
Ok(Some(files))
}
/// Returns the files that haven't been added to git yet.
pub fn get_git_untracked_files(git_dir: Option<&Path>) -> Result<Option<Vec<String>>, String> {
let Ok(_updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
let mut git = Command::new("git");
if let Some(git_dir) = git_dir {
git.current_dir(git_dir);
}
let files = output_result(git.arg("ls-files").arg("--others").arg("--exclude-standard"))?
.lines()
.map(|s| s.trim().to_owned())
.collect();
Ok(Some(files))
}

View file

@ -9,6 +9,7 @@ diff = "0.1.10"
unified-diff = "0.2.1"
getopts = "0.2"
miropt-test-tools = { path = "../miropt-test-tools" }
build_helper = { path = "../build_helper" }
tracing = "0.1"
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
regex = "1.0"

View file

@ -380,6 +380,9 @@ pub struct Config {
/// Whether to rerun tests even if the inputs are unchanged.
pub force_rerun: bool,
/// Only rerun the tests that result has been modified accoring to Git status
pub only_modified: bool,
pub target_cfg: LazyCell<TargetCfg>,
}

View file

@ -8,15 +8,17 @@ extern crate test;
use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
use crate::util::logv;
use build_helper::git::{get_git_modified_files, get_git_untracked_files};
use core::panic;
use getopts::Options;
use lazycell::LazyCell;
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::SystemTime;
use std::{env, vec};
use test::ColorConfig;
use tracing::*;
use walkdir::WalkDir;
@ -145,9 +147,10 @@ pub fn parse_config(args: Vec<String>) -> Config {
"",
"rustfix-coverage",
"enable this to generate a Rustfix coverage file, which is saved in \
`./<build_base>/rustfix_missing_coverage.txt`",
`./<build_base>/rustfix_missing_coverage.txt`",
)
.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
.optflag("", "only-modified", "only run tests that result been modified")
.optflag("h", "help", "show this message")
.reqopt("", "channel", "current Rust channel", "CHANNEL")
.optopt("", "edition", "default Rust edition", "EDITION");
@ -279,6 +282,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
lldb_python_dir: matches.opt_str("lldb-python-dir"),
verbose: matches.opt_present("verbose"),
quiet: matches.opt_present("quiet"),
only_modified: matches.opt_present("only-modified"),
color,
remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
@ -521,8 +525,16 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
debug!("making tests from {:?}", config.src_base.display());
let inputs = common_inputs_stamp(config);
collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
.unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
let modified_tests = modified_tests(config, &config.src_base);
collect_tests_from_dir(
config,
&config.src_base,
&PathBuf::new(),
&inputs,
tests,
&modified_tests,
)
.unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
}
/// Returns a stamp constructed from input files common to all test cases.
@ -561,12 +573,34 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
stamp
}
fn modified_tests(config: &Config, dir: &Path) -> Vec<PathBuf> {
if !config.only_modified {
return vec![];
}
let Ok(Some(files)) = get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"]) else { return vec![]; };
// Add new test cases to the list, it will be convenient in daily development.
let Ok(Some(untracked_files)) = get_git_untracked_files(None) else { return vec![]; };
let all_paths = [&files[..], &untracked_files[..]].concat();
let full_paths = {
let mut full_paths: Vec<PathBuf> = all_paths
.into_iter()
.map(|f| fs::canonicalize(&f).unwrap().with_extension("").with_extension("rs"))
.collect();
full_paths.dedup();
full_paths.sort_unstable();
full_paths
};
full_paths
}
fn collect_tests_from_dir(
config: &Config,
dir: &Path,
relative_dir_path: &Path,
inputs: &Stamp,
tests: &mut Vec<test::TestDescAndFn>,
only_modified: &Vec<PathBuf>,
) -> io::Result<()> {
// Ignore directories that contain a file named `compiletest-ignore-dir`.
if dir.join("compiletest-ignore-dir").exists() {
@ -597,7 +631,7 @@ fn collect_tests_from_dir(
let file = file?;
let file_path = file.path();
let file_name = file.file_name();
if is_test(&file_name) {
if is_test(&file_name) && (!config.only_modified || only_modified.contains(&file_path)) {
debug!("found test file: {:?}", file_path.display());
let paths =
TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
@ -607,7 +641,14 @@ fn collect_tests_from_dir(
let relative_file_path = relative_dir_path.join(file.file_name());
if &file_name != "auxiliary" {
debug!("found directory: {:?}", file_path.display());
collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
collect_tests_from_dir(
config,
&file_path,
&relative_file_path,
inputs,
tests,
only_modified,
)?;
}
} else {
debug!("found other file/directory: {:?}", file_path.display());