Repository: taiki-e/cargo-llvm-cov
Pull request #383 (2024-08-19 09:39:41)

Add `--mcdc` flag, and handle MC/DC fields correctly in LLVM JSON
This adds a new unstable `--mcdc` flag, similar to `--branch`.

Apart from that, the LLVM JSON parser also needed changes to recognize the mcdc-related fields, as it would otherwise silently drop those.

---

Clippy now rightfully complains that `Workspace::new` takes too many boolean parameters, so there surely is some more opportunity for improvement.

Swatinem: CI is segfaulting somewhere in the LLVM bindings, probably because mcdc support is broken in the compiler right now because of llvm 19 changes:
https://github.com/rust-lang/rust/issues/126672 / https://github.com/rust-lang/rust/pull/126733

I had no problem running things locally with a slightly older nightly (presumably pre llvm-19 update).

I’m okay with holding off until this is properly fixed in the compiler.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ae3b763..22c4a6f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -66,9 +66,9 @@ jobs:
     timeout-minutes: 60
     steps:
       - uses: taiki-e/checkout-action@v1
-      - name: Install Rust
-        run: rustup toolchain add "${{ matrix.rust }}" --no-self-update && rustup default "${{ matrix.rust }}"
-      - run: rustup toolchain add nightly --no-self-update
+      - uses: taiki-e/github-actions/install-rust@main
+        with:
+          toolchain: ${{ matrix.rust }}
       - uses: taiki-e/install-action@cargo-hack
       - uses: taiki-e/install-action@cargo-minimal-versions
       - uses: taiki-e/install-action@nextest
@@ -115,9 +115,11 @@ jobs:
           pushd -- tests/fixtures/crates/cargo_config >/dev/null
           # TODO: --fail-under-branches?
           cargo llvm-cov test --branch --text --fail-under-lines 80
+          cargo llvm-cov test --mcdc --text --fail-under-lines 80
           popd >/dev/null
           pushd -- easytime >/dev/null
           cargo llvm-cov test --branch --doctests --text --fail-under-lines 30
+          cargo llvm-cov test --mcdc --doctests --text --fail-under-lines 30
           popd >/dev/null
 
           # Test minimum runnable Cargo version.
@@ -166,8 +168,7 @@ jobs:
     timeout-minutes: 60
     steps:
       - uses: taiki-e/checkout-action@v1
-      - name: Install Rust
-        run: rustup update stable --no-self-update
+      - uses: taiki-e/github-actions/install-rust@stable
       - name: Install LLVM
         run: |
           if type -P clang-"${{ matrix.llvm }}" &>/dev/null; then
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1da48ce..80855da 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -55,8 +55,7 @@ jobs:
       contents: write
     steps:
       - uses: taiki-e/checkout-action@v1
-      - name: Install Rust
-        run: rustup update stable --no-self-update
+      - uses: taiki-e/github-actions/install-rust@stable
       - uses: taiki-e/setup-cross-toolchain-action@v1
         with:
           target: ${{ matrix.target }}
diff --git a/README.md b/README.md
index 5679199..6710e6d 100644
--- a/README.md
+++ b/README.md
@@ -184,6 +184,9 @@ OPTIONS:
         --branch
             Enable branch coverage. (unstable)
 
+        --mcdc
+            Enable mcdc coverage. (unstable)
+
         --doctests
             Including doc tests (unstable)
 
diff --git a/docs/cargo-llvm-cov-run.txt b/docs/cargo-llvm-cov-run.txt
index 913929f..a9456d5 100644
--- a/docs/cargo-llvm-cov-run.txt
+++ b/docs/cargo-llvm-cov-run.txt
@@ -138,6 +138,9 @@ OPTIONS:
         --branch
             Enable branch coverage. (unstable)
 
+        --mcdc
+            Enable mcdc coverage. (unstable)
+
         --ignore-run-fail
             Run all tests regardless of failure and generate report
 
diff --git a/docs/cargo-llvm-cov.txt b/docs/cargo-llvm-cov.txt
index 38efc34..84ccecb 100644
--- a/docs/cargo-llvm-cov.txt
+++ b/docs/cargo-llvm-cov.txt
@@ -138,6 +138,9 @@ OPTIONS:
         --branch
             Enable branch coverage. (unstable)
 
+        --mcdc
+            Enable mcdc coverage. (unstable)
+
         --doctests
             Including doc tests (unstable)
 
