gitlab_quality-test_tooling 2.26.0 → 2.27.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 125bc8b9a38435053e2e82849dd2a41c241d05d47784626234f01702c5619671
4
- data.tar.gz: 56255580e30a0fd8896a456e4f079bea4a8de9c2a363d73263dc641f613df786
3
+ metadata.gz: 9627a5294f93832f5513406c664eb60589037f0317bca00ad7dd7d9a6668ee85
4
+ data.tar.gz: 540672e53766c7c64743575b77600ffeda9916e7aa8337d269405eadb80bec12
5
5
  SHA512:
6
- metadata.gz: 041e97993894d0edc5d61c0e5e4c5bba688f81fdd92d6bec8d06e19787b3219daad4400d2aedc6c9da2a35e44f526bc8cc4960dce8016ad269f769be482c6ee5
7
- data.tar.gz: eb2eef2912be06edf872af18ef1ff8ae1722462b694aa07f57f7a42e138ae8a0327aaa35914a778472e9104b9d08d407075026d2a48ac7ca9b0a5a9ad64944ad
6
+ metadata.gz: a5c0623b34d029ac924e8cb52ba3b8e22999c246c17b947c29995ccdb61d1b7738fa57428afea6a8bf77138b235e780dc6d2e53e270b5691762e083025249782
7
+ data.tar.gz: 1e9f61e86067efcd19c3f6e945d75e8bbc797383c3c7e0f0b5f9aa656edf020fa42a4236f49a02b63a6651af2a3f81a9b1433cde5853d4d2f0b16d9875304f5a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (2.26.0)
4
+ gitlab_quality-test_tooling (2.27.0)
5
5
  activesupport (>= 7.0, < 7.3)
6
6
  amatch (~> 0.4.1)
7
7
  fog-google (~> 1.24, >= 1.24.1)
data/exe/test-coverage CHANGED
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "optparse"
5
+ require "uri"
5
6
 
6
7
  require_relative "../lib/gitlab_quality/test_tooling"
7
8
 
@@ -12,15 +13,24 @@ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/coverage_data
12
13
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/lcov_file'
13
14
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/artifacts'
14
15
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/rspec_report'
16
+ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/test_report'
15
17
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/test_map'
16
18
 
17
19
  params = {}
18
- required_params = [:rspec_reports, :coverage_report, :test_map]
20
+ required_params = [:coverage_report, :test_map, :clickhouse_url, :clickhouse_database, :clickhouse_username]
19
21
 
20
22
  options = OptionParser.new do |opts|
21
23
  opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
22
24
 
23
- opts.on('--rspec-reports GLOB', 'Glob pattern for RSpec JSON reports (e.g., "rspec/rspec-*.json")') do |pattern|
25
+ opts.separator ""
26
+ opts.separator "Options:"
27
+
28
+ opts.on('--test-reports GLOB',
29
+ 'Glob pattern for test JSON reports (RSpec or Jest) (e.g., "reports/**/*.json"). Takes precedence over --rspec-reports if both are provided.') do |pattern|
30
+ params[:test_reports] = pattern
31
+ end
32
+
33
+ opts.on('--rspec-reports GLOB', '[DEPRECATED] Use --test-reports instead. Glob pattern for RSpec JSON reports. Ignored if --test-reports is also provided.') do |pattern|
24
34
  params[:rspec_reports] = pattern
25
35
  end
26
36
 
@@ -32,6 +42,23 @@ options = OptionParser.new do |opts|
32
42
  params[:test_map] = path
33
43
  end
34
44
 
45
+ opts.on('--clickhouse-url URL', 'ClickHouse server URL') do |url|
46
+ params[:clickhouse_url] = url
47
+ end
48
+
49
+ opts.on('--clickhouse-database DATABASE', 'ClickHouse database name') do |database|
50
+ params[:clickhouse_database] = database
51
+ end
52
+
53
+ opts.on('--clickhouse-username USERNAME', 'ClickHouse username') do |username|
54
+ params[:clickhouse_username] = username
55
+ end
56
+
57
+ opts.separator ""
58
+ opts.separator "Environment variables:"
59
+ opts.separator " GLCI_CLICKHOUSE_METRICS_PASSWORD ClickHouse password (required, not passed via CLI for security)"
60
+ opts.separator ""
61
+
35
62
  opts.on('-h', '--help', 'Show the usage') do
36
63
  puts opts
37
64
  puts "\nExamples:"
@@ -49,24 +76,64 @@ options = OptionParser.new do |opts|
49
76
  end
50
77
 
51
78
  if params.any? && (required_params - params.keys).none?
