gitlab_quality-test_tooling 2.27.0 → 3.0.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: 9627a5294f93832f5513406c664eb60589037f0317bca00ad7dd7d9a6668ee85
4
- data.tar.gz: 540672e53766c7c64743575b77600ffeda9916e7aa8337d269405eadb80bec12
3
+ metadata.gz: f037a73b3fd4a445324a37084915ea2d020475cca6f4e3b1d23045f290351082
4
+ data.tar.gz: e32bc045856df09004e725b9b84317c74ffb66b59ed640b078e292f46a3ff565
5
5
  SHA512:
6
- metadata.gz: a5c0623b34d029ac924e8cb52ba3b8e22999c246c17b947c29995ccdb61d1b7738fa57428afea6a8bf77138b235e780dc6d2e53e270b5691762e083025249782
7
- data.tar.gz: 1e9f61e86067efcd19c3f6e945d75e8bbc797383c3c7e0f0b5f9aa656edf020fa42a4236f49a02b63a6651af2a3f81a9b1433cde5853d4d2f0b16d9875304f5a
6
+ metadata.gz: 400c1e51bf57a3f10cdb36c083444153326da8710edd828839881867aed4d9282da4b21fa16357f49bb498ac48199dda888e422e1ddf4f5453769cb5bdc92211
7
+ data.tar.gz: cc071d0ca3c54a0d3408f6fc91165348c403ab5109c473d7bc518701a17f90d992f02139614dfb99fd53cac7e0ffef688241c7c232cf373344269ffe3e8b4436
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (2.27.0)
4
+ gitlab_quality-test_tooling (3.0.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
@@ -12,12 +12,12 @@ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/click_house/c
12
12
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/coverage_data'
13
13
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/lcov_file'
14
14
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/artifacts'
15
- require_relative '../lib/gitlab_quality/test_tooling/code_coverage/rspec_report'
16
15
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/test_report'
17
16
  require_relative '../lib/gitlab_quality/test_tooling/code_coverage/test_map'
17
+ require_relative '../lib/gitlab_quality/test_tooling/code_coverage/source_file_classifier'
18
18
 
19
19
  params = {}
20
- required_params = [:coverage_report, :test_map, :clickhouse_url, :clickhouse_database, :clickhouse_username]
20
+ required_params = [:test_reports, :coverage_report, :test_map, :clickhouse_url, :clickhouse_database, :clickhouse_username]
21
21
 
22
22
  options = OptionParser.new do |opts|
23
23
  opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
@@ -26,14 +26,10 @@ options = OptionParser.new do |opts|
26
26
  opts.separator "Options:"
27
27
 
28
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|
29
+ 'Glob pattern for test JSON reports (RSpec or Jest) (e.g., "reports/**/*.json")') do |pattern|
30
30
  params[:test_reports] = pattern
31
31
  end
32
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|
34
- params[:rspec_reports] = pattern
35
- end
36
-
37
33
  opts.on('--coverage-report PATH', 'Path to the LCOV coverage report (e.g., "coverage/lcov/gitlab.lcov")') do |path|
38
34
  params[:coverage_report] = path
39
35
  end
@@ -100,17 +96,10 @@ if params.any? && (required_params - params.keys).none?
100
96
  exit 1
101
97
  end
102
98
 
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
-
109
99
  artifacts = GitlabQuality::TestTooling::CodeCoverage::Artifacts.new(
110
100
  coverage_report: params[:coverage_report],
111
101
  test_map: params[:test_map],
112
- test_reports: params[:test_reports],
113
- rspec_reports: params[:rspec_reports]
102
+ test_reports: params[:test_reports]
114
103
  )
115
104
 
116
105
  coverage_report = artifacts.coverage_report
@@ -120,28 +109,24 @@ if params.any? && (required_params - params.keys).none?
120
109
 
121
110
  source_file_to_tests = GitlabQuality::TestTooling::CodeCoverage::TestMap.new(test_map).source_to_tests
122
111
 
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
112
+ # Process test reports
113
+ tests_to_categories = artifacts.test_reports.reduce({}) do |combined_hash, test_report_file|
114
+ file_categories = GitlabQuality::TestTooling::CodeCoverage::TestReport.new(test_report_file).tests_to_categories
115
+ combined_hash.merge(file_categories) { |_, old_val, new_val| (old_val + new_val).uniq }
116
+ end
137
117
 
