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