| //- |
| // Copyright 2018 Jason Lingle |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! Internal module which parses and modifies the rust test command-line. |
| |
| use std::env; |
| |
| use error::*; |
| |
| /// How a hyphen-prefixed argument passed to the parent process should be |
| /// handled when constructing the command-line for the child process. |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| enum FlagType { |
| /// Pass the flag through unchanged. The boolean indicates whether the flag |
| /// is followed by an argument. |
| Pass(bool), |
| /// Drop the flag entirely. The boolean indicates whether the flag is |
| /// followed by an argument. |
| Drop(bool), |
| /// Indicates a known flag that should never be encountered. The string is |
| /// a human-readable error message. |
| Error(&'static str), |
| } |
| |
| /// Table of all flags in the 2018-02-23 nightly build. |
| /// |
| /// A number of these that affect output are dropped because we append our own |
| /// options. |
| static KNOWN_FLAGS: &[(&str, FlagType)] = &[ |
| ("--ignored", FlagType::Pass(false)), |
| ("--test", FlagType::Pass(false)), |
| ("--bench", FlagType::Pass(false)), |
| ("--list", FlagType::Error("Tests run but --list passed to process?")), |
| ("-h", FlagType::Error("Tests run but -h passed to process?")), |
| ("--help", FlagType::Error("Tests run but --help passed to process?")), |
| ("--logfile", FlagType::Drop(true)), |
| ("--nocapture", FlagType::Drop(true)), |
| ("--test-threads", FlagType::Drop(true)), |
| ("--skip", FlagType::Drop(true)), |
| ("-q", FlagType::Drop(false)), |
| ("--quiet", FlagType::Drop(false)), |
| ("--exact", FlagType::Drop(false)), |
| ("--color", FlagType::Pass(true)), |
| ("--format", FlagType::Drop(true)), |
| ("-Z", FlagType::Pass(true)), |
| ]; |
| |
| fn look_up_flag_from_table(flag: &str) -> Option<FlagType> { |
| KNOWN_FLAGS.iter().cloned().filter(|&(name, _)| name == flag) |
| .map(|(_, typ)| typ).next() |
| } |
| |
| pub(crate) fn env_var_for_flag(flag: &str) -> String { |
| let mut var = "RUSTY_FORK_FLAG_".to_owned(); |
| var.push_str( |
| &flag.trim_left_matches('-').to_uppercase().replace('-', "_")); |
| var |
| } |
| |
| fn look_up_flag_from_env(flag: &str) -> Option<FlagType> { |
| env::var(&env_var_for_flag(flag)).ok().map( |
| |value| match &*value { |
| "pass" => FlagType::Pass(false), |
| "pass-arg" => FlagType::Pass(true), |
| "drop" => FlagType::Drop(false), |
| "drop-arg" => FlagType::Drop(true), |
| _ => FlagType::Error("incorrect flag type in environment; \ |
| must be one of `pass`, `pass-arg`, \ |
| `drop`, `drop-arg`"), |
| }) |
| } |
| |
| fn look_up_flag(flag: &str) -> Option<FlagType> { |
| look_up_flag_from_table(flag).or_else(|| look_up_flag_from_env(flag)) |
| } |
| |
| fn look_up_flag_or_err(flag: &str) -> Result<(bool, bool)> { |
| match look_up_flag(flag) { |
| None => |
| Err(Error::UnknownFlag(flag.to_owned())), |
| Some(FlagType::Error(message)) => |
| Err(Error::DisallowedFlag(flag.to_owned(), message.to_owned())), |
| Some(FlagType::Pass(has_arg)) => Ok((true, has_arg)), |
| Some(FlagType::Drop(has_arg)) => Ok((false, has_arg)), |
| } |
| } |
| |
| /// Parse the full command line as would be given to the Rust test harness, and |
| /// strip out any flags that should be dropped as well as all filters. The |
| /// resulting argument list is also guaranteed to not have "--", so that new |
| /// flags can be appended. |
| /// |
| /// The zeroth argument (the command name) is also dropped. |
| pub(crate) fn strip_cmdline<A : Iterator<Item = String>> |
| (args: A) -> Result<Vec<String>> |
| { |
| #[derive(Clone, Copy)] |
| enum State { |
| Ground, PassingArg, DroppingArg, |
| } |
| |
| // Start in DroppingArg since we need to drop the exec name. |
| let mut state = State::DroppingArg; |
| let mut ret = Vec::new(); |
| |
| for arg in args { |
| match state { |
| State::DroppingArg => { |
| state = State::Ground; |
| }, |
| |
| State::PassingArg => { |
| ret.push(arg); |
| state = State::Ground; |
| }, |
| |
| State::Ground => { |
| if &arg == "--" { |
| // Everything after this point is a filter |
| break; |
| } else if &arg == "-" { |
| // "-" by itself is interpreted as a filter |
| continue; |
| } else if arg.starts_with("--") { |
| let (pass, has_arg) = look_up_flag_or_err( |
| arg.split('=').next().expect("split returned empty"))?; |
| // If there's an = sign, the physical argument also |
| // contains the associated value, so don't pay attention to |
| // has_arg. |
| let has_arg = has_arg && !arg.contains('='); |
| if pass { |
| ret.push(arg); |
| if has_arg { |
| state = State::PassingArg; |
| } |
| } else if has_arg { |
| state = State::DroppingArg; |
| } |
| } else if arg.starts_with("-") { |
| let mut chars = arg.chars(); |
| let mut to_pass = "-".to_owned(); |
| |
| chars.next(); // skip initial '-' |
| while let Some(flag_ch) = chars.next() { |
| let flag = format!("-{}", flag_ch); |
| let (pass, has_arg) = look_up_flag_or_err(&flag)?; |
| if pass { |
| to_pass.push(flag_ch); |
| if has_arg { |
| if chars.clone().next().is_some() { |
| // Arg is attached to this one |
| to_pass.extend(chars); |
| } else { |
| // Arg is separate |
| state = State::PassingArg; |
| } |
| break; |
| } |
| } else if has_arg { |
| if chars.clone().next().is_none() { |
| // Arg is separate |
| state = State::DroppingArg; |
| } |
| break; |
| } |
| } |
| |
| if "-" != &to_pass { |
| ret.push(to_pass); |
| } |
| } else { |
| // It's a filter, drop |
| } |
| }, |
| } |
| } |
| |
| Ok(ret) |
| } |
| |
| /// Extra arguments to add after the stripped command line when running a |
| /// single test. |
| pub(crate) static RUN_TEST_ARGS: &[&str] = &[ |
| // --quiet because the test runner output is redundant |
| "--quiet", |
| // Single threaded because we get parallelism from the parent process |
| "--test-threads", "1", |
| // Disable capture since we want the output to be captured by the *parent* |
| // process. |
| "--nocapture", |
| // Match our test filter exactly so we run exactly one test |
| "--exact", |
| // Ensure everything else is interpreted as filters |
| "--", |
| ]; |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| fn strip(cmdline: &str) -> Result<String> { |
| strip_cmdline(cmdline.split_whitespace().map(|s| s.to_owned())) |
| .map(|strs| strs.join(" ")) |
| } |
| |
| #[test] |
| fn test_strip() { |
| assert_eq!("", &strip("test").unwrap()); |
| assert_eq!("--ignored", &strip("test --ignored").unwrap()); |
| assert_eq!("", &strip("test --quiet").unwrap()); |
| assert_eq!("", &strip("test -q").unwrap()); |
| assert_eq!("", &strip("test -qq").unwrap()); |
| assert_eq!("", &strip("test --test-threads 42").unwrap()); |
| assert_eq!("-Z unstable-options", |
| &strip("test -Z unstable-options").unwrap()); |
| assert_eq!("-Zunstable-options", |
| &strip("test -Zunstable-options").unwrap()); |
| assert_eq!("-Zunstable-options", |
| &strip("test -qZunstable-options").unwrap()); |
| assert_eq!("--color auto", &strip("test --color auto").unwrap()); |
| assert_eq!("--color=auto", &strip("test --color=auto").unwrap()); |
| assert_eq!("", &strip("test filter filter2").unwrap()); |
| assert_eq!("", &strip("test -- --color=auto").unwrap()); |
| |
| match strip("test --plugh").unwrap_err() { |
| Error::UnknownFlag(ref flag) => assert_eq!("--plugh", flag), |
| e => panic!("Unexpected error: {}", e), |
| } |
| match strip("test --help").unwrap_err() { |
| Error::DisallowedFlag(ref flag, _) => assert_eq!("--help", flag), |
| e => panic!("Unexpected error: {}", e), |
| } |
| } |
| |
| // Subprocess so we can change the environment without affecting other |
| // tests |
| rusty_fork_test! { |
| #[test] |
| fn define_args_via_env() { |
| env::set_var("RUSTY_FORK_FLAG_X", "pass"); |
| env::set_var("RUSTY_FORK_FLAG_FOO", "pass-arg"); |
| env::set_var("RUSTY_FORK_FLAG_BAR", "drop"); |
| env::set_var("RUSTY_FORK_FLAG_BAZ", "drop-arg"); |
| |
| assert_eq!("-X", &strip("test -X foo").unwrap()); |
| assert_eq!("--foo bar", &strip("test --foo bar").unwrap()); |
| assert_eq!("", &strip("test --bar").unwrap()); |
| assert_eq!("", &strip("test --baz --notaflag").unwrap()); |
| } |
| } |
| } |