gitlab_quality-test_tooling 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +47 -0
- data/exe/sync-category-owners +99 -0
- data/exe/test-coverage +9 -8
- data/lib/gitlab_quality/test_tooling/code_coverage/category_owners.rb +6 -2
- data/lib/gitlab_quality/test_tooling/code_coverage/click_house/coverage_metrics_table.rb +45 -0
- data/lib/gitlab_quality/test_tooling/code_coverage/click_house/test_file_mappings_table.rb +5 -1
- data/lib/gitlab_quality/test_tooling/code_coverage/coverage_data.rb +7 -1
- data/lib/gitlab_quality/test_tooling/code_coverage/test_file_mapping_data.rb +17 -4
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a734c368f480307b04c5cb1fb3e91059e0a8c357da88eb10e42b06071b7a528
|
|
4
|
+
data.tar.gz: 032f619282b68ef105086a34e1a67cb29bf9e620e3031d30fe941ddb9e36abc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 409cff4f9f3afd710b9b8e06d15e8b90cc550d59d15237ce8cbd11b813cdee938bba0873cbefcca8df55beb817c5a0fa4507d880d7124e520e74e549c48fdbe6
|
|
7
|
+
data.tar.gz: 9d1d9bcee36c44006824cee0f9cfafe80c1a8da902975dc6f53d9c7c64286775513b7e1830dee811487c9784a6223b31d853e3252a20488ceb98e012c8e4c8b7
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -276,6 +276,53 @@ Usage: exe/feature-readiness-evaluation [options]
|
|
|
276
276
|
-h, --help Show the usage
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
+
### `exe/test-coverage`
|
|
280
|
+
|
|
281
|
+
```shell
|
|
282
|
+
Purpose: Export test coverage metrics to ClickHouse
|
|
283
|
+
Usage: exe/test-coverage [options]
|
|
284
|
+
|
|
285
|
+
Options:
|
|
286
|
+
--test-reports GLOB Glob pattern for test JSON reports (RSpec or Jest) (e.g., "reports/**/*.json")
|
|
287
|
+
--coverage-report PATH Path to the LCOV coverage report (e.g., "coverage/lcov/gitlab.lcov")
|
|
288
|
+
--test-map PATH Path to the test map file (e.g., "crystalball/packed-mapping.json.gz")
|
|
289
|
+
--clickhouse-url URL ClickHouse server URL
|
|
290
|
+
--clickhouse-database DATABASE
|
|
291
|
+
ClickHouse database name
|
|
292
|
+
--clickhouse-username USERNAME
|
|
293
|
+
ClickHouse username
|
|
294
|
+
--clickhouse-shared-database DATABASE
|
|
295
|
+
ClickHouse shared database name (default: shared)
|
|
296
|
+
--responsibility-patterns PATH
|
|
297
|
+
Path to YAML file with responsibility classification patterns
|
|
298
|
+
|
|
299
|
+
Environment variables:
|
|
300
|
+
GLCI_CLICKHOUSE_METRICS_PASSWORD ClickHouse password (required, not passed via CLI for security)
|
|
301
|
+
|
|
302
|
+
-h, --help Show the usage
|
|
303
|
+
-v, --version Show the version
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### `exe/sync-category-owners`
|
|
307
|
+
|
|
308
|
+
```shell
|
|
309
|
+
Purpose: Sync feature category ownership data from stages.yml to ClickHouse
|
|
310
|
+
Usage: exe/sync-category-owners [options]
|
|
311
|
+
|
|
312
|
+
Options:
|
|
313
|
+
--clickhouse-url URL ClickHouse server URL
|
|
314
|
+
--clickhouse-database DATABASE
|
|
315
|
+
ClickHouse database name (default: shared)
|
|
316
|
+
--clickhouse-username USERNAME
|
|
317
|
+
ClickHouse username
|
|
318
|
+
|
|
319
|
+
Environment variables:
|
|
320
|
+
GLCI_CLICKHOUSE_METRICS_PASSWORD ClickHouse password (required)
|
|
321
|
+
|
|
322
|
+
-h, --help Show the usage
|
|
323
|
+
-v, --version Show the version
|
|
324
|
+
```
|
|
325
|
+
|
|
279
326
|
## Development
|
|
280
327
|
|
|
281
328
|
### Initial setup
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
require_relative "../lib/gitlab_quality/test_tooling"
|
|
8
|
+
|
|
9
|
+
require_relative '../lib/gitlab_quality/test_tooling/code_coverage/category_owners'
|
|
10
|
+
require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/category_owners_table'
|
|
11
|
+
|
|
12
|
+
params = {}
|
|
13
|
+
required_params = [:clickhouse_url, :clickhouse_database, :clickhouse_username]
|
|
14
|
+
|
|
15
|
+
options = OptionParser.new do |opts|
|
|
16
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
|
17
|
+
|
|
18
|
+
opts.separator ""
|
|
19
|
+
opts.separator "Syncs feature category ownership data from stages.yml to ClickHouse."
|
|
20
|
+
opts.separator ""
|
|
21
|
+
opts.separator "Options:"
|
|
22
|
+
|
|
23
|
+
opts.on('--clickhouse-url URL', 'ClickHouse server URL') do |url|
|
|
24
|
+
params[:clickhouse_url] = url
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.on('--clickhouse-database DATABASE', 'ClickHouse database name (default: shared)') do |database|
|
|
28
|
+
params[:clickhouse_database] = database
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts.on('--clickhouse-username USERNAME', 'ClickHouse username') do |username|
|
|
32
|
+
params[:clickhouse_username] = username
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.separator ""
|
|
36
|
+
opts.separator "Environment variables:"
|
|
37
|
+
opts.separator " GLCI_CLICKHOUSE_METRICS_PASSWORD ClickHouse password (required)"
|
|
38
|
+
opts.separator ""
|
|
39
|
+
|
|
40
|
+
opts.on('-h', '--help', 'Show the usage') do
|
|
41
|
+
puts opts
|
|
42
|
+
exit
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
|
46
|
+
require_relative "../lib/gitlab_quality/test_tooling/version"
|
|
47
|
+
puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
|
|
48
|
+
exit
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
opts.parse(ARGV)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Default database to 'shared' if not specified
|
|
55
|
+
params[:clickhouse_database] ||= 'shared'
|
|
56
|
+
|
|
57
|
+
if params.any? && (required_params - params.keys).none?
|
|
58
|
+
clickhouse_password = ENV.fetch('GLCI_CLICKHOUSE_METRICS_PASSWORD', nil)
|
|
59
|
+
if clickhouse_password.to_s.strip.empty?
|
|
60
|
+
puts "Error: GLCI_CLICKHOUSE_METRICS_PASSWORD environment variable must be set and not empty"
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[:clickhouse_url, :clickhouse_database, :clickhouse_username].each do |param|
|
|
65
|
+
if params[param].to_s.strip.empty?
|
|
66
|
+
puts "Error: --#{param.to_s.tr('_', '-')} cannot be empty"
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
uri = URI.parse(params[:clickhouse_url])
|
|
73
|
+
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
74
|
+
puts "Error: --clickhouse-url must be a valid HTTP or HTTPS URL"
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
rescue URI::InvalidURIError
|
|
78
|
+
puts "Error: --clickhouse-url is not a valid URL format"
|
|
79
|
+
exit 1
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
category_owners = GitlabQuality::TestTooling::CodeCoverage::CategoryOwners.new
|
|
83
|
+
|
|
84
|
+
category_owners_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable.new(
|
|
85
|
+
url: params[:clickhouse_url],
|
|
86
|
+
database: params[:clickhouse_database],
|
|
87
|
+
username: params[:clickhouse_username],
|
|
88
|
+
password: clickhouse_password
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
category_owners_table.truncate
|
|
92
|
+
category_owners_table.push(category_owners.as_db_table)
|
|
93
|
+
|
|
94
|
+
puts "Successfully synced #{category_owners.as_db_table.length} feature categories to ClickHouse"
|
|
95
|
+
else
|
|
96
|
+
puts "Missing argument(s). Required arguments are: #{required_params.map { |p| "--#{p.to_s.tr('_', '-')}" }.join(', ')}"
|
|
97
|
+
puts options
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
data/exe/test-coverage
CHANGED
|
@@ -178,17 +178,18 @@ if params.any? && (required_params - params.keys).none?
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
category_owners_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable.new(**shared_clickhouse_data)
|
|
181
|
-
coverage_metrics_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CoverageMetricsTable.new(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
category_owners_table.push(category_owners.as_db_table)
|
|
186
|
-
end
|
|
187
|
-
|
|
181
|
+
coverage_metrics_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CoverageMetricsTable.new(
|
|
182
|
+
category_owners_table: category_owners_table,
|
|
183
|
+
**clickhouse_data
|
|
184
|
+
)
|
|
188
185
|
coverage_metrics_table.push(coverage_data.as_db_table)
|
|
189
186
|
|
|
190
187
|
# Export test-to-file mappings
|
|
191
|
-
test_file_mapping_data = GitlabQuality::TestTooling::CodeCoverage::TestFileMappingData.new(
|
|
188
|
+
test_file_mapping_data = GitlabQuality::TestTooling::CodeCoverage::TestFileMappingData.new(
|
|
189
|
+
test_to_sources,
|
|
190
|
+
tests_to_categories: tests_to_categories,
|
|
191
|
+
feature_categories_to_teams: category_owners.feature_categories_to_teams
|
|
192
|
+
)
|
|
192
193
|
test_file_mappings_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::TestFileMappingsTable.new(**shared_clickhouse_data)
|
|
193
194
|
test_file_mappings_table.push(test_file_mapping_data.as_db_table)
|
|
194
195
|
else
|
|
@@ -116,14 +116,18 @@ module GitlabQuality
|
|
|
116
116
|
groups = stage_data['groups'] || {}
|
|
117
117
|
next unless section
|
|
118
118
|
|
|
119
|
-
groups.each
|
|
119
|
+
groups.each do |group, group_data|
|
|
120
|
+
add_hierarchy_entry(section, stage, group, group_data['categories'])
|
|
121
|
+
add_hierarchy_entry(section, stage, group, group_data['maintained_categories'])
|
|
122
|
+
end
|
|
120
123
|
end
|
|
121
124
|
end
|
|
122
125
|
|
|
123
126
|
def add_hierarchy_entry(section, stage, group, categories)
|
|
124
127
|
@hierarchy[section] ||= {}
|
|
125
128
|
@hierarchy[section][stage] ||= {}
|
|
126
|
-
@hierarchy[section][stage][group]
|
|
129
|
+
@hierarchy[section][stage][group] ||= []
|
|
130
|
+
@hierarchy[section][stage][group].concat(Array(categories))
|
|
127
131
|
end
|
|
128
132
|
|
|
129
133
|
def populate_feature_categories_map(data, current_section = nil, current_stage = nil, current_group = nil)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'table'
|
|
4
|
+
require_relative 'category_owners_table'
|
|
4
5
|
|
|
5
6
|
module GitlabQuality
|
|
6
7
|
module TestTooling
|
|
@@ -9,8 +10,15 @@ module GitlabQuality
|
|
|
9
10
|
class CoverageMetricsTable < GitlabQuality::TestTooling::CodeCoverage::ClickHouse::Table
|
|
10
11
|
TABLE_NAME = "coverage_metrics"
|
|
11
12
|
|
|
13
|
+
def initialize(category_owners_table: nil, **args)
|
|
14
|
+
super(**args)
|
|
15
|
+
@category_owners_table = category_owners_table
|
|
16
|
+
end
|
|
17
|
+
|
|
12
18
|
private
|
|
13
19
|
|
|
20
|
+
attr_reader :category_owners_table
|
|
21
|
+
|
|
14
22
|
# @return [Boolean] True if the record is valid, false otherwise
|
|
15
23
|
def valid_record?(record)
|
|
16
24
|
valid_file?(record) &&
|
|
@@ -68,10 +76,47 @@ module GitlabQuality
|
|
|
68
76
|
is_responsible: record[:is_responsible],
|
|
69
77
|
is_dependent: record[:is_dependent],
|
|
70
78
|
category: record[:feature_category],
|
|
79
|
+
**coverage_counts(record),
|
|
80
|
+
**org_data(record[:feature_category]),
|
|
71
81
|
**ci_metadata
|
|
72
82
|
}
|
|
73
83
|
end
|
|
74
84
|
|
|
85
|
+
# @return [Hash] Raw coverage counts from the record
|
|
86
|
+
def coverage_counts(record)
|
|
87
|
+
{
|
|
88
|
+
total_lines: record[:total_lines] || 0,
|
|
89
|
+
covered_lines: record[:covered_lines] || 0,
|
|
90
|
+
total_branches: record[:total_branches] || 0,
|
|
91
|
+
covered_branches: record[:covered_branches] || 0,
|
|
92
|
+
total_functions: record[:total_functions] || 0,
|
|
93
|
+
covered_functions: record[:covered_functions] || 0
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param category [String, nil] Feature category name
|
|
98
|
+
# @return [Hash] Organization data (group, stage, section) for the category
|
|
99
|
+
def org_data(category)
|
|
100
|
+
return { group: '', stage: '', section: '' } if category.nil? || category_owners_table.nil?
|
|
101
|
+
|
|
102
|
+
@org_data_cache ||= {}
|
|
103
|
+
@org_data_cache[category] ||= fetch_org_data(category)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @param category [String] Feature category name
|
|
107
|
+
# @return [Hash] Organization data fetched from category_owners_table
|
|
108
|
+
def fetch_org_data(category)
|
|
109
|
+
owners = category_owners_table.owners(category)
|
|
110
|
+
{
|
|
111
|
+
group: owners['group'] || '',
|
|
112
|
+
stage: owners['stage'] || '',
|
|
113
|
+
section: owners['section'] || ''
|
|
114
|
+
}
|
|
115
|
+
rescue CategoryOwnersTable::MissingMappingError
|
|
116
|
+
logger.warn("#{LOG_PREFIX} No org data found for category '#{category}', using empty values")
|
|
117
|
+
{ group: '', stage: '', section: '' }
|
|
118
|
+
end
|
|
119
|
+
|
|
75
120
|
# @return [Hash] CI-related metadata
|
|
76
121
|
def ci_metadata
|
|
77
122
|
{
|
|
@@ -38,7 +38,11 @@ module GitlabQuality
|
|
|
38
38
|
timestamp: time,
|
|
39
39
|
test_file: record[:test_file],
|
|
40
40
|
source_file: record[:source_file],
|
|
41
|
-
ci_project_path: ENV.fetch('CI_PROJECT_PATH', nil)
|
|
41
|
+
ci_project_path: ENV.fetch('CI_PROJECT_PATH', nil),
|
|
42
|
+
category: record[:category] || '',
|
|
43
|
+
group: record[:group] || '',
|
|
44
|
+
stage: record[:stage] || '',
|
|
45
|
+
section: record[:section] || ''
|
|
42
46
|
}
|
|
43
47
|
end
|
|
44
48
|
end
|
|
@@ -80,7 +80,13 @@ module GitlabQuality
|
|
|
80
80
|
line_coverage: coverage_data&.dig(:percentage),
|
|
81
81
|
branch_coverage: coverage_data&.dig(:branch_percentage),
|
|
82
82
|
function_coverage: coverage_data&.dig(:function_percentage),
|
|
83
|
-
source_file_type: @source_file_types[file] || 'other'
|
|
83
|
+
source_file_type: @source_file_types[file] || 'other',
|
|
84
|
+
total_lines: coverage_data&.dig(:total_lines) || 0,
|
|
85
|
+
covered_lines: coverage_data&.dig(:covered_lines) || 0,
|
|
86
|
+
total_branches: coverage_data&.dig(:total_branches) || 0,
|
|
87
|
+
covered_branches: coverage_data&.dig(:covered_branches) || 0,
|
|
88
|
+
total_functions: coverage_data&.dig(:total_functions) || 0,
|
|
89
|
+
covered_functions: coverage_data&.dig(:covered_functions) || 0
|
|
84
90
|
}
|
|
85
91
|
end
|
|
86
92
|
|
|
@@ -6,23 +6,36 @@ module GitlabQuality
|
|
|
6
6
|
class TestFileMappingData
|
|
7
7
|
# @param [Hash<String, Array<String>>] test_to_sources Test files
|
|
8
8
|
# mapped to all source files they cover
|
|
9
|
-
|
|
9
|
+
# @param [Hash<String, Array<String>>] tests_to_categories Test files
|
|
10
|
+
# mapped to their feature categories
|
|
11
|
+
# @param [Hash<String, Hash>] feature_categories_to_teams Feature categories
|
|
12
|
+
# mapped to their org hierarchy (group, stage, section)
|
|
13
|
+
def initialize(test_to_sources, tests_to_categories: {}, feature_categories_to_teams: {})
|
|
10
14
|
@test_to_sources = test_to_sources
|
|
15
|
+
@tests_to_categories = tests_to_categories
|
|
16
|
+
@feature_categories_to_teams = feature_categories_to_teams
|
|
11
17
|
end
|
|
12
18
|
|
|
13
19
|
# @return [Array<Hash<Symbol, String>>] Mapping data formatted for database insertion
|
|
14
20
|
# @example Return value
|
|
15
21
|
# [
|
|
16
|
-
# { test_file: "spec/models/user_spec.rb", source_file: "app/models/user.rb"
|
|
17
|
-
#
|
|
22
|
+
# { test_file: "spec/models/user_spec.rb", source_file: "app/models/user.rb",
|
|
23
|
+
# category: "team_planning", group: "project_management", stage: "plan", section: "dev" },
|
|
18
24
|
# ...
|
|
19
25
|
# ]
|
|
20
26
|
def as_db_table
|
|
21
27
|
@test_to_sources.flat_map do |test_file, source_files|
|
|
28
|
+
category = @tests_to_categories[test_file]&.first || ''
|
|
29
|
+
team = @feature_categories_to_teams[category] || {}
|
|
30
|
+
|
|
22
31
|
source_files.map do |source_file|
|
|
23
32
|
{
|
|
24
33
|
test_file: test_file,
|
|
25
|
-
source_file: source_file
|
|
34
|
+
source_file: source_file,
|
|
35
|
+
category: category,
|
|
36
|
+
group: team[:group] || '',
|
|
37
|
+
stage: team[:stage] || '',
|
|
38
|
+
section: team[:section] || ''
|
|
26
39
|
}
|
|
27
40
|
end
|
|
28
41
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab_quality-test_tooling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GitLab Quality
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: climate_control
|
|
@@ -427,6 +427,7 @@ executables:
|
|
|
427
427
|
- report-results
|
|
428
428
|
- slow-test-issues
|
|
429
429
|
- slow-test-merge-request-report-note
|
|
430
|
+
- sync-category-owners
|
|
430
431
|
- test-coverage
|
|
431
432
|
- update-screenshot-paths
|
|
432
433
|
- update-test-meta
|
|
@@ -460,6 +461,7 @@ files:
|
|
|
460
461
|
- exe/report-results
|
|
461
462
|
- exe/slow-test-issues
|
|
462
463
|
- exe/slow-test-merge-request-report-note
|
|
464
|
+
- exe/sync-category-owners
|
|
463
465
|
- exe/test-coverage
|
|
464
466
|
- exe/update-screenshot-paths
|
|
465
467
|
- exe/update-test-meta
|