Repository: swellaby/rusty-hook (rust)

Issue:
Switch to tarpaulin for coverage
Up to this point `cargo kcov` has served us fairly well, but it's been causing some problems of late that aren't terribly surprising considerking that kcov's primarily focused on C/C++ code. We've notably had incorrect coverage reports on things like functions w/implicit expression returns and it's convinced me it is time to switch. 

I've already done some local and CI testing with tarpaulin and confirmed it's working well (including the cases kcov was having challenges with) and believe that's the way to go.

Pull request:
chore: prep for v0.10.0 release w/nias
## Changes
<!-- Summary of changes included in this PR -->
- [X] Update readme with new version info
- [X] Update min cli version to match latest with nias lib
- [x] Switch to tarpaulin for code coverage

## Related Issues
<!-- List any related/impacted issues -->
- Closes #74 


Patch:
diff --git a/.azure-pipelines/build/pr.linux.yml b/.azure-pipelines/build/pr.linux.yml
index cca4a1b..97d3466 100644
--- a/.azure-pipelines/build/pr.linux.yml
+++ b/.azure-pipelines/build/pr.linux.yml
@@ -15,13 +15,13 @@ steps:
   displayName: 'Install Rust'
 
 - task: DownloadPipelineArtifact@2
-  displayName: 'Download Pipeline Artifact'
+  displayName: 'Download cargo-tarpaulin'
   inputs:
     buildType: specific
     project: 'a20e510e-3abe-41cd-bd26-c0ef57b8df43'
-    artifactName: 'kcov'
+    artifactName: 'cargo-tarpaulin'
     targetPath: '$(cargoBinPath)'
-    definition: 51
+    definition: 120
 
 - task: DownloadPipelineArtifact@0
   displayName: 'Download cargo-junit executable'
@@ -31,38 +31,27 @@ steps:
     targetPath: '$(cargoBinPath)'
 
 - script: |
-    sudo chmod +rwx $(cargoBinPath)/kcov
-    sudo chmod +rwx $(cargoBinPath)/cargo-kcov
+    sudo chmod +rwx $(cargoBinPath)/cargo-tarpaulin
     sudo chmod +rwx $(cargoBinPath)/cargo-junit
     mkdir -p $(Build.SourcesDirectory)/.testresults/unit
+    mkdir -p $(Build.SourcesDirectory)/.coverage/unit
   displayName: 'Prep test tools'
 
-- script: |
+- bash: |
     set -eo pipefail
-    cargo kcov --kcov $(cargoBinPath)/kcov -o .coverage/unit --bin rusty-hook -- --exclude-pattern=$HOME/.cargo,/.cargo,_test.rs,main.rs
+    cargo tarpaulin -o Xml --exclude-files *_test.rs,main.rs
     cargo junit --name $(Build.SourcesDirectory)/.testresults/unit/junit.xml
+    cp cobertura.xml .coverage/unit/cobertura.xml
     sudo chmod +rw .coverage -R
     sudo chmod +rw .testresults -R
   displayName: 'Run tests'
 
