Split options parsing into several functions

This commit is contained in:
Igor Aleksanov 2019-10-17 19:10:17 +03:00
parent 12397e9dd5
commit ddc6a5fd0e

View file

@ -40,7 +40,7 @@ impl TestOpts {
/// Result of parsing the options.
pub type OptRes = Result<TestOpts, String>;
/// Result of parsing the option part.
type OptPartRes<T> = Result<Option<T>, String>;
type OptPartRes<T> = Result<T, String>;
fn optgroups() -> getopts::Options {
let mut opts = getopts::Options::new();
@ -186,6 +186,96 @@ Test Attributes:
);
}
/// Parses command line arguments into test options.
/// Returns `None` if help was requested (since we only show help message and don't run tests),
/// returns `Some(Err(..))` if provided arguments are incorrect,
/// otherwise creates a `TestOpts` object and returns it.
pub fn parse_opts(args: &[String]) -> Option<OptRes> {
// Parse matches.
let opts = optgroups();
let args = args.get(1..).unwrap_or(args);
let matches = match opts.parse(args) {
Ok(m) => m,
Err(f) => return Some(Err(f.to_string())),
};
// Check if help was requested.
if matches.opt_present("h") {
// Show help and do nothing more.
usage(&args[0], &opts);
return None;
}
// Actually parse the opts.
let opts_result = parse_opts_impl(matches);
Some(opts_result)
}
// Gets the option value and checks if unstable features are enabled.
macro_rules! unstable_optflag {
($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
let opt = $matches.opt_present($option_name);
if !$allow_unstable && opt {
return Err(format!(
"The \"{}\" flag is only accepted on the nightly compiler",
$option_name
));
}
opt
}};
}
// Implementation of `parse_opts` that doesn't care about help message
// and returns a `Result`.
fn parse_opts_impl(matches: getopts::Matches) -> OptRes {
let allow_unstable = get_allow_unstable(&matches)?;
// Unstable flags
let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic");
let include_ignored = unstable_optflag!(matches, allow_unstable, "include-ignored");
let time_options = get_time_options(&matches, allow_unstable)?;
let quiet = matches.opt_present("quiet");
let exact = matches.opt_present("exact");
let list = matches.opt_present("list");
let skip = matches.opt_strs("skip");
let bench_benchmarks = matches.opt_present("bench");
let run_tests = !bench_benchmarks || matches.opt_present("test");
let logfile = get_log_file(&matches)?;
let run_ignored = get_run_ignored(&matches, include_ignored)?;
let filter = get_filter(&matches)?;
let nocapture = get_nocapture(&matches)?;
let test_threads = get_test_threads(&matches)?;
let color = get_color_config(&matches)?;
let format = get_format(&matches, quiet, allow_unstable)?;
let options = Options::new().display_output(matches.opt_present("show-output"));
let test_opts = TestOpts {
list,
filter,
filter_exact: exact,
exclude_should_panic,
run_ignored,
run_tests,
bench_benchmarks,
logfile,
nocapture,
color,
format,
test_threads,
skip,
time_options,
options,
};
Ok(test_opts)
}
// FIXME: Copied from libsyntax until linkage errors are resolved. Issue #47566
fn is_nightly() -> bool {
// Whether this is a feature-staged build, i.e., on the beta or stable channel
@ -196,26 +286,11 @@ fn is_nightly() -> bool {
bootstrap || !disable_unstable_features
}
// Gets the option value and checks if unstable features are enabled.
macro_rules! unstable_optflag {
($matches:ident, $allow_unstable:ident, $option_name:literal) => {{
let opt = $matches.opt_present($option_name);
if !$allow_unstable && opt {
return Some(Err(format!(
"The \"{}\" flag is only accepted on the nightly compiler",
$option_name
)));
}
opt
}};
}
// Gets the CLI options assotiated with `report-time` feature.
fn get_time_options(
matches: &getopts::Matches,
allow_unstable: bool)
-> Option<OptPartRes<TestTimeOptions>> {
-> OptPartRes<Option<TestTimeOptions>> {
let report_time = unstable_optflag!(matches, allow_unstable, "report-time");
let colored_opt_str = matches.opt_str("report-time");
let mut report_time_colored = report_time && colored_opt_str == Some("colored".into());
@ -232,71 +307,73 @@ fn get_time_options(
None
};
Some(Ok(options))
Ok(options)
}
// Parses command line arguments into test options
pub fn parse_opts(args: &[String]) -> Option<OptRes> {
let mut allow_unstable = false;
let opts = optgroups();
let args = args.get(1..).unwrap_or(args);
let matches = match opts.parse(args) {
Ok(m) => m,
Err(f) => return Some(Err(f.to_string())),
fn get_test_threads(matches: &getopts::Matches) -> OptPartRes<Option<usize>> {
let test_threads = match matches.opt_str("test-threads") {
Some(n_str) => match n_str.parse::<usize>() {
Ok(0) => return Err("argument for --test-threads must not be 0".to_string()),
Ok(n) => Some(n),
Err(e) => {
return Err(format!(
"argument for --test-threads must be a number > 0 \
(error: {})",
e
));
}
},
None => None,
};
if let Some(opt) = matches.opt_str("Z") {
if !is_nightly() {
return Some(Err(
"the option `Z` is only accepted on the nightly compiler".into(),
Ok(test_threads)
}
fn get_format(matches: &getopts::Matches, quiet: bool, allow_unstable: bool) -> OptPartRes<OutputFormat> {
let format = match matches.opt_str("format").as_ref().map(|s| &**s) {
None if quiet => OutputFormat::Terse,
Some("pretty") | None => OutputFormat::Pretty,
Some("terse") => OutputFormat::Terse,
Some("json") => {
if !allow_unstable {
return Err(
"The \"json\" format is only accepted on the nightly compiler".into(),
);
}
OutputFormat::Json
}
Some(v) => {
return Err(format!(
"argument for --format must be pretty, terse, or json (was \
{})",
v
));
}
match &*opt {
"unstable-options" => {
allow_unstable = true;
}
_ => {
return Some(Err("Unrecognized option to `Z`".into()));
}
}
};
if matches.opt_present("h") {
usage(&args[0], &opts);
return None;
}
Ok(format)
}
let filter = if !matches.free.is_empty() {
Some(matches.free[0].clone())
} else {
None
};
fn get_color_config(matches: &getopts::Matches) -> OptPartRes<ColorConfig> {
let color = match matches.opt_str("color").as_ref().map(|s| &**s) {
Some("auto") | None => ColorConfig::AutoColor,
Some("always") => ColorConfig::AlwaysColor,
Some("never") => ColorConfig::NeverColor,
let exclude_should_panic = unstable_optflag!(matches, allow_unstable, "exclude-should-panic");
let include_ignored = unstable_optflag!(matches, allow_unstable, "include-ignored");
let run_ignored = match (include_ignored, matches.opt_present("ignored")) {
(true, true) => {
return Some(Err(
"the options --include-ignored and --ignored are mutually exclusive".into(),
Some(v) => {
return Err(format!(
"argument for --color must be auto, always, or never (was \
{})",
v
));
}
(true, false) => RunIgnored::Yes,
(false, true) => RunIgnored::Only,
(false, false) => RunIgnored::No,
};
let quiet = matches.opt_present("quiet");
let exact = matches.opt_present("exact");
let list = matches.opt_present("list");
let logfile = matches.opt_str("logfile");
let logfile = logfile.map(|s| PathBuf::from(&s));
let bench_benchmarks = matches.opt_present("bench");
let run_tests = !bench_benchmarks || matches.opt_present("test");
Ok(color)
}
fn get_nocapture(matches: &getopts::Matches) -> OptPartRes<bool> {
let mut nocapture = matches.opt_present("nocapture");
if !nocapture {
nocapture = match env::var("RUST_TEST_NOCAPTURE") {
@ -305,80 +382,59 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
};
}
let time_options = match get_time_options(&matches, allow_unstable) {
Some(Ok(val)) => val,
Some(Err(e)) => return Some(Err(e)),
None => panic!("Unexpected output from `get_time_options`"),
};
let test_threads = match matches.opt_str("test-threads") {
Some(n_str) => match n_str.parse::<usize>() {
Ok(0) => return Some(Err("argument for --test-threads must not be 0".to_string())),
Ok(n) => Some(n),
Err(e) => {
return Some(Err(format!(
"argument for --test-threads must be a number > 0 \
(error: {})",
e
)));
}
},
None => None,
};
let color = match matches.opt_str("color").as_ref().map(|s| &**s) {
Some("auto") | None => ColorConfig::AutoColor,
Some("always") => ColorConfig::AlwaysColor,
Some("never") => ColorConfig::NeverColor,
Some(v) => {
return Some(Err(format!(
"argument for --color must be auto, always, or never (was \
{})",
v
)));
}
};
let format = match matches.opt_str("format").as_ref().map(|s| &**s) {
None if quiet => OutputFormat::Terse,
Some("pretty") | None => OutputFormat::Pretty,
Some("terse") => OutputFormat::Terse,
Some("json") => {
if !allow_unstable {
return Some(Err(
"The \"json\" format is only accepted on the nightly compiler".into(),
));
}
OutputFormat::Json
}
Some(v) => {
return Some(Err(format!(
"argument for --format must be pretty, terse, or json (was \
{})",
v
)));
}
};
let test_opts = TestOpts {
list,
filter,
filter_exact: exact,
exclude_should_panic,
run_ignored,
run_tests,
bench_benchmarks,
logfile,
nocapture,
color,
format,
test_threads,
skip: matches.opt_strs("skip"),
time_options,
options: Options::new().display_output(matches.opt_present("show-output")),
};
Some(Ok(test_opts))
Ok(nocapture)
}
fn get_run_ignored(matches: &getopts::Matches, include_ignored: bool) -> OptPartRes<RunIgnored> {
let run_ignored = match (include_ignored, matches.opt_present("ignored")) {
(true, true) => {
return Err(
"the options --include-ignored and --ignored are mutually exclusive".into(),
);
}
(true, false) => RunIgnored::Yes,
(false, true) => RunIgnored::Only,
(false, false) => RunIgnored::No,
};
Ok(run_ignored)
}
fn get_filter(matches: &getopts::Matches) -> OptPartRes<Option<String>> {
let filter = if !matches.free.is_empty() {
Some(matches.free[0].clone())
} else {
None
};
Ok(filter)
}
fn get_allow_unstable(matches: &getopts::Matches) -> OptPartRes<bool> {
let mut allow_unstable = false;
if let Some(opt) = matches.opt_str("Z") {
if !is_nightly() {
return Err(
"the option `Z` is only accepted on the nightly compiler".into(),
);
}
match &*opt {
"unstable-options" => {
allow_unstable = true;
}
_ => {
return Err("Unrecognized option to `Z`".into());
}
}
};
Ok(allow_unstable)
}
fn get_log_file(matches: &getopts::Matches) -> OptPartRes<Option<PathBuf>> {
let logfile = matches.opt_str("logfile").map(|s| PathBuf::from(&s));
Ok(logfile)
}