79
+ clickhouse_password = ENV.fetch('GLCI_CLICKHOUSE_METRICS_PASSWORD', nil)
80
+ if clickhouse_password.to_s.strip.empty?
81
+ puts "Error: GLCI_CLICKHOUSE_METRICS_PASSWORD environment variable must be set and not empty"
82
+ exit 1
83
+ end
84
+
85
+ [:clickhouse_url, :clickhouse_database, :clickhouse_username].each do |param|
86
+ if params[param].to_s.strip.empty?
87
+ puts "Error: --#{param.to_s.tr('_', '-')} cannot be empty"
88
+ exit 1
89
+ end
90
+ end
91
+
92
+ begin
93
+ uri = URI.parse(params[:clickhouse_url])
94
+ unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
95
+ puts "Error: --clickhouse-url must be a valid HTTP or HTTPS URL"
96
+ exit 1
97
+ end
98
+ rescue URI::InvalidURIError
99
+ puts "Error: --clickhouse-url is not a valid URL format"
100
+ exit 1
101
+ end
102
+
103
+ # Validate that at least one of test_reports or rspec_reports is provided
104
+ if params[:test_reports].nil? && params[:rspec_reports].nil?
105
+ puts "Error: At least one of --test-reports or --rspec-reports must be provided"
106
+ exit 1
107
+ end
108
+
52
109
  artifacts = GitlabQuality::TestTooling::CodeCoverage::Artifacts.new(
53
- rspec_reports: params[:rspec_reports],
54
110
  coverage_report: params[:coverage_report],
55
- test_map: params[:test_map]
111
+ test_map: params[:test_map],
112
+ test_reports: params[:test_reports],
113
+ rspec_reports: params[:rspec_reports]
56
114
  )
57
115
 
58
116
  coverage_report = artifacts.coverage_report
59
- rspec_reports = artifacts.rspec_reports
60
117
  test_map = artifacts.test_map
61
118
 
62
119
  code_coverage_by_source_file = GitlabQuality::TestTooling::CodeCoverage::LcovFile.new(coverage_report).parsed_content
63
120
 
64
121
  source_file_to_tests = GitlabQuality::TestTooling::CodeCoverage::TestMap.new(test_map).source_to_tests
65
122
 
66
- tests_to_categories = rspec_reports.reduce({}) do |combined_hash, rspec_report_file|
67
- file_categories = GitlabQuality::TestTooling::CodeCoverage::RspecReport.new(rspec_report_file).tests_to_categories
68
- combined_hash.merge(file_categories) { |_, old_val, new_val| (old_val + new_val).uniq }
69
- end
123
+ # Process test reports using the new unified approach or fall back to legacy rspec_reports
124
+ tests_to_categories = if params[:test_reports]
125
+ # Use new TestReport class for unified test reports
126
+ artifacts.test_reports.reduce({}) do |combined_hash, test_report_file|
127
+ file_categories = GitlabQuality::TestTooling::CodeCoverage::TestReport.new(test_report_file).tests_to_categories
128
+ combined_hash.merge(file_categories) { |_, old_val, new_val| (old_val + new_val).uniq }
129
+ end
130
+ else
131
+ # Fall back to legacy RspecReport for backward compatibility
132
+ artifacts.rspec_reports.reduce({}) do |combined_hash, rspec_report_file|
133
+ file_categories = GitlabQuality::TestTooling::CodeCoverage::RspecReport.new(rspec_report_file).tests_to_categories
134
+ combined_hash.merge(file_categories) { |_, old_val, new_val| (old_val + new_val).uniq }
135
+ end
136
+ end
70
137
 
71
138
  category_owners = GitlabQuality::TestTooling::CodeCoverage::CategoryOwners.new
72
139
 
@@ -77,35 +144,25 @@ if params.any? && (required_params - params.keys).none?
77
144
  category_owners.categories_to_teams
78
145
  )
79
146
 
80
- if ENV.fetch('CLICKHOUSE_URL', nil) &&
81
- ENV.fetch('CLICKHOUSE_DATABASE', nil) &&
82
- ENV.fetch('CLICKHOUSE_USERNAME', nil) &&
83
- ENV.fetch('CLICKHOUSE_PASSWORD', nil)
84
-
85
- clickhouse_data = {
86
- url: ENV.fetch('CLICKHOUSE_URL', nil),
87
- database: ENV.fetch('CLICKHOUSE_DATABASE', nil),
88
- username: ENV.fetch('CLICKHOUSE_USERNAME', nil),
89
- password: ENV.fetch('CLICKHOUSE_PASSWORD', nil)
90
- }
147
+ clickhouse_data = {
148
+ url: params[:clickhouse_url],
149
+ database: params[:clickhouse_database],
150
+ username: params[:clickhouse_username],
151
+ password: clickhouse_password
152
+ }
91
153
 
