Chapter 14Lesson 01~115 minutes

Coverage, Test Execution Data, Duplication, and External Analyzer Reports: Core Concepts and Mental Model

Understand SonarQube as an evidence consumer: test tools and third-party analyzers produce reports, the scanner imports them from explicit paths, duplication is computed from indexed code, and only then can server-side measures and issues be interpreted.

CoverageReportsTest executionDuplicationExternal issues

Learning objectives

  • Explain why SonarQube does not run unit tests and why producer tools must create coverage/test artifacts before the scanner starts.
  • Distinguish coverage data, test-execution data, duplication metrics, native Sonar findings, and imported external findings as separate evidence families.
  • Trace producer tool/version → report format/path → scanner import parameter/sensor → analysis report → Compute Engine → persisted measure/issue.
  • Explain path resolution relative to sonar.projectBaseDir and why local, container, and CI workspace paths must refer to the same visible artifact.
  • Describe current Python coverage/test parameters plus generic coverage, generic execution, generic external-issue, and SARIF import paths.
  • Explain why duplication is computed by SonarQube from indexed source rather than imported from pytest, coverage.py, or SARIF.

1. The practical problem: evidence exists, but SonarQube cannot use what it cannot see

Chapter 13 interpreted measures after they already existed. Chapter 14 moves one step upstream: where coverage, test execution, duplication, and third-party findings come from. The most common beginner mistake is to treat SonarQube as a test runner or omniscient artifact collector. It is neither.

Your build/test/analyzer tool runs first. It creates an artifact in a particular format at a particular path. The scanner must then be told how to import that artifact—or use a documented default—and the scanner process must be able to see that path from its own workspace. Only imported evidence becomes part of the analysis report that the server processes.

producer tool/version → report bytes + format + path → scanner import parameter/sensor → scanner analysis report → upload → Compute Engine → project measures/issues/gate

2. Keep five evidence families separate

Coverage

Which executable lines/conditions were exercised. Produced by tools such as coverage.py, JaCoCo, LCOV producers, dotCover, etc. SonarQube imports the report.

Test execution

How many tests ran, failed, errored, skipped, and how long they took. It is not the same data as coverage even if one test command creates both files.

Duplication

Detected by SonarQube from indexed source via copy/paste detection (CPD). There is normally no pytest-style duplication report to import.

Native Sonar findings

Raised by Sonar analyzers/rules active in the project quality profile. Rule configuration and lifecycle are Sonar-owned.

External findings

Produced by third-party analyzers and imported through a dedicated integration, generic format, or SARIF. Rule definition/configuration remains owned by the external tool.

3. Coverage is external test evidence, not a Sonar test run

For Python, current Community Build consumes Cobertura-style XML through sonar.python.coverage.reportPaths. The report can be created by coverage.py directly or by pytest-cov. SonarQube does not invoke pytest for you.

python -m pytest --cov=src --cov-report=xml:reports/coverage.xml
# then, later:
sonar-scanner -Dsonar.python.coverage.reportPaths=reports/coverage.xml

Those two commands modify different state. The first changes producer evidence on disk. The second reads that evidence and changes scanner report/server analysis state. If the report is generated after sonar-scanner finishes, that analysis cannot retroactively acquire it.

4. Test execution data answers different questions

Coverage asks “which code was exercised?” Test execution asks “which tests ran and what happened?” Current Python analysis can import an xUnit-style report through sonar.python.xunit.reportPath; all-language Generic test execution uses sonar.testExecutionReportPaths.

Evidence Typical producer Example Sonar parameter Typical measures
Python coverage XML coverage.py / pytest-cov sonar.python.coverage.reportPaths coverage, lines_to_cover, uncovered_lines
Python xUnit XML pytest --junitxml sonar.python.xunit.reportPath tests, failures/errors/skips/time
Generic coverage XML converted unsupported producer sonar.coverageReportPaths coverage family
Generic execution XML converted execution result sonar.testExecutionReportPaths test-execution family
Current branch/PR boundary: test-execution reports are currently supported on project branches, including main, but not on pull requests. Coverage reports have different PR support. Do not assume every imported test artifact behaves identically in PR analysis.

5. A path is part of the evidence contract

When a scanner property accepts a report path, the scanner resolves it in its own filesystem context, normally relative to sonar.projectBaseDir. A path that exists on the Docker host may not exist inside a scanner container; a path from one CI job may not exist in another job unless the artifact was passed between them.

host: C:\work\reports\coverage.xml ≠ container: /workspace/reports/coverage.xml ≠ CI job B with no downloaded artifact

