Class: GitlabQuality::TestTooling::CodeCoverage::PerTestCoverageData
- Inherits:
-
Object
- Object
- GitlabQuality::TestTooling::CodeCoverage::PerTestCoverageData
- Defined in:
- lib/gitlab_quality/test_tooling/code_coverage/per_test_coverage_data.rb
Overview
Reads per-test coverage files and produces rows for ‘PerTestCoverageTable`.
Two input formats are supported, dispatched by file extension:
‘.json`: one document with the example id as the outer key.
{
"spec/path/to/test_spec.rb[1:1]": {
"app/path/to/source.rb": [null, 1, 0, 5, 1, ...]
},
...
}
‘.ndjson`: one JSON object per line, with `id` and `files` fields.
{"id":"spec/path/to/test_spec.rb[1:1]","files":{"app/path/to/source.rb":[null,1,0,5,1]}}
{"id":"spec/path/to/test_spec.rb[1:2]","files":{"app/path/to/source.rb":[null,0,1,0,1]}}
The NDJSON form lets the producing formatter stream per-example data to disk without holding the full suite in memory. Both forms carry the same per-test data; the parser is symmetric.
Inner key (in either form) is a source file path. Inner value is a 0-indexed array of per-line hit counts. ‘null` means non-executable; `0` means executable but not hit by this test; positive integer means executed. This is the standard Ruby `Coverage` module output shape, also produced by any per-test capture that emits one line-hit array per (test, file) pair.
This class:
- strips `[<example_uid>]` from the example id to get a per-test-file key
- converts each line-hit array into a (covered_lines, total_lines) pair
- pre-aggregates within (test_file, source_file): unions covered
lines across all examples in the same test file, takes the max
total_lines
- drops rows with empty bitmaps (file imported but no line hit)
- enriches with feature_category / group / stage / section when test
metadata is provided
Constant Summary collapse
- ParseError =
Raised when a coverage artifact can’t be parsed. Wraps the underlying ‘JSON::ParserError` or `Errno::ENOENT` so callers outside the gitlab-org/gitlab CI context (where upstream `needs:` ordering guarantees well-formed artifacts) can rescue precisely without catching unrelated standard exceptions.
Class.new(StandardError)
Instance Method Summary collapse
-
#as_db_table ⇒ Array<Hash<Symbol, Object>>
Per-test-file, per-source-file rows for PerTestCoverageTable.
-
#initialize(coverage_files, tests_to_categories: {}, feature_categories_to_teams: {}, captured_sha: '') ⇒ PerTestCoverageData
constructor
A new instance of PerTestCoverageData.
Constructor Details
#initialize(coverage_files, tests_to_categories: {}, feature_categories_to_teams: {}, captured_sha: '') ⇒ PerTestCoverageData
Returns a new instance of PerTestCoverageData.
61 62 63 64 65 66 |
# File 'lib/gitlab_quality/test_tooling/code_coverage/per_test_coverage_data.rb', line 61 def initialize(coverage_files, tests_to_categories: {}, feature_categories_to_teams: {}, captured_sha: '') @coverage_files = Array(coverage_files) @tests_to_categories = tests_to_categories @feature_categories_to_teams = feature_categories_to_teams @captured_sha = captured_sha.to_s end |
Instance Method Details
#as_db_table ⇒ Array<Hash<Symbol, Object>>
Returns per-test-file, per-source-file rows for PerTestCoverageTable.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/gitlab_quality/test_tooling/code_coverage/per_test_coverage_data.rb', line 69 def as_db_table # rubocop:disable Metrics/AbcSize aggregated = {} @coverage_files.each do |path| each_example(path) do |example_id, files| test_file = extract_test_file_path(example_id) files.each do |source_file, line_hits| covered, total = parse_line_hits(line_hits) next if covered.empty? key = [test_file, source_file] if aggregated.key?(key) aggregated[key][:covered_lines].merge(covered) # max rather than picking either side: examples within the # same test file may report arrays of different lengths if # the source file was edited mid-run. Pragmatic, not exact. aggregated[key][:total_lines] = [aggregated[key][:total_lines], total].max else # dup so the merge above can never alias a Set returned by # parse_line_hits to a different key later in the loop. aggregated[key] = { covered_lines: covered.dup, total_lines: total } end end end end aggregated.map do |(test_file, source_file), agg| category = @tests_to_categories[test_file]&.first || '' team = @feature_categories_to_teams[category] || {} { test_file: test_file, source_file: source_file, covered_lines: agg[:covered_lines].to_a.sort, total_lines: agg[:total_lines], feature_category: category, group: team[:group] || '', stage: team[:stage] || '', section: team[:section] || '', captured_sha: @captured_sha } end end |