92
- category_owners_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable.new(**clickhouse_data)
93
- coverage_metrics_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CoverageMetricsTable.new(**clickhouse_data)
154
+ category_owners_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CategoryOwnersTable.new(**clickhouse_data)
155
+ coverage_metrics_table = GitlabQuality::TestTooling::CodeCoverage::ClickHouse::CoverageMetricsTable.new(**clickhouse_data)
94
156
 
95
- category_owners_table.create if ENV['CLICKHOUSE_CREATE_CATEGORY_OWNERS_TABLE'] == 'true'
96
- coverage_metrics_table.create if ENV['CLICKHOUSE_CREATE_COVERAGE_METRICS_TABLE'] == 'true'
157
+ category_owners_table.create if ENV['CLICKHOUSE_CREATE_CATEGORY_OWNERS_TABLE'] == 'true'
158
+ coverage_metrics_table.create if ENV['CLICKHOUSE_CREATE_COVERAGE_METRICS_TABLE'] == 'true'
97
159
 
98
- if ENV['CLICKHOUSE_PUSH_CATEGORY_DATA'] == 'true'
99
- category_owners_table.truncate
100
- category_owners_table.push(category_owners.as_db_table)
101
- end
102
-
103
- coverage_metrics_table.push(coverage_data.as_db_table)
104
- else
105
- puts "ClickHouse configuration not found.\n" \
106
- 'Set CLICKHOUSE_URL, CLICKHOUSE_DATABASE, CLICKHOUSE_USERNAME, ' \
107
- 'CLICKHOUSE_PASSWORD environment variables to enable ClickHouse export.'
160
+ if ENV['CLICKHOUSE_PUSH_CATEGORY_DATA'] == 'true'
161
+ category_owners_table.truncate
162
+ category_owners_table.push(category_owners.as_db_table)
108
163
  end
164
+
165
+ coverage_metrics_table.push(coverage_data.as_db_table)
109
166
  else
110
167
  puts "Missing argument(s). Required arguments are: #{required_params}\nPassed arguments are: #{params}\n"
111
168
  puts options
@@ -10,15 +10,34 @@ module GitlabQuality
10
10
  class Artifacts
11
11
  # Loads coverage artifacts from the filesystem
12
12
  #
13
- # @param rspec_reports [String] Glob pattern for RSpec JSON report files (e.g., "rspec/rspec-*.json")
13
+ # @param test_reports [String, nil] Glob pattern for test JSON report files (RSpec or Jest) (e.g., "reports/**/*.json")
14
+ # @param rspec_reports [String, nil] [DEPRECATED] Use test_reports instead. Glob pattern for RSpec JSON report files
14
15
  # @param coverage_report [String] Path to the LCOV coverage report file (e.g., "coverage/lcov/gitlab.lcov")
15
16
  # @param test_map [String] Path to the test map file, gzipped or plain JSON (e.g., "crystalball/packed-mapping.json.gz")
16
- def initialize(rspec_reports:, coverage_report:, test_map:)
17
+ def initialize(coverage_report:, test_map:, test_reports: nil, rspec_reports: nil)
18
+ @test_reports_glob = test_reports
17
19
  @rspec_reports_glob = rspec_reports
18
20
  @coverage_report_path = coverage_report
19
21
  @test_map_path = test_map
20
22
  end
21
23
 
24
+ # Loads and parses test JSON report files (RSpec or Jest)
25
+ #
26
+ # @return [Array<Hash>] Array of parsed JSON test reports
27
+ # @raise [RuntimeError] If no test reports are found or if JSON parsing fails
28
+ def test_reports
29
+ @test_report_files ||= test_reports_paths.map do |report_path|
30
+ JSON.parse(File.read(report_path))
31
+ rescue JSON::ParserError => e
32
+ raise "Invalid JSON in test report file #{report_path}: #{e.message}"
33
+ end
34
+ end
35
+
36
+ # Loads and parses RSpec JSON report files
37
+ #
38
+ # @deprecated Use {#test_reports} instead
39
+ # @return [Array<Hash>] Array of parsed JSON RSpec reports
40
+ # @raise [RuntimeError] If no RSpec reports are found or if JSON parsing fails
22
41
  def rspec_reports
23
42
  @rspec_report_files ||= rspec_reports_paths.map do |report_path|
24
43
  JSON.parse(File.read(report_path))
@@ -27,17 +46,39 @@ module GitlabQuality
27
46
  end
28
47
  end
29
48
 
49
+ # Loads the LCOV coverage report file
50
+ #
51
+ # @return [String] Raw content of the LCOV coverage report
52
+ # @raise [RuntimeError] If the coverage report file is not found
30
53
  def coverage_report
31
54
  @coverage_report ||= read_coverage_reports
32
55
  end