138
118
  category_owners = GitlabQuality::TestTooling::CodeCoverage::CategoryOwners.new
139
119
 
120
+ # Classify source files by type (frontend, backend, etc.)
121
+ source_file_classifier = GitlabQuality::TestTooling::CodeCoverage::SourceFileClassifier.new
122
+ source_file_types = source_file_classifier.classify(code_coverage_by_source_file.keys)
123
+
140
124
  coverage_data = GitlabQuality::TestTooling::CodeCoverage::CoverageData.new(
141
125
  code_coverage_by_source_file,
142
126
  source_file_to_tests,
143
127
  tests_to_categories,
144
- category_owners.categories_to_teams
128
+ category_owners.categories_to_teams,
129
+ source_file_types
145
130
  )
146
131
 
147
132
  clickhouse_data = {
@@ -3,6 +3,7 @@
3
3
  require 'json'
4
4
  require 'stringio'
5
5
  require 'zlib'
6
+ require 'active_support/core_ext/object/blank'
6
7
 
7
8
  module GitlabQuality
8
9
  module TestTooling
@@ -10,13 +11,13 @@ module GitlabQuality
10
11
  class Artifacts
11
12
  # Loads coverage artifacts from the filesystem
12
13
  #
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
+ # @param test_reports [String] Glob pattern for test JSON report files (RSpec or Jest) (e.g., "reports/**/*.json")
15
15
  # @param coverage_report [String] Path to the LCOV coverage report file (e.g., "coverage/lcov/gitlab.lcov")
16
16
  # @param test_map [String] Path to the test map file, gzipped or plain JSON (e.g., "crystalball/packed-mapping.json.gz")
17
- def initialize(coverage_report:, test_map:, test_reports: nil, rspec_reports: nil)
17
+ def initialize(coverage_report:, test_map:, test_reports:)
18
+ raise ArgumentError, "test_reports cannot be blank" if test_reports.blank?
19
+
18
20
  @test_reports_glob = test_reports
19
- @rspec_reports_glob = rspec_reports
20
21
  @coverage_report_path = coverage_report
21
22
  @test_map_path = test_map
22
23
  end
@@ -33,19 +34,6 @@ module GitlabQuality
33
34
  end
34
35
  end
35
36
 
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
41
- def rspec_reports
42
- @rspec_report_files ||= rspec_reports_paths.map do |report_path|
43
- JSON.parse(File.read(report_path))
44
- rescue JSON::ParserError => e
45
- raise "Invalid JSON in RSpec report file #{report_path}: #{e.message}"
46
- end
47
- end
48
-
49
37
  # Loads the LCOV coverage report file
50
38
  #
51
39
  # @return [String] Raw content of the LCOV coverage report
@@ -65,8 +53,6 @@ module GitlabQuality
65
53
  private
66
54
 
67
55
  def test_reports_paths
68
- return [] if @test_reports_glob.nil? || @test_reports_glob.empty?
69
-
70
56
  @test_reports_paths ||= begin
71
57
  paths = Dir.glob(@test_reports_glob)
72
58
 
@@ -76,18 +62,6 @@ module GitlabQuality
76
62
  end
77
63
  end
78
64
 
79
- def rspec_reports_paths
80
- return [] if @rspec_reports_glob.nil? || @rspec_reports_glob.empty?
81
-
82
- @rspec_reports_paths ||= begin
83
- paths = Dir.glob(@rspec_reports_glob)
84
-
85
- raise "No RSpec reports found matching pattern: #{@rspec_reports_glob}" if paths.empty?
86
-
87
- paths
88
- end
89
- end
90
-
91
65
  def read_coverage_reports
92
66
  raise "Coverage report not found in: #{@coverage_report_path}" unless File.exist?(@coverage_report_path)
93
67
 
@@ -19,7 +19,10 @@ module GitlabQuality
19
19
  CREATE TABLE IF NOT EXISTS #{table_name} (
20
20
  timestamp DateTime64(6, 'UTC'),
21
21
  file String,
22
- coverage Float64,
22
+ line_coverage Float64,
23
+ branch_coverage Nullable(Float64),
24
+ function_coverage Nullable(Float64),
25
+ source_file_type String,
23
26
  category Nullable(String),
24
27
  ci_project_id Nullable(UInt32),
25
28
  ci_project_path Nullable(String),
@@ -42,22 +45,47 @@ module GitlabQuality
42
45
 
43
46
  # @return [Boolean] True if the record is valid, false otherwise
44
47
  def valid_record?(record)
45
- if record[:file].nil?
46
- logger.warn("#{LOG_PREFIX} Skipping record with nil file: #{record}")
47
- return false
48
- end
48
+ valid_file?(record) &&
49
+ valid_line_coverage?(record) &&
50
+ valid_branch_coverage?(record) &&
51
+ valid_function_coverage?(record)
52
+ end
49
53
 
50
- if record[:coverage].nil?
51
- logger.warn("#{LOG_PREFIX} Skipping record with nil coverage: #{record}")
52
- return false
53
- end
54
+ # @return [Boolean] True if the file field is present
55
+ def valid_file?(record)
56
+ return true unless record[:file].nil?
57
+
58
+ logger.warn("#{LOG_PREFIX} Skipping record with nil file: #{record}")
59
+ false
60
+ end
54
61
 
55
- if record[:coverage].nan?
56
- logger.warn("#{LOG_PREFIX} Skipping record with NaN coverage: #{record}")
62
+ # @return [Boolean] True if line_coverage is present and not NaN
63
+ def valid_line_coverage?(record)
64
+ if record[:line_coverage].nil?
65
+ logger.warn("#{LOG_PREFIX} Skipping record with nil line_coverage: #{record}")
57
66
  return false
58
67
  end
59
68
 
60
- true
69
+ return true unless record[:line_coverage].nan?
70
+
71
+ logger.warn("#{LOG_PREFIX} Skipping record with NaN line_coverage: #{record}")
72
+ false
73
+ end
74
+
75
+ # @return [Boolean] True if branch_coverage is not NaN (or is nil)
76
+ def valid_branch_coverage?(record)
77
+ return true unless record[:branch_coverage]&.nan?
78
+
79
+ logger.warn("#{LOG_PREFIX} Skipping record with NaN branch_coverage: #{record}")
80
+ false
81
+ end
82
+
83
+ # @return [Boolean] True if function_coverage is not NaN (or is nil)
84
+ def valid_function_coverage?(record)
85
+ return true unless record[:function_coverage]&.nan?
86
+
87
+ logger.warn("#{LOG_PREFIX} Skipping record with NaN function_coverage: #{record}")
88
+ false
61
89
  end
62
90
 
63
91
  # @return [Hash] Transformed coverage data including timestamp and CI metadata
@@ -65,7 +93,10 @@ module GitlabQuality
65
93
  {
66
94
  timestamp: time,
67
95
  file: record[:file],
68
- coverage: record[:coverage],
96
+ line_coverage: record[:line_coverage],
97
+ branch_coverage: record[:branch_coverage],
98
+ function_coverage: record[:function_coverage],
99
+ source_file_type: record[:source_file_type],
69
100
  category: record[:category],
70
101
  **ci_metadata
71
102
  }
@@ -23,6 +23,8 @@ module GitlabQuality
23
23
  logger.debug("#{LOG_PREFIX} Starting data export to ClickHouse")
24
24
  sanitized_data = sanitize(data)
25
25
 
26
+ return logger.warn("#{LOG_PREFIX} No valid data found after sanitization, skipping ClickHouse export!") if sanitized_data.empty?
27
+
26
28
  client.insert_json_data(table_name, sanitized_data)
27
29
  logger.info("#{LOG_PREFIX} Successfully pushed #{sanitized_data.size} records to #{full_table_name}!")
28
30
  rescue StandardError => e
@@ -12,11 +12,14 @@ module GitlabQuality
12
12
  # mapped to all feature categories they belong to
13
13
  # @param [Hash<String, Hash>] categories_to_teams Mapping of categories
14
14
  # to teams (i.e., groups, stages, sections)
15
- def initialize(code_coverage_by_source_file, source_file_to_tests, tests_to_categories, categories_to_teams)
15
+ # @param [Hash<String, String>] source_file_types Mapping of source files
16
+ # to their types (frontend, backend, etc.)
17
+ def initialize(code_coverage_by_source_file, source_file_to_tests, tests_to_categories, categories_to_teams, source_file_types = {})
16
18
  @code_coverage_by_source_file = code_coverage_by_source_file
17
19
  @source_file_to_tests = source_file_to_tests
18
20
  @tests_to_categories = tests_to_categories
19
21
  @categories_to_teams = categories_to_teams
22
+ @source_file_types = source_file_types
20
23
  end
21
24
 
22
25
  # @return [Array<Hash<Symbol, String>>] Mapping of column name to row
@@ -25,7 +28,10 @@ module GitlabQuality
25
28
  # [
26
29
  # {
27
30
  # file: "app/channels/application_cable/channel.rb"
28
- # coverage: 100.0
31
+ # line_coverage: 100.0
32
+ # branch_coverage: 95.0
33
+ # function_coverage: 100.0
34
+ # source_file_type: "backend"
29
35
  # category: "team_planning"
30
36
  # group: "project_management"
31
37
  # stage: "plan"
@@ -35,13 +41,25 @@ module GitlabQuality
35
41
  # ]
36
42
  def as_db_table
37
43
  all_files.flat_map do |file|
38
- coverage = @code_coverage_by_source_file[file]&.dig(:percentage)
44
+ coverage_data = @code_coverage_by_source_file[file]
45
+ line_coverage = coverage_data&.dig(:percentage)
46
+ branch_coverage = coverage_data&.dig(:branch_percentage)
47
+ function_coverage = coverage_data&.dig(:function_percentage)
48
+
39
49
  categories = categories_for(file)
50
+ base_data = {
51
+ file: file,
52
+ line_coverage: line_coverage,
53
+ branch_coverage: branch_coverage,
54
+ function_coverage: function_coverage,
55
+ source_file_type: @source_file_types[file] || 'other'
56
+ }
57
+
40
58
  if categories.empty?
41
- { file: file, coverage: coverage }.merge(no_owner_info)
59
+ base_data.merge(no_owner_info)
42
60
  else
43
61
  categories.map do |category|
44
- { file: file, coverage: coverage }.merge(owner_info(category))
62
+ base_data.merge(owner_info(category))
45
63
  end
46
64
  end
47
65
  end
@@ -17,7 +17,13 @@ module GitlabQuality
17
17
  # branch_coverage: {},
18
18
  # total_lines: 2,
19
19
  # covered_lines: 1,
20
- # percentage: 50.0
20
+ # percentage: 50.0,
21
+ # total_branches: 4,
22
+ # covered_branches: 3,
23
+ # branch_percentage: 75.0,
24
+ # total_functions: 2,
25
+ # covered_functions: 1,
26
+ # function_percentage: 50.0
21
27
  # },
22
28
  # ...
23
29
  # }
@@ -27,18 +33,7 @@ module GitlabQuality
27
33
  @parsed_content = {}
28
34
  @current_file = nil
29
35
 
30
- @lcov_file_content.each_line do |line|
31
- case line
32
- when /^SF:(.+)$/
33
- register_source_file(::Regexp.last_match(1))
34
- when /^DA:(\d+),(\d+)$/
35
- register_line_data(::Regexp.last_match(1), ::Regexp.last_match(2))
36
- when /^BRDA:(\d+),(\d+),(\d+),(-|\d+)$/
37
- register_branch_data(::Regexp.last_match(1), ::Regexp.last_match(4))
38
- when /^end_of_record$/
39
- @current_file = nil
40
- end
41
- end
36
+ @lcov_file_content.each_line { |line| parse_line(line) }
42
37
 
43
38
  include_coverage
44
39
  @parsed_content
@@ -46,10 +41,32 @@ module GitlabQuality
46
41
 
47
42
  private
48
43
 
44
+ def parse_line(line)
45
+ case line
46
+ when /^SF:(.+)$/
47
+ register_source_file(::Regexp.last_match(1))
48
+ when /^DA:(\d+),(\d+)$/
49
+ register_line_data(::Regexp.last_match(1), ::Regexp.last_match(2))
50
+ when /^BRDA:(\d+),(\d+),(\d+),(-|\d+)$/
51
+ register_branch_data(::Regexp.last_match(1), ::Regexp.last_match(4))
52
+ when /^FNF:(\d+)$/
53
+ register_functions_found(::Regexp.last_match(1))
54
+ when /^FNH:(\d+)$/
55
+ register_functions_hit(::Regexp.last_match(1))
56
+ when /^BRF:(\d+)$/
57
+ register_branches_found(::Regexp.last_match(1))
58
+ when /^BRH:(\d+)$/
59
+ register_branches_hit(::Regexp.last_match(1))
60
+ when /^end_of_record$/
61
+ @current_file = nil
62
+ end
63
+ end
64
+
49
65
  def include_coverage
50
66
  @parsed_content.each_key do |file|
51
- # TODO: Support branch coverage too
52
67
  @parsed_content[file].merge!(line_coverage_for(file))
68
+ @parsed_content[file].merge!(branch_coverage_for(file))
69
+ @parsed_content[file].merge!(function_coverage_for(file))
53
70
  end
54
71
  end
55
72
 
@@ -69,7 +86,12 @@ module GitlabQuality
69
86
 
70
87
  def register_source_file(filename)
71
88
  @current_file = filename.gsub(%r{^\./}, '')
72
- @parsed_content[@current_file] = { line_coverage: {}, branch_coverage: {} }
89
+ @parsed_content[@current_file] = {
90
+ line_coverage: {},
91
+ branch_coverage: {},
92
+ branches: { found: 0, hit: 0 },
93
+ functions: { found: 0, hit: 0 }
94
+ }
73
95
  end
74
96
 
75
97
  def register_line_data(line_no, count)
@@ -85,6 +107,62 @@ module GitlabQuality
85
107
  @parsed_content[@current_file][:branch_coverage][line_no.to_i] ||= []
86
108
  @parsed_content[@current_file][:branch_coverage][line_no.to_i] << taken_count
87
109
  end
110
+
111
+ def register_functions_found(count)
112
+ return unless @current_file
113
+
114
+ @parsed_content[@current_file][:functions][:found] = count.to_i
115
+ end
116
+
117
+ def register_functions_hit(count)
118
+ return unless @current_file
119
+
120
+ @parsed_content[@current_file][:functions][:hit] = count.to_i
121
+ end
122
+
123
+ def register_branches_found(count)
124
+ return unless @current_file
125
+
126
+ @parsed_content[@current_file][:branches][:found] = count.to_i
127
+ end
128
+
129
+ def register_branches_hit(count)
130
+ return unless @current_file
131
+
132
+ @parsed_content[@current_file][:branches][:hit] = count.to_i
133
+ end
134
+
135
+ def branch_coverage_for(file)
136
+ data = @parsed_content[file]
137
+ return {} unless data && data[:branches]
138
+
139
+ total = data[:branches][:found]
140
+ covered = data[:branches][:hit]
141
+
142
+ return {} if total.nil? || total.zero?
143
+
144
+ {
145
+ total_branches: total,
146
+ covered_branches: covered,
147
+ branch_percentage: (covered.to_f / total * 100).round(2)
148
+ }
149
+ end
150
+
151
+ def function_coverage_for(file)
152
+ data = @parsed_content[file]
153
+ return {} unless data && data[:functions]
154
+
155
+ total = data[:functions][:found]
156
+ covered = data[:functions][:hit]
157
+
158
+ return {} if total.nil? || total.zero?
159
+
160
+ {
161
+ total_functions: total,
162
+ covered_functions: covered,
163
+ function_percentage: (covered.to_f / total * 100).round(2)
164
+ }
165
+ end
88
166
  end
89
167
  end
90
168
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitlabQuality
4
+ module TestTooling
5
+ module CodeCoverage
6
+ class SourceFileClassifier
7
+ PATTERNS = {
8
+ 'frontend' => [
9
+ %r{^app/assets/javascripts/.*\.(js|jsx|ts|tsx|vue)$},
10
+ %r{^app/assets/stylesheets/.*\.(css|scss)$},
11
+ %r{^ee/app/assets/javascripts/.*\.(js|jsx|ts|tsx|vue)$},
12
+ %r{^ee/app/assets/stylesheets/.*\.(css|scss)$},
13
+ %r{^jh/app/assets/javascripts/.*\.(js|jsx|ts|tsx|vue)$},
14
+ %r{^spec/frontend/},
15
+ %r{^ee/spec/frontend/},
16
+ %r{^spec/frontend_integration/},
17
+ %r{^app/assets/javascripts/.*\.graphql$},
18
+ /\.stories\.js$/
19
+ ],
20
+ 'backend' => [
21
+ %r{^app/(models|controllers|services|workers|helpers|mailers|policies|presenters|uploaders|validators|enums|events|experiments|facades|channels)/.*\.rb$},
22
+ %r{^app/serializers/.*\.rb$},
23
+ %r{^app/graphql/.*\.rb$},
24
+ %r{^app/components/.*\.rb$},
25
+ %r{^app/views/.*\.(haml|erb)$},
26
+ %r{^lib/.*\.rb$},
27
+ %r{^lib/api/.*\.rb$},
28
+ %r{^ee/app/.*\.rb$},
29
+ %r{^ee/lib/.*\.rb$},
30
+ %r{^jh/app/.*\.rb$},
31
+ %r{^jh/lib/.*\.rb$},
32
+ %r{^spec/.*_spec\.rb$},
33
+ %r{^ee/spec/.*_spec\.rb$},
34
+ %r{^lib/tasks/.*\.rake$}
35
+ ],
36
+ 'database' => [
37
+ %r{^db/migrate/.*\.rb$},
38
+ %r{^db/post_migrate/.*\.rb$},
39
+ %r{^ee/db/geo/migrate/.*\.rb$},
40
+ %r{^db/structure\.sql$},
41
+ %r{^db/seeds\.rb$},
42
+ %r{^db/fixtures/}
43
+ ],
44
+ 'infrastructure' => [
45
+ /^\.gitlab-ci\.yml$/,
46
+ %r{^\.gitlab/ci/.*\.(yml|yaml)$},
47
+ /Dockerfile/,
48
+ /\.dockerfile$/,
49
+ %r{^scripts/pipeline/}
50
+ ],
51
+ 'qa' => [
52
+ %r{^qa/.*\.rb$}
53
+ ],
54
+ 'workhorse' => [
55
+ %r{^workhorse/.*\.go$}
56
+ ],
57
+ 'tooling' => [
58
+ %r{^tooling/.*\.(rb|js)$},
59
+ %r{^rubocop/.*\.rb$},
60
+ %r{^danger/.*\.rb$},
61
+ /^\.rubocop\.yml$/
62
+ ],
63
+ 'configuration' => [
64
+ %r{^config/.*\.(yml|yaml|rb)$}
65
+ ]
66
+ }.freeze
67
+
68
+ # Classifies a collection of file paths into their respective types
69
+ #
70
+ # @param file_paths [Array<String>] List of file paths to classify
71
+ # @return [Hash<String, String>] Hash mapping file path to file type
72
+ def classify(file_paths)
73
+ file_paths.each_with_object({}) do |file_path, result|
74
+ result[file_path] = determine_type(file_path)
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # Determines the type of a single file based on pattern matching
81
+ #
82
+ # @param file_path [String] The file path to classify
83
+ # @return [String] The file type category
84
+ def determine_type(file_path)
85
+ PATTERNS.each do |type, patterns|
86
+ return type if patterns.any? { |pattern| file_path.match?(pattern) }
87
+ end
88
+
89
+ 'other'
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -8,7 +8,6 @@ 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",
12
11
  "unexpected token at 'GitLab is not responding'",
13
12
  "GitLab: Internal API error (502).",
14
13
  "could not be found (502)",
@@ -128,26 +127,14 @@ module GitlabQuality
128
127
  end
129
128
 
130
129
  def full_stacktrace
131
- page_error_failure = ""
132
- first_non_ignored_failure = ""
133
-
134
130
  failures.each do |failure|
135
131
  message = failure['message'] || ""
136
132
  message_lines = failure['message_lines'] || []
137
133
 
138
134
  next if IGNORED_FAILURES.any? { |e| message.include?(e) }
139
135
 
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
136
+ return message_lines.empty? ? message : message_lines.join("\n")
147
137
  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
151
138
  end
152
139
 
153
140
  def calls_shared_examples?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "2.27.0"
5
+ VERSION = "3.0.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.27.0
4
+ version: 3.0.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-13 00:00:00.000000000 Z
11
+ date: 2025-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -498,6 +498,7 @@ files:
498
498
  - lib/gitlab_quality/test_tooling/code_coverage/coverage_data.rb
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
+ - lib/gitlab_quality/test_tooling/code_coverage/source_file_classifier.rb
501
502
  - lib/gitlab_quality/test_tooling/code_coverage/test_map.rb
502
503
  - lib/gitlab_quality/test_tooling/code_coverage/test_report.rb
503
504
  - lib/gitlab_quality/test_tooling/code_coverage/utils.rb