summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/config_cli.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/config_cli.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/config_cli.rs564
1 files changed, 564 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/config_cli.rs b/src/tools/cargo/tests/testsuite/config_cli.rs
new file mode 100644
index 000000000..1120e279d
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/config_cli.rs
@@ -0,0 +1,564 @@
+//! Tests for the --config CLI option.
+
+use super::config::{
+ assert_error, assert_match, read_output, write_config, write_config_at, ConfigBuilder,
+};
+use cargo::util::config::Definition;
+use cargo_test_support::paths;
+use std::{collections::HashMap, fs};
+
+#[cargo_test]
+fn basic() {
+ // Simple example.
+ let config = ConfigBuilder::new().config_arg("foo='bar'").build();
+ assert_eq!(config.get::<String>("foo").unwrap(), "bar");
+}
+
+#[cargo_test]
+fn cli_priority() {
+ // Command line takes priority over files and env vars.
+ write_config(
+ "
+ demo_list = ['a']
+ [build]
+ jobs = 3
+ rustc = 'file'
+ [term]
+ quiet = false
+ verbose = false
+ ",
+ );
+ let config = ConfigBuilder::new().build();
+ assert_eq!(config.get::<i32>("build.jobs").unwrap(), 3);
+ assert_eq!(config.get::<String>("build.rustc").unwrap(), "file");
+ assert_eq!(config.get::<bool>("term.quiet").unwrap(), false);
+ assert_eq!(config.get::<bool>("term.verbose").unwrap(), false);
+
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_JOBS", "2")
+ .env("CARGO_BUILD_RUSTC", "env")
+ .env("CARGO_TERM_VERBOSE", "false")
+ .config_arg("build.jobs=1")
+ .config_arg("build.rustc='cli'")
+ .config_arg("term.verbose=true")
+ .build();
+ assert_eq!(config.get::<i32>("build.jobs").unwrap(), 1);
+ assert_eq!(config.get::<String>("build.rustc").unwrap(), "cli");
+ assert_eq!(config.get::<bool>("term.verbose").unwrap(), true);
+
+ // Setting both term.verbose and term.quiet is invalid and is tested
+ // in the run test suite.
+ let config = ConfigBuilder::new()
+ .env("CARGO_TERM_QUIET", "false")
+ .config_arg("term.quiet=true")
+ .build();
+ assert_eq!(config.get::<bool>("term.quiet").unwrap(), true);
+}
+
+#[cargo_test]
+fn merge_primitives_for_multiple_cli_occurrences() {
+ let config_path0 = ".cargo/file0.toml";
+ write_config_at(config_path0, "k = 'file0'");
+ let config_path1 = ".cargo/file1.toml";
+ write_config_at(config_path1, "k = 'file1'");
+
+ // k=env0
+ let config = ConfigBuilder::new().env("CARGO_K", "env0").build();
+ assert_eq!(config.get::<String>("k").unwrap(), "env0");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "cli1");
+
+ // Env has a lower priority when comparing with file from CLI arg.
+ //
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "file0");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ // --config k='cli2'
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .config_arg("k='cli2'")
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "cli2");
+
+ // k=env0
+ // --config k='cli0'
+ // --config k='cli1'
+ // --config .cargo/file0.toml
+ // --config k='cli2'
+ // --config .cargo/file1.toml
+ let config = ConfigBuilder::new()
+ .env("CARGO_K", "env0")
+ .config_arg("k='cli0'")
+ .config_arg("k='cli1'")
+ .config_arg(config_path0)
+ .config_arg("k='cli2'")
+ .config_arg(config_path1)
+ .build();
+ assert_eq!(config.get::<String>("k").unwrap(), "file1");
+}
+
+#[cargo_test]
+fn merges_array() {
+ // Array entries are appended.
+ write_config(
+ "
+ [build]
+ rustflags = ['--file']
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli"]
+ );
+
+ // With normal env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_RUSTFLAGS", "--env1 --env2")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ // The order of cli/env is a little questionable here, but would require
+ // much more complex merging logic.
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli", "--env1", "--env2"]
+ );
+
+ // With advanced-env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_BUILD_RUSTFLAGS", "--env")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--cli", "--env"]
+ );
+
+ // Merges multiple instances.
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags=['--one']")
+ .config_arg("build.rustflags=['--two']")
+ .build();
+ assert_eq!(
+ config.get::<Vec<String>>("build.rustflags").unwrap(),
+ ["--file", "--one", "--two"]
+ );
+}
+
+#[cargo_test]
+fn string_list_array() {
+ // Using the StringList type.
+ write_config(
+ "
+ [build]
+ rustflags = ['--file']
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli"]
+ );
+
+ // With normal env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_BUILD_RUSTFLAGS", "--env1 --env2")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli", "--env1", "--env2"]
+ );
+
+ // With advanced-env.
+ let config = ConfigBuilder::new()
+ .unstable_flag("advanced-env")
+ .env("CARGO_BUILD_RUSTFLAGS", "['--env']")
+ .config_arg("build.rustflags = ['--cli']")
+ .build();
+ assert_eq!(
+ config
+ .get::<cargo::util::config::StringList>("build.rustflags")
+ .unwrap()
+ .as_slice(),
+ ["--file", "--cli", "--env"]
+ );
+}
+
+#[cargo_test]
+fn merges_table() {
+ // Tables are merged.
+ write_config(
+ "
+ [foo]
+ key1 = 1
+ key2 = 2
+ key3 = 3
+ ",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("foo.key2 = 4")
+ .config_arg("foo.key3 = 5")
+ .config_arg("foo.key4 = 6")
+ .build();
+ assert_eq!(config.get::<i32>("foo.key1").unwrap(), 1);
+ assert_eq!(config.get::<i32>("foo.key2").unwrap(), 4);
+ assert_eq!(config.get::<i32>("foo.key3").unwrap(), 5);
+ assert_eq!(config.get::<i32>("foo.key4").unwrap(), 6);
+
+ // With env.
+ let config = ConfigBuilder::new()
+ .env("CARGO_FOO_KEY3", "7")
+ .env("CARGO_FOO_KEY4", "8")
+ .env("CARGO_FOO_KEY5", "9")
+ .config_arg("foo.key2 = 4")
+ .config_arg("foo.key3 = 5")
+ .config_arg("foo.key4 = 6")
+ .build();
+ assert_eq!(config.get::<i32>("foo.key1").unwrap(), 1);
+ assert_eq!(config.get::<i32>("foo.key2").unwrap(), 4);
+ assert_eq!(config.get::<i32>("foo.key3").unwrap(), 5);
+ assert_eq!(config.get::<i32>("foo.key4").unwrap(), 6);
+ assert_eq!(config.get::<i32>("foo.key5").unwrap(), 9);
+}
+
+#[cargo_test]
+fn merge_array_mixed_def_paths() {
+ // Merging of arrays with different def sites.
+ write_config(
+ "
+ paths = ['file']
+ ",
+ );
+ // Create a directory for CWD to differentiate the paths.
+ let somedir = paths::root().join("somedir");
+ fs::create_dir(&somedir).unwrap();
+ let config = ConfigBuilder::new()
+ .cwd(&somedir)
+ .config_arg("paths=['cli']")
+ // env is currently ignored for get_list()
+ .env("CARGO_PATHS", "env")
+ .build();
+ let paths = config.get_list("paths").unwrap().unwrap();
+ // The definition for the root value is somewhat arbitrary, but currently starts with the file because that is what is loaded first.
+ assert_eq!(paths.definition, Definition::Path(paths::root()));
+ assert_eq!(paths.val.len(), 2);
+ assert_eq!(paths.val[0].0, "file");
+ assert_eq!(paths.val[0].1.root(&config), paths::root());
+ assert_eq!(paths.val[1].0, "cli");
+ assert_eq!(paths.val[1].1.root(&config), somedir);
+}
+
+#[cargo_test]
+fn enforces_format() {
+ // These dotted key expressions should all be fine.
+ let config = ConfigBuilder::new()
+ .config_arg("a=true")
+ .config_arg(" b.a = true ")
+ .config_arg("c.\"b\".'a'=true")
+ .config_arg("d.\"=\".'='=true")
+ .config_arg("e.\"'\".'\"'=true")
+ .build();
+ assert_eq!(config.get::<bool>("a").unwrap(), true);
+ assert_eq!(
+ config.get::<HashMap<String, bool>>("b").unwrap(),
+ HashMap::from([("a".to_string(), true)])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("c")
+ .unwrap(),
+ HashMap::from([("b".to_string(), HashMap::from([("a".to_string(), true)]))])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("d")
+ .unwrap(),
+ HashMap::from([("=".to_string(), HashMap::from([("=".to_string(), true)]))])
+ );
+ assert_eq!(
+ config
+ .get::<HashMap<String, HashMap<String, bool>>>("e")
+ .unwrap(),
+ HashMap::from([("'".to_string(), HashMap::from([("\"".to_string(), true)]))])
+ );
+
+ // But anything that's not a dotted key expression should be disallowed.
+ let _ = ConfigBuilder::new()
+ .config_arg("[a] foo=true")
+ .build_err()
+ .unwrap_err();
+ let _ = ConfigBuilder::new()
+ .config_arg("a = true\nb = true")
+ .build_err()
+ .unwrap_err();
+
+ // We also disallow overwriting with tables since it makes merging unclear.
+ let _ = ConfigBuilder::new()
+ .config_arg("a = { first = true, second = false }")
+ .build_err()
+ .unwrap_err();
+ let _ = ConfigBuilder::new()
+ .config_arg("a = { first = true }")
+ .build_err()
+ .unwrap_err();
+}
+
+#[cargo_test]
+fn unused_key() {
+ // Unused key passed on command line.
+ let config = ConfigBuilder::new().config_arg("build.unused = 2").build();
+
+ config.build_config().unwrap();
+ let output = read_output(config);
+ let expected = "\
+warning: unused config key `build.unused` in `--config cli option`
+";
+ assert_match(expected, &output);
+}
+
+#[cargo_test]
+fn rerooted_remains() {
+ // Re-rooting keeps cli args.
+ let somedir = paths::root().join("somedir");
+ fs::create_dir_all(somedir.join(".cargo")).unwrap();
+ fs::write(
+ somedir.join(".cargo").join("config"),
+ "
+ a = 'file1'
+ b = 'file2'
+ ",
+ )
+ .unwrap();
+ let mut config = ConfigBuilder::new()
+ .cwd(&somedir)
+ .config_arg("b='cli1'")
+ .config_arg("c='cli2'")
+ .build();
+ assert_eq!(config.get::<String>("a").unwrap(), "file1");
+ assert_eq!(config.get::<String>("b").unwrap(), "cli1");
+ assert_eq!(config.get::<String>("c").unwrap(), "cli2");
+
+ config.reload_rooted_at(paths::root()).unwrap();
+
+ assert_eq!(config.get::<Option<String>>("a").unwrap(), None);
+ assert_eq!(config.get::<String>("b").unwrap(), "cli1");
+ assert_eq!(config.get::<String>("c").unwrap(), "cli2");
+}
+
+#[cargo_test]
+fn bad_parse() {
+ // Fail to TOML parse.
+ let config = ConfigBuilder::new().config_arg("abc").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to parse value from --config argument `abc` as a dotted key expression
+
+Caused by:
+ TOML parse error at line 1, column 4
+ |
+1 | abc
+ | ^
+expected `.`, `=`
+",
+ );
+
+ let config = ConfigBuilder::new().config_arg("").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn too_many_values() {
+ // Currently restricted to only 1 value.
+ let config = ConfigBuilder::new().config_arg("a=1\nb=2").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `a=1
+b=2` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn no_disallowed_values() {
+ let config = ConfigBuilder::new()
+ .config_arg("registry.token=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registry.token cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registries.crates-io.token=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registries.crates-io.token cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registry.secret-key=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registry.secret-key cannot be set through --config for security reasons",
+ );
+ let config = ConfigBuilder::new()
+ .config_arg("registries.crates-io.secret-key=\"hello\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "registries.crates-io.secret-key cannot be set through --config for security reasons",
+ );
+}
+
+#[cargo_test]
+fn no_inline_table_value() {
+ // Disallow inline tables
+ let config = ConfigBuilder::new()
+ .config_arg("a.b={c = \"d\"}")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `a.b={c = \"d\"}` sets a value to an inline table, which is not accepted"
+ );
+}
+
+#[cargo_test]
+fn no_array_of_tables_values() {
+ // Disallow array-of-tables when not in dotted form
+ let config = ConfigBuilder::new()
+ .config_arg("[[a.b]]\nc = \"d\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `[[a.b]]
+c = \"d\"` was not a TOML dotted key expression (such as `build.jobs = 2`)",
+ );
+}
+
+#[cargo_test]
+fn no_comments() {
+ // Disallow comments in dotted form.
+ let config = ConfigBuilder::new()
+ .config_arg("a.b = \"c\" # exactly")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `a.b = \"c\" # exactly` includes non-whitespace decoration",
+ );
+
+ let config = ConfigBuilder::new()
+ .config_arg("# exactly\na.b = \"c\"")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+--config argument `# exactly\na.b = \"c\"` includes non-whitespace decoration",
+ );
+}
+
+#[cargo_test]
+fn bad_cv_convert() {
+ // ConfigValue does not support all TOML types.
+ let config = ConfigBuilder::new().config_arg("a=2019-12-01").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to convert --config argument `a=2019-12-01`
+
+Caused by:
+ failed to parse key `a`
+
+Caused by:
+ found TOML configuration value of unknown type `datetime`",
+ );
+}
+
+#[cargo_test]
+fn fail_to_merge_multiple_args() {
+ // Error message when multiple args fail to merge.
+ let config = ConfigBuilder::new()
+ .config_arg("foo='a'")
+ .config_arg("foo=['a']")
+ .build_err();
+ // This is a little repetitive, but hopefully the user can figure it out.
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to merge --config argument `foo=['a']`
+
+Caused by:
+ failed to merge key `foo` between --config cli option and --config cli option
+
+Caused by:
+ failed to merge config value from `--config cli option` into `--config cli option`: \
+ expected string, but found array",
+ );
+}
+
+#[cargo_test]
+fn cli_path() {
+ // --config path_to_file
+ fs::write(paths::root().join("myconfig.toml"), "key = 123").unwrap();
+ let config = ConfigBuilder::new()
+ .cwd(paths::root())
+ .config_arg("myconfig.toml")
+ .build();
+ assert_eq!(config.get::<u32>("key").unwrap(), 123);
+
+ let config = ConfigBuilder::new().config_arg("missing.toml").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "\
+failed to parse value from --config argument `missing.toml` as a dotted key expression
+
+Caused by:
+ TOML parse error at line 1, column 13
+ |
+1 | missing.toml
+ | ^
+expected `.`, `=`
+",
+ );
+}