33
56
 
57
+ # Loads and parses the test map file (supports gzipped or plain JSON)
58
+ #
59
+ # @return [Hash] Parsed test map data
60
+ # @raise [RuntimeError] If the test map file is not found or cannot be parsed
34
61
  def test_map
35
62
  @test_map ||= fetch_test_map
36
63
  end
37
64
 
38
65
  private
39
66
 
67
+ def test_reports_paths
68
+ return [] if @test_reports_glob.nil? || @test_reports_glob.empty?
69
+
70
+ @test_reports_paths ||= begin
71
+ paths = Dir.glob(@test_reports_glob)
72
+
73
+ raise "No test reports found matching pattern: #{@test_reports_glob}" if paths.empty?
74
+
75
+ paths
76
+ end
77
+ end
78
+
40
79
  def rspec_reports_paths
80
+ return [] if @rspec_reports_glob.nil? || @rspec_reports_glob.empty?
81
+
41
82
  @rspec_reports_paths ||= begin
42
83
  paths = Dir.glob(@rspec_reports_glob)
43
84
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module GitlabQuality
6
+ module TestTooling
7
+ module CodeCoverage
8
+ class TestReport
9
+ # @param [Hash<String, Object>] test_report The content of a test
10
+ # report (RSpec or Jest)
11
+ def initialize(test_report)
12
+ @test_report = test_report
13
+ end
14
+
15
+ # @return [Array<Hash<String, String>>] Content of the "examples"
16
+ # section of the test report
17
+ def examples
18
+ @examples ||= @test_report['examples']
19
+ end
20
+
21
+ # @return [Hash<String, Array<String>>] Test files mapped to all feature
22
+ # categories they belong to
23
+ # @example Return value
24
+ # {
25
+ # "spec/path/to/file_spec.rb" => [
26
+ # "feature_category1", "feature_category2"
27
+ # ],
28
+ # ...
29
+ # }
30
+ def tests_to_categories
31
+ @tests_to_categories ||= examples.to_a.filter_map do |example|
32
+ next unless example.is_a?(Hash)
33
+
34
+ file_path = example['file_path']
35
+ next unless file_path.is_a?(String)
36
+
37
+ [file_path.gsub('./', ''), Array(example['feature_category']).compact]
38
+ end.to_h
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -8,6 +8,7 @@ module GitlabQuality
8
8
  "Net::ReadTimeout",
9
9
  "403 Forbidden - Your account has been blocked",
10
10
  "API failed (502) with `GitLab is not responding",
11
+ "Error Code: 502",
11
12
  "unexpected token at 'GitLab is not responding'",
12
13
  "GitLab: Internal API error (502).",
13
14
  "could not be found (502)",
@@ -127,14 +128,26 @@ module GitlabQuality
127
128
  end
128
129
 
129
130
  def full_stacktrace
131
+ page_error_failure = ""
132
+ first_non_ignored_failure = ""
133
+
130
134
  failures.each do |failure|
131
135
  message = failure['message'] || ""
132
136
  message_lines = failure['message_lines'] || []
133
137
 
134
138
  next if IGNORED_FAILURES.any? { |e| message.include?(e) }
135
139
 
136
- return message_lines.empty? ? message : message_lines.join("\n")
140
+ formatted_failure = message_lines.empty? ? message : message_lines.join("\n")
141
+
142
+ if message.include?("PageErrorChecker")
143
+ page_error_failure = formatted_failure
144
+ elsif first_non_ignored_failure.empty?
145
+ first_non_ignored_failure = formatted_failure
146
+ end
137
147
  end
148
+
149
+ # Return PageErrorChecker failure if found, otherwise first non-ignored failure
150
+ page_error_failure.empty? ? first_non_ignored_failure : page_error_failure
138
151
  end
139
152
 
140
153
  def calls_shared_examples?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "2.26.0"
5
+ VERSION = "2.27.0"
6
6
  end
7
7
  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: 2.26.0
4
+ version: 2.27.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: 2025-11-05 00:00:00.000000000 Z
11
+ date: 2025-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -499,6 +499,7 @@ files:
499
499
  - lib/gitlab_quality/test_tooling/code_coverage/lcov_file.rb
500
500
  - lib/gitlab_quality/test_tooling/code_coverage/rspec_report.rb
501
501
  - lib/gitlab_quality/test_tooling/code_coverage/test_map.rb
502
+ - lib/gitlab_quality/test_tooling/code_coverage/test_report.rb
502
503
  - lib/gitlab_quality/test_tooling/code_coverage/utils.rb
503
504
  - lib/gitlab_quality/test_tooling/concerns/find_set_dri.rb
504
505
  - lib/gitlab_quality/test_tooling/failed_jobs_table.rb