| //! Tests for config settings. |
| |
| use cargo::core::{PackageIdSpec, Shell}; |
| use cargo::util::config::{self, Config, Definition, SslVersionConfig, StringList}; |
| use cargo::util::interning::InternedString; |
| use cargo::util::toml::{self as cargo_toml, VecStringOrBool as VSOB}; |
| use cargo::CargoResult; |
| use cargo_test_support::compare; |
| use cargo_test_support::{panic_error, paths, project, symlink_supported, t}; |
| use serde::Deserialize; |
| use std::borrow::Borrow; |
| use std::collections::{BTreeMap, HashMap}; |
| use std::fs; |
| use std::io; |
| use std::os; |
| use std::path::{Path, PathBuf}; |
| |
| /// Helper for constructing a `Config` object. |
| pub struct ConfigBuilder { |
| env: HashMap<String, String>, |
| unstable: Vec<String>, |
| config_args: Vec<String>, |
| cwd: Option<PathBuf>, |
| enable_nightly_features: bool, |
| } |
| |
| impl ConfigBuilder { |
| pub fn new() -> ConfigBuilder { |
| ConfigBuilder { |
| env: HashMap::new(), |
| unstable: Vec::new(), |
| config_args: Vec::new(), |
| cwd: None, |
| enable_nightly_features: false, |
| } |
| } |
| |
| /// Passes a `-Z` flag. |
| pub fn unstable_flag(&mut self, s: impl Into<String>) -> &mut Self { |
| self.unstable.push(s.into()); |
| self |
| } |
| |
| /// Sets an environment variable. |
| pub fn env(&mut self, key: impl Into<String>, val: impl Into<String>) -> &mut Self { |
| self.env.insert(key.into(), val.into()); |
| self |
| } |
| |
| /// Unconditionally enable nightly features, even on stable channels. |
| pub fn nightly_features_allowed(&mut self, allowed: bool) -> &mut Self { |
| self.enable_nightly_features = allowed; |
| self |
| } |
| |
| /// Passes a `--config` flag. |
| pub fn config_arg(&mut self, arg: impl Into<String>) -> &mut Self { |
| self.config_args.push(arg.into()); |
| self |
| } |
| |
| /// Sets the current working directory where config files will be loaded. |
| pub fn cwd(&mut self, path: impl AsRef<Path>) -> &mut Self { |
| self.cwd = Some(paths::root().join(path.as_ref())); |
| self |
| } |
| |
| /// Creates the `Config`. |
| pub fn build(&self) -> Config { |
| self.build_err().unwrap() |
| } |
| |
| /// Creates the `Config`, returning a Result. |
| pub fn build_err(&self) -> CargoResult<Config> { |
| let output = Box::new(fs::File::create(paths::root().join("shell.out")).unwrap()); |
| let shell = Shell::from_write(output); |
| let cwd = self.cwd.clone().unwrap_or_else(|| paths::root()); |
| let homedir = paths::home(); |
| let mut config = Config::new(shell, cwd, homedir); |
| config.nightly_features_allowed = self.enable_nightly_features || !self.unstable.is_empty(); |
| config.set_env(self.env.clone()); |
| config.set_search_stop_path(paths::root()); |
| config.configure( |
| 0, |
| false, |
| None, |
| false, |
| false, |
| false, |
| &None, |
| &self.unstable, |
| &self.config_args, |
| )?; |
| Ok(config) |
| } |
| } |
| |
| fn new_config() -> Config { |
| ConfigBuilder::new().build() |
| } |
| |
| /// Read the output from Config. |
| pub fn read_output(config: Config) -> String { |
| drop(config); // Paranoid about flushing the file. |
| let path = paths::root().join("shell.out"); |
| fs::read_to_string(path).unwrap() |
| } |
| |
| #[cargo_test] |
| fn read_env_vars_for_config() { |
| let p = project() |
| .file( |
| "Cargo.toml", |
| r#" |
| [package] |
| name = "foo" |
| authors = [] |
| version = "0.0.0" |
| build = "build.rs" |
| "#, |
| ) |
| .file("src/lib.rs", "") |
| .file( |
| "build.rs", |
| r#" |
| use std::env; |
| fn main() { |
| assert_eq!(env::var("NUM_JOBS").unwrap(), "100"); |
| } |
| "#, |
| ) |
| .build(); |
| |
| p.cargo("check").env("CARGO_BUILD_JOBS", "100").run(); |
| } |
| |
| pub fn write_config(config: &str) { |
| write_config_at(paths::root().join(".cargo/config"), config); |
| } |
| |
| pub fn write_config_at(path: impl AsRef<Path>, contents: &str) { |
| let path = paths::root().join(path.as_ref()); |
| fs::create_dir_all(path.parent().unwrap()).unwrap(); |
| fs::write(path, contents).unwrap(); |
| } |
| |
| pub fn write_config_toml(config: &str) { |
| write_config_at(paths::root().join(".cargo/config.toml"), config); |
| } |
| |
| #[cfg(unix)] |
| fn symlink_file(target: &Path, link: &Path) -> io::Result<()> { |
| os::unix::fs::symlink(target, link) |
| } |
| |
| #[cfg(windows)] |
| fn symlink_file(target: &Path, link: &Path) -> io::Result<()> { |
| os::windows::fs::symlink_file(target, link) |
| } |
| |
| fn symlink_config_to_config_toml() { |
| let toml_path = paths::root().join(".cargo/config.toml"); |
| let symlink_path = paths::root().join(".cargo/config"); |
| t!(symlink_file(&toml_path, &symlink_path)); |
| } |
| |
| #[track_caller] |
| pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str) { |
| let causes = error |
| .borrow() |
| .chain() |
| .enumerate() |
| .map(|(i, e)| { |
| if i == 0 { |
| e.to_string() |
| } else { |
| format!("Caused by:\n {}", e) |
| } |
| }) |
| .collect::<Vec<_>>() |
| .join("\n\n"); |
| assert_match(msgs, &causes); |
| } |
| |
| #[track_caller] |
| pub fn assert_match(expected: &str, actual: &str) { |
| if let Err(e) = compare::match_exact(expected, actual, "output", "", None) { |
| panic_error("", e); |
| } |
| } |
| |
| #[cargo_test] |
| fn get_config() { |
| write_config( |
| "\ |
| [S] |
| f1 = 123 |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| #[derive(Debug, Deserialize, Eq, PartialEq)] |
| struct S { |
| f1: Option<i64>, |
| } |
| let s: S = config.get("S").unwrap(); |
| assert_eq!(s, S { f1: Some(123) }); |
| let config = ConfigBuilder::new().env("CARGO_S_F1", "456").build(); |
| let s: S = config.get("S").unwrap(); |
| assert_eq!(s, S { f1: Some(456) }); |
| } |
| |
| #[cfg(windows)] |
| #[cargo_test] |
| fn environment_variable_casing() { |
| // Issue #11814: Environment variable names are case-insensitive on Windows. |
| let config = ConfigBuilder::new() |
| .env("Path", "abc") |
| .env("Two-Words", "abc") |
| .env("two_words", "def") |
| .build(); |
| |
| let var = config.get_env("PATH").unwrap(); |
| assert_eq!(var, String::from("abc")); |
| |
| let var = config.get_env("path").unwrap(); |
| assert_eq!(var, String::from("abc")); |
| |
| let var = config.get_env("TWO-WORDS").unwrap(); |
| assert_eq!(var, String::from("abc")); |
| |
| // Make sure that we can still distinguish between dashes and underscores |
| // in variable names. |
| let var = config.get_env("Two_Words").unwrap(); |
| assert_eq!(var, String::from("def")); |
| } |
| |
| #[cargo_test] |
| fn config_works_with_extension() { |
| write_config_toml( |
| "\ |
| [foo] |
| f1 = 1 |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1)); |
| } |
| |
| #[cargo_test] |
| fn config_ambiguous_filename_symlink_doesnt_warn() { |
| // Windows requires special permissions to create symlinks. |
| // If we don't have permission, just skip this test. |
| if !symlink_supported() { |
| return; |
| }; |
| |
| write_config_toml( |
| "\ |
| [foo] |
| f1 = 1 |
| ", |
| ); |
| |
| symlink_config_to_config_toml(); |
| |
| let config = new_config(); |
| |
| assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1)); |
| |
| // It should NOT have warned for the symlink. |
| let output = read_output(config); |
| assert_eq!(output, ""); |
| } |
| |
| #[cargo_test] |
| fn config_ambiguous_filename() { |
| write_config( |
| "\ |
| [foo] |
| f1 = 1 |
| ", |
| ); |
| |
| write_config_toml( |
| "\ |
| [foo] |
| f1 = 2 |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| // It should use the value from the one without the extension for |
| // backwards compatibility. |
| assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1)); |
| |
| // But it also should have warned. |
| let output = read_output(config); |
| let expected = "\ |
| warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config` |
| "; |
| assert_match(expected, &output); |
| } |
| |
| #[cargo_test] |
| fn config_unused_fields() { |
| write_config( |
| "\ |
| [S] |
| unused = 456 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_S_UNUSED2", "1") |
| .env("CARGO_S2_UNUSED", "2") |
| .build(); |
| |
| #[derive(Debug, Deserialize, Eq, PartialEq)] |
| struct S { |
| f1: Option<i64>, |
| } |
| // This prints a warning (verified below). |
| let s: S = config.get("S").unwrap(); |
| assert_eq!(s, S { f1: None }); |
| // This does not print anything, we cannot easily/reliably warn for |
| // environment variables. |
| let s: S = config.get("S2").unwrap(); |
| assert_eq!(s, S { f1: None }); |
| |
| // Verify the warnings. |
| let output = read_output(config); |
| let expected = "\ |
| warning: unused config key `S.unused` in `[..]/.cargo/config` |
| "; |
| assert_match(expected, &output); |
| } |
| |
| #[cargo_test] |
| fn config_load_toml_profile() { |
| write_config( |
| "\ |
| [profile.dev] |
| opt-level = 's' |
| lto = true |
| codegen-units=4 |
| debug = true |
| debug-assertions = true |
| rpath = true |
| panic = 'abort' |
| overflow-checks = true |
| incremental = true |
| |
| [profile.dev.build-override] |
| opt-level = 1 |
| |
| [profile.dev.package.bar] |
| codegen-units = 9 |
| |
| [profile.no-lto] |
| inherits = 'dev' |
| dir-name = 'without-lto' |
| lto = false |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_PROFILE_DEV_CODEGEN_UNITS", "5") |
| .env("CARGO_PROFILE_DEV_BUILD_OVERRIDE_CODEGEN_UNITS", "11") |
| .env("CARGO_PROFILE_DEV_PACKAGE_env_CODEGEN_UNITS", "13") |
| .env("CARGO_PROFILE_DEV_PACKAGE_bar_OPT_LEVEL", "2") |
| .build(); |
| |
| // TODO: don't use actual `tomlprofile`. |
| let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); |
| let mut packages = BTreeMap::new(); |
| let key = |
| cargo_toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("bar").unwrap()); |
| let o_profile = cargo_toml::TomlProfile { |
| opt_level: Some(cargo_toml::TomlOptLevel("2".to_string())), |
| codegen_units: Some(9), |
| ..Default::default() |
| }; |
| packages.insert(key, o_profile); |
| let key = |
| cargo_toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("env").unwrap()); |
| let o_profile = cargo_toml::TomlProfile { |
| codegen_units: Some(13), |
| ..Default::default() |
| }; |
| packages.insert(key, o_profile); |
| |
| assert_eq!( |
| p, |
| cargo_toml::TomlProfile { |
| opt_level: Some(cargo_toml::TomlOptLevel("s".to_string())), |
| lto: Some(cargo_toml::StringOrBool::Bool(true)), |
| codegen_units: Some(5), |
| debug: Some(cargo_toml::U32OrBool::Bool(true)), |
| debug_assertions: Some(true), |
| rpath: Some(true), |
| panic: Some("abort".to_string()), |
| overflow_checks: Some(true), |
| incremental: Some(true), |
| package: Some(packages), |
| build_override: Some(Box::new(cargo_toml::TomlProfile { |
| opt_level: Some(cargo_toml::TomlOptLevel("1".to_string())), |
| codegen_units: Some(11), |
| ..Default::default() |
| })), |
| ..Default::default() |
| } |
| ); |
| |
| let p: cargo_toml::TomlProfile = config.get("profile.no-lto").unwrap(); |
| assert_eq!( |
| p, |
| cargo_toml::TomlProfile { |
| lto: Some(cargo_toml::StringOrBool::Bool(false)), |
| dir_name: Some(InternedString::new("without-lto")), |
| inherits: Some(InternedString::new("dev")), |
| ..Default::default() |
| } |
| ); |
| } |
| |
| #[cargo_test] |
| fn profile_env_var_prefix() { |
| // Check for a bug with collision on DEBUG vs DEBUG_ASSERTIONS. |
| let config = ConfigBuilder::new() |
| .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false") |
| .build(); |
| let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); |
| assert_eq!(p.debug_assertions, Some(false)); |
| assert_eq!(p.debug, None); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_PROFILE_DEV_DEBUG", "1") |
| .build(); |
| let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); |
| assert_eq!(p.debug_assertions, None); |
| assert_eq!(p.debug, Some(cargo_toml::U32OrBool::U32(1))); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false") |
| .env("CARGO_PROFILE_DEV_DEBUG", "1") |
| .build(); |
| let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); |
| assert_eq!(p.debug_assertions, Some(false)); |
| assert_eq!(p.debug, Some(cargo_toml::U32OrBool::U32(1))); |
| } |
| |
| #[cargo_test] |
| fn config_deserialize_any() { |
| // Some tests to exercise deserialize_any for deserializers that need to |
| // be told the format. |
| write_config( |
| "\ |
| a = true |
| b = ['b'] |
| c = ['c'] |
| ", |
| ); |
| |
| // advanced-env |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_ENVB", "false") |
| .env("CARGO_C", "['d']") |
| .env("CARGO_ENVL", "['a', 'b']") |
| .build(); |
| assert_eq!(config.get::<VSOB>("a").unwrap(), VSOB::Bool(true)); |
| assert_eq!( |
| config.get::<VSOB>("b").unwrap(), |
| VSOB::VecString(vec!["b".to_string()]) |
| ); |
| assert_eq!( |
| config.get::<VSOB>("c").unwrap(), |
| VSOB::VecString(vec!["c".to_string(), "d".to_string()]) |
| ); |
| assert_eq!(config.get::<VSOB>("envb").unwrap(), VSOB::Bool(false)); |
| assert_eq!( |
| config.get::<VSOB>("envl").unwrap(), |
| VSOB::VecString(vec!["a".to_string(), "b".to_string()]) |
| ); |
| |
| // Demonstrate where merging logic isn't very smart. This could be improved. |
| let config = ConfigBuilder::new().env("CARGO_A", "x y").build(); |
| assert_error( |
| config.get::<VSOB>("a").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_A`: could not load config key `a` |
| |
| Caused by: |
| invalid type: string \"x y\", expected a boolean or vector of strings", |
| ); |
| |
| // Normal env. |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_B", "d e") |
| .env("CARGO_C", "f g") |
| .build(); |
| assert_eq!( |
| config.get::<VSOB>("b").unwrap(), |
| VSOB::VecString(vec!["b".to_string(), "d".to_string(), "e".to_string()]) |
| ); |
| assert_eq!( |
| config.get::<VSOB>("c").unwrap(), |
| VSOB::VecString(vec!["c".to_string(), "f".to_string(), "g".to_string()]) |
| ); |
| |
| // config-cli |
| // This test demonstrates that ConfigValue::merge isn't very smart. |
| // It would be nice if it was smarter. |
| let config = ConfigBuilder::new().config_arg("a = ['a']").build_err(); |
| assert_error( |
| config.unwrap_err(), |
| "\ |
| failed to merge --config key `a` into `[..]/.cargo/config` |
| |
| Caused by: |
| failed to merge config value from `--config cli option` into `[..]/.cargo/config`: \ |
| expected boolean, but found array", |
| ); |
| |
| // config-cli and advanced-env |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .config_arg("b=['clib']") |
| .config_arg("c=['clic']") |
| .env("CARGO_B", "env1 env2") |
| .env("CARGO_C", "['e1', 'e2']") |
| .build(); |
| assert_eq!( |
| config.get::<VSOB>("b").unwrap(), |
| VSOB::VecString(vec![ |
| "b".to_string(), |
| "clib".to_string(), |
| "env1".to_string(), |
| "env2".to_string() |
| ]) |
| ); |
| assert_eq!( |
| config.get::<VSOB>("c").unwrap(), |
| VSOB::VecString(vec![ |
| "c".to_string(), |
| "clic".to_string(), |
| "e1".to_string(), |
| "e2".to_string() |
| ]) |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_toml_errors() { |
| write_config( |
| "\ |
| [profile.dev] |
| opt-level = 'foo' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| assert_error( |
| config |
| .get::<cargo_toml::TomlProfile>("profile.dev") |
| .unwrap_err(), |
| "\ |
| error in [..]/.cargo/config: could not load config key `profile.dev.opt-level` |
| |
| Caused by: |
| must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"foo\"", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_PROFILE_DEV_OPT_LEVEL", "asdf") |
| .build(); |
| |
| assert_error( |
| config.get::<cargo_toml::TomlProfile>("profile.dev").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_PROFILE_DEV_OPT_LEVEL`: could not load config key `profile.dev.opt-level` |
| |
| Caused by: |
| must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"asdf\"", |
| ); |
| } |
| |
| #[cargo_test] |
| fn load_nested() { |
| write_config( |
| "\ |
| [nest.foo] |
| f1 = 1 |
| f2 = 2 |
| [nest.bar] |
| asdf = 3 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_NEST_foo_f2", "3") |
| .env("CARGO_NESTE_foo_f1", "1") |
| .env("CARGO_NESTE_foo_f2", "3") |
| .env("CARGO_NESTE_bar_asdf", "3") |
| .build(); |
| |
| type Nested = HashMap<String, HashMap<String, u8>>; |
| |
| let n: Nested = config.get("nest").unwrap(); |
| let mut expected = HashMap::new(); |
| let mut foo = HashMap::new(); |
| foo.insert("f1".to_string(), 1); |
| foo.insert("f2".to_string(), 3); |
| expected.insert("foo".to_string(), foo); |
| let mut bar = HashMap::new(); |
| bar.insert("asdf".to_string(), 3); |
| expected.insert("bar".to_string(), bar); |
| assert_eq!(n, expected); |
| |
| let n: Nested = config.get("neste").unwrap(); |
| assert_eq!(n, expected); |
| } |
| |
| #[cargo_test] |
| fn get_errors() { |
| write_config( |
| "\ |
| [S] |
| f1 = 123 |
| f2 = 'asdf' |
| big = 123456789 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_E_S", "asdf") |
| .env("CARGO_E_BIG", "123456789") |
| .build(); |
| assert_error( |
| config.get::<i64>("foo").unwrap_err(), |
| "missing config key `foo`", |
| ); |
| assert_error( |
| config.get::<i64>("foo.bar").unwrap_err(), |
| "missing config key `foo.bar`", |
| ); |
| assert_error( |
| config.get::<i64>("S.f2").unwrap_err(), |
| "error in [..]/.cargo/config: `S.f2` expected an integer, but found a string", |
| ); |
| assert_error( |
| config.get::<u8>("S.big").unwrap_err(), |
| "\ |
| error in [..].cargo/config: could not load config key `S.big` |
| |
| Caused by: |
| invalid value: integer `123456789`, expected u8", |
| ); |
| |
| // Environment variable type errors. |
| assert_error( |
| config.get::<i64>("e.s").unwrap_err(), |
| "error in environment variable `CARGO_E_S`: invalid digit found in string", |
| ); |
| assert_error( |
| config.get::<i8>("e.big").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_E_BIG`: could not load config key `e.big` |
| |
| Caused by: |
| invalid value: integer `123456789`, expected i8", |
| ); |
| |
| #[derive(Debug, Deserialize)] |
| #[allow(dead_code)] |
| struct S { |
| f1: i64, |
| f2: String, |
| f3: i64, |
| big: i64, |
| } |
| assert_error(config.get::<S>("S").unwrap_err(), "missing field `f3`"); |
| } |
| |
| #[cargo_test] |
| fn config_get_option() { |
| write_config( |
| "\ |
| [foo] |
| f1 = 1 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new().env("CARGO_BAR_ASDF", "3").build(); |
| |
| assert_eq!(config.get::<Option<i32>>("a").unwrap(), None); |
| assert_eq!(config.get::<Option<i32>>("a.b").unwrap(), None); |
| assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1)); |
| assert_eq!(config.get::<Option<i32>>("bar.asdf").unwrap(), Some(3)); |
| assert_eq!(config.get::<Option<i32>>("bar.zzzz").unwrap(), None); |
| } |
| |
| #[cargo_test] |
| fn config_bad_toml() { |
| write_config("asdf"); |
| let config = new_config(); |
| assert_error( |
| config.get::<i32>("foo").unwrap_err(), |
| "\ |
| could not load Cargo configuration |
| |
| Caused by: |
| could not parse TOML configuration in `[..]/.cargo/config` |
| |
| Caused by: |
| could not parse input as TOML |
| |
| Caused by: |
| TOML parse error at line 1, column 5 |
| | |
| 1 | asdf |
| | ^ |
| expected `.`, `=`", |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_get_list() { |
| write_config( |
| "\ |
| l1 = [] |
| l2 = ['one', 'two'] |
| l3 = 123 |
| l4 = ['one', 'two'] |
| |
| [nested] |
| l = ['x'] |
| |
| [nested2] |
| l = ['y'] |
| |
| [nested-empty] |
| ", |
| ); |
| |
| type L = Vec<String>; |
| |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_L4", "['three', 'four']") |
| .env("CARGO_L5", "['a']") |
| .env("CARGO_ENV_EMPTY", "[]") |
| .env("CARGO_ENV_BLANK", "") |
| .env("CARGO_ENV_NUM", "1") |
| .env("CARGO_ENV_NUM_LIST", "[1]") |
| .env("CARGO_ENV_TEXT", "asdf") |
| .env("CARGO_LEPAIR", "['a', 'b']") |
| .env("CARGO_NESTED2_L", "['z']") |
| .env("CARGO_NESTEDE_L", "['env']") |
| .env("CARGO_BAD_ENV", "[zzz]") |
| .build(); |
| |
| assert_eq!(config.get::<L>("unset").unwrap(), vec![] as Vec<String>); |
| assert_eq!(config.get::<L>("l1").unwrap(), vec![] as Vec<String>); |
| assert_eq!(config.get::<L>("l2").unwrap(), vec!["one", "two"]); |
| assert_error( |
| config.get::<L>("l3").unwrap_err(), |
| "\ |
| invalid configuration for key `l3` |
| expected a list, but found a integer for `l3` in [..]/.cargo/config", |
| ); |
| assert_eq!( |
| config.get::<L>("l4").unwrap(), |
| vec!["one", "two", "three", "four"] |
| ); |
| assert_eq!(config.get::<L>("l5").unwrap(), vec!["a"]); |
| assert_eq!(config.get::<L>("env-empty").unwrap(), vec![] as Vec<String>); |
| assert_eq!(config.get::<L>("env-blank").unwrap(), vec![] as Vec<String>); |
| assert_eq!(config.get::<L>("env-num").unwrap(), vec!["1".to_string()]); |
| assert_error( |
| config.get::<L>("env-num-list").unwrap_err(), |
| "error in environment variable `CARGO_ENV_NUM_LIST`: \ |
| expected string, found integer", |
| ); |
| assert_eq!( |
| config.get::<L>("env-text").unwrap(), |
| vec!["asdf".to_string()] |
| ); |
| // "invalid number" here isn't the best error, but I think it's just toml.rs. |
| assert_error( |
| config.get::<L>("bad-env").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_BAD_ENV`: could not parse TOML list: TOML parse error at line 1, column 2 |
| | |
| 1 | [zzz] |
| | ^ |
| invalid array |
| expected `]` |
| ", |
| ); |
| |
| // Try some other sequence-like types. |
| assert_eq!( |
| config |
| .get::<(String, String, String, String)>("l4") |
| .unwrap(), |
| ( |
| "one".to_string(), |
| "two".to_string(), |
| "three".to_string(), |
| "four".to_string() |
| ) |
| ); |
| assert_eq!(config.get::<(String,)>("l5").unwrap(), ("a".to_string(),)); |
| |
| // Tuple struct |
| #[derive(Debug, Deserialize, Eq, PartialEq)] |
| struct TupS(String, String); |
| assert_eq!( |
| config.get::<TupS>("lepair").unwrap(), |
| TupS("a".to_string(), "b".to_string()) |
| ); |
| |
| // Nested with an option. |
| #[derive(Debug, Deserialize, Eq, PartialEq)] |
| struct S { |
| l: Option<Vec<String>>, |
| } |
| assert_eq!(config.get::<S>("nested-empty").unwrap(), S { l: None }); |
| assert_eq!( |
| config.get::<S>("nested").unwrap(), |
| S { |
| l: Some(vec!["x".to_string()]), |
| } |
| ); |
| assert_eq!( |
| config.get::<S>("nested2").unwrap(), |
| S { |
| l: Some(vec!["y".to_string(), "z".to_string()]), |
| } |
| ); |
| assert_eq!( |
| config.get::<S>("nestede").unwrap(), |
| S { |
| l: Some(vec!["env".to_string()]), |
| } |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_get_other_types() { |
| write_config( |
| "\ |
| ns = 123 |
| ns2 = 456 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_NSE", "987") |
| .env("CARGO_NS2", "654") |
| .build(); |
| |
| #[derive(Debug, Deserialize, Eq, PartialEq)] |
| #[serde(transparent)] |
| struct NewS(i32); |
| assert_eq!(config.get::<NewS>("ns").unwrap(), NewS(123)); |
| assert_eq!(config.get::<NewS>("ns2").unwrap(), NewS(654)); |
| assert_eq!(config.get::<NewS>("nse").unwrap(), NewS(987)); |
| assert_error( |
| config.get::<NewS>("unset").unwrap_err(), |
| "missing config key `unset`", |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_relative_path() { |
| write_config(&format!( |
| "\ |
| p1 = 'foo/bar' |
| p2 = '../abc' |
| p3 = 'b/c' |
| abs = '{}' |
| ", |
| paths::home().display(), |
| )); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_EPATH", "a/b") |
| .env("CARGO_P3", "d/e") |
| .build(); |
| |
| assert_eq!( |
| config |
| .get::<config::ConfigRelativePath>("p1") |
| .unwrap() |
| .resolve_path(&config), |
| paths::root().join("foo/bar") |
| ); |
| assert_eq!( |
| config |
| .get::<config::ConfigRelativePath>("p2") |
| .unwrap() |
| .resolve_path(&config), |
| paths::root().join("../abc") |
| ); |
| assert_eq!( |
| config |
| .get::<config::ConfigRelativePath>("p3") |
| .unwrap() |
| .resolve_path(&config), |
| paths::root().join("d/e") |
| ); |
| assert_eq!( |
| config |
| .get::<config::ConfigRelativePath>("abs") |
| .unwrap() |
| .resolve_path(&config), |
| paths::home() |
| ); |
| assert_eq!( |
| config |
| .get::<config::ConfigRelativePath>("epath") |
| .unwrap() |
| .resolve_path(&config), |
| paths::root().join("a/b") |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_get_integers() { |
| write_config( |
| "\ |
| npos = 123456789 |
| nneg = -123456789 |
| i64max = 9223372036854775807 |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_EPOS", "123456789") |
| .env("CARGO_ENEG", "-1") |
| .env("CARGO_EI64MAX", "9223372036854775807") |
| .build(); |
| |
| assert_eq!( |
| config.get::<u64>("i64max").unwrap(), |
| 9_223_372_036_854_775_807 |
| ); |
| assert_eq!( |
| config.get::<i64>("i64max").unwrap(), |
| 9_223_372_036_854_775_807 |
| ); |
| assert_eq!( |
| config.get::<u64>("ei64max").unwrap(), |
| 9_223_372_036_854_775_807 |
| ); |
| assert_eq!( |
| config.get::<i64>("ei64max").unwrap(), |
| 9_223_372_036_854_775_807 |
| ); |
| |
| assert_error( |
| config.get::<u32>("nneg").unwrap_err(), |
| "\ |
| error in [..].cargo/config: could not load config key `nneg` |
| |
| Caused by: |
| invalid value: integer `-123456789`, expected u32", |
| ); |
| assert_error( |
| config.get::<u32>("eneg").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_ENEG`: could not load config key `eneg` |
| |
| Caused by: |
| invalid value: integer `-1`, expected u32", |
| ); |
| assert_error( |
| config.get::<i8>("npos").unwrap_err(), |
| "\ |
| error in [..].cargo/config: could not load config key `npos` |
| |
| Caused by: |
| invalid value: integer `123456789`, expected i8", |
| ); |
| assert_error( |
| config.get::<i8>("epos").unwrap_err(), |
| "\ |
| error in environment variable `CARGO_EPOS`: could not load config key `epos` |
| |
| Caused by: |
| invalid value: integer `123456789`, expected i8", |
| ); |
| } |
| |
| #[cargo_test] |
| fn config_get_ssl_version_missing() { |
| write_config( |
| "\ |
| [http] |
| hello = 'world' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| assert!(config |
| .get::<Option<SslVersionConfig>>("http.ssl-version") |
| .unwrap() |
| .is_none()); |
| } |
| |
| #[cargo_test] |
| fn config_get_ssl_version_single() { |
| write_config( |
| "\ |
| [http] |
| ssl-version = 'tlsv1.2' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| let a = config |
| .get::<Option<SslVersionConfig>>("http.ssl-version") |
| .unwrap() |
| .unwrap(); |
| match a { |
| SslVersionConfig::Single(v) => assert_eq!(&v, "tlsv1.2"), |
| SslVersionConfig::Range(_) => panic!("Did not expect ssl version min/max."), |
| }; |
| } |
| |
| #[cargo_test] |
| fn config_get_ssl_version_min_max() { |
| write_config( |
| "\ |
| [http] |
| ssl-version.min = 'tlsv1.2' |
| ssl-version.max = 'tlsv1.3' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| let a = config |
| .get::<Option<SslVersionConfig>>("http.ssl-version") |
| .unwrap() |
| .unwrap(); |
| match a { |
| SslVersionConfig::Single(_) => panic!("Did not expect exact ssl version."), |
| SslVersionConfig::Range(range) => { |
| assert_eq!(range.min, Some(String::from("tlsv1.2"))); |
| assert_eq!(range.max, Some(String::from("tlsv1.3"))); |
| } |
| }; |
| } |
| |
| #[cargo_test] |
| fn config_get_ssl_version_both_forms_configured() { |
| // this is not allowed |
| write_config( |
| "\ |
| [http] |
| ssl-version = 'tlsv1.1' |
| ssl-version.min = 'tlsv1.2' |
| ssl-version.max = 'tlsv1.3' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| assert_error( |
| config |
| .get::<SslVersionConfig>("http.ssl-version") |
| .unwrap_err(), |
| "\ |
| could not load Cargo configuration |
| |
| Caused by: |
| could not parse TOML configuration in `[..]/.cargo/config` |
| |
| Caused by: |
| could not parse input as TOML |
| |
| Caused by: |
| TOML parse error at line 3, column 1 |
| | |
| 3 | ssl-version.min = 'tlsv1.2' |
| | ^ |
| dotted key `ssl-version` attempted to extend non-table type (string) |
| ", |
| ); |
| } |
| |
| #[cargo_test] |
| /// Assert that unstable options can be configured with the `unstable` table in |
| /// cargo config files |
| fn unstable_table_notation() { |
| write_config( |
| "\ |
| [unstable] |
| print-im-a-teapot = true |
| ", |
| ); |
| let config = ConfigBuilder::new().nightly_features_allowed(true).build(); |
| assert_eq!(config.cli_unstable().print_im_a_teapot, true); |
| } |
| |
| #[cargo_test] |
| /// Assert that dotted notation works for configuring unstable options |
| fn unstable_dotted_notation() { |
| write_config( |
| "\ |
| unstable.print-im-a-teapot = true |
| ", |
| ); |
| let config = ConfigBuilder::new().nightly_features_allowed(true).build(); |
| assert_eq!(config.cli_unstable().print_im_a_teapot, true); |
| } |
| |
| #[cargo_test] |
| /// Assert that Zflags on the CLI take precedence over those from config |
| fn unstable_cli_precedence() { |
| write_config( |
| "\ |
| unstable.print-im-a-teapot = true |
| ", |
| ); |
| let config = ConfigBuilder::new().nightly_features_allowed(true).build(); |
| assert_eq!(config.cli_unstable().print_im_a_teapot, true); |
| |
| let config = ConfigBuilder::new() |
| .unstable_flag("print-im-a-teapot=no") |
| .build(); |
| assert_eq!(config.cli_unstable().print_im_a_teapot, false); |
| } |
| |
| #[cargo_test] |
| /// Assert that attempting to set an unstable flag that doesn't exist via config |
| /// is ignored on stable |
| fn unstable_invalid_flag_ignored_on_stable() { |
| write_config( |
| "\ |
| unstable.an-invalid-flag = 'yes' |
| ", |
| ); |
| assert!(ConfigBuilder::new().build_err().is_ok()); |
| } |
| |
| #[cargo_test] |
| /// Assert that unstable options can be configured with the `unstable` table in |
| /// cargo config files |
| fn unstable_flags_ignored_on_stable() { |
| write_config( |
| "\ |
| [unstable] |
| print-im-a-teapot = true |
| ", |
| ); |
| // Enforce stable channel even when testing on nightly. |
| let config = ConfigBuilder::new().nightly_features_allowed(false).build(); |
| assert_eq!(config.cli_unstable().print_im_a_teapot, false); |
| } |
| |
| #[cargo_test] |
| fn table_merge_failure() { |
| // Config::merge fails to merge entries in two tables. |
| write_config_at( |
| "foo/.cargo/config", |
| " |
| [table] |
| key = ['foo'] |
| ", |
| ); |
| write_config_at( |
| ".cargo/config", |
| " |
| [table] |
| key = 'bar' |
| ", |
| ); |
| |
| #[derive(Debug, Deserialize)] |
| #[allow(dead_code)] |
| struct Table { |
| key: StringList, |
| } |
| let config = ConfigBuilder::new().cwd("foo").build(); |
| assert_error( |
| config.get::<Table>("table").unwrap_err(), |
| "\ |
| could not load Cargo configuration |
| |
| Caused by: |
| failed to merge configuration at `[..]/.cargo/config` |
| |
| Caused by: |
| failed to merge key `table` between [..]/foo/.cargo/config and [..]/.cargo/config |
| |
| Caused by: |
| failed to merge key `key` between [..]/foo/.cargo/config and [..]/.cargo/config |
| |
| Caused by: |
| failed to merge config value from `[..]/.cargo/config` into `[..]/foo/.cargo/config`: \ |
| expected array, but found string", |
| ); |
| } |
| |
| #[cargo_test] |
| fn non_string_in_array() { |
| // Currently only strings are supported. |
| write_config("foo = [1, 2, 3]"); |
| let config = new_config(); |
| assert_error( |
| config.get::<Vec<i32>>("foo").unwrap_err(), |
| "\ |
| could not load Cargo configuration |
| |
| Caused by: |
| failed to load TOML configuration from `[..]/.cargo/config` |
| |
| Caused by: |
| failed to parse key `foo` |
| |
| Caused by: |
| expected string but found integer in list", |
| ); |
| } |
| |
| #[cargo_test] |
| fn struct_with_opt_inner_struct() { |
| // Struct with a key that is Option of another struct. |
| // Check that can be defined with environment variable. |
| #[derive(Deserialize)] |
| struct Inner { |
| value: Option<i32>, |
| } |
| #[derive(Deserialize)] |
| struct Foo { |
| inner: Option<Inner>, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_FOO_INNER_VALUE", "12") |
| .build(); |
| let f: Foo = config.get("foo").unwrap(); |
| assert_eq!(f.inner.unwrap().value.unwrap(), 12); |
| } |
| |
| #[cargo_test] |
| fn struct_with_default_inner_struct() { |
| // Struct with serde defaults. |
| // Check that can be defined with environment variable. |
| #[derive(Deserialize, Default)] |
| #[serde(default)] |
| struct Inner { |
| value: i32, |
| } |
| #[derive(Deserialize, Default)] |
| #[serde(default)] |
| struct Foo { |
| inner: Inner, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_FOO_INNER_VALUE", "12") |
| .build(); |
| let f: Foo = config.get("foo").unwrap(); |
| assert_eq!(f.inner.value, 12); |
| } |
| |
| #[cargo_test] |
| fn overlapping_env_config() { |
| // Issue where one key is a prefix of another. |
| #[derive(Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| struct Ambig { |
| debug: Option<u32>, |
| debug_assertions: Option<bool>, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true") |
| .build(); |
| |
| let s: Ambig = config.get("ambig").unwrap(); |
| assert_eq!(s.debug_assertions, Some(true)); |
| assert_eq!(s.debug, None); |
| |
| let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "0").build(); |
| let s: Ambig = config.get("ambig").unwrap(); |
| assert_eq!(s.debug_assertions, None); |
| assert_eq!(s.debug, Some(0)); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_AMBIG_DEBUG", "1") |
| .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true") |
| .build(); |
| let s: Ambig = config.get("ambig").unwrap(); |
| assert_eq!(s.debug_assertions, Some(true)); |
| assert_eq!(s.debug, Some(1)); |
| } |
| |
| #[cargo_test] |
| fn overlapping_env_with_defaults_errors_out() { |
| // Issue where one key is a prefix of another. |
| // This is a limitation of mapping environment variables on to a hierarchy. |
| // Check that we error out when we hit ambiguity in this way, rather than |
| // the more-surprising defaulting through. |
| // If, in the future, we can handle this more correctly, feel free to delete |
| // this test. |
| #[derive(Deserialize, Default)] |
| #[serde(default, rename_all = "kebab-case")] |
| struct Ambig { |
| debug: u32, |
| debug_assertions: bool, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true") |
| .build(); |
| let err = config.get::<Ambig>("ambig").err().unwrap(); |
| assert!(format!("{}", err).contains("missing config key `ambig.debug`")); |
| |
| let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "5").build(); |
| let s: Ambig = config.get("ambig").unwrap(); |
| assert_eq!(s.debug_assertions, bool::default()); |
| assert_eq!(s.debug, 5); |
| |
| let config = ConfigBuilder::new() |
| .env("CARGO_AMBIG_DEBUG", "1") |
| .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true") |
| .build(); |
| let s: Ambig = config.get("ambig").unwrap(); |
| assert_eq!(s.debug_assertions, true); |
| assert_eq!(s.debug, 1); |
| } |
| |
| #[cargo_test] |
| fn struct_with_overlapping_inner_struct_and_defaults() { |
| // Struct with serde defaults. |
| // Check that can be defined with environment variable. |
| #[derive(Deserialize, Default)] |
| #[serde(default)] |
| struct Inner { |
| value: i32, |
| } |
| |
| // Containing struct with a prefix of inner |
| // |
| // This is a limitation of mapping environment variables on to a hierarchy. |
| // Check that we error out when we hit ambiguity in this way, rather than |
| // the more-surprising defaulting through. |
| // If, in the future, we can handle this more correctly, feel free to delete |
| // this case. |
| #[derive(Deserialize, Default)] |
| #[serde(default)] |
| struct PrefixContainer { |
| inn: bool, |
| inner: Inner, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12") |
| .build(); |
| let err = config |
| .get::<PrefixContainer>("prefixcontainer") |
| .err() |
| .unwrap(); |
| assert!(format!("{}", err).contains("missing config key `prefixcontainer.inn`")); |
| let config = ConfigBuilder::new() |
| .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12") |
| .env("CARGO_PREFIXCONTAINER_INN", "true") |
| .build(); |
| let f: PrefixContainer = config.get("prefixcontainer").unwrap(); |
| assert_eq!(f.inner.value, 12); |
| assert_eq!(f.inn, true); |
| |
| // Containing struct where the inner value's field is a prefix of another |
| // |
| // This is a limitation of mapping environment variables on to a hierarchy. |
| // Check that we error out when we hit ambiguity in this way, rather than |
| // the more-surprising defaulting through. |
| // If, in the future, we can handle this more correctly, feel free to delete |
| // this case. |
| #[derive(Deserialize, Default)] |
| #[serde(default)] |
| struct InversePrefixContainer { |
| inner_field: bool, |
| inner: Inner, |
| } |
| let config = ConfigBuilder::new() |
| .env("CARGO_INVERSEPREFIXCONTAINER_INNER_VALUE", "12") |
| .build(); |
| let f: InversePrefixContainer = config.get("inverseprefixcontainer").unwrap(); |
| assert_eq!(f.inner_field, bool::default()); |
| assert_eq!(f.inner.value, 12); |
| } |
| |
| #[cargo_test] |
| fn string_list_tricky_env() { |
| // Make sure StringList handles typed env values. |
| let config = ConfigBuilder::new() |
| .env("CARGO_KEY1", "123") |
| .env("CARGO_KEY2", "true") |
| .env("CARGO_KEY3", "1 2") |
| .build(); |
| let x = config.get::<StringList>("key1").unwrap(); |
| assert_eq!(x.as_slice(), &["123".to_string()]); |
| let x = config.get::<StringList>("key2").unwrap(); |
| assert_eq!(x.as_slice(), &["true".to_string()]); |
| let x = config.get::<StringList>("key3").unwrap(); |
| assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]); |
| } |
| |
| #[cargo_test] |
| fn string_list_wrong_type() { |
| // What happens if StringList is given then wrong type. |
| write_config("some_list = 123"); |
| let config = ConfigBuilder::new().build(); |
| assert_error( |
| config.get::<StringList>("some_list").unwrap_err(), |
| "\ |
| invalid configuration for key `some_list` |
| expected a string or array of strings, but found a integer for `some_list` in [..]/.cargo/config", |
| ); |
| |
| write_config("some_list = \"1 2\""); |
| let config = ConfigBuilder::new().build(); |
| let x = config.get::<StringList>("some_list").unwrap(); |
| assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]); |
| } |
| |
| #[cargo_test] |
| fn string_list_advanced_env() { |
| // StringList with advanced env. |
| let config = ConfigBuilder::new() |
| .unstable_flag("advanced-env") |
| .env("CARGO_KEY1", "[]") |
| .env("CARGO_KEY2", "['1 2', '3']") |
| .env("CARGO_KEY3", "[123]") |
| .build(); |
| let x = config.get::<StringList>("key1").unwrap(); |
| assert_eq!(x.as_slice(), &[] as &[String]); |
| let x = config.get::<StringList>("key2").unwrap(); |
| assert_eq!(x.as_slice(), &["1 2".to_string(), "3".to_string()]); |
| assert_error( |
| config.get::<StringList>("key3").unwrap_err(), |
| "error in environment variable `CARGO_KEY3`: expected string, found integer", |
| ); |
| } |
| |
| #[cargo_test] |
| fn parse_strip_with_string() { |
| write_config( |
| "\ |
| [profile.release] |
| strip = 'debuginfo' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| let p: cargo_toml::TomlProfile = config.get("profile.release").unwrap(); |
| let strip = p.strip.unwrap(); |
| assert_eq!( |
| strip, |
| cargo_toml::StringOrBool::String("debuginfo".to_string()) |
| ); |
| } |
| |
| #[cargo_test] |
| fn cargo_target_empty_cfg() { |
| write_config( |
| "\ |
| [build] |
| target-dir = '' |
| ", |
| ); |
| |
| let config = new_config(); |
| |
| assert_error( |
| config.target_dir().unwrap_err(), |
| "the target directory is set to an empty string in [..]/.cargo/config", |
| ); |
| } |
| |
| #[cargo_test] |
| fn cargo_target_empty_env() { |
| let project = project().build(); |
| |
| project.cargo("check") |
| .env("CARGO_TARGET_DIR", "") |
| .with_stderr("error: the target directory is set to an empty string in the `CARGO_TARGET_DIR` environment variable") |
| .with_status(101) |
| .run() |
| } |
| |
| #[cargo_test] |
| fn all_profile_options() { |
| // Check that all profile options can be serialized/deserialized. |
| let base_settings = cargo_toml::TomlProfile { |
| opt_level: Some(cargo_toml::TomlOptLevel("0".to_string())), |
| lto: Some(cargo_toml::StringOrBool::String("thin".to_string())), |
| codegen_backend: Some(InternedString::new("example")), |
| codegen_units: Some(123), |
| debug: Some(cargo_toml::U32OrBool::U32(1)), |
| split_debuginfo: Some("packed".to_string()), |
| debug_assertions: Some(true), |
| rpath: Some(true), |
| panic: Some("abort".to_string()), |
| overflow_checks: Some(true), |
| incremental: Some(true), |
| dir_name: Some(InternedString::new("dir_name")), |
| inherits: Some(InternedString::new("debug")), |
| strip: Some(cargo_toml::StringOrBool::String("symbols".to_string())), |
| package: None, |
| build_override: None, |
| rustflags: None, |
| }; |
| let mut overrides = BTreeMap::new(); |
| let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap()); |
| overrides.insert(key, base_settings.clone()); |
| let profile = cargo_toml::TomlProfile { |
| build_override: Some(Box::new(base_settings.clone())), |
| package: Some(overrides), |
| ..base_settings |
| }; |
| let profile_toml = toml::to_string(&profile).unwrap(); |
| let roundtrip: cargo_toml::TomlProfile = toml::from_str(&profile_toml).unwrap(); |
| let roundtrip_toml = toml::to_string(&roundtrip).unwrap(); |
| compare::assert_match_exact(&profile_toml, &roundtrip_toml); |
| } |
| |
| #[cargo_test] |
| fn value_in_array() { |
| // Value<String> in an array should work |
| let root_path = paths::root().join(".cargo/config.toml"); |
| write_config_at( |
| &root_path, |
| "\ |
| [net.ssh] |
| known-hosts = [ |
| \"example.com ...\", |
| \"example.net ...\", |
| ] |
| ", |
| ); |
| |
| let foo_path = paths::root().join("foo/.cargo/config.toml"); |
| write_config_at( |
| &foo_path, |
| "\ |
| [net.ssh] |
| known-hosts = [ |
| \"example.org ...\", |
| ] |
| ", |
| ); |
| |
| let config = ConfigBuilder::new() |
| .cwd("foo") |
| // environment variables don't actually work for known-hosts due to |
| // space splitting, but this is included here just to validate that |
| // they work (particularly if other Vec<Value> config vars are added |
| // in the future). |
| .env("CARGO_NET_SSH_KNOWN_HOSTS", "env-example") |
| .build(); |
| let net_config = config.net_config().unwrap(); |
| let kh = net_config |
| .ssh |
| .as_ref() |
| .unwrap() |
| .known_hosts |
| .as_ref() |
| .unwrap(); |
| assert_eq!(kh.len(), 4); |
| assert_eq!(kh[0].val, "example.org ..."); |
| assert_eq!(kh[0].definition, Definition::Path(foo_path.clone())); |
| assert_eq!(kh[1].val, "example.com ..."); |
| assert_eq!(kh[1].definition, Definition::Path(root_path.clone())); |
| assert_eq!(kh[2].val, "example.net ..."); |
| assert_eq!(kh[2].definition, Definition::Path(root_path.clone())); |
| assert_eq!(kh[3].val, "env-example"); |
| assert_eq!( |
| kh[3].definition, |
| Definition::Environment("CARGO_NET_SSH_KNOWN_HOSTS".to_string()) |
| ); |
| } |