1 //! Tests for config settings.
2
3 use cargo::core::Shell;
4 use cargo::util::config::{self, Config, SslVersionConfig, StringList};
5 use cargo::util::interning::InternedString;
6 use cargo::util::toml::{self, VecStringOrBool as VSOB};
7 use cargo::CargoResult;
8 use cargo_test_support::{normalized_lines_match, paths, project, t};
9 use serde::Deserialize;
10 use std::borrow::Borrow;
11 use std::collections::{BTreeMap, HashMap};
12 use std::fs;
13 use std::io;
14 use std::os;
15 use std::path::{Path, PathBuf};
16
17 /// Helper for constructing a `Config` object.
18 pub struct ConfigBuilder {
19 env: HashMap<String, String>,
20 unstable: Vec<String>,
21 config_args: Vec<String>,
22 cwd: Option<PathBuf>,
23 enable_nightly_features: bool,
24 }
25
26 impl ConfigBuilder {
new() -> ConfigBuilder27 pub fn new() -> ConfigBuilder {
28 ConfigBuilder {
29 env: HashMap::new(),
30 unstable: Vec::new(),
31 config_args: Vec::new(),
32 cwd: None,
33 enable_nightly_features: false,
34 }
35 }
36
37 /// Passes a `-Z` flag.
unstable_flag(&mut self, s: impl Into<String>) -> &mut Self38 pub fn unstable_flag(&mut self, s: impl Into<String>) -> &mut Self {
39 self.unstable.push(s.into());
40 self
41 }
42
43 /// Sets an environment variable.
env(&mut self, key: impl Into<String>, val: impl Into<String>) -> &mut Self44 pub fn env(&mut self, key: impl Into<String>, val: impl Into<String>) -> &mut Self {
45 self.env.insert(key.into(), val.into());
46 self
47 }
48
49 /// Unconditionaly enable nightly features, even on stable channels.
nightly_features_allowed(&mut self, allowed: bool) -> &mut Self50 pub fn nightly_features_allowed(&mut self, allowed: bool) -> &mut Self {
51 self.enable_nightly_features = allowed;
52 self
53 }
54
55 /// Passes a `--config` flag.
config_arg(&mut self, arg: impl Into<String>) -> &mut Self56 pub fn config_arg(&mut self, arg: impl Into<String>) -> &mut Self {
57 if !self.unstable.iter().any(|s| s == "unstable-options") {
58 // --config is current unstable
59 self.unstable_flag("unstable-options");
60 }
61 self.config_args.push(arg.into());
62 self
63 }
64
65 /// Sets the current working directory where config files will be loaded.
cwd(&mut self, path: impl AsRef<Path>) -> &mut Self66 pub fn cwd(&mut self, path: impl AsRef<Path>) -> &mut Self {
67 self.cwd = Some(paths::root().join(path.as_ref()));
68 self
69 }
70
71 /// Creates the `Config`.
build(&self) -> Config72 pub fn build(&self) -> Config {
73 self.build_err().unwrap()
74 }
75
76 /// Creates the `Config`, returning a Result.
build_err(&self) -> CargoResult<Config>77 pub fn build_err(&self) -> CargoResult<Config> {
78 let output = Box::new(fs::File::create(paths::root().join("shell.out")).unwrap());
79 let shell = Shell::from_write(output);
80 let cwd = self.cwd.clone().unwrap_or_else(|| paths::root());
81 let homedir = paths::home();
82 let mut config = Config::new(shell, cwd, homedir);
83 config.nightly_features_allowed = self.enable_nightly_features || !self.unstable.is_empty();
84 config.set_env(self.env.clone());
85 config.set_search_stop_path(paths::root());
86 config.configure(
87 0,
88 false,
89 None,
90 false,
91 false,
92 false,
93 &None,
94 &self.unstable,
95 &self.config_args,
96 )?;
97 Ok(config)
98 }
99 }
100
new_config() -> Config101 fn new_config() -> Config {
102 ConfigBuilder::new().build()
103 }
104
105 /// Read the output from Config.
read_output(config: Config) -> String106 pub fn read_output(config: Config) -> String {
107 drop(config); // Paranoid about flushing the file.
108 let path = paths::root().join("shell.out");
109 fs::read_to_string(path).unwrap()
110 }
111
112 #[cargo_test]
read_env_vars_for_config()113 fn read_env_vars_for_config() {
114 let p = project()
115 .file(
116 "Cargo.toml",
117 r#"
118 [package]
119 name = "foo"
120 authors = []
121 version = "0.0.0"
122 build = "build.rs"
123 "#,
124 )
125 .file("src/lib.rs", "")
126 .file(
127 "build.rs",
128 r#"
129 use std::env;
130 fn main() {
131 assert_eq!(env::var("NUM_JOBS").unwrap(), "100");
132 }
133 "#,
134 )
135 .build();
136
137 p.cargo("build").env("CARGO_BUILD_JOBS", "100").run();
138 }
139
write_config(config: &str)140 pub fn write_config(config: &str) {
141 write_config_at(paths::root().join(".cargo/config"), config);
142 }
143
write_config_at(path: impl AsRef<Path>, contents: &str)144 pub fn write_config_at(path: impl AsRef<Path>, contents: &str) {
145 let path = paths::root().join(path.as_ref());
146 fs::create_dir_all(path.parent().unwrap()).unwrap();
147 fs::write(path, contents).unwrap();
148 }
149
write_config_toml(config: &str)150 fn write_config_toml(config: &str) {
151 write_config_at(paths::root().join(".cargo/config.toml"), config);
152 }
153
154 // Several test fail on windows if the user does not have permission to
155 // create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of
156 // disabling these test on Windows, use this function to test whether we
157 // have permission, and return otherwise. This way, we still don't run these
158 // tests most of the time, but at least we do if the user has the right
159 // permissions.
160 // This function is derived from libstd fs tests.
got_symlink_permission() -> bool161 pub fn got_symlink_permission() -> bool {
162 if cfg!(unix) {
163 return true;
164 }
165 let link = paths::root().join("some_hopefully_unique_link_name");
166 let target = paths::root().join("nonexisting_target");
167
168 match symlink_file(&target, &link) {
169 Ok(_) => true,
170 // ERROR_PRIVILEGE_NOT_HELD = 1314
171 Err(ref err) if err.raw_os_error() == Some(1314) => false,
172 Err(_) => true,
173 }
174 }
175
176 #[cfg(unix)]
symlink_file(target: &Path, link: &Path) -> io::Result<()>177 fn symlink_file(target: &Path, link: &Path) -> io::Result<()> {
178 os::unix::fs::symlink(target, link)
179 }
180
181 #[cfg(windows)]
symlink_file(target: &Path, link: &Path) -> io::Result<()>182 fn symlink_file(target: &Path, link: &Path) -> io::Result<()> {
183 os::windows::fs::symlink_file(target, link)
184 }
185
symlink_config_to_config_toml()186 fn symlink_config_to_config_toml() {
187 let toml_path = paths::root().join(".cargo/config.toml");
188 let symlink_path = paths::root().join(".cargo/config");
189 t!(symlink_file(&toml_path, &symlink_path));
190 }
191
192 #[track_caller]
assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str)193 pub fn assert_error<E: Borrow<anyhow::Error>>(error: E, msgs: &str) {
194 let causes = error
195 .borrow()
196 .chain()
197 .enumerate()
198 .map(|(i, e)| {
199 if i == 0 {
200 e.to_string()
201 } else {
202 format!("Caused by:\n {}", e)
203 }
204 })
205 .collect::<Vec<_>>()
206 .join("\n\n");
207 assert_match(msgs, &causes);
208 }
209
210 #[track_caller]
assert_match(expected: &str, actual: &str)211 pub fn assert_match(expected: &str, actual: &str) {
212 if !normalized_lines_match(expected, actual, None) {
213 panic!(
214 "Did not find expected:\n{}\nActual:\n{}\n",
215 expected, actual
216 );
217 }
218 }
219
220 #[cargo_test]
get_config()221 fn get_config() {
222 write_config(
223 "\
224 [S]
225 f1 = 123
226 ",
227 );
228
229 let config = new_config();
230
231 #[derive(Debug, Deserialize, Eq, PartialEq)]
232 struct S {
233 f1: Option<i64>,
234 }
235 let s: S = config.get("S").unwrap();
236 assert_eq!(s, S { f1: Some(123) });
237 let config = ConfigBuilder::new().env("CARGO_S_F1", "456").build();
238 let s: S = config.get("S").unwrap();
239 assert_eq!(s, S { f1: Some(456) });
240 }
241
242 #[cargo_test]
config_works_with_extension()243 fn config_works_with_extension() {
244 write_config_toml(
245 "\
246 [foo]
247 f1 = 1
248 ",
249 );
250
251 let config = new_config();
252
253 assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
254 }
255
256 #[cargo_test]
config_ambiguous_filename_symlink_doesnt_warn()257 fn config_ambiguous_filename_symlink_doesnt_warn() {
258 // Windows requires special permissions to create symlinks.
259 // If we don't have permission, just skip this test.
260 if !got_symlink_permission() {
261 return;
262 };
263
264 write_config_toml(
265 "\
266 [foo]
267 f1 = 1
268 ",
269 );
270
271 symlink_config_to_config_toml();
272
273 let config = new_config();
274
275 assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
276
277 // It should NOT have warned for the symlink.
278 let output = read_output(config);
279 let unexpected = "\
280 warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
281 ";
282 if normalized_lines_match(unexpected, &output, None) {
283 panic!(
284 "Found unexpected:\n{}\nActual error:\n{}\n",
285 unexpected, output
286 );
287 }
288 }
289
290 #[cargo_test]
config_ambiguous_filename()291 fn config_ambiguous_filename() {
292 write_config(
293 "\
294 [foo]
295 f1 = 1
296 ",
297 );
298
299 write_config_toml(
300 "\
301 [foo]
302 f1 = 2
303 ",
304 );
305
306 let config = new_config();
307
308 // It should use the value from the one without the extension for
309 // backwards compatibility.
310 assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
311
312 // But it also should have warned.
313 let output = read_output(config);
314 let expected = "\
315 warning: Both `[..]/.cargo/config` and `[..]/.cargo/config.toml` exist. Using `[..]/.cargo/config`
316 ";
317 assert_match(expected, &output);
318 }
319
320 #[cargo_test]
config_unused_fields()321 fn config_unused_fields() {
322 write_config(
323 "\
324 [S]
325 unused = 456
326 ",
327 );
328
329 let config = ConfigBuilder::new()
330 .env("CARGO_S_UNUSED2", "1")
331 .env("CARGO_S2_UNUSED", "2")
332 .build();
333
334 #[derive(Debug, Deserialize, Eq, PartialEq)]
335 struct S {
336 f1: Option<i64>,
337 }
338 // This prints a warning (verified below).
339 let s: S = config.get("S").unwrap();
340 assert_eq!(s, S { f1: None });
341 // This does not print anything, we cannot easily/reliably warn for
342 // environment variables.
343 let s: S = config.get("S2").unwrap();
344 assert_eq!(s, S { f1: None });
345
346 // Verify the warnings.
347 let output = read_output(config);
348 let expected = "\
349 warning: unused config key `S.unused` in `[..]/.cargo/config`
350 ";
351 assert_match(expected, &output);
352 }
353
354 #[cargo_test]
config_load_toml_profile()355 fn config_load_toml_profile() {
356 write_config(
357 "\
358 [profile.dev]
359 opt-level = 's'
360 lto = true
361 codegen-units=4
362 debug = true
363 debug-assertions = true
364 rpath = true
365 panic = 'abort'
366 overflow-checks = true
367 incremental = true
368
369 [profile.dev.build-override]
370 opt-level = 1
371
372 [profile.dev.package.bar]
373 codegen-units = 9
374
375 [profile.no-lto]
376 inherits = 'dev'
377 dir-name = 'without-lto'
378 lto = false
379 ",
380 );
381
382 let config = ConfigBuilder::new()
383 .unstable_flag("advanced-env")
384 .env("CARGO_PROFILE_DEV_CODEGEN_UNITS", "5")
385 .env("CARGO_PROFILE_DEV_BUILD_OVERRIDE_CODEGEN_UNITS", "11")
386 .env("CARGO_PROFILE_DEV_PACKAGE_env_CODEGEN_UNITS", "13")
387 .env("CARGO_PROFILE_DEV_PACKAGE_bar_OPT_LEVEL", "2")
388 .build();
389
390 // TODO: don't use actual `tomlprofile`.
391 let p: toml::TomlProfile = config.get("profile.dev").unwrap();
392 let mut packages = BTreeMap::new();
393 let key = toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("bar").unwrap());
394 let o_profile = toml::TomlProfile {
395 opt_level: Some(toml::TomlOptLevel("2".to_string())),
396 codegen_units: Some(9),
397 ..Default::default()
398 };
399 packages.insert(key, o_profile);
400 let key = toml::ProfilePackageSpec::Spec(::cargo::core::PackageIdSpec::parse("env").unwrap());
401 let o_profile = toml::TomlProfile {
402 codegen_units: Some(13),
403 ..Default::default()
404 };
405 packages.insert(key, o_profile);
406
407 assert_eq!(
408 p,
409 toml::TomlProfile {
410 opt_level: Some(toml::TomlOptLevel("s".to_string())),
411 lto: Some(toml::StringOrBool::Bool(true)),
412 codegen_units: Some(5),
413 debug: Some(toml::U32OrBool::Bool(true)),
414 debug_assertions: Some(true),
415 rpath: Some(true),
416 panic: Some("abort".to_string()),
417 overflow_checks: Some(true),
418 incremental: Some(true),
419 package: Some(packages),
420 build_override: Some(Box::new(toml::TomlProfile {
421 opt_level: Some(toml::TomlOptLevel("1".to_string())),
422 codegen_units: Some(11),
423 ..Default::default()
424 })),
425 ..Default::default()
426 }
427 );
428
429 let p: toml::TomlProfile = config.get("profile.no-lto").unwrap();
430 assert_eq!(
431 p,
432 toml::TomlProfile {
433 lto: Some(toml::StringOrBool::Bool(false)),
434 dir_name: Some(InternedString::new("without-lto")),
435 inherits: Some(InternedString::new("dev")),
436 ..Default::default()
437 }
438 );
439 }
440
441 #[cargo_test]
profile_env_var_prefix()442 fn profile_env_var_prefix() {
443 // Check for a bug with collision on DEBUG vs DEBUG_ASSERTIONS.
444 let config = ConfigBuilder::new()
445 .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false")
446 .build();
447 let p: toml::TomlProfile = config.get("profile.dev").unwrap();
448 assert_eq!(p.debug_assertions, Some(false));
449 assert_eq!(p.debug, None);
450
451 let config = ConfigBuilder::new()
452 .env("CARGO_PROFILE_DEV_DEBUG", "1")
453 .build();
454 let p: toml::TomlProfile = config.get("profile.dev").unwrap();
455 assert_eq!(p.debug_assertions, None);
456 assert_eq!(p.debug, Some(toml::U32OrBool::U32(1)));
457
458 let config = ConfigBuilder::new()
459 .env("CARGO_PROFILE_DEV_DEBUG_ASSERTIONS", "false")
460 .env("CARGO_PROFILE_DEV_DEBUG", "1")
461 .build();
462 let p: toml::TomlProfile = config.get("profile.dev").unwrap();
463 assert_eq!(p.debug_assertions, Some(false));
464 assert_eq!(p.debug, Some(toml::U32OrBool::U32(1)));
465 }
466
467 #[cargo_test]
config_deserialize_any()468 fn config_deserialize_any() {
469 // Some tests to exercise deserialize_any for deserializers that need to
470 // be told the format.
471 write_config(
472 "\
473 a = true
474 b = ['b']
475 c = ['c']
476 ",
477 );
478
479 // advanced-env
480 let config = ConfigBuilder::new()
481 .unstable_flag("advanced-env")
482 .env("CARGO_ENVB", "false")
483 .env("CARGO_C", "['d']")
484 .env("CARGO_ENVL", "['a', 'b']")
485 .build();
486 assert_eq!(config.get::<VSOB>("a").unwrap(), VSOB::Bool(true));
487 assert_eq!(
488 config.get::<VSOB>("b").unwrap(),
489 VSOB::VecString(vec!["b".to_string()])
490 );
491 assert_eq!(
492 config.get::<VSOB>("c").unwrap(),
493 VSOB::VecString(vec!["c".to_string(), "d".to_string()])
494 );
495 assert_eq!(config.get::<VSOB>("envb").unwrap(), VSOB::Bool(false));
496 assert_eq!(
497 config.get::<VSOB>("envl").unwrap(),
498 VSOB::VecString(vec!["a".to_string(), "b".to_string()])
499 );
500
501 // Demonstrate where merging logic isn't very smart. This could be improved.
502 let config = ConfigBuilder::new().env("CARGO_A", "x y").build();
503 assert_error(
504 config.get::<VSOB>("a").unwrap_err(),
505 "\
506 error in environment variable `CARGO_A`: could not load config key `a`
507
508 Caused by:
509 invalid type: string \"x y\", expected a boolean or vector of strings",
510 );
511
512 // Normal env.
513 let config = ConfigBuilder::new()
514 .unstable_flag("advanced-env")
515 .env("CARGO_B", "d e")
516 .env("CARGO_C", "f g")
517 .build();
518 assert_eq!(
519 config.get::<VSOB>("b").unwrap(),
520 VSOB::VecString(vec!["b".to_string(), "d".to_string(), "e".to_string()])
521 );
522 assert_eq!(
523 config.get::<VSOB>("c").unwrap(),
524 VSOB::VecString(vec!["c".to_string(), "f".to_string(), "g".to_string()])
525 );
526
527 // config-cli
528 // This test demonstrates that ConfigValue::merge isn't very smart.
529 // It would be nice if it was smarter.
530 let config = ConfigBuilder::new().config_arg("a = ['a']").build_err();
531 assert_error(
532 config.unwrap_err(),
533 "\
534 failed to merge --config key `a` into `[..]/.cargo/config`
535
536 Caused by:
537 failed to merge config value from `--config cli option` into `[..]/.cargo/config`: \
538 expected boolean, but found array",
539 );
540
541 // config-cli and advanced-env
542 let config = ConfigBuilder::new()
543 .unstable_flag("advanced-env")
544 .config_arg("b=['clib']")
545 .config_arg("c=['clic']")
546 .env("CARGO_B", "env1 env2")
547 .env("CARGO_C", "['e1', 'e2']")
548 .build();
549 assert_eq!(
550 config.get::<VSOB>("b").unwrap(),
551 VSOB::VecString(vec![
552 "b".to_string(),
553 "clib".to_string(),
554 "env1".to_string(),
555 "env2".to_string()
556 ])
557 );
558 assert_eq!(
559 config.get::<VSOB>("c").unwrap(),
560 VSOB::VecString(vec![
561 "c".to_string(),
562 "clic".to_string(),
563 "e1".to_string(),
564 "e2".to_string()
565 ])
566 );
567 }
568
569 #[cargo_test]
config_toml_errors()570 fn config_toml_errors() {
571 write_config(
572 "\
573 [profile.dev]
574 opt-level = 'foo'
575 ",
576 );
577
578 let config = new_config();
579
580 assert_error(
581 config.get::<toml::TomlProfile>("profile.dev").unwrap_err(),
582 "\
583 error in [..]/.cargo/config: could not load config key `profile.dev.opt-level`
584
585 Caused by:
586 must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"foo\"",
587 );
588
589 let config = ConfigBuilder::new()
590 .env("CARGO_PROFILE_DEV_OPT_LEVEL", "asdf")
591 .build();
592
593 assert_error(
594 config.get::<toml::TomlProfile>("profile.dev").unwrap_err(),
595 "\
596 error in environment variable `CARGO_PROFILE_DEV_OPT_LEVEL`: could not load config key `profile.dev.opt-level`
597
598 Caused by:
599 must be `0`, `1`, `2`, `3`, `s` or `z`, but found the string: \"asdf\"",
600 );
601 }
602
603 #[cargo_test]
load_nested()604 fn load_nested() {
605 write_config(
606 "\
607 [nest.foo]
608 f1 = 1
609 f2 = 2
610 [nest.bar]
611 asdf = 3
612 ",
613 );
614
615 let config = ConfigBuilder::new()
616 .unstable_flag("advanced-env")
617 .env("CARGO_NEST_foo_f2", "3")
618 .env("CARGO_NESTE_foo_f1", "1")
619 .env("CARGO_NESTE_foo_f2", "3")
620 .env("CARGO_NESTE_bar_asdf", "3")
621 .build();
622
623 type Nested = HashMap<String, HashMap<String, u8>>;
624
625 let n: Nested = config.get("nest").unwrap();
626 let mut expected = HashMap::new();
627 let mut foo = HashMap::new();
628 foo.insert("f1".to_string(), 1);
629 foo.insert("f2".to_string(), 3);
630 expected.insert("foo".to_string(), foo);
631 let mut bar = HashMap::new();
632 bar.insert("asdf".to_string(), 3);
633 expected.insert("bar".to_string(), bar);
634 assert_eq!(n, expected);
635
636 let n: Nested = config.get("neste").unwrap();
637 assert_eq!(n, expected);
638 }
639
640 #[cargo_test]
get_errors()641 fn get_errors() {
642 write_config(
643 "\
644 [S]
645 f1 = 123
646 f2 = 'asdf'
647 big = 123456789
648 ",
649 );
650
651 let config = ConfigBuilder::new()
652 .env("CARGO_E_S", "asdf")
653 .env("CARGO_E_BIG", "123456789")
654 .build();
655 assert_error(
656 config.get::<i64>("foo").unwrap_err(),
657 "missing config key `foo`",
658 );
659 assert_error(
660 config.get::<i64>("foo.bar").unwrap_err(),
661 "missing config key `foo.bar`",
662 );
663 assert_error(
664 config.get::<i64>("S.f2").unwrap_err(),
665 "error in [..]/.cargo/config: `S.f2` expected an integer, but found a string",
666 );
667 assert_error(
668 config.get::<u8>("S.big").unwrap_err(),
669 "\
670 error in [..].cargo/config: could not load config key `S.big`
671
672 Caused by:
673 invalid value: integer `123456789`, expected u8",
674 );
675
676 // Environment variable type errors.
677 assert_error(
678 config.get::<i64>("e.s").unwrap_err(),
679 "error in environment variable `CARGO_E_S`: invalid digit found in string",
680 );
681 assert_error(
682 config.get::<i8>("e.big").unwrap_err(),
683 "\
684 error in environment variable `CARGO_E_BIG`: could not load config key `e.big`
685
686 Caused by:
687 invalid value: integer `123456789`, expected i8",
688 );
689
690 #[derive(Debug, Deserialize)]
691 struct S {
692 f1: i64,
693 f2: String,
694 f3: i64,
695 big: i64,
696 }
697 assert_error(config.get::<S>("S").unwrap_err(), "missing field `f3`");
698 }
699
700 #[cargo_test]
config_get_option()701 fn config_get_option() {
702 write_config(
703 "\
704 [foo]
705 f1 = 1
706 ",
707 );
708
709 let config = ConfigBuilder::new().env("CARGO_BAR_ASDF", "3").build();
710
711 assert_eq!(config.get::<Option<i32>>("a").unwrap(), None);
712 assert_eq!(config.get::<Option<i32>>("a.b").unwrap(), None);
713 assert_eq!(config.get::<Option<i32>>("foo.f1").unwrap(), Some(1));
714 assert_eq!(config.get::<Option<i32>>("bar.asdf").unwrap(), Some(3));
715 assert_eq!(config.get::<Option<i32>>("bar.zzzz").unwrap(), None);
716 }
717
718 #[cargo_test]
config_bad_toml()719 fn config_bad_toml() {
720 write_config("asdf");
721 let config = new_config();
722 assert_error(
723 config.get::<i32>("foo").unwrap_err(),
724 "\
725 could not load Cargo configuration
726
727 Caused by:
728 could not parse TOML configuration in `[..]/.cargo/config`
729
730 Caused by:
731 could not parse input as TOML
732
733 Caused by:
734 expected an equals, found eof at line 1 column 5",
735 );
736 }
737
738 #[cargo_test]
config_get_list()739 fn config_get_list() {
740 write_config(
741 "\
742 l1 = []
743 l2 = ['one', 'two']
744 l3 = 123
745 l4 = ['one', 'two']
746
747 [nested]
748 l = ['x']
749
750 [nested2]
751 l = ['y']
752
753 [nested-empty]
754 ",
755 );
756
757 type L = Vec<String>;
758
759 let config = ConfigBuilder::new()
760 .unstable_flag("advanced-env")
761 .env("CARGO_L4", "['three', 'four']")
762 .env("CARGO_L5", "['a']")
763 .env("CARGO_ENV_EMPTY", "[]")
764 .env("CARGO_ENV_BLANK", "")
765 .env("CARGO_ENV_NUM", "1")
766 .env("CARGO_ENV_NUM_LIST", "[1]")
767 .env("CARGO_ENV_TEXT", "asdf")
768 .env("CARGO_LEPAIR", "['a', 'b']")
769 .env("CARGO_NESTED2_L", "['z']")
770 .env("CARGO_NESTEDE_L", "['env']")
771 .env("CARGO_BAD_ENV", "[zzz]")
772 .build();
773
774 assert_eq!(config.get::<L>("unset").unwrap(), vec![] as Vec<String>);
775 assert_eq!(config.get::<L>("l1").unwrap(), vec![] as Vec<String>);
776 assert_eq!(config.get::<L>("l2").unwrap(), vec!["one", "two"]);
777 assert_error(
778 config.get::<L>("l3").unwrap_err(),
779 "\
780 invalid configuration for key `l3`
781 expected a list, but found a integer for `l3` in [..]/.cargo/config",
782 );
783 assert_eq!(
784 config.get::<L>("l4").unwrap(),
785 vec!["one", "two", "three", "four"]
786 );
787 assert_eq!(config.get::<L>("l5").unwrap(), vec!["a"]);
788 assert_eq!(config.get::<L>("env-empty").unwrap(), vec![] as Vec<String>);
789 assert_eq!(config.get::<L>("env-blank").unwrap(), vec![] as Vec<String>);
790 assert_eq!(config.get::<L>("env-num").unwrap(), vec!["1".to_string()]);
791 assert_error(
792 config.get::<L>("env-num-list").unwrap_err(),
793 "error in environment variable `CARGO_ENV_NUM_LIST`: \
794 expected string, found integer",
795 );
796 assert_eq!(
797 config.get::<L>("env-text").unwrap(),
798 vec!["asdf".to_string()]
799 );
800 // "invalid number" here isn't the best error, but I think it's just toml.rs.
801 assert_error(
802 config.get::<L>("bad-env").unwrap_err(),
803 "error in environment variable `CARGO_BAD_ENV`: \
804 could not parse TOML list: invalid TOML value, did you mean to use a quoted string? at line 1 column 8",
805 );
806
807 // Try some other sequence-like types.
808 assert_eq!(
809 config
810 .get::<(String, String, String, String)>("l4")
811 .unwrap(),
812 (
813 "one".to_string(),
814 "two".to_string(),
815 "three".to_string(),
816 "four".to_string()
817 )
818 );
819 assert_eq!(config.get::<(String,)>("l5").unwrap(), ("a".to_string(),));
820
821 // Tuple struct
822 #[derive(Debug, Deserialize, Eq, PartialEq)]
823 struct TupS(String, String);
824 assert_eq!(
825 config.get::<TupS>("lepair").unwrap(),
826 TupS("a".to_string(), "b".to_string())
827 );
828
829 // Nested with an option.
830 #[derive(Debug, Deserialize, Eq, PartialEq)]
831 struct S {
832 l: Option<Vec<String>>,
833 }
834 assert_eq!(config.get::<S>("nested-empty").unwrap(), S { l: None });
835 assert_eq!(
836 config.get::<S>("nested").unwrap(),
837 S {
838 l: Some(vec!["x".to_string()]),
839 }
840 );
841 assert_eq!(
842 config.get::<S>("nested2").unwrap(),
843 S {
844 l: Some(vec!["y".to_string(), "z".to_string()]),
845 }
846 );
847 assert_eq!(
848 config.get::<S>("nestede").unwrap(),
849 S {
850 l: Some(vec!["env".to_string()]),
851 }
852 );
853 }
854
855 #[cargo_test]
config_get_other_types()856 fn config_get_other_types() {
857 write_config(
858 "\
859 ns = 123
860 ns2 = 456
861 ",
862 );
863
864 let config = ConfigBuilder::new()
865 .env("CARGO_NSE", "987")
866 .env("CARGO_NS2", "654")
867 .build();
868
869 #[derive(Debug, Deserialize, Eq, PartialEq)]
870 #[serde(transparent)]
871 struct NewS(i32);
872 assert_eq!(config.get::<NewS>("ns").unwrap(), NewS(123));
873 assert_eq!(config.get::<NewS>("ns2").unwrap(), NewS(654));
874 assert_eq!(config.get::<NewS>("nse").unwrap(), NewS(987));
875 assert_error(
876 config.get::<NewS>("unset").unwrap_err(),
877 "missing config key `unset`",
878 );
879 }
880
881 #[cargo_test]
config_relative_path()882 fn config_relative_path() {
883 write_config(&format!(
884 "\
885 p1 = 'foo/bar'
886 p2 = '../abc'
887 p3 = 'b/c'
888 abs = '{}'
889 ",
890 paths::home().display(),
891 ));
892
893 let config = ConfigBuilder::new()
894 .env("CARGO_EPATH", "a/b")
895 .env("CARGO_P3", "d/e")
896 .build();
897
898 assert_eq!(
899 config
900 .get::<config::ConfigRelativePath>("p1")
901 .unwrap()
902 .resolve_path(&config),
903 paths::root().join("foo/bar")
904 );
905 assert_eq!(
906 config
907 .get::<config::ConfigRelativePath>("p2")
908 .unwrap()
909 .resolve_path(&config),
910 paths::root().join("../abc")
911 );
912 assert_eq!(
913 config
914 .get::<config::ConfigRelativePath>("p3")
915 .unwrap()
916 .resolve_path(&config),
917 paths::root().join("d/e")
918 );
919 assert_eq!(
920 config
921 .get::<config::ConfigRelativePath>("abs")
922 .unwrap()
923 .resolve_path(&config),
924 paths::home()
925 );
926 assert_eq!(
927 config
928 .get::<config::ConfigRelativePath>("epath")
929 .unwrap()
930 .resolve_path(&config),
931 paths::root().join("a/b")
932 );
933 }
934
935 #[cargo_test]
config_get_integers()936 fn config_get_integers() {
937 write_config(
938 "\
939 npos = 123456789
940 nneg = -123456789
941 i64max = 9223372036854775807
942 ",
943 );
944
945 let config = ConfigBuilder::new()
946 .env("CARGO_EPOS", "123456789")
947 .env("CARGO_ENEG", "-1")
948 .env("CARGO_EI64MAX", "9223372036854775807")
949 .build();
950
951 assert_eq!(
952 config.get::<u64>("i64max").unwrap(),
953 9_223_372_036_854_775_807
954 );
955 assert_eq!(
956 config.get::<i64>("i64max").unwrap(),
957 9_223_372_036_854_775_807
958 );
959 assert_eq!(
960 config.get::<u64>("ei64max").unwrap(),
961 9_223_372_036_854_775_807
962 );
963 assert_eq!(
964 config.get::<i64>("ei64max").unwrap(),
965 9_223_372_036_854_775_807
966 );
967
968 assert_error(
969 config.get::<u32>("nneg").unwrap_err(),
970 "\
971 error in [..].cargo/config: could not load config key `nneg`
972
973 Caused by:
974 invalid value: integer `-123456789`, expected u32",
975 );
976 assert_error(
977 config.get::<u32>("eneg").unwrap_err(),
978 "\
979 error in environment variable `CARGO_ENEG`: could not load config key `eneg`
980
981 Caused by:
982 invalid value: integer `-1`, expected u32",
983 );
984 assert_error(
985 config.get::<i8>("npos").unwrap_err(),
986 "\
987 error in [..].cargo/config: could not load config key `npos`
988
989 Caused by:
990 invalid value: integer `123456789`, expected i8",
991 );
992 assert_error(
993 config.get::<i8>("epos").unwrap_err(),
994 "\
995 error in environment variable `CARGO_EPOS`: could not load config key `epos`
996
997 Caused by:
998 invalid value: integer `123456789`, expected i8",
999 );
1000 }
1001
1002 #[cargo_test]
config_get_ssl_version_missing()1003 fn config_get_ssl_version_missing() {
1004 write_config(
1005 "\
1006 [http]
1007 hello = 'world'
1008 ",
1009 );
1010
1011 let config = new_config();
1012
1013 assert!(config
1014 .get::<Option<SslVersionConfig>>("http.ssl-version")
1015 .unwrap()
1016 .is_none());
1017 }
1018
1019 #[cargo_test]
config_get_ssl_version_single()1020 fn config_get_ssl_version_single() {
1021 write_config(
1022 "\
1023 [http]
1024 ssl-version = 'tlsv1.2'
1025 ",
1026 );
1027
1028 let config = new_config();
1029
1030 let a = config
1031 .get::<Option<SslVersionConfig>>("http.ssl-version")
1032 .unwrap()
1033 .unwrap();
1034 match a {
1035 SslVersionConfig::Single(v) => assert_eq!(&v, "tlsv1.2"),
1036 SslVersionConfig::Range(_) => panic!("Did not expect ssl version min/max."),
1037 };
1038 }
1039
1040 #[cargo_test]
config_get_ssl_version_min_max()1041 fn config_get_ssl_version_min_max() {
1042 write_config(
1043 "\
1044 [http]
1045 ssl-version.min = 'tlsv1.2'
1046 ssl-version.max = 'tlsv1.3'
1047 ",
1048 );
1049
1050 let config = new_config();
1051
1052 let a = config
1053 .get::<Option<SslVersionConfig>>("http.ssl-version")
1054 .unwrap()
1055 .unwrap();
1056 match a {
1057 SslVersionConfig::Single(_) => panic!("Did not expect exact ssl version."),
1058 SslVersionConfig::Range(range) => {
1059 assert_eq!(range.min, Some(String::from("tlsv1.2")));
1060 assert_eq!(range.max, Some(String::from("tlsv1.3")));
1061 }
1062 };
1063 }
1064
1065 #[cargo_test]
config_get_ssl_version_both_forms_configured()1066 fn config_get_ssl_version_both_forms_configured() {
1067 // this is not allowed
1068 write_config(
1069 "\
1070 [http]
1071 ssl-version = 'tlsv1.1'
1072 ssl-version.min = 'tlsv1.2'
1073 ssl-version.max = 'tlsv1.3'
1074 ",
1075 );
1076
1077 let config = new_config();
1078
1079 assert_error(
1080 config
1081 .get::<SslVersionConfig>("http.ssl-version")
1082 .unwrap_err(),
1083 "\
1084 could not load Cargo configuration
1085
1086 Caused by:
1087 could not parse TOML configuration in `[..]/.cargo/config`
1088
1089 Caused by:
1090 could not parse input as TOML
1091
1092 Caused by:
1093 dotted key attempted to extend non-table type at line 2 column 15",
1094 );
1095 assert!(config
1096 .get::<Option<SslVersionConfig>>("http.ssl-version")
1097 .unwrap()
1098 .is_none());
1099 }
1100
1101 #[cargo_test]
1102 /// Assert that unstable options can be configured with the `unstable` table in
1103 /// cargo config files
unstable_table_notation()1104 fn unstable_table_notation() {
1105 write_config(
1106 "\
1107 [unstable]
1108 print-im-a-teapot = true
1109 ",
1110 );
1111 let config = ConfigBuilder::new().nightly_features_allowed(true).build();
1112 assert_eq!(config.cli_unstable().print_im_a_teapot, true);
1113 }
1114
1115 #[cargo_test]
1116 /// Assert that dotted notation works for configuring unstable options
unstable_dotted_notation()1117 fn unstable_dotted_notation() {
1118 write_config(
1119 "\
1120 unstable.print-im-a-teapot = true
1121 ",
1122 );
1123 let config = ConfigBuilder::new().nightly_features_allowed(true).build();
1124 assert_eq!(config.cli_unstable().print_im_a_teapot, true);
1125 }
1126
1127 #[cargo_test]
1128 /// Assert that Zflags on the CLI take precedence over those from config
unstable_cli_precedence()1129 fn unstable_cli_precedence() {
1130 write_config(
1131 "\
1132 unstable.print-im-a-teapot = true
1133 ",
1134 );
1135 let config = ConfigBuilder::new().nightly_features_allowed(true).build();
1136 assert_eq!(config.cli_unstable().print_im_a_teapot, true);
1137
1138 let config = ConfigBuilder::new()
1139 .unstable_flag("print-im-a-teapot=no")
1140 .build();
1141 assert_eq!(config.cli_unstable().print_im_a_teapot, false);
1142 }
1143
1144 #[cargo_test]
1145 /// Assert that atempting to set an unstable flag that doesn't exist via config
1146 /// is ignored on stable
unstable_invalid_flag_ignored_on_stable()1147 fn unstable_invalid_flag_ignored_on_stable() {
1148 write_config(
1149 "\
1150 unstable.an-invalid-flag = 'yes'
1151 ",
1152 );
1153 assert!(ConfigBuilder::new().build_err().is_ok());
1154 }
1155
1156 #[cargo_test]
1157 /// Assert that unstable options can be configured with the `unstable` table in
1158 /// cargo config files
unstable_flags_ignored_on_stable()1159 fn unstable_flags_ignored_on_stable() {
1160 write_config(
1161 "\
1162 [unstable]
1163 print-im-a-teapot = true
1164 ",
1165 );
1166 // Enforce stable channel even when testing on nightly.
1167 let config = ConfigBuilder::new().nightly_features_allowed(false).build();
1168 assert_eq!(config.cli_unstable().print_im_a_teapot, false);
1169 }
1170
1171 #[cargo_test]
table_merge_failure()1172 fn table_merge_failure() {
1173 // Config::merge fails to merge entries in two tables.
1174 write_config_at(
1175 "foo/.cargo/config",
1176 "
1177 [table]
1178 key = ['foo']
1179 ",
1180 );
1181 write_config_at(
1182 ".cargo/config",
1183 "
1184 [table]
1185 key = 'bar'
1186 ",
1187 );
1188
1189 #[derive(Debug, Deserialize)]
1190 struct Table {
1191 key: StringList,
1192 }
1193 let config = ConfigBuilder::new().cwd("foo").build();
1194 assert_error(
1195 config.get::<Table>("table").unwrap_err(),
1196 "\
1197 could not load Cargo configuration
1198
1199 Caused by:
1200 failed to merge configuration at `[..]/.cargo/config`
1201
1202 Caused by:
1203 failed to merge key `table` between [..]/foo/.cargo/config and [..]/.cargo/config
1204
1205 Caused by:
1206 failed to merge key `key` between [..]/foo/.cargo/config and [..]/.cargo/config
1207
1208 Caused by:
1209 failed to merge config value from `[..]/.cargo/config` into `[..]/foo/.cargo/config`: \
1210 expected array, but found string",
1211 );
1212 }
1213
1214 #[cargo_test]
non_string_in_array()1215 fn non_string_in_array() {
1216 // Currently only strings are supported.
1217 write_config("foo = [1, 2, 3]");
1218 let config = new_config();
1219 assert_error(
1220 config.get::<Vec<i32>>("foo").unwrap_err(),
1221 "\
1222 could not load Cargo configuration
1223
1224 Caused by:
1225 failed to load TOML configuration from `[..]/.cargo/config`
1226
1227 Caused by:
1228 failed to parse key `foo`
1229
1230 Caused by:
1231 expected string but found integer in list",
1232 );
1233 }
1234
1235 #[cargo_test]
struct_with_opt_inner_struct()1236 fn struct_with_opt_inner_struct() {
1237 // Struct with a key that is Option of another struct.
1238 // Check that can be defined with environment variable.
1239 #[derive(Deserialize)]
1240 struct Inner {
1241 value: Option<i32>,
1242 }
1243 #[derive(Deserialize)]
1244 struct Foo {
1245 inner: Option<Inner>,
1246 }
1247 let config = ConfigBuilder::new()
1248 .env("CARGO_FOO_INNER_VALUE", "12")
1249 .build();
1250 let f: Foo = config.get("foo").unwrap();
1251 assert_eq!(f.inner.unwrap().value.unwrap(), 12);
1252 }
1253
1254 #[cargo_test]
struct_with_default_inner_struct()1255 fn struct_with_default_inner_struct() {
1256 // Struct with serde defaults.
1257 // Check that can be defined with environment variable.
1258 #[derive(Deserialize, Default)]
1259 #[serde(default)]
1260 struct Inner {
1261 value: i32,
1262 }
1263 #[derive(Deserialize, Default)]
1264 #[serde(default)]
1265 struct Foo {
1266 inner: Inner,
1267 }
1268 let config = ConfigBuilder::new()
1269 .env("CARGO_FOO_INNER_VALUE", "12")
1270 .build();
1271 let f: Foo = config.get("foo").unwrap();
1272 assert_eq!(f.inner.value, 12);
1273 }
1274
1275 #[cargo_test]
overlapping_env_config()1276 fn overlapping_env_config() {
1277 // Issue where one key is a prefix of another.
1278 #[derive(Deserialize)]
1279 #[serde(rename_all = "kebab-case")]
1280 struct Ambig {
1281 debug: Option<u32>,
1282 debug_assertions: Option<bool>,
1283 }
1284 let config = ConfigBuilder::new()
1285 .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1286 .build();
1287
1288 let s: Ambig = config.get("ambig").unwrap();
1289 assert_eq!(s.debug_assertions, Some(true));
1290 assert_eq!(s.debug, None);
1291
1292 let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "0").build();
1293 let s: Ambig = config.get("ambig").unwrap();
1294 assert_eq!(s.debug_assertions, None);
1295 assert_eq!(s.debug, Some(0));
1296
1297 let config = ConfigBuilder::new()
1298 .env("CARGO_AMBIG_DEBUG", "1")
1299 .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1300 .build();
1301 let s: Ambig = config.get("ambig").unwrap();
1302 assert_eq!(s.debug_assertions, Some(true));
1303 assert_eq!(s.debug, Some(1));
1304 }
1305
1306 #[cargo_test]
overlapping_env_with_defaults_errors_out()1307 fn overlapping_env_with_defaults_errors_out() {
1308 // Issue where one key is a prefix of another.
1309 // This is a limitation of mapping environment variables on to a hierarchy.
1310 // Check that we error out when we hit ambiguity in this way, rather than
1311 // the more-surprising defaulting through.
1312 // If, in the future, we can handle this more correctly, feel free to delete
1313 // this test.
1314 #[derive(Deserialize, Default)]
1315 #[serde(default, rename_all = "kebab-case")]
1316 struct Ambig {
1317 debug: u32,
1318 debug_assertions: bool,
1319 }
1320 let config = ConfigBuilder::new()
1321 .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1322 .build();
1323 let err = config.get::<Ambig>("ambig").err().unwrap();
1324 assert!(format!("{}", err).contains("missing config key `ambig.debug`"));
1325
1326 let config = ConfigBuilder::new().env("CARGO_AMBIG_DEBUG", "5").build();
1327 let s: Ambig = config.get("ambig").unwrap();
1328 assert_eq!(s.debug_assertions, bool::default());
1329 assert_eq!(s.debug, 5);
1330
1331 let config = ConfigBuilder::new()
1332 .env("CARGO_AMBIG_DEBUG", "1")
1333 .env("CARGO_AMBIG_DEBUG_ASSERTIONS", "true")
1334 .build();
1335 let s: Ambig = config.get("ambig").unwrap();
1336 assert_eq!(s.debug_assertions, true);
1337 assert_eq!(s.debug, 1);
1338 }
1339
1340 #[cargo_test]
struct_with_overlapping_inner_struct_and_defaults()1341 fn struct_with_overlapping_inner_struct_and_defaults() {
1342 // Struct with serde defaults.
1343 // Check that can be defined with environment variable.
1344 #[derive(Deserialize, Default)]
1345 #[serde(default)]
1346 struct Inner {
1347 value: i32,
1348 }
1349
1350 // Containing struct with a prefix of inner
1351 //
1352 // This is a limitation of mapping environment variables on to a hierarchy.
1353 // Check that we error out when we hit ambiguity in this way, rather than
1354 // the more-surprising defaulting through.
1355 // If, in the future, we can handle this more correctly, feel free to delete
1356 // this case.
1357 #[derive(Deserialize, Default)]
1358 #[serde(default)]
1359 struct PrefixContainer {
1360 inn: bool,
1361 inner: Inner,
1362 }
1363 let config = ConfigBuilder::new()
1364 .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12")
1365 .build();
1366 let err = config
1367 .get::<PrefixContainer>("prefixcontainer")
1368 .err()
1369 .unwrap();
1370 assert!(format!("{}", err).contains("missing config key `prefixcontainer.inn`"));
1371 let config = ConfigBuilder::new()
1372 .env("CARGO_PREFIXCONTAINER_INNER_VALUE", "12")
1373 .env("CARGO_PREFIXCONTAINER_INN", "true")
1374 .build();
1375 let f: PrefixContainer = config.get("prefixcontainer").unwrap();
1376 assert_eq!(f.inner.value, 12);
1377 assert_eq!(f.inn, true);
1378
1379 // Containing struct where the inner value's field is a prefix of another
1380 //
1381 // This is a limitation of mapping environment variables on to a hierarchy.
1382 // Check that we error out when we hit ambiguity in this way, rather than
1383 // the more-surprising defaulting through.
1384 // If, in the future, we can handle this more correctly, feel free to delete
1385 // this case.
1386 #[derive(Deserialize, Default)]
1387 #[serde(default)]
1388 struct InversePrefixContainer {
1389 inner_field: bool,
1390 inner: Inner,
1391 }
1392 let config = ConfigBuilder::new()
1393 .env("CARGO_INVERSEPREFIXCONTAINER_INNER_VALUE", "12")
1394 .build();
1395 let f: InversePrefixContainer = config.get("inverseprefixcontainer").unwrap();
1396 assert_eq!(f.inner_field, bool::default());
1397 assert_eq!(f.inner.value, 12);
1398 }
1399
1400 #[cargo_test]
string_list_tricky_env()1401 fn string_list_tricky_env() {
1402 // Make sure StringList handles typed env values.
1403 let config = ConfigBuilder::new()
1404 .env("CARGO_KEY1", "123")
1405 .env("CARGO_KEY2", "true")
1406 .env("CARGO_KEY3", "1 2")
1407 .build();
1408 let x = config.get::<StringList>("key1").unwrap();
1409 assert_eq!(x.as_slice(), &["123".to_string()]);
1410 let x = config.get::<StringList>("key2").unwrap();
1411 assert_eq!(x.as_slice(), &["true".to_string()]);
1412 let x = config.get::<StringList>("key3").unwrap();
1413 assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
1414 }
1415
1416 #[cargo_test]
string_list_wrong_type()1417 fn string_list_wrong_type() {
1418 // What happens if StringList is given then wrong type.
1419 write_config("some_list = 123");
1420 let config = ConfigBuilder::new().build();
1421 assert_error(
1422 config.get::<StringList>("some_list").unwrap_err(),
1423 "\
1424 invalid configuration for key `some_list`
1425 expected a string or array of strings, but found a integer for `some_list` in [..]/.cargo/config",
1426 );
1427
1428 write_config("some_list = \"1 2\"");
1429 let config = ConfigBuilder::new().build();
1430 let x = config.get::<StringList>("some_list").unwrap();
1431 assert_eq!(x.as_slice(), &["1".to_string(), "2".to_string()]);
1432 }
1433
1434 #[cargo_test]
string_list_advanced_env()1435 fn string_list_advanced_env() {
1436 // StringList with advanced env.
1437 let config = ConfigBuilder::new()
1438 .unstable_flag("advanced-env")
1439 .env("CARGO_KEY1", "[]")
1440 .env("CARGO_KEY2", "['1 2', '3']")
1441 .env("CARGO_KEY3", "[123]")
1442 .build();
1443 let x = config.get::<StringList>("key1").unwrap();
1444 assert_eq!(x.as_slice(), &[] as &[String]);
1445 let x = config.get::<StringList>("key2").unwrap();
1446 assert_eq!(x.as_slice(), &["1 2".to_string(), "3".to_string()]);
1447 assert_error(
1448 config.get::<StringList>("key3").unwrap_err(),
1449 "error in environment variable `CARGO_KEY3`: expected string, found integer",
1450 );
1451 }
1452
1453 #[cargo_test]
parse_strip_with_string()1454 fn parse_strip_with_string() {
1455 write_config(
1456 "\
1457 [profile.release]
1458 strip = 'debuginfo'
1459 ",
1460 );
1461
1462 let config = new_config();
1463
1464 let p: toml::TomlProfile = config.get("profile.release").unwrap();
1465 let strip = p.strip.unwrap();
1466 assert_eq!(strip, toml::StringOrBool::String("debuginfo".to_string()));
1467 }
1468
1469 #[cargo_test]
cargo_target_empty_cfg()1470 fn cargo_target_empty_cfg() {
1471 write_config(
1472 "\
1473 [build]
1474 target-dir = ''
1475 ",
1476 );
1477
1478 let config = new_config();
1479
1480 assert_error(
1481 config.target_dir().unwrap_err(),
1482 "the target directory is set to an empty string in [..]/.cargo/config",
1483 );
1484 }
1485
1486 #[cargo_test]
cargo_target_empty_env()1487 fn cargo_target_empty_env() {
1488 let project = project().build();
1489
1490 project.cargo("build")
1491 .env("CARGO_TARGET_DIR", "")
1492 .with_stderr("error: the target directory is set to an empty string in the `CARGO_TARGET_DIR` environment variable")
1493 .with_status(101)
1494 .run()
1495 }
1496