doc/development/pipelines/code_coverage.md
Coverage data powers, among others, predictive test selection, coverage analytics dashboards, and flaky test analysis.
Coverage is collected from multiple test suites using different tools.
RSpec tests collect coverage using SimpleCov, configured in spec/simplecov_env.rb.
coverage/lcov/gitlab.lcov (LCOV format)crystalball/packed-mapping.json.gz containing source file to test file mappingsJest tests collect coverage using Istanbul, configured in jest.config.base.js.
coverage-frontend/*/coverage-final.json (Istanbul JSON format)jest-test-mapping/jest-source-to-test.jsonWorkhorse tests collect coverage using Go's built-in coverage tooling.
workhorse/coverage.lcov (converted to LCOV format)workhorse-source-to-test.jsonE2E tests collect coverage from a running GitLab instance.
The coverband_formatter.rb collects backend coverage by calling the GitLab coverage API before and after each test.
tmp/coverband-coverage-*.json and tmp/test-code-paths-mapping-*.json/-/coverband/coverage_data APIFrontend E2E coverage is collected via Istanbul instrumentation of the running GitLab instance.
coverage-e2e-frontend/coverage-final.json and js-coverage-by-example-*.jsonAfter tests complete, coverage from parallel jobs and E2E tests is merged.
The merge_backend_coverage.rb script merges:
coverage/lcov/gitlab.lcovcoverage-e2e-backend/coverband-*.jsonOutput: coverage-backend/coverage.lcov
The merge_e2e_backend_test_mapping.rb script merges:
crystalball/packed-mapping.json.gz (includes both DescribedClassStrategy and CoverageStrategy mappings)e2e-test-mapping/test-code-paths-mapping-*.jsonOutput: crystalball/merged-mapping.json.gz
File paths are normalized during merge to ensure consistent relative paths (for example, /builds/gitlab-org/gitlab/app/models/user.rb becomes app/models/user.rb).
The merge_coverage_frontend.js script merges:
coverage-frontend/*/coverage-final.jsoncoverage-e2e-frontend/coverage-final.jsonOutput: coverage-frontend/lcov.info and Cobertura XML
The merge_e2e_frontend_test_mapping.js script merges Jest and E2E test mappings.
Output: jest-test-mapping/merged-source-to-test.json
Before export to ClickHouse, coverage data is enriched with metadata.
Source files are classified by type based on file path patterns:
frontend - JavaScript/Vue/CSS filesbackend - Ruby files (models, controllers, services, etc.)database - Migrations and schema filesinfrastructure - CI configuration, Dockerfilesqa - QA test filesworkhorse - Go filestooling - Tooling and RuboCop filesconfiguration - Config filesother - Files not matching any patternSee the SourceFileClassifier for the full pattern definitions.
Tests are classified as either:
spec/models/user_spec.rb for app/models/user.rb)Classification uses patterns defined in .gitlab/coverage/responsibility_patterns.yml.
Each source file is attributed to one or more feature categories based on test metadata. This enables per-team coverage tracking.
For files covered by tests with multiple feature categories, multiple coverage records are created (one per category).
Feature categories are enriched with organization hierarchy (group, stage, section) from the category_owners reference table.
Coverage data is exported to ClickHouse for analytics and dashboards.
| Table | Database | Description |
|---|---|---|
coverage_metrics | code_coverage | Per-file coverage percentages with organization data |
test_file_mappings | shared | Source file to test file relationships |
category_owners | shared | Feature category to organization hierarchy mapping |
Coverage is exported by the gitlab_quality-test_tooling gem:
test-coverage:export-rspec-and-e2e - Backend coveragetest-coverage:export-jest-and-e2e - Frontend coveragetest-coverage:export-workhorse - Workhorse coverageFor implementation details, see the gem's code coverage README.
If coverage export fails, check:
E2E coverage may contain absolute paths. The merge scripts normalize paths, but if you see path mismatches:
app/models/user.rb)./ prefix or absolute paths like /builds/gitlab-org/gitlab/Coverage percentages may be NaN if a file has zero lines to cover. These records are filtered out during export.