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.
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 |
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?
No. pytest/coverage.py must run first and produce a supported report; the scanner imports it.
Coverage XML and JUnit/xUnit XML come from the same test run. Are they interchangeable?
No. Coverage describes exercised code; execution data describes test outcomes/duration.
Why can a host absolute path fail inside a scanner container?
The scanner resolves paths in its own filesystem namespace. The host path may not exist or may be mounted elsewhere.
Where do imported SARIF rule definitions belong?
They remain owned by the external producer; imported SARIF rules are not native quality-profile rules.
A Python project repeats eight short lines and reports 0 duplicated blocks. Is that automatically a Sonar bug?
No. Current non-Java CPD thresholds require enough tokens and lines; inspect thresholds/indexing before concluding detection failed.
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.reportPathsandsonar.coverageReportPaths. -
Test execution parameters
— execution-report keys including
sonar.testExecutionReportPathsandsonar.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.externalIssuesReportPathsand 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.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.