Record the producer working directory, scanner project base directory, report path, file size/hash, and execution order. That is far more useful than “coverage missing.”

6. Duplication is scanner/analyzer evidence, not imported coverage evidence

Current SonarQube metric definitions calculate duplicated_lines_density = duplicated_lines / lines × 100. For non-Java languages, duplicate blocks generally require at least 100 successive duplicated tokens and—outside COBOL/ABAP—at least 10 lines. Java uses 10 successive duplicated statements.

Therefore a tiny two-line repeated helper may correctly produce 0% duplication. Diagnosis starts with indexed files, supported language, CPD logs, thresholds, and sonar.cpd.exclusions—not with searching for a missing duplication XML file.

7. External issues extend visibility without becoming native Sonar rules

Supported external analyzers can use dedicated properties such as Python Pylint, Bandit, Flake8, Mypy, or Ruff report paths. Unsupported tools can produce SonarQube generic external-issue format or SARIF 2.1.0. For generic/SARIF imports, the external rules do not appear in SonarQube quality profiles; rule configuration remains in the producer.

This distinction matters operationally. A native issue can be traced to an active Sonar rule/profile. An external issue must also retain producer/tool version, external rule identifier, and report provenance.

8. Read-only inspection before changing anything

pwd
git rev-parse HEAD
sonar-scanner --version
python --version
python -m pytest --version
python -m coverage --version || true

find reports -maxdepth 1 -type f -print -exec wc -c {} \;
sha256sum reports/* 2>/dev/null || true

grep -E '^(sonar\.(projectBaseDir|sources|tests|python\.coverage\.reportPaths|python\.xunit\.reportPath|coverageReportPaths|testExecutionReportPaths|sarifReportPaths|externalIssuesReportPaths|cpd\.exclusions|coverage\.exclusions))=' sonar-project.properties 2>/dev/null || true

Only after the artifact exists and its path/format is known should you change an import property.

Knowledge check

Does SonarQube run pytest when it calculates Python coverage?

Coverage XML and JUnit/xUnit XML come from the same test run. Are they interchangeable?

Why can a host absolute path fail inside a scanner container?

Where do imported SARIF rule definitions belong?

A Python project repeats eight short lines and reports 0 duplicated blocks. Is that automatically a Sonar bug?

Next lesson

Generate real reports, break one path, then repair it

Lesson 2 turns the mental model into an evidence-producing local workflow.

Official references and version notes

  • Test coverage overview — SonarQube consumes coverage reports generated by external build/test tools; it does not execute tests.
  • Test coverage parameters — language-specific and generic coverage import keys including sonar.python.coverage.reportPaths and sonar.coverageReportPaths.
  • Test execution parameters — execution-report keys including sonar.testExecutionReportPaths and sonar.python.xunit.reportPath; execution reports are branch-only.
  • Generic test data — generic coverage and execution XML formats when a producer has no native integration.
  • About external issues — supported external-analyzer integrations and how imported results participate in analysis.
  • External analyzer reports — analyzer-specific report paths including Python Pylint/Bandit/Flake8/Mypy/Ruff integrations.
  • Generic external-issue format — sonar.externalIssuesReportPaths and ownership semantics for third-party rules.
  • SARIF reports — SARIF 2.1.0 requirements and sonar.sarifReportPaths.
  • Scanner-only parameters — external report paths, report-task.txt, quality-gate wait behavior, and path resolution.
  • Metric definitions — coverage/test metrics and current duplication/CPD thresholds and keys.
  • SonarQube downloads — current Community Build release identity.
  • SonarScanner CLI 8.1.0.6389 — standalone scanner baseline used in the local lab.
Version and compatibility note

Rechecked 2026-09-07. Mandatory examples target SonarQube Community Build 26.9.0.129388 and SonarScanner CLI 8.1.0.6389. The Python lab uses Python 3.11+ with pytest/pytest-cov and Cobertura XML. Current Community Build accepts Python coverage through sonar.python.coverage.reportPaths, Python xUnit execution data through sonar.python.xunit.reportPath, generic coverage through sonar.coverageReportPaths, generic test execution through sonar.testExecutionReportPaths, generic external issues through sonar.externalIssuesReportPaths, and SARIF through sonar.sarifReportPaths. Paths are interpreted relative to sonar.projectBaseDir unless the property documents otherwise. Duplication is calculated from indexed source; non-Java duplication currently requires at least 100 successive duplicated tokens spread across at least 10 lines for languages other than COBOL/ABAP, while Java uses 10 successive duplicated statements. Re-check producer format compatibility and report parameters before applying these examples to another release/language.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.