cargo_tarpaulin/config/
mod.rs

1use self::parse::*;
2pub use self::types::*;
3use crate::path_utils::fix_unc_path;
4use crate::{args::ConfigArgs, cargo::supports_llvm_coverage};
5use cargo_metadata::{Metadata, MetadataCommand};
6#[cfg(feature = "coveralls")]
7use coveralls_api::CiService;
8use glob::Pattern;
9use humantime_serde::deserialize as humantime_serde;
10use indexmap::IndexMap;
11use serde::{Deserialize, Serialize};
12use std::cell::{Ref, RefCell};
13use std::collections::HashSet;
14use std::env;
15use std::fs;
16use std::io::{Error, ErrorKind};
17use std::path::{Path, PathBuf};
18use std::time::Duration;
19use tracing::{error, info, warn};
20
21mod parse;
22pub mod types;
23
24#[derive(Debug)]
25pub struct ConfigWrapper(pub Vec<Config>);
26
27/// Specifies the current configuration tarpaulin is using.
28#[derive(Debug, Clone, Deserialize, Serialize)]
29#[serde(default)]
30pub struct Config {
31    pub name: String,
32    /// Path to the projects cargo manifest
33    #[serde(rename = "manifest-path")]
34    manifest: PathBuf,
35    /// Path to a tarpaulin.toml config file
36    pub config: Option<PathBuf>,
37    /// Path to the projects cargo manifest
38    root: Option<PathBuf>,
39    /// Flag to also run tests with the ignored attribute
40    #[serde(rename = "ignored")]
41    pub run_ignored: bool,
42    /// Ignore panic macros in code.
43    #[serde(rename = "ignore-panics")]
44    pub ignore_panics: bool,
45    /// Flag to add a clean step when preparing the target project
46    #[serde(rename = "force-clean")]
47    force_clean: bool,
48    /// The opposite of --force-clean
49    #[serde(rename = "skip-clean")]
50    skip_clean: bool,
51    /// Verbose flag for printing information to the user
52    pub verbose: bool,
53    /// Debug flag for printing internal debugging information to the user
54    pub debug: bool,
55    /// Enable the event logger
56    #[serde(rename = "dump-traces")]
57    pub dump_traces: bool,
58    /// Flag to count hits in coverage
59    pub count: bool,
60    /// Flag specifying to run line coverage (default)
61    #[serde(rename = "line")]
62    pub line_coverage: bool,
63    /// Flag specifying to run branch coverage
64    #[serde(rename = "branch")]
65    pub branch_coverage: bool,
66    /// Directory to write output files
67    #[serde(rename = "output-dir")]
68    pub output_directory: Option<PathBuf>,
69    /// Key relating to coveralls service or repo
70    pub coveralls: Option<String>,
71    /// Enum representing CI tool used.
72    #[serde(rename = "ciserver", deserialize_with = "deserialize_ci_server")]
73    #[cfg(feature = "coveralls")]
74    pub ci_tool: Option<CiService>,
75    /// Only valid if coveralls option is set. If coveralls option is set,
76    /// as well as report_uri, then the report will be sent to this endpoint
77    /// instead.
78    #[serde(rename = "report-uri")]
79    pub report_uri: Option<String>,
80    /// Forward unexpected signals back to the tracee. Used for tests which
81    /// rely on signals to work.
82    #[serde(rename = "forward")]
83    pub forward_signals: bool,
84    /// Doesn't link projects with `-Clink-dead-code`
85    #[serde(rename = "no-dead-code")]
86    pub no_dead_code: bool,
87    /// Include all available features in target build
88    #[serde(rename = "all-features")]
89    pub all_features: bool,
90    /// Do not include default features in target build
91    #[serde(rename = "no-default-features")]
92    pub no_default_features: bool,
93    /// Build all packages in the workspace
94    #[serde(alias = "workspace")]
95    pub all: bool,
96    /// Duration to wait before a timeout occurs
97    #[serde(deserialize_with = "humantime_serde", rename = "timeout")]
98    pub test_timeout: Duration,
99    /// Build in release mode
100    pub release: bool,
101    /// Build the tests only don't run coverage
102    #[serde(rename = "no-run")]
103    pub no_run: bool,
104    /// Don't update `Cargo.lock`.
105    pub locked: bool,
106    /// Don't update `Cargo.lock` or any caches.
107    pub frozen: bool,
108    /// Build for the target triple.
109    pub target: Option<String>,
110    /// Directory for generated artifacts
111    #[serde(rename = "target-dir")]
112    target_dir: Option<PathBuf>,
113    /// Run tarpaulin on project without accessing the network
114    pub offline: bool,
115    /// Cargo subcommand to run. So far only test and build are supported
116    pub command: Mode,
117    /// Types of tests for tarpaulin to collect coverage on
118    #[serde(rename = "run-types")]
119    pub run_types: Vec<RunType>,
120    /// Packages to include when building the target project
121    pub packages: Vec<String>,
122    /// Packages to exclude from testing
123    pub exclude: Vec<String>,
124    /// Files to exclude from testing in their compiled form
125    #[serde(skip_deserializing, skip_serializing)]
126    excluded_files: RefCell<Vec<glob::Pattern>>,
127    /// Files to exclude from testing in uncompiled form (for serde)
128    #[serde(rename = "exclude-files")]
129    excluded_files_raw: Vec<String>,
130    /// Files to include in testing in their compiled form
131    #[serde(skip_deserializing, skip_serializing)]
132    included_files: RefCell<Vec<glob::Pattern>>,
133    /// Files to include in testing in uncompiled form (for serde)
134    #[serde(rename = "include-files")]
135    included_files_raw: Vec<String>,
136    /// Varargs to be forwarded to the test executables.
137    #[serde(rename = "args")]
138    pub varargs: Vec<String>,
139    /// Features to include in the target project build, e.g. "feature1 feature2"
140    pub features: Option<String>,
141    /// Unstable cargo features to use
142    #[serde(rename = "Z")]
143    pub unstable_features: Vec<String>,
144    /// Output files to generate
145    #[serde(rename = "out")]
146    pub generate: Vec<OutputFile>,
147    /// Names of tests to run corresponding to `cargo --test <NAME>...`
148    #[serde(rename = "test")]
149    pub test_names: HashSet<String>,
150    /// Names of binaries to run corresponding to `cargo --bin <NAME>...`
151    #[serde(rename = "bin")]
152    pub bin_names: HashSet<String>,
153    /// Names of examples to run corresponding to `cargo --example <NAME>...`
154    #[serde(rename = "example")]
155    pub example_names: HashSet<String>,
156    /// Names of benches to run corresponding to `cargo --bench <NAME>...`
157    #[serde(rename = "bench")]
158    pub bench_names: HashSet<String>,
159    /// Whether to carry on or stop when a test failure occurs
160    #[serde(rename = "no-fail-fast")]
161    pub no_fail_fast: bool,
162    /// Run with the given profile
163    pub profile: Option<String>,
164    /// returns a non-zero code if coverage is below the threshold
165    #[serde(rename = "fail-under")]
166    pub fail_under: Option<f64>,
167    /// Result of cargo_metadata ran on the crate
168    #[serde(skip_deserializing, skip_serializing)]
169    pub metadata: RefCell<Option<Metadata>>,
170    /// Don't pass --cfg=tarpaulin to the 'RUSTFLAG'
171    pub avoid_cfg_tarpaulin: bool,
172    /// Colouring of logging
173    pub color: Color,
174    /// Follow traced executables down
175    #[serde(rename = "follow-exec")]
176    pub follow_exec: bool,
177    /// Number of jobs used for building the tests
178    pub jobs: Option<usize>,
179    /// Allow test to use an implicit test threads
180    #[serde(rename = "implicit-test-threads")]
181    pub implicit_test_threads: bool,
182    /// Engine to use to collect coverage
183    engine: RefCell<TraceEngine>,
184    /// Specifying per-config rust flags
185    pub rustflags: Option<String>,
186    /// Flag to include test functions in coverage statistics
187    #[serde(rename = "include-tests")]
188    include_tests: bool,
189    #[serde(rename = "post-test-delay")]
190    /// Delay after test to collect instrumentation files (LLVM only)
191    pub post_test_delay: Option<Duration>,
192    /// Other objects that should be included to get counter values from for instrumentation
193    /// coverage
194    objects: Vec<PathBuf>,
195    /// Joined to target/tarpaulin to store profraws
196    profraw_folder: PathBuf,
197    /// Option to fail immediately after a single test fails
198    pub fail_immediately: bool,
199    /// Log to stderr instead
200    pub stderr: bool,
201}
202
203fn default_test_timeout() -> Duration {
204    Duration::from_secs(60)
205}
206
207impl Default for Config {
208    fn default() -> Config {
209        Config {
210            name: String::new(),
211            command: Mode::Test,
212            run_types: vec![],
213            manifest: default_manifest(),
214            config: None,
215            root: Default::default(),
216            run_ignored: false,
217            include_tests: false,
218            ignore_panics: false,
219            force_clean: true,
220            skip_clean: false,
221            no_dead_code: false,
222            verbose: false,
223            debug: false,
224            follow_exec: false,
225            #[cfg(not(test))]
226            dump_traces: false,
227            #[cfg(test)]
228            dump_traces: true,
229            count: false,
230            line_coverage: true,
231            branch_coverage: false,
232            generate: vec![],
233            output_directory: Default::default(),
234            coveralls: None,
235            #[cfg(feature = "coveralls")]
236            ci_tool: None,
237            report_uri: None,
238            forward_signals: true,
239            no_default_features: false,
240            features: None,
241            unstable_features: vec![],
242            all: false,
243            packages: vec![],
244            exclude: vec![],
245            excluded_files: RefCell::new(vec![]),
246            excluded_files_raw: vec![],
247            included_files: RefCell::new(vec![]),
248            included_files_raw: vec![],
249            varargs: vec![],
250            test_timeout: default_test_timeout(),
251            release: false,
252            all_features: false,
253            no_run: false,
254            locked: false,
255            frozen: false,
256            implicit_test_threads: false,
257            target: None,
258            target_dir: None,
259            offline: false,
260            test_names: HashSet::new(),
261            example_names: HashSet::new(),
262            bin_names: HashSet::new(),
263            bench_names: HashSet::new(),
264            no_fail_fast: false,
265            profile: None,
266            fail_under: None,
267            metadata: RefCell::new(None),
268            avoid_cfg_tarpaulin: false,
269            jobs: None,
270            color: Color::Auto,
271            engine: RefCell::default(),
272            rustflags: None,
273            post_test_delay: Some(Duration::from_secs(1)),
274            objects: vec![],
275            profraw_folder: PathBuf::from("profraws"),
276            fail_immediately: false,
277            stderr: false,
278        }
279    }
280}
281
282impl From<ConfigArgs> for ConfigWrapper {
283    fn from(args: ConfigArgs) -> Self {
284        info!("Creating config");
285
286        let features = args.features;
287        let features = if features.is_empty() {
288            None
289        } else {
290            Some(features.join(" "))
291        };
292
293        let force_clean = match (args.force_clean, args.skip_clean) {
294            (true, false) | (false, false) => true,
295            (false, true) => false,
296            _ => {
297                warn!("skip-clean and force-clean are incompatible. Selecting force-clean");
298                true
299            }
300        };
301
302        let args_config = Config {
303            name: String::new(),
304            manifest: process_manifest(args.manifest_path, args.root.clone()),
305            config: None,
306            root: args.root,
307            engine: RefCell::new(args.engine.unwrap_or_default()),
308            command: args.command.unwrap_or(Mode::Test),
309            verbose: args.logging.verbose || args.logging.debug,
310            debug: args.logging.debug,
311            dump_traces: args.logging.debug || args.logging.dump_traces,
312            color: args.logging.color.unwrap_or(Color::Auto),
313            run_types: args.run_types.collect(),
314            run_ignored: args.ignored,
315            include_tests: args.include_tests,
316            ignore_panics: args.ignore_panics,
317            no_dead_code: args.no_dead_code,
318            force_clean,
319            skip_clean: !force_clean,
320            no_fail_fast: args.no_fail_fast,
321            follow_exec: args.follow_exec,
322            count: args.count,
323            line_coverage: args.line || !args.branch,
324            branch_coverage: args.branch || !args.line,
325            generate: args.out,
326            output_directory: args.output_dir,
327            coveralls: args.coveralls,
328            #[cfg(feature = "coveralls")]
329            ci_tool: args.ciserver.map(|c| c.0),
330            report_uri: args.report_uri,
331            forward_signals: true, // No longer an option
332            all_features: args.all_features,
333            no_default_features: args.no_default_features,
334            features,
335            unstable_features: args.unstable_features,
336            all: args.all | args.workspace,
337            packages: args.packages,
338            exclude: args.exclude,
339            excluded_files_raw: args.exclude_files.iter().map(Pattern::to_string).collect(),
340            excluded_files: RefCell::new(args.exclude_files),
341            included_files_raw: args.include_files.iter().map(Pattern::to_string).collect(),
342            included_files: RefCell::new(args.include_files),
343            varargs: args.args,
344            test_timeout: Duration::from_secs(args.timeout.unwrap_or(60)),
345            release: args.release,
346            no_run: args.no_run,
347            locked: args.locked,
348            frozen: args.frozen,
349            target: args.target,
350            target_dir: process_target_dir(args.target_dir),
351            offline: args.offline,
352            test_names: args.test.into_iter().collect(),
353            bin_names: args.bin.into_iter().collect(),
354            bench_names: args.bench.into_iter().collect(),
355            example_names: args.example.into_iter().collect(),
356            fail_under: args.fail_under,
357            jobs: args.jobs,
358            profile: args.profile,
359            metadata: RefCell::new(None),
360            avoid_cfg_tarpaulin: args.avoid_cfg_tarpaulin,
361            implicit_test_threads: args.implicit_test_threads,
362            rustflags: args.rustflags,
363            post_test_delay: args.post_test_delay.map(Duration::from_secs),
364            objects: canonicalize_paths(args.objects),
365            profraw_folder: PathBuf::from("profraws"),
366            fail_immediately: args.fail_immediately,
367            stderr: args.logging.stderr,
368        };
369        if args.ignore_config {
370            Self(vec![args_config])
371        } else if let Some(mut path) = args.config {
372            if path.is_relative() {
373                path = env::current_dir()
374                    .unwrap()
375                    .join(path)
376                    .canonicalize()
377                    .unwrap();
378            }
379            let confs = Config::load_config_file(path);
380            Config::get_config_vec(confs, args_config)
381        } else if let Some(cfg) = args_config.check_for_configs() {
382            let confs = Config::load_config_file(cfg);
383            Config::get_config_vec(confs, args_config)
384        } else {
385            Self(vec![args_config])
386        }
387    }
388}
389
390impl Config {
391    /// This returns the engine selected for tarpaulin to run. This function will not return Auto
392    /// instead it will resolve to the best-fit `TraceEngine` for the given configuration
393    pub fn engine(&self) -> TraceEngine {
394        let engine = *self.engine.borrow();
395        match engine {
396            TraceEngine::Auto | TraceEngine::Llvm if supports_llvm_coverage() => TraceEngine::Llvm,
397            engine => {
398                if engine == TraceEngine::Llvm {
399                    error!("unable to utilise llvm coverage, due to compiler support. Falling back to Ptrace");
400                    self.engine.replace(TraceEngine::Ptrace);
401                }
402                TraceEngine::Ptrace
403            }
404        }
405    }
406
407    pub fn set_engine(&self, engine: TraceEngine) {
408        self.engine.replace(engine);
409    }
410
411    pub fn set_clean(&mut self, clean: bool) {
412        self.force_clean = clean;
413        self.skip_clean = !clean;
414    }
415
416    pub fn set_include_tests(&mut self, include: bool) {
417        self.include_tests = include;
418    }
419
420    pub fn include_tests(&self) -> bool {
421        self.include_tests
422    }
423
424    pub fn force_clean(&self) -> bool {
425        // default is force clean true skip clean false. So if one isn't default we pick that one
426        // as precedence.
427        self.force_clean && !self.skip_clean
428    }
429
430    pub fn target_dir(&self) -> PathBuf {
431        let res = if let Some(s) = &self.target_dir {
432            s.clone()
433        } else {
434            match *self.get_metadata() {
435                Some(ref meta) => PathBuf::from(meta.target_directory.clone()),
436                _ => self
437                    .manifest
438                    .parent()
439                    .map(fix_unc_path)
440                    .unwrap_or_default()
441                    .join("target"),
442            }
443        };
444        fix_unc_path(&res)
445    }
446
447    /// Get directory profraws are stored in
448    pub fn profraw_dir(&self) -> PathBuf {
449        if self.profraw_folder.is_relative() {
450            self.target_dir()
451                .join("tarpaulin")
452                .join(&self.profraw_folder)
453        } else {
454            self.profraw_folder.clone()
455        }
456    }
457
458    /// If a relative directory is joined to `$TARGET_DIR/tarpaulin/` otherwise is placed at
459    /// absolute directory location
460    pub fn set_profraw_folder(&mut self, path: PathBuf) {
461        self.profraw_folder = path;
462    }
463
464    /// Sets the target dir explicitly
465    pub fn set_target_dir(&mut self, target_dir: PathBuf) {
466        self.target_dir = Some(target_dir);
467    }
468
469    pub fn doctest_dir(&self) -> PathBuf {
470        // https://github.com/rust-lang/rust/issues/98690
471        let mut result = self.target_dir();
472        result.push("doctests");
473        result
474    }
475
476    pub(crate) fn get_metadata(&self) -> Ref<Option<Metadata>> {
477        if self.metadata.borrow().is_none() {
478            match MetadataCommand::new().manifest_path(&self.manifest).exec() {
479                Ok(meta) => {
480                    self.metadata.replace(Some(meta));
481                }
482                Err(e) => warn!("Couldn't get project metadata {}", e),
483            }
484        }
485        self.metadata.borrow()
486    }
487
488    pub fn root(&self) -> PathBuf {
489        let res = match *self.get_metadata() {
490            Some(ref meta) => PathBuf::from(meta.workspace_root.clone()),
491            _ => self
492                .manifest
493                .parent()
494                .map(Path::to_path_buf)
495                .unwrap_or_default(),
496        };
497        fix_unc_path(&res)
498    }
499
500    pub fn manifest(&self) -> PathBuf {
501        fix_unc_path(&self.manifest)
502    }
503
504    pub fn set_manifest(&mut self, manifest: PathBuf) {
505        self.manifest = manifest;
506    }
507
508    pub fn output_dir(&self) -> PathBuf {
509        let path = if let Some(ref path) = self.output_directory {
510            if path.is_relative() {
511                self.root().join(path)
512            } else {
513                path.clone()
514            }
515        } else {
516            self.root()
517        };
518        fix_unc_path(&path)
519    }
520
521    pub fn get_config_vec(file_configs: std::io::Result<Vec<Self>>, backup: Self) -> ConfigWrapper {
522        if let Ok(mut confs) = file_configs {
523            for c in &mut confs {
524                c.merge(&backup);
525            }
526            if confs.is_empty() {
527                ConfigWrapper(vec![backup])
528            } else {
529                ConfigWrapper(confs)
530            }
531        } else {
532            warn!("Failed to deserialize config file falling back to provided args");
533            ConfigWrapper(vec![backup])
534        }
535    }
536
537    /// Taking an existing config look for any relevant config files
538    pub fn check_for_configs(&self) -> Option<PathBuf> {
539        if let Some(config_file) = env::var_os("CARGO_TARPAULIN_CONFIG_FILE") {
540            Some(config_file.into())
541        } else if let Some(root) = &self.root {
542            Self::check_path_for_configs(root)
543        } else if let Some(root) = self.manifest.clone().parent() {
544            Self::check_path_for_configs(root)
545        } else {
546            None
547        }
548    }
549
550    fn check_path_for_configs<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
551        let mut path_1 = PathBuf::from(path.as_ref());
552        let mut path_2 = path_1.clone();
553        path_1.push("tarpaulin.toml");
554        path_2.push(".tarpaulin.toml");
555        if path_1.exists() {
556            Some(path_1)
557        } else if path_2.exists() {
558            Some(path_2)
559        } else {
560            None
561        }
562    }
563
564    pub fn load_config_file<P: AsRef<Path>>(file: P) -> std::io::Result<Vec<Self>> {
565        let buffer = fs::read_to_string(file.as_ref())?;
566        let mut res = Self::parse_config_toml(&buffer);
567        let parent = match file.as_ref().parent() {
568            Some(p) => p.to_path_buf(),
569            None => PathBuf::new(),
570        };
571        if let Ok(cfs) = res.as_mut() {
572            for c in cfs.iter_mut() {
573                c.config = Some(file.as_ref().to_path_buf());
574                c.manifest = make_absolute_with_parent(&c.manifest, &parent);
575                if let Some(root) = c.root.as_mut() {
576                    *root = make_absolute_with_parent(&root, &parent);
577                }
578                if let Some(root) = c.output_directory.as_mut() {
579                    *root = make_absolute_with_parent(&root, &parent);
580                }
581                if let Some(root) = c.target_dir.as_mut() {
582                    *root = make_absolute_with_parent(&root, &parent);
583                }
584            }
585        }
586        res
587    }
588
589    pub fn parse_config_toml(buffer: &str) -> std::io::Result<Vec<Self>> {
590        let mut map: IndexMap<String, Self> = toml::from_str(buffer).map_err(|e| {
591            error!("Invalid config file {}", e);
592            Error::new(ErrorKind::InvalidData, format!("{e}"))
593        })?;
594
595        let mut result = Vec::new();
596        for (name, conf) in map.iter_mut() {
597            conf.name = name.to_string();
598            result.push(conf.clone());
599        }
600        if result.is_empty() {
601            Err(Error::new(ErrorKind::InvalidData, "No config tables"))
602        } else {
603            Ok(result)
604        }
605    }
606
607    /// Given a config made from args ignoring the config file take the
608    /// relevant settings that should be carried across and move them
609    pub fn merge(&mut self, other: &Config) {
610        if other.debug {
611            self.debug = other.debug;
612            self.verbose = other.verbose;
613        } else if other.verbose {
614            self.verbose = other.verbose;
615        }
616        self.no_run |= other.no_run;
617        self.no_default_features |= other.no_default_features;
618        self.ignore_panics |= other.ignore_panics;
619        // Since true is the default
620        self.forward_signals |= other.forward_signals;
621        self.run_ignored |= other.run_ignored;
622        self.release |= other.release;
623        self.no_dead_code |= other.no_dead_code;
624        self.count |= other.count;
625        self.all_features |= other.all_features;
626        self.implicit_test_threads |= other.implicit_test_threads;
627        self.line_coverage |= other.line_coverage;
628        self.branch_coverage |= other.branch_coverage;
629        self.dump_traces |= other.dump_traces;
630        self.offline |= other.offline;
631        self.stderr |= other.stderr;
632        if self.manifest != other.manifest && self.manifest == default_manifest() {
633            self.manifest = other.manifest.clone();
634        }
635        for obj in &other.objects {
636            if !self.objects.contains(obj) {
637                self.objects.push(obj.clone());
638            }
639        }
640        self.root = Config::pick_optional_config(&self.root, &other.root);
641        self.coveralls = Config::pick_optional_config(&self.coveralls, &other.coveralls);
642
643        cfg_if::cfg_if! {
644            if #[cfg(feature = "coveralls")] {
645                self.ci_tool = Config::pick_optional_config(&self.ci_tool, &other.ci_tool);
646            }
647        }
648
649        self.report_uri = Config::pick_optional_config(&self.report_uri, &other.report_uri);
650        self.target = Config::pick_optional_config(&self.target, &other.target);
651        self.target_dir = Config::pick_optional_config(&self.target_dir, &other.target_dir);
652        self.output_directory =
653            Config::pick_optional_config(&self.output_directory, &other.output_directory);
654        self.all |= other.all;
655        self.frozen |= other.frozen;
656        self.locked |= other.locked;
657        // This is &= because force_clean true is the default. If one is false then that is
658        // non-default
659        self.force_clean &= other.force_clean;
660        self.skip_clean |= other.skip_clean;
661        self.include_tests |= other.include_tests;
662        self.no_fail_fast |= other.no_fail_fast;
663
664        let end_delay = match (self.post_test_delay, other.post_test_delay) {
665            (Some(d), None) | (None, Some(d)) => Some(d),
666            (None, None) => None,
667            (Some(a), Some(b)) => Some(a.max(b)),
668        };
669        self.post_test_delay = end_delay;
670        // The two flags now don't agree, if one is set to non-default then prioritise that
671        match (self.force_clean, self.skip_clean) {
672            (true, false) | (false, true) => {}
673            (false, _) => {
674                self.skip_clean = true;
675            }
676            (_, true) => {
677                self.force_clean = false;
678            }
679        }
680
681        let new_flags = match (self.rustflags.as_ref(), other.rustflags.as_ref()) {
682            (Some(a), Some(b)) => Some(format!("{a} {b}")),
683            (Some(a), None) => Some(a.clone()),
684            (None, Some(b)) => Some(b.clone()),
685            _ => None,
686        };
687        self.rustflags = new_flags;
688
689        if self.jobs.is_none() {
690            self.jobs = other.jobs;
691        }
692        if self.fail_under.is_none()
693            || other.fail_under.is_some() && other.fail_under.unwrap() < self.fail_under.unwrap()
694        {
695            self.fail_under = other.fail_under;
696        }
697
698        if other.test_timeout != default_test_timeout() {
699            self.test_timeout = other.test_timeout;
700        }
701
702        if self.profile.is_none() && other.profile.is_some() {
703            self.profile = other.profile.clone();
704        }
705        if other.features.is_some() {
706            if self.features.is_none() {
707                self.features = other.features.clone();
708            } else if let Some(features) = self.features.as_mut() {
709                features.push(' ');
710                features.push_str(other.features.as_ref().unwrap());
711            }
712        }
713
714        let additional_packages = other
715            .packages
716            .iter()
717            .filter(|package| !self.packages.contains(package))
718            .cloned()
719            .collect::<Vec<String>>();
720        self.packages.extend(additional_packages);
721
722        let additional_outs = other
723            .generate
724            .iter()
725            .filter(|out| !self.generate.contains(out))
726            .copied()
727            .collect::<Vec<_>>();
728        self.generate.extend(additional_outs);
729
730        let additional_excludes = other
731            .exclude
732            .iter()
733            .filter(|package| !self.exclude.contains(package))
734            .cloned()
735            .collect::<Vec<String>>();
736        self.exclude.extend(additional_excludes);
737
738        let additional_varargs = other
739            .varargs
740            .iter()
741            .filter(|package| !self.varargs.contains(package))
742            .cloned()
743            .collect::<Vec<String>>();
744        self.varargs.extend(additional_varargs);
745
746        let additional_z_opts = other
747            .unstable_features
748            .iter()
749            .filter(|package| !self.unstable_features.contains(package))
750            .cloned()
751            .collect::<Vec<String>>();
752        self.unstable_features.extend(additional_z_opts);
753
754        let exclude = &self.exclude;
755        self.packages.retain(|package| {
756            let keep = !exclude.contains(package);
757            if !keep {
758                info!("{} is in exclude list removing from packages", package);
759            }
760            keep
761        });
762
763        for test in &other.test_names {
764            self.test_names.insert(test.clone());
765        }
766        for test in &other.bin_names {
767            self.bin_names.insert(test.clone());
768        }
769        for test in &other.example_names {
770            self.example_names.insert(test.clone());
771        }
772        for test in &other.bench_names {
773            self.bench_names.insert(test.clone());
774        }
775        for ty in &other.run_types {
776            if !self.run_types.contains(ty) {
777                self.run_types.push(*ty);
778            }
779        }
780
781        if !other.excluded_files_raw.is_empty() {
782            self.excluded_files_raw
783                .extend_from_slice(&other.excluded_files_raw);
784
785            // Now invalidated the compiled regex cache so clear it
786            let mut excluded_files = self.excluded_files.borrow_mut();
787            excluded_files.clear();
788        }
789
790        if !other.included_files_raw.is_empty() {
791            self.included_files_raw
792                .extend_from_slice(&other.included_files_raw);
793
794            // Now invalidated the compiled regex cache so clear it
795            let mut included_files = self.included_files.borrow_mut();
796            included_files.clear();
797        }
798    }
799
800    pub fn pick_optional_config<T: Clone>(
801        base_config: &Option<T>,
802        override_config: &Option<T>,
803    ) -> Option<T> {
804        if override_config.is_some() {
805            override_config.clone()
806        } else {
807            base_config.clone()
808        }
809    }
810
811    pub fn objects(&self) -> &[PathBuf] {
812        &self.objects
813    }
814
815    pub fn has_named_tests(&self) -> bool {
816        !(self.test_names.is_empty()
817            && self.bin_names.is_empty()
818            && self.example_names.is_empty()
819            && self.bench_names.is_empty())
820    }
821
822    #[inline]
823    pub fn is_coveralls(&self) -> bool {
824        self.coveralls.is_some()
825    }
826
827    #[inline]
828    pub fn exclude_path(&self, path: &Path) -> bool {
829        if self.excluded_files.borrow().len() != self.excluded_files_raw.len() {
830            let mut excluded_files = self.excluded_files.borrow_mut();
831            let mut compiled = globs_from_excluded(&self.excluded_files_raw);
832            excluded_files.clear();
833            excluded_files.append(&mut compiled);
834        }
835        let project = self.strip_base_dir(path);
836
837        self.excluded_files
838            .borrow()
839            .iter()
840            .any(|x| x.matches_path(&project))
841    }
842
843    #[inline]
844    pub fn include_path(&self, path: &Path) -> bool {
845        if self.included_files.borrow().len() != self.included_files_raw.len() {
846            let mut included_files = self.included_files.borrow_mut();
847            let mut compiled = globs_from_excluded(&self.included_files_raw);
848            included_files.clear();
849            included_files.append(&mut compiled);
850        }
851
852        let project = self.strip_base_dir(path);
853
854        //if empty, then parameter not used, thus all files are included by default
855        if self.included_files.borrow().is_empty() {
856            return true;
857        }
858
859        self.included_files
860            .borrow()
861            .iter()
862            .any(|x| x.matches_path(&project))
863    }
864
865    /// returns the relative path from the base_dir
866    /// uses root if set, else env::current_dir()
867    #[inline]
868    pub fn get_base_dir(&self) -> PathBuf {
869        let root = self.root();
870        let res = if root.is_absolute() {
871            root
872        } else {
873            let base_dir = env::current_dir().unwrap();
874            if let Ok(res) = base_dir.join(root).canonicalize() {
875                res
876            } else {
877                base_dir
878            }
879        };
880        fix_unc_path(&res)
881    }
882
883    /// returns the relative path from the base_dir
884    #[inline]
885    pub fn strip_base_dir(&self, path: &Path) -> PathBuf {
886        path_relative_from(path, &self.get_base_dir()).unwrap_or_else(|| path.to_path_buf())
887    }
888
889    #[inline]
890    pub fn is_default_output_dir(&self) -> bool {
891        self.output_directory.is_none()
892    }
893}
894
895fn make_absolute_with_parent(path: impl AsRef<Path>, parent: impl AsRef<Path>) -> PathBuf {
896    let path = path.as_ref();
897    if path.is_relative() {
898        parent.as_ref().join(path)
899    } else {
900        path.to_path_buf()
901    }
902}
903
904/// Gets the relative path from one directory to another, if it exists.
905/// Credit to brson from this commit from 2015
906/// https://github.com/rust-lang/rust/pull/23283/files
907///
908pub fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
909    use std::path::Component;
910
911    if path.is_absolute() != base.is_absolute() {
912        if path.is_absolute() {
913            Some(path.to_path_buf())
914        } else {
915            None
916        }
917    } else {
918        let mut ita = path.components();
919        let mut itb = base.components();
920        let mut comps = vec![];
921
922        loop {
923            match (ita.next(), itb.next()) {
924                (None, None) => break,
925                (Some(a), None) => {
926                    comps.push(a);
927                    comps.extend(ita.by_ref());
928                    break;
929                }
930                (None, _) => comps.push(Component::ParentDir),
931                (Some(a), Some(b)) if comps.is_empty() && a == b => (),
932                (Some(a), Some(Component::CurDir)) => comps.push(a),
933                (Some(_), Some(Component::ParentDir)) => return None,
934                (Some(a), Some(_)) => {
935                    comps.push(Component::ParentDir);
936                    for _ in itb {
937                        comps.push(Component::ParentDir);
938                    }
939                    comps.push(a);
940                    comps.extend(ita.by_ref());
941                    break;
942                }
943            }
944        }
945        Some(comps.iter().map(|c| c.as_os_str()).collect())
946    }
947}
948
949#[cfg(test)]
950mod tests {
951    use clap::Parser;
952
953    use crate::args::TarpaulinCli;
954
955    use super::*;
956
957    #[test]
958    fn is_root_absolute() {
959        let args = TarpaulinCli::parse_from(vec!["tarpaulin", "-r", "."]);
960        let conf = ConfigWrapper::from(args.config).0;
961        assert!(conf[0].root().is_absolute());
962    }
963
964    #[test]
965    fn features_args() {
966        let args = TarpaulinCli::parse_from(vec![
967            "tarpaulin",
968            "--ignore-config",
969            "--features",
970            "a",
971            "--features",
972            "b",
973        ]);
974        let conf = ConfigWrapper::from(args.config).0;
975        assert_eq!(conf.len(), 1);
976        assert_eq!(conf[0].features, Some("a b".to_string()));
977
978        let args =
979            TarpaulinCli::parse_from(vec!["tarpaulin", "--ignore-config", "--features", "a b"]);
980        let conf = ConfigWrapper::from(args.config).0;
981        assert_eq!(conf.len(), 1);
982        assert_eq!(conf[0].features, Some("a b".to_string()));
983    }
984
985    #[test]
986    fn exclude_paths() {
987        let args = TarpaulinCli::parse_from(vec!["tarpaulin", "--exclude-files", "*module*"]);
988        let conf = ConfigWrapper::from(args.config).0;
989        assert_eq!(conf.len(), 1);
990        assert!(conf[0].exclude_path(Path::new("src/module/file.rs")));
991        assert!(!conf[0].exclude_path(Path::new("src/mod.rs")));
992        assert!(!conf[0].exclude_path(Path::new("unrelated.rs")));
993        assert!(conf[0].exclude_path(Path::new("module.rs")));
994    }
995
996    #[test]
997    fn exclude_paths_directory_separators() {
998        let args = TarpaulinCli::parse_from(vec![
999            "tarpaulin",
1000            "--exclude-files",
1001            "src/foo/*",
1002            "src\\bar\\*",
1003        ]);
1004        let conf = ConfigWrapper::from(args.config).0;
1005        assert_eq!(conf.len(), 1);
1006        assert!(conf[0].exclude_path(Path::new("src/foo/file.rs")));
1007        assert!(conf[0].exclude_path(Path::new("src\\bar\\file.rs")));
1008
1009        cfg_if::cfg_if! {
1010            if #[cfg(windows)] {
1011                assert!(conf[0].exclude_path(Path::new("src\\foo\\file.rs")));
1012                assert!(conf[0].exclude_path(Path::new("src/bar/file.rs")));
1013            } else {
1014                assert!(!conf[0].exclude_path(Path::new("src\\foo\\file.rs")));
1015                assert!(!conf[0].exclude_path(Path::new("src/bar/file.rs")));
1016            }
1017        }
1018    }
1019
1020    #[test]
1021    fn include_paths_directory_separators() {
1022        let args = TarpaulinCli::parse_from(vec![
1023            "tarpaulin",
1024            "--include-files",
1025            "src/foo/*",
1026            "src\\bar\\*",
1027        ]);
1028        let conf = ConfigWrapper::from(args.config).0;
1029        assert_eq!(conf.len(), 1);
1030        assert!(conf[0].include_path(Path::new("src/foo/file.rs")));
1031        assert!(conf[0].include_path(Path::new("src\\bar\\file.rs")));
1032
1033        cfg_if::cfg_if! {
1034            if #[cfg(windows)] {
1035                assert!(conf[0].include_path(Path::new("src\\foo\\file.rs")));
1036                assert!(conf[0].include_path(Path::new("src/bar/file.rs")));
1037            } else {
1038                assert!(!conf[0].include_path(Path::new("src\\foo\\file.rs")));
1039                assert!(!conf[0].include_path(Path::new("src/bar/file.rs")));
1040            }
1041        }
1042    }
1043
1044    #[test]
1045    fn no_exclusions() {
1046        let args = TarpaulinCli::parse_from(vec!["tarpaulin"]);
1047        let conf = ConfigWrapper::from(args.config).0;
1048        assert_eq!(conf.len(), 1);
1049        assert!(!conf[0].exclude_path(Path::new("src/module/file.rs")));
1050        assert!(!conf[0].exclude_path(Path::new("src/mod.rs")));
1051        assert!(!conf[0].exclude_path(Path::new("unrelated.rs")));
1052        assert!(!conf[0].exclude_path(Path::new("module.rs")));
1053
1054        assert!(conf[0].include_path(Path::new("src/module/file.rs")));
1055        assert!(conf[0].include_path(Path::new("src/mod.rs")));
1056        assert!(conf[0].include_path(Path::new("unrelated.rs")));
1057        assert!(conf[0].include_path(Path::new("module.rs")));
1058    }
1059
1060    #[test]
1061    fn exclude_exact_file() {
1062        let args = TarpaulinCli::parse_from(vec!["tarpaulin", "--exclude-files", "*/lib.rs"]);
1063        let conf = ConfigWrapper::from(args.config).0;
1064        assert_eq!(conf.len(), 1);
1065        assert!(conf[0].exclude_path(Path::new("src/lib.rs")));
1066        assert!(!conf[0].exclude_path(Path::new("src/mod.rs")));
1067        assert!(!conf[0].exclude_path(Path::new("src/notlib.rs")));
1068        assert!(!conf[0].exclude_path(Path::new("lib.rs")));
1069    }
1070
1071    #[test]
1072    fn include_exact_file() {
1073        let args = TarpaulinCli::parse_from(vec!["tarpaulin", "--include-files", "*/lib.rs"]);
1074        let conf = ConfigWrapper::from(args.config).0;
1075        assert_eq!(conf.len(), 1);
1076        assert!(conf[0].include_path(Path::new("src/lib.rs")));
1077        assert!(!conf[0].include_path(Path::new("src/mod.rs")));
1078        assert!(!conf[0].include_path(Path::new("src/notlib.rs")));
1079        assert!(!conf[0].include_path(Path::new("lib.rs")));
1080    }
1081
1082    #[test]
1083    fn relative_path_test() {
1084        cfg_if::cfg_if! {
1085            if #[cfg(windows)] {
1086                let root_base = "C:";
1087            } else {
1088                let root_base = "";
1089            }
1090        }
1091        let path_a = PathBuf::from(format!("{root_base}/this/should/form/a/rel/path/"));
1092        let path_b = PathBuf::from(format!("{root_base}/this/should/form/b/rel/path/"));
1093
1094        let rel_path = path_relative_from(&path_b, &path_a);
1095        assert!(rel_path.is_some());
1096        assert_eq!(
1097            rel_path.unwrap(),
1098            Path::new("../../../b/rel/path"),
1099            "Wrong relative path"
1100        );
1101
1102        let path_a = PathBuf::from(format!("{root_base}/this/should/not/form/a/rel/path/"));
1103        let path_b = Path::new("this/should/not/form/a/rel/path/");
1104        assert!(!path_b.is_absolute());
1105        assert!(path_a.is_absolute());
1106        let rel_path = path_relative_from(path_b, &path_a);
1107        assert_eq!(rel_path, None, "Did not expect relative path");
1108
1109        let path_a = Path::new("this/should/form/a/rel/path/");
1110        let path_b = Path::new("this/should/form/b/rel/path/");
1111
1112        let rel_path = path_relative_from(path_b, path_a);
1113        assert!(rel_path.is_some());
1114        assert_eq!(
1115            rel_path.unwrap(),
1116            Path::new("../../../b/rel/path"),
1117            "Wrong relative path"
1118        );
1119    }
1120
1121    #[test]
1122    fn config_toml() {
1123        let toml = "[global]
1124        ignored= true
1125        coveralls= \"hello\"
1126
1127        [other]
1128        run-types = [\"Doctests\", \"Tests\"]";
1129
1130        let configs = Config::parse_config_toml(toml).unwrap();
1131        assert_eq!(configs.len(), 2);
1132        for c in &configs {
1133            if c.name == "global" {
1134                assert!(c.run_ignored);
1135                assert_eq!(c.coveralls, Some("hello".to_string()));
1136            } else if c.name == "other" {
1137                assert_eq!(c.run_types, vec![RunType::Doctests, RunType::Tests]);
1138            } else {
1139                panic!("Unexpected name {}", c.name);
1140            }
1141        }
1142    }
1143
1144    #[test]
1145    fn excluded_merge() {
1146        let toml = r#"[a]
1147        exclude-files = ["target/*"]
1148        [b]
1149        exclude-files = ["foo.rs"]
1150        "#;
1151
1152        let mut configs = Config::parse_config_toml(toml).unwrap();
1153        let mut config = configs.remove(0);
1154        config.merge(&configs[0]);
1155        assert!(config.excluded_files_raw.contains(&"target/*".to_string()));
1156        assert!(config.excluded_files_raw.contains(&"foo.rs".to_string()));
1157
1158        assert_eq!(config.excluded_files_raw.len(), 2);
1159        assert_eq!(configs[0].excluded_files_raw.len(), 1);
1160    }
1161
1162    #[test]
1163    fn target_merge() {
1164        let toml_a = r#""#;
1165        let toml_b = r#"target = "wasm32-unknown-unknown""#;
1166        let toml_c = r#"target = "x86_64-linux-gnu""#;
1167
1168        let mut a: Config = toml::from_str(toml_a).unwrap();
1169        let mut b: Config = toml::from_str(toml_b).unwrap();
1170        let c: Config = toml::from_str(toml_c).unwrap();
1171
1172        assert_eq!(a.target, None);
1173        assert_eq!(b.target, Some(String::from("wasm32-unknown-unknown")));
1174        assert_eq!(c.target, Some(String::from("x86_64-linux-gnu")));
1175
1176        b.merge(&c);
1177        assert_eq!(b.target, Some(String::from("x86_64-linux-gnu")));
1178
1179        a.merge(&b);
1180        assert_eq!(a.target, Some(String::from("x86_64-linux-gnu")));
1181    }
1182
1183    #[test]
1184    fn workspace_merge() {
1185        let toml_a = r#"workspace = false"#;
1186        let toml_b = r#"workspace = true"#;
1187
1188        let mut a: Config = toml::from_str(toml_a).unwrap();
1189        let b: Config = toml::from_str(toml_b).unwrap();
1190
1191        assert!(!a.all);
1192        assert!(b.all);
1193
1194        a.merge(&b);
1195        assert!(a.all);
1196    }
1197
1198    #[test]
1199    fn packages_merge() {
1200        let toml_a = r#"packages = []"#;
1201        let toml_b = r#"packages = ["a"]"#;
1202        let toml_c = r#"packages = ["b", "a"]"#;
1203
1204        let mut a: Config = toml::from_str(toml_a).unwrap();
1205        let mut b: Config = toml::from_str(toml_b).unwrap();
1206        let c: Config = toml::from_str(toml_c).unwrap();
1207
1208        assert_eq!(a.packages, Vec::<String>::new());
1209        assert_eq!(b.packages, vec![String::from("a")]);
1210        assert_eq!(c.packages, vec![String::from("b"), String::from("a")]);
1211
1212        a.merge(&c);
1213        assert_eq!(a.packages, vec![String::from("b"), String::from("a")]);
1214
1215        b.merge(&c);
1216        assert_eq!(b.packages, vec![String::from("a"), String::from("b")]);
1217    }
1218
1219    #[test]
1220    fn exclude_packages_merge() {
1221        let toml_a = r#"packages = []
1222                        exclude = ["a"]"#;
1223        let toml_b = r#"packages = ["a"]
1224                        exclude = ["b"]"#;
1225        let toml_c = r#"packages = ["b", "a"]
1226                        exclude = ["c"]"#;
1227
1228        let mut a: Config = toml::from_str(toml_a).unwrap();
1229        let mut b: Config = toml::from_str(toml_b).unwrap();
1230        let c: Config = toml::from_str(toml_c).unwrap();
1231
1232        assert_eq!(a.exclude, vec![String::from("a")]);
1233        assert_eq!(b.exclude, vec![String::from("b")]);
1234        assert_eq!(c.exclude, vec![String::from("c")]);
1235
1236        a.merge(&c);
1237        assert_eq!(a.packages, vec![String::from("b")]);
1238        assert_eq!(a.exclude, vec![String::from("a"), String::from("c")]);
1239
1240        b.merge(&c);
1241        assert_eq!(b.packages, vec![String::from("a")]);
1242        assert_eq!(b.exclude, vec![String::from("b"), String::from("c")]);
1243    }
1244
1245    #[cfg(feature = "coveralls")]
1246    #[test]
1247    fn coveralls_merge() {
1248        let toml = r#"[a]
1249        coveralls = "abcd"
1250        report-uri = "https://example.com/report"
1251
1252        [b]
1253        coveralls = "xyz"
1254        ciserver = "coveralls-ruby"
1255        "#;
1256
1257        let configs = Config::parse_config_toml(toml).unwrap();
1258        let mut a_config = configs.iter().find(|x| x.name == "a").unwrap().clone();
1259        let b_config = configs.iter().find(|x| x.name == "b").unwrap();
1260        a_config.merge(b_config);
1261        assert_eq!(a_config.coveralls, Some("xyz".to_string()));
1262        assert_eq!(
1263            a_config.ci_tool,
1264            Some(CiService::Other("coveralls-ruby".to_string()))
1265        );
1266        assert_eq!(
1267            a_config.report_uri,
1268            Some("https://example.com/report".to_string())
1269        );
1270    }
1271
1272    #[test]
1273    fn output_dir_merge() {
1274        cfg_if::cfg_if! {
1275            if #[cfg(windows)] {
1276                let toml = r#"[has_dir]
1277                output-dir = "C:/foo"
1278
1279                [no_dir]
1280                coveralls = "xyz"
1281
1282                [other_dir]
1283                output-dir = "C:/bar"
1284                "#;
1285                let foo_dir = PathBuf::from("C:/foo");
1286                let bar_dir = PathBuf::from("C:/bar");
1287            } else {
1288                let toml = r#"[has_dir]
1289                output-dir = "/foo"
1290
1291                [no_dir]
1292                coveralls = "xyz"
1293
1294                [other_dir]
1295                output-dir = "/bar"
1296                "#;
1297                let foo_dir = PathBuf::from("/foo");
1298                let bar_dir = PathBuf::from("/bar");
1299            }
1300        }
1301
1302        let configs = Config::parse_config_toml(toml).unwrap();
1303        let has_dir = configs
1304            .iter()
1305            .find(|x| x.name == "has_dir")
1306            .unwrap()
1307            .clone();
1308        let no_dir = configs.iter().find(|x| x.name == "no_dir").unwrap().clone();
1309        let other_dir = configs
1310            .iter()
1311            .find(|x| x.name == "other_dir")
1312            .unwrap()
1313            .clone();
1314
1315        let mut merged_into_has_dir = has_dir.clone();
1316        merged_into_has_dir.merge(&no_dir);
1317        assert_eq!(merged_into_has_dir.output_dir(), foo_dir);
1318
1319        let mut merged_into_no_dir = no_dir.clone();
1320        merged_into_no_dir.merge(&has_dir);
1321        assert_eq!(merged_into_no_dir.output_dir(), foo_dir);
1322
1323        let mut neither_merged_dir = no_dir.clone();
1324        neither_merged_dir.merge(&no_dir);
1325        assert_eq!(neither_merged_dir.output_dir(), env::current_dir().unwrap());
1326
1327        let mut both_merged_dir = has_dir;
1328        both_merged_dir.merge(&other_dir);
1329        assert_eq!(both_merged_dir.output_dir(), bar_dir);
1330    }
1331
1332    #[test]
1333    fn rustflags_merge() {
1334        let toml = r#"
1335        [flag1]
1336        rustflags = "xyz"
1337
1338        [flag2]
1339        rustflags = "bar"
1340        "#;
1341
1342        let configs = Config::parse_config_toml(toml).unwrap();
1343        let flag1 = configs.iter().find(|x| x.name == "flag1").unwrap().clone();
1344        let flag2 = configs.iter().find(|x| x.name == "flag2").unwrap().clone();
1345        let noflags = Config::default();
1346
1347        let mut yes_no = flag1.clone();
1348        yes_no.merge(&noflags);
1349        assert_eq!(yes_no.rustflags, Some("xyz".to_string()));
1350
1351        let mut no_yes = noflags.clone();
1352        no_yes.merge(&flag2);
1353        assert_eq!(no_yes.rustflags, Some("bar".to_string()));
1354
1355        let mut f1_2 = flag1;
1356        f1_2.merge(&flag2);
1357        let flags = f1_2.rustflags.unwrap();
1358        let split = flags.split_ascii_whitespace().collect::<Vec<_>>();
1359        assert_eq!(split.len(), 2);
1360        assert!(split.contains(&"xyz"));
1361        assert!(split.contains(&"bar"));
1362    }
1363
1364    #[test]
1365    fn all_toml_options() {
1366        let toml = r#"[all]
1367        debug = true
1368        verbose = true
1369        ignore-panics = true
1370        count = true
1371        ignored = true
1372        force-clean = true
1373        branch = true
1374        forward = true
1375        coveralls = "hello"
1376        report-uri = "http://hello.com"
1377        no-default-features = true
1378        features = "a b"
1379        all-features = true
1380        workspace = true
1381        packages = ["pack_1"]
1382        exclude = ["pack_2"]
1383        exclude-files = ["fuzz/*"]
1384        timeout = "5s"
1385        release = true
1386        no-run = true
1387        locked = true
1388        frozen = true
1389        target = "wasm32-unknown-unknown"
1390        target-dir = "/tmp"
1391        offline = true
1392        Z = ["something-nightly"]
1393        out = ["Html"]
1394        run-types = ["Doctests"]
1395        root = "/home/rust"
1396        manifest-path = "/home/rust/foo/Cargo.toml"
1397        ciserver = "travis-ci"
1398        args = ["--nocapture"]
1399        test = ["test1", "test2"]
1400        bin = ["bin"]
1401        example = ["example"]
1402        bench = ["bench"]
1403        no-fail-fast = true
1404        profile = "Release"
1405        dump-traces = true
1406        all-targets = true
1407        "#;
1408        let mut configs = Config::parse_config_toml(toml).unwrap();
1409        assert_eq!(configs.len(), 1);
1410        let config = configs.remove(0);
1411        assert!(config.debug);
1412        assert!(config.verbose);
1413        assert!(config.dump_traces);
1414        assert!(config.ignore_panics);
1415        assert!(config.count);
1416        assert!(config.run_ignored);
1417        assert!(config.force_clean);
1418        assert!(config.branch_coverage);
1419        assert!(config.forward_signals);
1420        assert_eq!(config.coveralls, Some("hello".to_string()));
1421        assert_eq!(config.report_uri, Some("http://hello.com".to_string()));
1422        assert!(config.no_default_features);
1423        assert!(config.all_features);
1424        assert!(config.all);
1425        assert!(config.release);
1426        assert!(config.no_run);
1427        assert!(config.locked);
1428        assert!(config.frozen);
1429        assert_eq!(Some(String::from("wasm32-unknown-unknown")), config.target);
1430        assert_eq!(Some(Path::new("/tmp").to_path_buf()), config.target_dir);
1431        assert!(config.offline);
1432        assert_eq!(config.test_timeout, Duration::from_secs(5));
1433        assert_eq!(config.unstable_features.len(), 1);
1434        assert_eq!(config.unstable_features[0], "something-nightly");
1435        assert_eq!(config.varargs.len(), 1);
1436        assert_eq!(config.varargs[0], "--nocapture");
1437        assert_eq!(config.features, Some(String::from("a b")));
1438        assert_eq!(config.excluded_files_raw.len(), 1);
1439        assert_eq!(config.excluded_files_raw[0], "fuzz/*");
1440        assert_eq!(config.packages.len(), 1);
1441        assert_eq!(config.packages[0], "pack_1");
1442        assert_eq!(config.exclude.len(), 1);
1443        assert_eq!(config.exclude[0], "pack_2");
1444        assert_eq!(config.generate.len(), 1);
1445        assert_eq!(config.generate[0], OutputFile::Html);
1446        assert_eq!(config.run_types.len(), 1);
1447        assert_eq!(config.run_types[0], RunType::Doctests);
1448        assert_eq!(config.ci_tool, Some(CiService::Travis));
1449        assert_eq!(config.root, Some("/home/rust".into()));
1450        assert_eq!(config.manifest, PathBuf::from("/home/rust/foo/Cargo.toml"));
1451        assert_eq!(config.profile, Some("Release".to_string()));
1452        assert!(config.no_fail_fast);
1453        assert!(config.test_names.contains("test1"));
1454        assert!(config.test_names.contains("test2"));
1455        assert!(config.bin_names.contains("bin"));
1456        assert!(config.example_names.contains("example"));
1457        assert!(config.bench_names.contains("bench"));
1458    }
1459}