Repository: xd009642/tarpaulin (rust)

Issue:
`skip-clean` is not being picked up from tarpaulin.toml
**Describe the bug**

Adding `skip-clean` to the config yaml has no impact. One still needs to supply it with the command line, so I guess not really a high priority. 

**To Reproduce**

A simple rust project with the following as in the root `tarpaulin.toml`:

```yaml
[all]
exclude-files=[
    "bench/*",
    "build.rs",
    "src/lib.rs",
    "test/**/*",
    "mod.rs"
]
fail-under=70
no-fail-fast=true
skip-clean=true
```

With a simple GitHub actions workflow:

```yaml
name: CI

on:
  pull_request:
    branches:
      - main

jobs:
  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run cargo-tarpaulin
        uses: actions-rs/tarpaulin@v0.1
        with:
          version: '0.18.5'
          args: '--output-dir ./dev/coverage'
          out-type: Html

      - name: Upload code coverage results as artifacts
        uses: actions/upload-artifact@v1
        with:
          name: code-coverage-report
          path: dev/coverage/tarpaulin-report.html
```

The `fail-under` is picking up, so I know the config is being recognized. This will still clean before. You'll need to pass `--skip-clean` to the `jobs.coverage.step.[1].with.args` in the GitHub actions yaml for it to not clean.

**Expected behavior**
I expect that 

Pull request:
Implement skip-clean in config file Fixes #922
This adds skip-clean to the config file and yet again makes config stuff
a smidge more complicated with prioritisation of antonyms. However, this
seems an acceptable level of complexity

<!--- When contributing to tarpaulin all contributions should branch off the -->
<!--- develop branch as master is only used for releases. --> 

<!--- Check CONTRIBUTING.md for more information-->

<!--- Please describe the changes in the PR and them motivation below -->


Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 9c60e6f..2656d28 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -72,8 +72,7 @@ jobs:
         version:
           - nightly
         target:
-          - x86_64-pc-windows-gnu
-          - x86_64-pc-windows-msvc
+          - x86_64-apple-darwin	
       fail-fast: false
     steps:
       - uses: actions/checkout@v2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a40a8f2..de5c596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ file.
 ### Added
 - Added support for `RUST_TEST_THREADS` to specify test threads instead of --test-threads
 
+### Changed
+- Support skip-clean in config files and implement prioritisation in merges
+
 ## [0.19.0] 2021-12-27
 ### Added
 - Check build script output from cargo build and use it to set `LD_LIBRARY_PATH` to match cargo test behaviour
