From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/config_cli.rs | 564 ++++++++++++++++++++++++++ 1 file changed, 564 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/config_cli.rs (limited to 'src/tools/cargo/tests/testsuite/config_cli.rs') 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::("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::("build.jobs").unwrap(), 3); + assert_eq!(config.get::("build.rustc").unwrap(), "file"); + assert_eq!(config.get::("term.quiet").unwrap(), false); + assert_eq!(config.get::("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::("build.jobs").unwrap(), 1); + assert_eq!(config.get::("build.rustc").unwrap(), "cli"); + assert_eq!(config.get::("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::("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::("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::("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::("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::("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::("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::>("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::>("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::>("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::>("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::("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::("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::("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::("foo.key1").unwrap(), 1); + assert_eq!(config.get::("foo.key2").unwrap(), 4); + assert_eq!(config.get::("foo.key3").unwrap(), 5); + assert_eq!(config.get::("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::("foo.key1").unwrap(), 1); + assert_eq!(config.get::("foo.key2").unwrap(), 4); + assert_eq!(config.get::("foo.key3").unwrap(), 5); + assert_eq!(config.get::("foo.key4").unwrap(), 6); + assert_eq!(config.get::("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::("a").unwrap(), true); + assert_eq!( + config.get::>("b").unwrap(), + HashMap::from([("a".to_string(), true)]) + ); + assert_eq!( + config + .get::>>("c") + .unwrap(), + HashMap::from([("b".to_string(), HashMap::from([("a".to_string(), true)]))]) + ); + assert_eq!( + config + .get::>>("d") + .unwrap(), + HashMap::from([("=".to_string(), HashMap::from([("=".to_string(), true)]))]) + ); + assert_eq!( + config + .get::>>("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::("a").unwrap(), "file1"); + assert_eq!(config.get::("b").unwrap(), "cli1"); + assert_eq!(config.get::("c").unwrap(), "cli2"); + + config.reload_rooted_at(paths::root()).unwrap(); + + assert_eq!(config.get::>("a").unwrap(), None); + assert_eq!(config.get::("b").unwrap(), "cli1"); + assert_eq!(config.get::("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::("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 `.`, `=` +", + ); +} -- cgit v1.2.3