//! Tests for the --config CLI option. use super::config::{assert_error, assert_match, read_output, write_config, ConfigBuilder}; use cargo::util::config::Definition; use cargo_test_support::{paths, project}; use std::fs; #[cargo_test] fn config_gated() { // Requires -Zunstable-options let p = project().file("src/lib.rs", "").build(); p.cargo("build --config --config build.jobs=1") .with_status(101) .with_stderr( "\ [ERROR] the `--config` flag is unstable, [..] See [..] See [..] ", ) .run(); } #[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 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 unused_key() { // Unused key passed on command line. let config = ConfigBuilder::new() .config_arg("build={jobs=1, 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 --config argument `abc` Caused by: TOML parse error at line 1, column 4 | 1 | abc | ^ Unexpected end of input Expected `.` or `=` ", ); } #[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` expected exactly one key=value pair, got 2 keys", ); let config = ConfigBuilder::new().config_arg("").build_err(); assert_error( config.unwrap_err(), "\ --config argument `` expected exactly one key=value pair, got 0 keys", ); } #[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", ); }