mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00

Also make it a little less allocation-heavy by tweaking the API to encourage incremental building of the key and incremental destruction as we walk throughout the configuration tree.
912 lines
23 KiB
Rust
912 lines
23 KiB
Rust
use std::borrow::Borrow;
|
|
use std::collections;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::os;
|
|
use std::path::Path;
|
|
|
|
use cargo::core::{enable_nightly_features, Shell};
|
|
use cargo::util::config::{self, Config, SslVersionConfig};
|
|
use cargo::util::toml::{self, VecStringOrBool as VSOB};
|
|
use cargo_test_support::{paths, project, t};
|
|
use serde::Deserialize;
|
|
|
|
fn lines_match(a: &str, b: &str) -> bool {
|
|
// Perform a small amount of normalization for filesystem paths before we
|
|
// send this to the `lines_match` function.
|
|
cargo_test_support::lines_match(&a.replace("\\", "/"), &b.replace("\\", "/"))
|
|
}
|
|
|
|
#[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("build").env("CARGO_BUILD_JOBS", "100").run();
|
|
}
|
|
|
|
fn write_config(config: &str) {
|
|
let path = paths::root().join(".cargo/config");
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(path, config).unwrap();
|
|
}
|
|
|
|
fn write_config_toml(config: &str) {
|
|
let path = paths::root().join(".cargo/config.toml");
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(path, config).unwrap();
|
|
}
|
|
|
|
// Several test fail on windows if the user does not have permission to
|
|
// create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of
|
|
// disabling these test on Windows, use this function to test whether we
|
|
// have permission, and return otherwise. This way, we still don't run these
|
|
// tests most of the time, but at least we do if the user has the right
|
|
// permissions.
|
|
// This function is derived from libstd fs tests.
|
|
pub fn got_symlink_permission() -> bool {
|
|
if cfg!(unix) {
|
|
return true;
|
|
}
|
|
let link = paths::root().join("some_hopefully_unique_link_name");
|
|
let target = paths::root().join("nonexisting_target");
|
|
|
|
match symlink_file(&target, &link) {
|
|
Ok(_) => true,
|
|
// ERROR_PRIVILEGE_NOT_HELD = 1314
|
|
Err(ref err) if err.raw_os_error() == Some(1314) => false,
|
|
Err(_) => true,
|
|
}
|
|
}
|
|
|
|
#[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));
|
|
}
|
|
|
|
fn new_config(env: &[(&str, &str)]) -> Config {
|
|
enable_nightly_features(); // -Z advanced-env
|
|
let output = Box::new(fs::File::create(paths::root().join("shell.out")).unwrap());
|
|
let shell = Shell::from_write(output);
|
|
let cwd = paths::root();
|
|
let homedir = paths::home();
|
|
let env = env
|
|
.iter()
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
.collect();
|
|
let mut config = Config::new(shell, cwd, homedir);
|
|
config.set_env(env);
|
|
config
|
|
.configure(
|
|
0,
|
|
None,
|
|
&None,
|
|
false,
|
|
false,
|
|
false,
|
|
&None,
|
|
&["advanced-env".into()],
|
|
)
|
|
.unwrap();
|
|
config
|
|
}
|
|
|
|
fn assert_error<E: Borrow<failure::Error>>(error: E, msgs: &str) {
|
|
let causes = error
|
|
.borrow()
|
|
.iter_chain()
|
|
.map(|e| e.to_string())
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
if !lines_match(msgs, &causes) {
|
|
panic!(
|
|
"Did not find expected:\n{}\nActual error:\n{}\n",
|
|
msgs, causes
|
|
);
|
|
}
|
|
}
|
|
|
|
#[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 = new_config(&[("CARGO_S_F1", "456")]);
|
|
let s: S = config.get("S").unwrap();
|
|
assert_eq!(s, S { f1: Some(456) });
|
|
}
|
|
|
|
#[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 !got_symlink_permission() {
|
|
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.
|
|
drop(config); // Paranoid about flushing the file.
|
|
let path = paths::root().join("shell.out");
|
|
let output = fs::read_to_string(path).unwrap();
|
|
let unexpected = "\
|
|
warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
|
|
";
|
|
if lines_match(unexpected, &output) {
|
|
panic!(
|
|
"Found unexpected:\n{}\nActual error:\n{}\n",
|
|
unexpected, 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.
|
|
drop(config); // Paranoid about flushing the file.
|
|
let path = paths::root().join("shell.out");
|
|
let output = fs::read_to_string(path).unwrap();
|
|
let expected = "\
|
|
warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
|
|
";
|
|
if !lines_match(expected, &output) {
|
|
panic!(
|
|
"Did not find expected:\n{}\nActual error:\n{}\n",
|
|
expected, output
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn config_unused_fields() {
|
|
write_config(
|
|
"\
|
|
[S]
|
|
unused = 456
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[("CARGO_S_UNUSED2", "1"), ("CARGO_S2_UNUSED", "2")]);
|
|
|
|
#[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.
|
|
drop(config); // Paranoid about flushing the file.
|
|
let path = paths::root().join("shell.out");
|
|
let output = fs::read_to_string(path).unwrap();
|
|
let expected = "\
|
|
warning: unused key `S.unused` in config file `[..]/.cargo/config`
|
|
";
|
|
if !lines_match(expected, &output) {
|
|
panic!(
|
|
"Did not find expected:\n{}\nActual error:\n{}\n",
|
|
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.overrides.bar]
|
|
codegen-units = 9
|
|
|
|
[profile.no-lto]
|
|
inherits = 'dev'
|
|
dir-name = 'without-lto'
|
|
lto = false
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[
|
|
("CARGO_PROFILE_DEV_CODEGEN_UNITS", "5"),
|
|
("CARGO_PROFILE_DEV_BUILD_OVERRIDE_CODEGEN_UNITS", "11"),
|
|
("CARGO_PROFILE_DEV_OVERRIDES_env_CODEGEN_UNITS", "13"),
|
|
("CARGO_PROFILE_DEV_OVERRIDES_bar_OPT_LEVEL", "2"),
|
|
]);
|
|
|
|
// TODO: don't use actual `tomlprofile`.
|
|
let p: toml::TomlProfile = config.get("profile.dev").unwrap();
|
|
let mut overrides = collections::BTreeMap::new();
|
|
let key = toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("bar").unwrap());
|
|
let o_profile = toml::TomlProfile {
|
|
opt_level: Some(toml::TomlOptLevel("2".to_string())),
|
|
codegen_units: Some(9),
|
|
..Default::default()
|
|
};
|
|
overrides.insert(key, o_profile);
|
|
let key = toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("env").unwrap());
|
|
let o_profile = toml::TomlProfile {
|
|
codegen_units: Some(13),
|
|
..Default::default()
|
|
};
|
|
overrides.insert(key, o_profile);
|
|
|
|
assert_eq!(
|
|
p,
|
|
toml::TomlProfile {
|
|
opt_level: Some(toml::TomlOptLevel("s".to_string())),
|
|
lto: Some(toml::StringOrBool::Bool(true)),
|
|
codegen_units: Some(5),
|
|
debug: Some(toml::U32OrBool::Bool(true)),
|
|
debug_assertions: Some(true),
|
|
rpath: Some(true),
|
|
panic: Some("abort".to_string()),
|
|
overflow_checks: Some(true),
|
|
incremental: Some(true),
|
|
overrides: Some(overrides),
|
|
build_override: Some(Box::new(toml::TomlProfile {
|
|
opt_level: Some(toml::TomlOptLevel("1".to_string())),
|
|
codegen_units: Some(11),
|
|
..Default::default()
|
|
})),
|
|
..Default::default()
|
|
}
|
|
);
|
|
|
|
let p: toml::TomlProfile = config.get("profile.no-lto").unwrap();
|
|
assert_eq!(
|
|
p,
|
|
toml::TomlProfile {
|
|
lto: Some(toml::StringOrBool::Bool(false)),
|
|
dir_name: Some("without-lto".to_string()),
|
|
inherits: Some("dev".to_string()),
|
|
..Default::default()
|
|
}
|
|
);
|
|
}
|
|
|
|
#[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']
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[
|
|
("CARGO_ENVB", "false"),
|
|
("CARGO_C", "['d']"),
|
|
("CARGO_ENVL", "['a', 'b']"),
|
|
]);
|
|
|
|
let a = config.get::<VSOB>("a").unwrap();
|
|
match a {
|
|
VSOB::VecString(_) => panic!("expected bool"),
|
|
VSOB::Bool(b) => assert_eq!(b, true),
|
|
}
|
|
let b = config.get::<VSOB>("b").unwrap();
|
|
match b {
|
|
VSOB::VecString(l) => assert_eq!(l, vec!["b".to_string()]),
|
|
VSOB::Bool(_) => panic!("expected list"),
|
|
}
|
|
let c = config.get::<VSOB>("c").unwrap();
|
|
match c {
|
|
VSOB::VecString(l) => assert_eq!(l, vec!["c".to_string(), "d".to_string()]),
|
|
VSOB::Bool(_) => panic!("expected list"),
|
|
}
|
|
let envb = config.get::<VSOB>("envb").unwrap();
|
|
match envb {
|
|
VSOB::VecString(_) => panic!("expected bool"),
|
|
VSOB::Bool(b) => assert_eq!(b, false),
|
|
}
|
|
let envl = config.get::<VSOB>("envl").unwrap();
|
|
match envl {
|
|
VSOB::VecString(l) => assert_eq!(l, vec!["a".to_string(), "b".to_string()]),
|
|
VSOB::Bool(_) => panic!("expected list"),
|
|
}
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn config_toml_errors() {
|
|
write_config(
|
|
"\
|
|
[profile.dev]
|
|
opt-level = 'foo'
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[]);
|
|
|
|
assert_error(
|
|
config.get::<toml::TomlProfile>("profile.dev").unwrap_err(),
|
|
"error in [..]/.cargo/config: \
|
|
could not load config key `profile.dev.opt-level`: \
|
|
must be an integer, `z`, or `s`, but found: foo",
|
|
);
|
|
|
|
let config = new_config(&[("CARGO_PROFILE_DEV_OPT_LEVEL", "asdf")]);
|
|
|
|
assert_error(
|
|
config.get::<toml::TomlProfile>("profile.dev").unwrap_err(),
|
|
"error in environment variable `CARGO_PROFILE_DEV_OPT_LEVEL`: \
|
|
could not load config key `profile.dev.opt-level`: \
|
|
must be an integer, `z`, or `s`, but found: asdf",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn load_nested() {
|
|
write_config(
|
|
"\
|
|
[nest.foo]
|
|
f1 = 1
|
|
f2 = 2
|
|
[nest.bar]
|
|
asdf = 3
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[
|
|
("CARGO_NEST_foo_f2", "3"),
|
|
("CARGO_NESTE_foo_f1", "1"),
|
|
("CARGO_NESTE_foo_f2", "3"),
|
|
("CARGO_NESTE_bar_asdf", "3"),
|
|
]);
|
|
|
|
type Nested = collections::HashMap<String, collections::HashMap<String, u8>>;
|
|
|
|
let n: Nested = config.get("nest").unwrap();
|
|
let mut expected = collections::HashMap::new();
|
|
let mut foo = collections::HashMap::new();
|
|
foo.insert("f1".to_string(), 1);
|
|
foo.insert("f2".to_string(), 3);
|
|
expected.insert("foo".to_string(), foo);
|
|
let mut bar = collections::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 = new_config(&[("CARGO_E_S", "asdf"), ("CARGO_E_BIG", "123456789")]);
|
|
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`: \
|
|
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`: \
|
|
invalid value: integer `123456789`, expected i8",
|
|
);
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct S {
|
|
f1: i64,
|
|
f2: String,
|
|
f3: i64,
|
|
big: i64,
|
|
}
|
|
assert_error(
|
|
config.get::<S>("S").unwrap_err(),
|
|
"missing config key `S.f3`",
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn config_get_option() {
|
|
write_config(
|
|
"\
|
|
[foo]
|
|
f1 = 1
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[("CARGO_BAR_ASDF", "3")]);
|
|
|
|
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:
|
|
expected an equals, found eof at line 1 column 5",
|
|
);
|
|
}
|
|
|
|
#[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 = new_config(&[
|
|
("CARGO_L4", "['three', 'four']"),
|
|
("CARGO_L5", "['a']"),
|
|
("CARGO_ENV_EMPTY", "[]"),
|
|
("CARGO_ENV_BLANK", ""),
|
|
("CARGO_ENV_NUM", "1"),
|
|
("CARGO_ENV_NUM_LIST", "[1]"),
|
|
("CARGO_ENV_TEXT", "asdf"),
|
|
("CARGO_LEPAIR", "['a', 'b']"),
|
|
("CARGO_NESTED2_L", "['z']"),
|
|
("CARGO_NESTEDE_L", "['env']"),
|
|
("CARGO_BAD_ENV", "[zzz]"),
|
|
]);
|
|
|
|
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_error(
|
|
config.get::<L>("env-blank").unwrap_err(),
|
|
"error in environment variable `CARGO_ENV_BLANK`: \
|
|
should have TOML list syntax, found ``",
|
|
);
|
|
assert_error(
|
|
config.get::<L>("env-num").unwrap_err(),
|
|
"error in environment variable `CARGO_ENV_NUM`: \
|
|
should have TOML list syntax, found `1`",
|
|
);
|
|
assert_error(
|
|
config.get::<L>("env-num-list").unwrap_err(),
|
|
"error in environment variable `CARGO_ENV_NUM_LIST`: \
|
|
expected string, found integer",
|
|
);
|
|
assert_error(
|
|
config.get::<L>("env-text").unwrap_err(),
|
|
"error in environment variable `CARGO_ENV_TEXT`: \
|
|
should have TOML list syntax, found `asdf`",
|
|
);
|
|
// "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: invalid number at line 1 column 8",
|
|
);
|
|
|
|
// 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 = new_config(&[("CARGO_NSE", "987"), ("CARGO_NS2", "654")]);
|
|
|
|
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
|
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 = new_config(&[("CARGO_EPATH", "a/b"), ("CARGO_P3", "d/e")]);
|
|
|
|
assert_eq!(
|
|
config
|
|
.get::<config::ConfigRelativePath>("p1")
|
|
.unwrap()
|
|
.path(),
|
|
paths::root().join("foo/bar")
|
|
);
|
|
assert_eq!(
|
|
config
|
|
.get::<config::ConfigRelativePath>("p2")
|
|
.unwrap()
|
|
.path(),
|
|
paths::root().join("../abc")
|
|
);
|
|
assert_eq!(
|
|
config
|
|
.get::<config::ConfigRelativePath>("p3")
|
|
.unwrap()
|
|
.path(),
|
|
paths::root().join("d/e")
|
|
);
|
|
assert_eq!(
|
|
config
|
|
.get::<config::ConfigRelativePath>("abs")
|
|
.unwrap()
|
|
.path(),
|
|
paths::home()
|
|
);
|
|
assert_eq!(
|
|
config
|
|
.get::<config::ConfigRelativePath>("epath")
|
|
.unwrap()
|
|
.path(),
|
|
paths::root().join("a/b")
|
|
);
|
|
}
|
|
|
|
#[cargo_test]
|
|
fn config_get_integers() {
|
|
write_config(
|
|
"\
|
|
npos = 123456789
|
|
nneg = -123456789
|
|
i64max = 9223372036854775807
|
|
",
|
|
);
|
|
|
|
let config = new_config(&[
|
|
("CARGO_EPOS", "123456789"),
|
|
("CARGO_ENEG", "-1"),
|
|
("CARGO_EI64MAX", "9223372036854775807"),
|
|
]);
|
|
|
|
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`: \
|
|
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`: \
|
|
invalid value: integer `-1`, expected u32",
|
|
);
|
|
assert_error(
|
|
config.get::<i8>("npos").unwrap_err(),
|
|
"error in [..].cargo/config: \
|
|
could not load config key `npos`: \
|
|
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`: \
|
|
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!(config.get::<SslVersionConfig>("http.ssl-version").is_err());
|
|
assert!(config
|
|
.get::<Option<SslVersionConfig>>("http.ssl-version")
|
|
.unwrap()
|
|
.is_none());
|
|
}
|