diff --git a/src/cargo.rs b/src/cargo.rs
index d12fad7..f00c94d 100644
--- a/src/cargo.rs
+++ b/src/cargo.rs
@@ -198,7 +198,7 @@ lazy_static! {
 
 pub fn get_tests(config: &Config) -> Result<Vec<TestBinary>, RunError> {
     let mut result = vec![];
-    if config.force_clean {
+    if config.force_clean() {
         let cleanup_dir = if config.release {
             config.target_dir().join("release")
         } else {
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 1e458f6..e25671d 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -46,7 +46,10 @@ pub struct Config {
     pub ignore_panics: bool,
     /// Flag to add a clean step when preparing the target project
     #[serde(rename = "force-clean")]
-    pub force_clean: bool,
+    force_clean: bool,
+    /// The opposite of --force-clean
+    #[serde(rename = "skip-clean")]
+    skip_clean: bool,
     #[serde(rename = "all-targets")]
     pub all_targets: bool,
     /// Verbose flag for printing information to the user
@@ -193,6 +196,7 @@ impl Default for Config {
             ignore_tests: false,
             ignore_panics: false,
             force_clean: true,
+            skip_clean: false,
             verbose: false,
             debug: false,
             follow_exec: false,
@@ -283,6 +287,7 @@ impl<'a> From<&'a ArgMatches<'a>> for ConfigWrapper {
             ignore_tests: args.is_present("ignore-tests"),
             ignore_panics: args.is_present("ignore-panics"),
             force_clean,
+            skip_clean: !force_clean,
             no_fail_fast: args.is_present("no-fail-fast"),
             all_targets: args.is_present("all-targets"),
             follow_exec: args.is_present("follow-exec"),
@@ -367,6 +372,17 @@ impl Config {
         }
     }
 
+    pub fn set_clean(&mut self, clean: bool) {
+        self.force_clean = clean;
+        self.skip_clean = !clean;
+    }
+
+    pub fn force_clean(&self) -> bool {
+        // default is force clean true skip clean false. So if one isn't default we pick that one
+        // as precedence.
+        self.force_clean && !self.skip_clean
+    }
+
     pub fn target_dir(&self) -> PathBuf {
         if let Some(s) = &self.target_dir {
             s.clone()
@@ -550,9 +566,21 @@ impl Config {
         self.frozen |= other.frozen;
         self.locked |= other.locked;
         self.force_clean |= other.force_clean;
+        self.skip_clean |= other.skip_clean;
         self.ignore_tests |= other.ignore_tests;
         self.no_fail_fast |= other.no_fail_fast;
 
+        // The two flags now don't agree, if one is set to non-default then prioritise that
+        match (self.force_clean, self.skip_clean) {
+            (true, false) | (false, true) => {}
+            (false, _) => {
+                self.skip_clean = true;
+            }
+            (_, true) => {
+                self.force_clean = false;
+            }
+        }
+
         let new_flags = match (self.rustflags.as_ref(), other.rustflags.as_ref()) {
             (Some(a), Some(b)) => Some(format!("{} {}", a, b)),
             (Some(a), None) => Some(a.clone()),


Test patch:
diff --git a/tests/doc_coverage.rs b/tests/doc_coverage.rs
index 027177f..4c468e6 100644
--- a/tests/doc_coverage.rs
+++ b/tests/doc_coverage.rs
@@ -7,7 +7,7 @@ use std::time::Duration;
 #[test]
 fn doc_test_env() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doctest_env");
     env::set_current_dir(&test_dir).unwrap();
@@ -27,7 +27,7 @@ fn doc_test_env() {
 fn doc_test_coverage() {
     let mut config = Config::default();
     config.verbose = true;
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doc_coverage");
     env::set_current_dir(&test_dir).unwrap();
@@ -54,7 +54,7 @@ fn doc_test_coverage() {
 fn doc_test_panics() {
     let mut config = Config::default();
     config.verbose = true;
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doctest_should_panic");
     env::set_current_dir(&test_dir).unwrap();
@@ -81,7 +81,7 @@ fn doc_test_panics() {
 fn doc_test_panics_workspace() {
     let mut config = Config::default();
     config.verbose = true;
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doctest_workspace_should_panic");
     env::set_current_dir(&test_dir).unwrap();
@@ -108,7 +108,7 @@ fn doc_test_panics_workspace() {
 fn doc_test_compile_fail() {
     let mut config = Config::default();
     config.verbose = true;
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doctest_compile_fail_fail");
     env::set_current_dir(&test_dir).unwrap();
@@ -124,7 +124,7 @@ fn doc_test_compile_fail() {
 fn doc_test_no_run() {
     let mut config = Config::default();
     config.verbose = true;
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let test_dir = get_test_path("doctest_norun");
     env::set_current_dir(&test_dir).unwrap();
diff --git a/tests/failure_thresholds.rs b/tests/failure_thresholds.rs
index 66522cb..e94e3db 100644
--- a/tests/failure_thresholds.rs
+++ b/tests/failure_thresholds.rs
@@ -11,7 +11,7 @@ fn coverage_below_threshold() {
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
     config.fail_under = Some(100.0);
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = run(&[config]);
 
@@ -32,7 +32,7 @@ fn coverage_above_threshold() {
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
     config.fail_under = Some(30.0);
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = run(&[config]);
 
@@ -47,7 +47,7 @@ fn report_coverage_fail() {
     config.manifest = test_dir.clone();
     config.manifest.push("Cargo.toml");
     config.fail_under = Some(10.0);
-    config.force_clean = false;
+    config.set_clean(false);
 
     let mut report = Config::default();
     report.name = "report".to_string();
diff --git a/tests/failures.rs b/tests/failures.rs
index 55046f9..0812a4c 100644
--- a/tests/failures.rs
+++ b/tests/failures.rs
@@ -13,7 +13,7 @@ fn error_if_build_script_fails() {
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = launch_tarpaulin(&config, &None);
 
@@ -32,7 +32,7 @@ fn error_if_compilation_fails() {
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = launch_tarpaulin(&config, &None);
 
@@ -51,7 +51,7 @@ fn error_if_test_fails() {
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = run(&[config]);
 
@@ -71,7 +71,7 @@ fn issue_610() {
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     let result = run(&[config.clone()]);
     assert!(result.is_ok());
diff --git a/tests/line_coverage.rs b/tests/line_coverage.rs
index c0300ff..104e2c7 100644
--- a/tests/line_coverage.rs
+++ b/tests/line_coverage.rs
@@ -8,7 +8,7 @@ use std::time::Duration;
 #[test]
 fn simple_project_coverage() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("simple_project");
@@ -50,7 +50,7 @@ fn simple_project_coverage() {
 fn debug_info_0() {
     // From issue #601
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("simple_project");
     env::set_current_dir(&test_dir).unwrap();
@@ -71,7 +71,7 @@ fn debug_info_0() {
 #[test]
 fn test_threads_1() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("simple_project");
     env::set_current_dir(&test_dir).unwrap();
diff --git a/tests/mod.rs b/tests/mod.rs
index 08aa79e..92b971c 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -33,7 +33,7 @@ pub fn check_percentage_with_cli_args(minimum_coverage: f64, has_lines: bool, ar
     let mut configs = ConfigWrapper::from(&matches).0;
     let mut res = TraceMap::new();
     for config in configs.iter_mut() {
-        config.force_clean = false;
+        config.set_clean(false);
         let (t, _) = launch_tarpaulin(config, &None).unwrap();
         res.merge(&t);
     }
@@ -62,7 +62,7 @@ pub fn check_percentage_with_config(
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     // Note to contributors. If an integration test fails, uncomment this to be able to see the
     // tarpaulin logs
@@ -87,7 +87,7 @@ pub fn check_percentage_with_config(
 
 pub fn check_percentage(project_name: &str, minimum_coverage: f64, has_lines: bool) {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     check_percentage_with_config(project_name, minimum_coverage, has_lines, config);
 }
 
@@ -95,7 +95,7 @@ pub fn check_percentage(project_name: &str, minimum_coverage: f64, has_lines: bo
 fn incorrect_manifest_path() {
     let mut config = Config::default();
     config.manifest.push("__invalid_dir__");
-    config.force_clean = false;
+    config.set_clean(false);
     let launch = launch_tarpaulin(&config, &None);
     assert!(launch.is_err());
 }
@@ -104,7 +104,7 @@ fn incorrect_manifest_path() {
 fn proc_macro_link() {
     let mut config = Config::default();
     config.test_timeout = Duration::from_secs(60);
-    config.force_clean = false;
+    config.set_clean(false);
     let test_dir = get_test_path("proc_macro");
     config.manifest = test_dir.join("Cargo.toml");
     assert!(launch_tarpaulin(&config, &None).is_ok());
@@ -221,7 +221,7 @@ fn benchmark_coverage() {
     check_percentage(test, 0.0f64, true);
 
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.run_types = vec![RunType::Benchmarks];
     check_percentage_with_config(test, 1.0f64, true, config);
 }
@@ -230,7 +230,7 @@ fn benchmark_coverage() {
 fn cargo_run_coverage() {
     let mut config = Config::default();
     config.command = Mode::Build;
-    config.force_clean = false;
+    config.set_clean(false);
     check_percentage_with_config("run_coverage", 1.0f64, true, config);
 }
 
@@ -241,7 +241,7 @@ fn examples_coverage() {
 
     let mut config = Config::default();
     config.run_types = vec![RunType::Examples];
-    config.force_clean = false;
+    config.set_clean(false);
     check_percentage_with_config(test, 1.0f64, true, config.clone());
 
     config.run_types.clear();
@@ -282,7 +282,7 @@ fn cargo_home_filtering() {
 
     let mut config = Config::default();
     config.test_timeout = Duration::from_secs(60);
-    config.force_clean = false;
+    config.set_clean(false);
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("HttptestAndReqwest");
     env::set_current_dir(&test_dir).unwrap();
@@ -309,7 +309,7 @@ fn rustflags_handling() {
     check_percentage("rustflags", 1.0f64, true);
     env::set_var("RUSTFLAGS", "--cfg=foo");
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
 
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("rustflags");
@@ -331,7 +331,7 @@ fn rustflags_handling() {
 fn follow_exes_down() {
     let mut config = Config::default();
     config.follow_exec = true;
-    config.force_clean = false;
+    config.set_clean(false);
     check_percentage_with_config("follow_exe", 1.0f64, true, config);
 }
 
@@ -362,7 +362,7 @@ fn no_test_args() {
 fn dot_rs_in_dir_name() {
     // issue #857
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
 
     let restore_dir = env::current_dir().unwrap();
     let test_dir = get_test_path("not_a_file.rs");
diff --git a/tests/test_types.rs b/tests/test_types.rs
index 66c126a..4559061 100644
--- a/tests/test_types.rs
+++ b/tests/test_types.rs
@@ -9,7 +9,7 @@ fn mix_test_types() {
     // Issue 747 the clean would delete old tests leaving to only one run type effectively being
     // ran. This test covers against that mistake
     let mut config = Config::default();
-    config.force_clean = true;
+    config.set_clean(true);
     config.test_timeout = Duration::from_secs(60);
     config.run_types = vec![RunType::Tests, RunType::Examples];
     let restore_dir = env::current_dir().unwrap();
@@ -35,7 +35,7 @@ fn mix_test_types() {
 #[test]
 fn only_test_coverage() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     config.run_types = vec![RunType::Tests];
     let restore_dir = env::current_dir().unwrap();
@@ -61,7 +61,7 @@ fn only_test_coverage() {
 #[test]
 fn only_example_coverage() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     config.run_types = vec![RunType::Examples];
     let restore_dir = env::current_dir().unwrap();
@@ -88,7 +88,7 @@ fn only_example_coverage() {
 #[ignore]
 fn only_bench_coverage() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     config.run_types = vec![RunType::Benchmarks];
     let restore_dir = env::current_dir().unwrap();
@@ -115,7 +115,7 @@ fn only_bench_coverage() {
 #[cfg(feature = "nightly")]
 fn only_doctest_coverage() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
     config.test_timeout = Duration::from_secs(60);
     config.run_types = vec![RunType::Doctests];
     let restore_dir = env::current_dir().unwrap();
diff --git a/tests/workspaces.rs b/tests/workspaces.rs
index 030741d..6bb6388 100644
--- a/tests/workspaces.rs
+++ b/tests/workspaces.rs
@@ -11,7 +11,7 @@ fn package_exclude() {
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;
     config.manifest.push("Cargo.toml");
-    config.force_clean = false;
+    config.set_clean(false);
 
     config.all = true;
     let result = launch_tarpaulin(&config, &None);
@@ -41,7 +41,8 @@ fn package_exclude() {
 #[test]
 fn specify_package() {
     let mut config = Config::default();
-    config.force_clean = false;
+    config.set_clean(false);
+
     let test_dir = get_test_path("workspace");
     env::set_current_dir(&test_dir).unwrap();
     config.manifest = test_dir;