-- script: |
-    cat .coverage/unit/kcov-merged/cobertura.xml
-    bash <(curl -s https://codecov.io/bash) -t $(codecovToken)
-  displayName: 'Publish coverage to Codecov'
-
-- task: cobertura-transform@0
-  displayName: 'Transform Cobertura Format From 3 to 4'
-  inputs:
-    cobertura3FilePath: .coverage/unit/kcov-merged/cobertura.xml
-    cobertura4FilePath: .coverage/unit/kcov-merged/cobertura4.xml
-
-
 - task: PublishCodeCoverageResults@1
   inputs:
     codeCoverageTool: 'Cobertura'
-    summaryFileLocation: '$(Build.SourcesDirectory)/.coverage/unit/kcov-merged/cobertura4.xml'
-    reportDirectory: '$(Build.SourcesDirectory)/.coverage/unit/kcov-merged'
+    summaryFileLocation: '$(Build.SourcesDirectory)/.coverage/unit/cobertura.xml'
   displayName: 'Publish coverage results'
+  condition: succeededOrFailed()
 
 - task: PublishTestResults@2
   displayName: 'publish unit test results'
@@ -71,3 +60,9 @@ steps:
     testResultsFiles: 'junit.xml'
     searchFolder: $(Build.SourcesDirectory)/.testresults/unit
     testRunTitle: 'rusty-hook::Unit Tests::Linux PR - Build $(Build.BuildId)'
+  condition: succeededOrFailed()
+
+- script: |
+    bash <(curl -s https://codecov.io/bash) -t $(codecovToken)
+  displayName: 'Publish coverage to Codecov'
+  condition: succeededOrFailed()
diff --git a/.azure-pipelines/build/pr.mac.yml b/.azure-pipelines/build/pr.mac.yml
index 3cb0e5e..94cfb07 100644
--- a/.azure-pipelines/build/pr.mac.yml
+++ b/.azure-pipelines/build/pr.mac.yml
@@ -28,7 +28,7 @@ steps:
 
 - script: |
     set -eo pipefail
-    cargo test --bin rusty-hook
+    cargo test --no-run
     cargo junit --name .testresults/unit/junit.xml
   displayName: 'Run tests'
 
diff --git a/.azure-pipelines/build/pr.windows.yml b/.azure-pipelines/build/pr.windows.yml
index 68443a4..2fcb4a4 100644
--- a/.azure-pipelines/build/pr.windows.yml
+++ b/.azure-pipelines/build/pr.windows.yml
@@ -24,7 +24,7 @@ steps:
 
 - script: |
     set -e
-    cargo test --bin rusty-hook
+    cargo test --no-run
     mkdir $(Build.SourcesDirectory)\.testresults\unit
     cargo junit --name $(Build.SourcesDirectory)\.testresults\unit\junit.xml
   displayName: 'Run tests'
diff --git a/.gitignore b/.gitignore
index 21c46bc..b570003 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@
 **/*kcov*/
 .coverage/
 .testresults/
+cobertura.xml
+tarpaulin-report.html
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d3c511e..0a755f3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,7 +6,9 @@
     ".testresults": true,
     "**/kcov*/": true,
     "**/*.gcda": true,
-    "**/*.gcno": true
+    "**/*.gcno": true,
+    "**/cobertura.xml": true,
+    "**/tarpaulin-report.html": true
   },
   "cSpell.enabled": true,
   "cSpell.ignorePaths": [
diff --git a/Cargo.toml b/Cargo.toml
index dc350df..75f601f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,8 @@ exclude = [
   "**/*.gcda",
   "**/*.gcno",
   "tests/**",
+  "cobertura.xml",
+  "tarpaulin-report.html",
 ]
 
 [dependencies]
diff --git a/README.md b/README.md
index 5228df6..89aff83 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Just add `rusty-hook` as a dev dependency in your Cargo.toml file:
 
 ```toml
 [dev-dependencies]
-rusty-hook = "^0.9.4"
+rusty-hook = "^0.10.0"
 ```
 
 ## Initialize
diff --git a/src/config.rs b/src/config.rs
index 4be585a..5c1309c 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -11,6 +11,8 @@ const DEFAULT_CONFIG_FILE_NAME: &str = ".rusty-hook.toml";
 const CONFIG_FILE_NAMES: [&str; 2] = [DEFAULT_CONFIG_FILE_NAME, "rusty-hook.toml"];
 pub const NO_CONFIG_FILE_FOUND: &str = "No config file found";
 pub const MISSING_CONFIG_KEY: &str = "Missing config key";
+pub const FATAL_ERROR_DURING_CONFIG_LOOKUP: &str =
+    "Fatal error encountered while looking for existing config";
 
 fn find_config_file<F>(root_directory_path: &str, file_exists: F) -> Result<String, String>
 where
@@ -19,16 +21,14 @@ where
     for &config_file_name in CONFIG_FILE_NAMES.iter() {
         let path = format!("{}/{}", root_directory_path, config_file_name);
         match file_exists(&path) {
+            Err(_) => {
+                return Err(String::from(FATAL_ERROR_DURING_CONFIG_LOOKUP));
+            }
             Ok(found) => {
                 if found {
                     return Ok(path);
                 }
             }
-            Err(_) => {
-                return Err(String::from(
-                    "Fatal error encountered while looking for existing config",
-                ));
-            }
         };
     }
 
@@ -63,16 +63,14 @@ where
     G: Fn(&str) -> Result<bool, ()>,
 {
     match find_config_file(root_directory_path, &file_exists) {
+        Err(_) => {
+            return Err(String::from(FATAL_ERROR_DURING_CONFIG_LOOKUP));
+        }
         Ok(path) => {
             if path != NO_CONFIG_FILE_FOUND {
                 return Ok(());
             }
         }
-        Err(_) => {
-            return Err(String::from(
-                "Fatal error encountered while looking for existing config",
-            ));
-        }
     };
 
     let config_file = if CONFIG_FILE_NAMES
diff --git a/src/hooks.rs b/src/hooks.rs
index 80f54a9..3ed4ae5 100644
--- a/src/hooks.rs
+++ b/src/hooks.rs
@@ -2,8 +2,8 @@ pub const HOOK_CREATION_ERROR: &str =
     "Fatal error encountered while trying to create git hook files";
 pub const NO_CONFIG_FILE_FOUND_ERROR_CODE: i32 = 3;
 const MINIMUM_CLI_MAJOR_VERSION: i32 = 0;
-const MINIMUM_CLI_MINOR_VERSION: i32 = 9;
-const MINIMUM_CLI_PATCH_VERSION: i32 = 2;
+const MINIMUM_CLI_MINOR_VERSION: i32 = 10;
+const MINIMUM_CLI_PATCH_VERSION: i32 = 0;
 const MINIMUM_CLI_VERSION_ALLOW_PRERELEASE: bool = false;
 
 const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -35,11 +35,8 @@ const HOOK_NAMES: [&str; 19] = [
 const CLI_SCRIPT_NAME: &str = "cli.sh";
 const SEMVER_SCRIPT_NAME: &str = "semver.sh";
 
-// For unknown reasons, kcov is reporting an uncovered line for the closing `}`
-// when using the idiomatic expression.
-#[allow(clippy::needless_return)]
 fn get_hook_file_contents() -> String {
-    return String::from(HOOK_FILE_TEMPLATE).replace("{{VERSION}}", VERSION);
+    String::from(HOOK_FILE_TEMPLATE).replace("{{VERSION}}", VERSION)
 }
 
 fn get_cli_script_file_contents() -> String {
@@ -58,11 +55,8 @@ fn get_cli_script_file_contents() -> String {
         .replace("{{MINIMUM_ALLOW_PRE}}", minimum_allow_pre)
 }
 
-// For unknown reasons, kcov is reporting an uncovered line for the closing `}`
-// when using the idiomatic expression.
-#[allow(clippy::needless_return)]
 fn get_semver_script_file_contents() -> String {
-    return String::from(HOOK_SEMVER_SCRIPT_FILE_TEMPLATE).replace("{{VERSION}}", VERSION);
+    String::from(HOOK_SEMVER_SCRIPT_FILE_TEMPLATE).replace("{{VERSION}}", VERSION)
 }
 
 fn get_file_path(root_directory_path: &str, hooks_directory: &str, file: &str) -> String {
diff --git a/src/rusty_hook.rs b/src/rusty_hook.rs
index 9e54bc4..2a71ddc 100644
--- a/src/rusty_hook.rs
+++ b/src/rusty_hook.rs
@@ -92,11 +92,11 @@ where
     log(&message, log_details);
 
     match run_command(&script, Some(&root_directory_path)) {
+        Err(stderr) => Err(stderr),
         Ok(stdout) => {
             log(&stdout, log_details);
             Ok(())
         }
-        Err(stderr) => Err(stderr),
     }
 }
 


Test patch:
diff --git a/src/hooks_test.rs b/src/hooks_test.rs
index 2062ec3..8dd17bd 100644
--- a/src/hooks_test.rs
+++ b/src/hooks_test.rs
@@ -33,8 +33,8 @@ const EXP_CLI_SCRIPT_NAME: &str = "cli.sh";
 const EXP_SEMVER_SCRIPT_NAME: &str = "semver.sh";
 
 const EXP_MINIMUM_CLI_MAJOR_VERSION: i32 = 0;
-const EXP_MINIMUM_CLI_MINOR_VERSION: i32 = 9;
-const EXP_MINIMUM_CLI_PATCH_VERSION: i32 = 2;
+const EXP_MINIMUM_CLI_MINOR_VERSION: i32 = 10;
+const EXP_MINIMUM_CLI_PATCH_VERSION: i32 = 0;
 const EXP_MINIMUM_CLI_VERSION_ALLOW_PRERELEASE: bool = false;
 
 const EXP_HOOK_CREATION_ERROR: &str =