cargo/tests/testsuite/config.rs
2019-05-12 10:49:45 -07:00

696 lines
18 KiB
Rust

use std::borrow::Borrow;
use std::collections;
use std::fs;
use crate::support::{lines_match, paths, project};
use cargo::core::{enable_nightly_features, Shell};
use cargo::util::config::{self, Config};
use cargo::util::toml::{self, VecStringOrBool as VSOB};
use serde::Deserialize;
#[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 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
);
}
}
#[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) });
}
#[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
);
}
}
#[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
",
);
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())),
lto: None,
codegen_units: Some(9),
debug: None,
debug_assertions: None,
rpath: None,
panic: None,
overflow_checks: None,
incremental: None,
overrides: None,
build_override: None,
};
overrides.insert(key, o_profile);
let key = toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("env").unwrap());
let o_profile = toml::TomlProfile {
opt_level: None,
lto: None,
codegen_units: Some(13),
debug: None,
debug_assertions: None,
rpath: None,
panic: None,
overflow_checks: None,
incremental: None,
overrides: None,
build_override: None,
};
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())),
lto: None,
codegen_units: Some(11),
debug: None,
debug_assertions: None,
rpath: None,
panic: None,
overflow_checks: None,
incremental: None,
overrides: None,
build_override: None
}))
}
);
}
#[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"),
}
}
#[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",
);
}
#[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);
}
#[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`",
);
}
#[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);
}
#[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",
);
}
#[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",
);
// 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()]),
}
);
}
#[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`",
);
}
#[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")
);
}
#[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",
);
}