diff --git a/src/cargo.rs b/src/cargo.rs
index 5979c31..da2c680 100644
--- a/src/cargo.rs
+++ b/src/cargo.rs
@@ -36,11 +36,13 @@ pub(crate) struct Workspace {
 }
 
 impl Workspace {
+    #[allow(clippy::fn_params_excessive_bools)]
     pub(crate) fn new(
         options: &ManifestOptions,
         target: Option<&str>,
         doctests: bool,
         branch: bool,
+        mcdc: bool,
         show_env: bool,
     ) -> Result<Self> {
         // Metadata and config
@@ -64,6 +66,11 @@ impl Workspace {
         if branch && !rustc_version.nightly {
             bail!("--branch flag requires nightly toolchain; consider using `cargo +nightly llvm-cov`")
         }
+        if mcdc && !rustc_version.nightly {
+            bail!(
+                "--mcdc flag requires nightly toolchain; consider using `cargo +nightly llvm-cov`"
+            )
+        }
         let stable_coverage =
             rustc.clone().args(["-C", "help"]).read()?.contains("instrument-coverage");
         if !stable_coverage && !rustc_version.nightly {
diff --git a/src/clean.rs b/src/clean.rs
index 4d45571..d2810aa 100644
--- a/src/clean.rs
+++ b/src/clean.rs
@@ -21,7 +21,7 @@ use crate::{
 };
 
 pub(crate) fn run(args: &mut Args) -> Result<()> {
-    let ws = Workspace::new(&args.manifest, None, false, false, false)?;
+    let ws = Workspace::new(&args.manifest, None, false, false, false, false)?;
     cli::merge_config_to_args(&ws, &mut None, &mut args.verbose, &mut args.color);
     term::set_coloring(&mut args.color);
 
diff --git a/src/cli.rs b/src/cli.rs
index 42d1158..ae088b3 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -359,6 +359,8 @@ pub(crate) struct LlvmCovOptions {
     pub(crate) skip_functions: bool,
     /// Enable branch coverage. (unstable)
     pub(crate) branch: bool,
+    /// Enable mcdc coverage. (unstable)
+    pub(crate) mcdc: bool,
 }
 
 impl LlvmCovOptions {
@@ -513,6 +515,7 @@ impl Args {
         let mut dep_coverage = None;
         let mut skip_functions = false;
         let mut branch = false;
+        let mut mcdc = false;
 
         // build options
         let mut release = false;
@@ -678,6 +681,7 @@ impl Args {
                 Long("summary-only") => parse_flag!(summary_only),
                 Long("skip-functions") => parse_flag!(skip_functions),
                 Long("branch") => parse_flag!(branch),
+                Long("mcdc") => parse_flag!(mcdc),
                 Long("output-path") => parse_opt!(output_path),
                 Long("output-dir") => parse_opt!(output_dir),
                 Long("failure-mode") => parse_opt!(failure_mode),
@@ -1204,6 +1208,7 @@ impl Args {
                 dep_coverage,
                 skip_functions,
                 branch,
+                mcdc,
             },
             show_env: ShowEnvOptions { export_prefix },
             doctests,
diff --git a/src/context.rs b/src/context.rs
index 1161df6..0efdc6a 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -51,6 +51,7 @@ impl Context {
             args.target.as_deref(),
             args.doctests,
             args.cov.branch,
+            args.cov.mcdc,
             show_env,
         )?;
         cli::merge_config_to_args(&ws, &mut args.target, &mut args.verbose, &mut args.color);
@@ -74,6 +75,12 @@ impl Context {
             if args.cov.branch {
                 warn!("--branch option is unstable");
             }
+            if args.cov.mcdc {
+                warn!("--mcdc option is unstable");
+            }
+            if args.cov.mcdc && args.cov.branch {
+                warn!("the `--mcdc` option takes precedence over `--branch`");
+            }
             if args.doc {
                 warn!("--doc option is unstable");
             } else if args.doctests {
diff --git a/src/json.rs b/src/json.rs
index eae95c4..3f4251b 100644
--- a/src/json.rs
+++ b/src/json.rs
@@ -340,6 +340,11 @@ pub struct File {
     /// This is None if report is summary-only.
     #[serde(skip_serializing_if = "Option::is_none")]
     branches: Option<Vec<serde_json::Value>>,
+    /// List of MC/DC records contained in the file
+    ///
+    /// This is None if report is summary-only.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    mcdc_records: Option<Vec<serde_json::Value>>,
     /// List of expansion records
     ///
     /// This is None if report is summary-only.
@@ -406,6 +411,8 @@ impl fmt::Debug for Segment {
 #[cfg_attr(test, serde(deny_unknown_fields))]
 struct Function {
     branches: Vec<serde_json::Value>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    mcdc_records: Option<Vec<serde_json::Value>>,
     count: u64,
     /// List of filenames that the function relates to
     filenames: Vec<String>,
@@ -500,6 +507,9 @@ impl RegionLocation {
 struct Summary {
     /// Object summarizing branch coverage
     branches: CoverageCounts,
+    /// Object summarizing mcdc coverage
+    #[serde(skip_serializing_if = "Option::is_none")]
+    mcdc: Option<CoverageCounts>,
     /// Object summarizing function coverage
     functions: CoverageCounts,
     instantiations: CoverageCounts,
diff --git a/src/main.rs b/src/main.rs
index 9b519b1..901fa88 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -194,7 +194,11 @@ fn set_env(cx: &Context, env: &mut dyn EnvTarget, IsNextest(is_nextest): IsNexte
                 flags.push("codegen-units=1");
             }
         }
-        if cx.args.cov.branch {
+        if cx.args.cov.mcdc {
+            // Tracking issue: https://github.com/rust-lang/rust/issues/124144
+            flags.push("-Z");
+            flags.push("coverage-options=mcdc");
+        } else if cx.args.cov.branch {
             // Tracking issue: https://github.com/rust-lang/rust/issues/79649
             flags.push("-Z");
             flags.push("coverage-options=branch");
diff --git a/tools/tidy.sh b/tools/tidy.sh
index 87bf5f9..a3f77c4 100755
--- a/tools/tidy.sh
+++ b/tools/tidy.sh
@@ -53,6 +53,16 @@ check_install() {
         fi
     done
 }
+retry() {
+    for i in {1..10}; do
+        if "$@"; then
+            return 0
+        else
+            sleep "${i}"
+        fi
+    done
+    "$@"
+}
 error() {
     if [[ -n "${GITHUB_ACTIONS:-}" ]]; then
         printf '::error::%s\n' "$*"
@@ -191,12 +201,14 @@ if [[ -n "$(ls_files '*.rs')" ]]; then
         rustc_version=$(rustc -vV | grep -E '^release:' | cut -d' ' -f2)
         if [[ "${rustc_version}" =~ nightly|dev ]] || ! type -P rustup >/dev/null; then
             if type -P rustup >/dev/null; then
-                rustup component add rustfmt &>/dev/null
+                retry rustup component add rustfmt &>/dev/null
             fi
             info "running \`rustfmt \$(git ls-files '*.rs')\`"
             rustfmt $(ls_files '*.rs')
         else
-            rustup component add rustfmt --toolchain nightly &>/dev/null || true
+            if type -P rustup >/dev/null; then
+                retry rustup component add rustfmt --toolchain nightly &>/dev/null
+            fi
             info "running \`rustfmt +nightly \$(git ls-files '*.rs')\`"
             rustfmt +nightly $(ls_files '*.rs')
         fi


Test patch:
diff --git a/docs/cargo-llvm-cov-test.txt b/docs/cargo-llvm-cov-test.txt
index 9d51a0e..8beffa9 100644
--- a/docs/cargo-llvm-cov-test.txt
+++ b/docs/cargo-llvm-cov-test.txt
@@ -143,6 +143,9 @@ OPTIONS:
         --branch
             Enable branch coverage. (unstable)
 
+        --mcdc
+            Enable mcdc coverage. (unstable)
+
         --doctests
             Including doc tests (unstable)
 
diff --git a/tests/auxiliary/mod.rs b/tests/auxiliary/mod.rs
index 75bdb57..6d4a757 100644
--- a/tests/auxiliary/mod.rs
+++ b/tests/auxiliary/mod.rs
@@ -237,7 +237,7 @@ pub(crate) struct AssertOutput {
 }
 
 fn line_separated(lines: &str) -> impl Iterator<Item = &'_ str> {
-    lines.split('\n').map(str::trim).filter(|line| !line.is_empty())
+    lines.lines().map(str::trim).filter(|line| !line.is_empty())
 }
 
 impl AssertOutput {
diff --git a/tests/fixtures/coverage-reports/bin_crate/bin_crate.json b/tests/fixtures/coverage-reports/bin_crate/bin_crate.json
index d974895..24aaa1d 100644
--- a/tests/fixtures/coverage-reports/bin_crate/bin_crate.json
+++ b/tests/fixtures/coverage-reports/bin_crate/bin_crate.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/bin_crate/run.json b/tests/fixtures/coverage-reports/bin_crate/run.json
index 50c1926..1bbafda 100644
--- a/tests/fixtures/coverage-reports/bin_crate/run.json
+++ b/tests/fixtures/coverage-reports/bin_crate/run.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/cargo_config/cargo_config.json b/tests/fixtures/coverage-reports/cargo_config/cargo_config.json
index 4c06100..02c65cd 100644
--- a/tests/fixtures/coverage-reports/cargo_config/cargo_config.json
+++ b/tests/fixtures/coverage-reports/cargo_config/cargo_config.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/cargo_config_toml/cargo_config_toml.json b/tests/fixtures/coverage-reports/cargo_config_toml/cargo_config_toml.json
index 4c06100..02c65cd 100644
--- a/tests/fixtures/coverage-reports/cargo_config_toml/cargo_config_toml.json
+++ b/tests/fixtures/coverage-reports/cargo_config_toml/cargo_config_toml.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/coverage_helper/coverage_helper.json b/tests/fixtures/coverage-reports/coverage_helper/coverage_helper.json
index 1ec649c..5dbd367 100644
--- a/tests/fixtures/coverage-reports/coverage_helper/coverage_helper.json
+++ b/tests/fixtures/coverage-reports/coverage_helper/coverage_helper.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/instantiations/instantiations.json b/tests/fixtures/coverage-reports/instantiations/instantiations.json
index 36f3de2..f8ede17 100644
--- a/tests/fixtures/coverage-reports/instantiations/instantiations.json
+++ b/tests/fixtures/coverage-reports/instantiations/instantiations.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/merge/clean_ws.json b/tests/fixtures/coverage-reports/merge/clean_ws.json
index 4c06100..02c65cd 100644
--- a/tests/fixtures/coverage-reports/merge/clean_ws.json
+++ b/tests/fixtures/coverage-reports/merge/clean_ws.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/merge/merge.json b/tests/fixtures/coverage-reports/merge/merge.json
index 6a18274..88ffe6a 100644
--- a/tests/fixtures/coverage-reports/merge/merge.json
+++ b/tests/fixtures/coverage-reports/merge/merge.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/no_coverage/no_cfg_coverage.json b/tests/fixtures/coverage-reports/no_coverage/no_cfg_coverage.json
index 02ce7ff..0bc4cf5 100644
--- a/tests/fixtures/coverage-reports/no_coverage/no_cfg_coverage.json
+++ b/tests/fixtures/coverage-reports/no_coverage/no_cfg_coverage.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 4,
               "covered": 4,
diff --git a/tests/fixtures/coverage-reports/no_coverage/no_coverage.json b/tests/fixtures/coverage-reports/no_coverage/no_coverage.json
index 94ffec0..e692bbf 100644
--- a/tests/fixtures/coverage-reports/no_coverage/no_coverage.json
+++ b/tests/fixtures/coverage-reports/no_coverage/no_coverage.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/no_test/link_dead_code.json b/tests/fixtures/coverage-reports/no_test/link_dead_code.json
index 8105f0c..9af8ef7 100644
--- a/tests/fixtures/coverage-reports/no_test/link_dead_code.json
+++ b/tests/fixtures/coverage-reports/no_test/link_dead_code.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/no_test/no_test.json b/tests/fixtures/coverage-reports/no_test/no_test.json
index 8105f0c..9af8ef7 100644
--- a/tests/fixtures/coverage-reports/no_test/no_test.json
+++ b/tests/fixtures/coverage-reports/no_test/no_test.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/real1/all.json b/tests/fixtures/coverage-reports/real1/all.json
index 1089a8e..0a6378b 100644
--- a/tests/fixtures/coverage-reports/real1/all.json
+++ b/tests/fixtures/coverage-reports/real1/all.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/real1/exclude.json b/tests/fixtures/coverage-reports/real1/exclude.json
index e290be7..39c803d 100644
--- a/tests/fixtures/coverage-reports/real1/exclude.json
+++ b/tests/fixtures/coverage-reports/real1/exclude.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/real1/manifest_path.json b/tests/fixtures/coverage-reports/real1/manifest_path.json
index d4cc1f9..ee33845 100644
--- a/tests/fixtures/coverage-reports/real1/manifest_path.json
+++ b/tests/fixtures/coverage-reports/real1/manifest_path.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/real1/package1.json b/tests/fixtures/coverage-reports/real1/package1.json
index d4cc1f9..ee33845 100644
--- a/tests/fixtures/coverage-reports/real1/package1.json
+++ b/tests/fixtures/coverage-reports/real1/package1.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/real1/workspace_root.json b/tests/fixtures/coverage-reports/real1/workspace_root.json
index d33add0..31c7963 100644
--- a/tests/fixtures/coverage-reports/real1/workspace_root.json
+++ b/tests/fixtures/coverage-reports/real1/workspace_root.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude-from-report1.json b/tests/fixtures/coverage-reports/virtual1/exclude-from-report1.json
index 11bfbf5..085f78f 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude-from-report1.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude-from-report1.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 3,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude-from-report2.json b/tests/fixtures/coverage-reports/virtual1/exclude-from-report2.json
index 41d7d3e..6f54fad 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude-from-report2.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude-from-report2.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude-from-test1.json b/tests/fixtures/coverage-reports/virtual1/exclude-from-test1.json
index 22fe42d..28b7578 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude-from-test1.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude-from-test1.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 2,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude-from-test2.json b/tests/fixtures/coverage-reports/virtual1/exclude-from-test2.json
index 43669d3..fda9a1d 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude-from-test2.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude-from-test2.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 1,
@@ -107,6 +125,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude1.json b/tests/fixtures/coverage-reports/virtual1/exclude1.json
index 22fe42d..28b7578 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude1.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude1.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 2,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/virtual1/exclude2.json b/tests/fixtures/coverage-reports/virtual1/exclude2.json
index 41d7d3e..6f54fad 100644
--- a/tests/fixtures/coverage-reports/virtual1/exclude2.json
+++ b/tests/fixtures/coverage-reports/virtual1/exclude2.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/package1.json b/tests/fixtures/coverage-reports/virtual1/package1.json
index e91bc6a..ea4d0dc 100644
--- a/tests/fixtures/coverage-reports/virtual1/package1.json
+++ b/tests/fixtures/coverage-reports/virtual1/package1.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 1,
@@ -107,6 +125,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/package2.json b/tests/fixtures/coverage-reports/virtual1/package2.json
index d859789..7d26ff8 100644
--- a/tests/fixtures/coverage-reports/virtual1/package2.json
+++ b/tests/fixtures/coverage-reports/virtual1/package2.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 3,
@@ -107,6 +125,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
diff --git a/tests/fixtures/coverage-reports/virtual1/package3.json b/tests/fixtures/coverage-reports/virtual1/package3.json
index 1f0159c..62cf6e2 100644
--- a/tests/fixtures/coverage-reports/virtual1/package3.json
+++ b/tests/fixtures/coverage-reports/virtual1/package3.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 2,
diff --git a/tests/fixtures/coverage-reports/virtual1/package4.json b/tests/fixtures/coverage-reports/virtual1/package4.json
index 5ecbc74..86a3c13 100644
--- a/tests/fixtures/coverage-reports/virtual1/package4.json
+++ b/tests/fixtures/coverage-reports/virtual1/package4.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/virtual1/package5.json b/tests/fixtures/coverage-reports/virtual1/package5.json
index ba7f6d6..707f697 100644
--- a/tests/fixtures/coverage-reports/virtual1/package5.json
+++ b/tests/fixtures/coverage-reports/virtual1/package5.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/virtual1/package6.json b/tests/fixtures/coverage-reports/virtual1/package6.json
index 9a7bf09..c77420f 100644
--- a/tests/fixtures/coverage-reports/virtual1/package6.json
+++ b/tests/fixtures/coverage-reports/virtual1/package6.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 0,
diff --git a/tests/fixtures/coverage-reports/virtual1/workspace_root.json b/tests/fixtures/coverage-reports/virtual1/workspace_root.json
index 2e028fd..ec43831 100644
--- a/tests/fixtures/coverage-reports/virtual1/workspace_root.json
+++ b/tests/fixtures/coverage-reports/virtual1/workspace_root.json
@@ -11,6 +11,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 2,
               "covered": 2,
@@ -43,6 +49,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,
@@ -75,6 +87,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 3,
               "covered": 3,
@@ -107,6 +125,12 @@
               "notcovered": 0,
               "percent": 0.0
             },
+            "mcdc": {
+              "count": 0,
+              "covered": 0,
+              "notcovered": 0,
+              "percent": 0.0
+            },
             "functions": {
               "count": 1,
               "covered": 1,