gitlab_quality-test_tooling 3.0.0 → 3.5.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -4
  3. data/README.md +0 -14
  4. data/exe/test-coverage +51 -8
  5. data/lib/gitlab_quality/test_tooling/code_coverage/README.md +162 -0
  6. data/lib/gitlab_quality/test_tooling/code_coverage/artifacts.rb +5 -2
  7. data/lib/gitlab_quality/test_tooling/code_coverage/category_owners.rb +26 -26
  8. data/lib/gitlab_quality/test_tooling/code_coverage/click_house/category_owners_table.rb +13 -27
  9. data/lib/gitlab_quality/test_tooling/code_coverage/click_house/coverage_metrics_table.rb +3 -41
  10. data/lib/gitlab_quality/test_tooling/code_coverage/click_house/table.rb +17 -0
  11. data/lib/gitlab_quality/test_tooling/code_coverage/click_house/test_file_mappings_table.rb +48 -0
  12. data/lib/gitlab_quality/test_tooling/code_coverage/coverage_data.rb +71 -34
  13. data/lib/gitlab_quality/test_tooling/code_coverage/lcov_file.rb +11 -1
  14. data/lib/gitlab_quality/test_tooling/code_coverage/responsibility_classifier.rb +47 -0
  15. data/lib/gitlab_quality/test_tooling/code_coverage/responsibility_patterns_config.rb +46 -0
  16. data/lib/gitlab_quality/test_tooling/code_coverage/test_file_mapping_data.rb +33 -0
  17. data/lib/gitlab_quality/test_tooling/report/results_in_test_cases.rb +2 -4
  18. data/lib/gitlab_quality/test_tooling/runtime/env.rb +4 -0
  19. data/lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb +1 -1
  20. data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +4 -4
  21. data/lib/gitlab_quality/test_tooling/test_metrics_exporter/config.rb +8 -0
  22. data/lib/gitlab_quality/test_tooling/test_metrics_exporter/test_metrics.rb +36 -10
  23. data/lib/gitlab_quality/test_tooling/test_metrics_exporter/utils.rb +2 -0
  24. data/lib/gitlab_quality/test_tooling/test_quarantine/quarantine_formatter.rb +38 -0
  25. data/lib/gitlab_quality/test_tooling/test_quarantine/quarantine_helper.rb +76 -0
  26. data/lib/gitlab_quality/test_tooling/test_result/base_test_result.rb +15 -1
  27. data/lib/gitlab_quality/test_tooling/version.rb +1 -1
  28. metadata +9 -28
  29. data/exe/existing-test-health-issue +0 -59
  30. data/exe/generate-test-session +0 -70
  31. data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +0 -288
  32. data/lib/gitlab_quality/test_tooling/report/test_health_issue_finder.rb +0 -79
@@ -8,11 +8,13 @@ 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)",
14
15
  "Error reference number: 502",
15
16
  "(502): `GitLab is not responding`",
17
+ "(502): `502 Bad Gateway`",
16
18
  "<head><title>502 Bad Gateway</title></head>",
17
19
  "14:connections to all backends failing",
18
20
  "gitlab_canary=true cookie was set in browser but 'Next' badge was not shown on UI"
@@ -127,14 +129,26 @@ module GitlabQuality
127
129
  end
128
130
 
129
131
  def full_stacktrace
132
+ page_error_failure = ""
133
+ first_non_ignored_failure = ""
134
+
130
135
  failures.each do |failure|
131
136
  message = failure['message'] || ""
132
137
  message_lines = failure['message_lines'] || []
133
138
 
134
139
  next if IGNORED_FAILURES.any? { |e| message.include?(e) }
135
140
 
136
- return message_lines.empty? ? message : message_lines.join("\n")
141
+ formatted_failure = message_lines.empty? ? message : message_lines.join("\n")
142
+
143
+ if message.include?("PageErrorChecker")
144
+ page_error_failure = formatted_failure
145
+ elsif first_non_ignored_failure.empty?
146
+ first_non_ignored_failure = formatted_failure
147
+ end
137
148
  end
149
+
150
+ # Return PageErrorChecker failure if found, otherwise first non-ignored failure
151
+ page_error_failure.empty? ? first_non_ignored_failure : page_error_failure
138
152
  end
139
153
 
140
154
  def calls_shared_examples?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "3.0.0"
5
+ VERSION = "3.5.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: 3.0.0
4
+ version: 3.5.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-17 00:00:00.000000000 Z
11
+ date: 2026-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -227,9 +227,6 @@ dependencies:
227
227
  - - ">="
228
228
  - !ruby/object:Gem::Version
229
229
  version: '7.0'
230
- - - "<"
231
- - !ruby/object:Gem::Version
232
- version: '7.3'
233
230
  type: :runtime
234
231
  prerelease: false
235
232
  version_requirements: !ruby/object:Gem::Requirement
@@ -237,9 +234,6 @@ dependencies:
237
234
  - - ">="
238
235
  - !ruby/object:Gem::Version
239
236
  version: '7.0'
240
- - - "<"
241
- - !ruby/object:Gem::Version
242
- version: '7.3'
243
237
  - !ruby/object:Gem::Dependency
244
238
  name: amatch
245
239
  requirement: !ruby/object:Gem::Requirement
@@ -308,20 +302,6 @@ dependencies:
308
302
  - - "~>"
309
303
  - !ruby/object:Gem::Version
310
304
  version: '5.0'
311
- - !ruby/object:Gem::Dependency
312
- name: influxdb-client
313
- requirement: !ruby/object:Gem::Requirement
314
- requirements:
315
- - - "~>"
316
- - !ruby/object:Gem::Version
317
- version: '3.1'
318
- type: :runtime
319
- prerelease: false
320
- version_requirements: !ruby/object:Gem::Requirement
321
- requirements:
322
- - - "~>"
323
- - !ruby/object:Gem::Version
324
- version: '3.1'
325
305
  - !ruby/object:Gem::Dependency
326
306
  name: nokogiri
327
307
  requirement: !ruby/object:Gem::Requirement
@@ -436,12 +416,10 @@ email:
436
416
  executables:
437
417
  - detect-infrastructure-failures
438
418
  - epic-readiness-notification
439
- - existing-test-health-issue
440
419
  - failed-test-issues
441
420
  - feature-readiness-checklist
442
421
  - feature-readiness-evaluation
443
422
  - flaky-test-issues
444
- - generate-test-session
445
423
  - knapsack-report-issues
446
424
  - post-to-slack
447
425
  - prepare-stage-reports
@@ -471,12 +449,10 @@ files:
471
449
  - Rakefile
472
450
  - exe/detect-infrastructure-failures
473
451
  - exe/epic-readiness-notification
474
- - exe/existing-test-health-issue
475
452
  - exe/failed-test-issues
476
453
  - exe/feature-readiness-checklist
477
454
  - exe/feature-readiness-evaluation
478
455
  - exe/flaky-test-issues
479
- - exe/generate-test-session
480
456
  - exe/knapsack-report-issues
481
457
  - exe/post-to-slack
482
458
  - exe/prepare-stage-reports
@@ -490,15 +466,20 @@ files:
490
466
  - lefthook.yml
491
467
  - lib/gitlab_quality/test_tooling.rb
492
468
  - lib/gitlab_quality/test_tooling/click_house/client.rb
469
+ - lib/gitlab_quality/test_tooling/code_coverage/README.md
493
470
  - lib/gitlab_quality/test_tooling/code_coverage/artifacts.rb
494
471
  - lib/gitlab_quality/test_tooling/code_coverage/category_owners.rb
495
472
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/category_owners_table.rb
496
473
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/coverage_metrics_table.rb
497
474
  - lib/gitlab_quality/test_tooling/code_coverage/click_house/table.rb
475
+ - lib/gitlab_quality/test_tooling/code_coverage/click_house/test_file_mappings_table.rb
498
476
  - lib/gitlab_quality/test_tooling/code_coverage/coverage_data.rb
499
477
  - lib/gitlab_quality/test_tooling/code_coverage/lcov_file.rb
478
+ - lib/gitlab_quality/test_tooling/code_coverage/responsibility_classifier.rb
479
+ - lib/gitlab_quality/test_tooling/code_coverage/responsibility_patterns_config.rb
500
480
  - lib/gitlab_quality/test_tooling/code_coverage/rspec_report.rb
501
481
  - lib/gitlab_quality/test_tooling/code_coverage/source_file_classifier.rb
482
+ - lib/gitlab_quality/test_tooling/code_coverage/test_file_mapping_data.rb
502
483
  - lib/gitlab_quality/test_tooling/code_coverage/test_map.rb
503
484
  - lib/gitlab_quality/test_tooling/code_coverage/test_report.rb
504
485
  - lib/gitlab_quality/test_tooling/code_coverage/utils.rb
@@ -541,7 +522,6 @@ files:
541
522
  - lib/gitlab_quality/test_tooling/report/failed_test_issue.rb
542
523
  - lib/gitlab_quality/test_tooling/report/feature_readiness/report_on_epic.rb
543
524
  - lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb
544
- - lib/gitlab_quality/test_tooling/report/generate_test_session.rb
545
525
  - lib/gitlab_quality/test_tooling/report/group_issues/error_message_normalizer.rb
546
526
  - lib/gitlab_quality/test_tooling/report/group_issues/error_pattern_matcher.rb
547
527
  - lib/gitlab_quality/test_tooling/report/group_issues/failure_processor.rb
@@ -564,7 +544,6 @@ files:
564
544
  - lib/gitlab_quality/test_tooling/report/results_in_issues.rb
565
545
  - lib/gitlab_quality/test_tooling/report/results_in_test_cases.rb
566
546
  - lib/gitlab_quality/test_tooling/report/slow_test_issue.rb
567
- - lib/gitlab_quality/test_tooling/report/test_health_issue_finder.rb
568
547
  - lib/gitlab_quality/test_tooling/report/update_screenshot_path.rb
569
548
  - lib/gitlab_quality/test_tooling/runtime/env.rb
570
549
  - lib/gitlab_quality/test_tooling/runtime/logger.rb
@@ -595,6 +574,8 @@ files:
595
574
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/formatter.rb
596
575
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/test_metrics.rb
597
576
  - lib/gitlab_quality/test_tooling/test_metrics_exporter/utils.rb
577
+ - lib/gitlab_quality/test_tooling/test_quarantine/quarantine_formatter.rb
578
+ - lib/gitlab_quality/test_tooling/test_quarantine/quarantine_helper.rb
598
579
  - lib/gitlab_quality/test_tooling/test_result/base_test_result.rb
599
580
  - lib/gitlab_quality/test_tooling/test_result/j_unit_test_result.rb
600
581
  - lib/gitlab_quality/test_tooling/test_result/json_test_result.rb
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "optparse"
6
-
7
- require_relative "../lib/gitlab_quality/test_tooling"
8
-
9
- params = {}
10
- HEALTH_PROBLEM_TYPES = GitlabQuality::TestTooling::Report::TestHealthIssueFinder::HEALTH_PROBLEM_TYPE_TO_LABEL.keys
11
-
12
- options = OptionParser.new do |opts|
13
- opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
14
-
15
- opts.on('-i', '--input-files INPUT_FILES', String, 'JSON rspec-retry report files') do |input_files|
16
- params[:input_files] = input_files
17
- end
18
-
19
- opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
20
- params[:project] = project
21
- end
22
-
23
- opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and Maintainer permission in PROJECT') do |token|
24
- params[:token] = token
25
- end
26
-
27
- opts.on("--health-problem-type PROBLEM_TYPE", String, "Look for the given health problem type (#{HEALTH_PROBLEM_TYPES.join(', ')})") do |value|
28
- raise ArgumentError, "Invalid health problem type: #{value}. Valid options are: #{HEALTH_PROBLEM_TYPES.join(', ')}" unless HEALTH_PROBLEM_TYPES.include?(value)
29
-
30
- params[:health_problem_type] = value
31
- end
32
-
33
- opts.on_tail('-v', '--version', 'Show the version') do
34
- require_relative "../lib/gitlab_quality/test_tooling/version"
35
- puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
36
- exit
37
- end
38
-
39
- opts.on_tail('-h', '--help', 'Show the usage') do
40
- puts "Purpose: Checks whether tests coming from the rspec JSON report files has an existing test health issue opened."
41
- puts opts
42
- exit
43
- end
44
-
45
- opts.parse(ARGV)
46
- end
47
-
48
- if params.any?
49
- raise ArgumentError, "No health problem type given. Valid options are: #{HEALTH_PROBLEM_TYPES.join(', ')}" unless params.key?(:health_problem_type)
50
-
51
- if GitlabQuality::TestTooling::Report::TestHealthIssueFinder.new(**params).found_existing_unhealthy_test_issue?
52
- exit 0
53
- else
54
- exit 1
55
- end
56
- else
57
- puts options
58
- exit 1
59
- end
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "optparse"
6
-
7
- require_relative "../lib/gitlab_quality/test_tooling"
8
-
9
- params = {}
10
-
11
- options = OptionParser.new do |opts|
12
- opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
13
-
14
- opts.on('-i', '--input-files INPUT_FILES', String, 'RSpec report files (JSON or JUnit XML)') do |input_files|
15
- params[:input_files] = input_files
16
- end
17
-
18
- opts.on('-p', '--project PROJECT', String, 'Can be an integer or a group/project string') do |project|
19
- params[:project] = project
20
- end
21
-
22
- opts.on('-t', '--token TOKEN', String, 'A valid access token with `api` scope and Reporter permission in PROJECT') do |token|
23
- params[:token] = token
24
- end
25
-
26
- opts.on('-c', '--ci-project-token CI_PROJECT_TOKEN', String, 'A valid access token with `read_api` scope permission in current ENV["CI_PROJECT_ID"]') do |ci_project_token|
27
- params[:ci_project_token] = ci_project_token
28
- end
29
-
30
- opts.on('-f', '--issue-url-file ISSUE_URL_FILE', 'Output the created test session issue URL') do |issue_url_file|
31
- params[:issue_url_file] = issue_url_file
32
- end
33
-
34
- opts.on('--pipeline-stages STAGES', String, 'Comma-separated list of pipeline stages to include in test session issue') do |pipeline_stages|
35
- params[:pipeline_stages] = pipeline_stages.split(',')
36
- end
37
-
38
- opts.on('--confidential', "Makes test session issue confidential") do
39
- params[:confidential] = true
40
- end
41
-
42
- opts.on('--dry-run', "Perform a dry-run (don't create or update issues or test cases)") do
43
- params[:dry_run] = true
44
- end
45
-
46
- opts.on_tail('-v', '--version', 'Show the version') do
47
- require_relative "../lib/gitlab_quality/test_tooling/version"
48
- puts "#{$PROGRAM_NAME} : #{GitlabQuality::TestTooling::VERSION}"
49
- exit
50
- end
51
-
52
- opts.on_tail('-h', '--help', 'Show the usage') do
53
- puts "Purpose: Generate test session report based on RSpec report files (JSON or JUnit XML)"
54
- puts opts
55
- exit
56
- end
57
-
58
- opts.parse(ARGV)
59
- end
60
-
61
- issue_url_file = params.delete(:issue_url_file)
62
-
63
- if params.any?
64
- issue_url = GitlabQuality::TestTooling::Report::GenerateTestSession.new(**params).invoke!
65
-
66
- File.write(issue_url_file, issue_url) if issue_url_file && issue_url
67
- else
68
- puts options
69
- exit 1
70
- end
@@ -1,288 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'erb'
4
- require 'date'
5
-
6
- module GitlabQuality
7
- module TestTooling
8
- module Report
9
- class GenerateTestSession < ReportAsIssue
10
- def initialize(ci_project_token:, pipeline_stages: nil, **kwargs)
11
- super
12
- @ci_project_token = ci_project_token
13
- @pipeline_stages = Set.new(pipeline_stages)
14
- @issue_type = 'issue'
15
- end
16
-
17
- private
18
-
19
- attr_reader :ci_project_token, :pipeline_stages
20
-
21
- # rubocop:disable Metrics/AbcSize
22
- def run!
23
- puts "Generating test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
24
-
25
- tests = Dir.glob(files).flat_map do |path|
26
- puts "Loading tests in #{path}"
27
-
28
- TestResults::JsonTestResults.new(path: path).to_a
29
- end
30
-
31
- tests = tests.select { |test| pipeline_stages.include? test.report["stage"] } unless pipeline_stages.empty?
32
-
33
- issue = gitlab.create_issue(
34
- title: "#{Time.now.to_date.iso8601} Test session report | #{Runtime::Env.qa_run_type}",
35
- description: generate_description(tests),
36
- labels: ['automation:bot-authored', 'E2E', 'triage report', pipeline_name_label, 'suppress-contributor-links'],
37
- confidential: confidential
38
- )
39
-
40
- # Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/295493
41
- unless Runtime::Env.qa_issue_url.to_s.empty?
42
- gitlab.create_issue_note(
43
- iid: issue.iid,
44
- note: "/relate #{Runtime::Env.qa_issue_url}")
45
- end
46
-
47
- issue&.web_url # Issue isn't created in dry-run mode
48
- end
49
- # rubocop:enable Metrics/AbcSize
50
-
51
- def generate_description(tests)
52
- <<~MARKDOWN.rstrip
53
- ## Session summary
54
-
55
- * Deploy version: #{Runtime::Env.deploy_version}
56
- * Deploy environment: #{Runtime::Env.deploy_environment}
57
- * Pipeline: #{Runtime::Env.pipeline_from_project_name} [#{Runtime::Env.ci_pipeline_id}](#{Runtime::Env.ci_pipeline_url})
58
- #{generate_summary(tests: tests)}
59
-
60
- #{generate_failed_jobs_listing}
61
-
62
- #{generate_stages_listing(tests)}
63
-
64
- #{generate_qa_issue_relation}
65
-
66
- #{generate_link_to_dashboard}
67
- MARKDOWN
68
- end
69
-
70
- def generate_summary(tests:, tests_by_status: nil)
71
- tests_by_status ||= tests.group_by(&:status)
72
- total = tests.size
73
- passed = tests_by_status['passed']&.size || 0
74
- failed = tests_by_status['failed']&.size || 0
75
- others = total - passed - failed
76
-
77
- <<~MARKDOWN.chomp
78
- * Total #{total} tests
79
- * Passed #{passed} tests
80
- * Failed #{failed} tests
81
- * #{others} other tests (usually skipped)
82
- MARKDOWN
83
- end
84
-
85
- def generate_failed_jobs_listing
86
- failed_jobs = fetch_pipeline_failed_jobs
87
- listings = failed_jobs.filter_map do |job|
88
- next if pipeline_stages.any? && !pipeline_stages.include?(job.stage)
89
-
90
- allowed_to_fail = ' (allowed to fail)' if job.allow_failure
91
-
92
- "* [#{job.name}](#{job.web_url})#{allowed_to_fail}"
93
- end.join("\n")
94
-
95
- <<~MARKDOWN.chomp if failed_jobs.any?
96
- ## Failed jobs
97
-
98
- #{listings}
99
- MARKDOWN
100
- end
101
-
102
- def generate_stages_listing(tests)
103
- generate_tests_by_stage(tests).map do |stage, tests_for_stage|
104
- tests_by_status = tests_for_stage.group_by(&:status)
105
-
106
- <<~MARKDOWN.chomp
107
- ### #{stage&.capitalize || 'Unknown'}
108
-
109
- #{generate_summary(
110
- tests: tests_for_stage, tests_by_status: tests_by_status)}
111
-
112
- #{generate_testcase_listing_by_status(
113
- tests: tests_for_stage, tests_by_status: tests_by_status)}
114
- MARKDOWN
115
- end.join("\n\n")
116
- end
117
-
118
- def generate_tests_by_stage(tests)
119
- # https://about.gitlab.com/handbook/product/product-categories/#devops-stages
120
- ordering = %w[
121
- manage
122
- plan
123
- create
124
- verify
125
- package
126
- release
127
- configure
128
- monitor
129
- secure
130
- defend
131
- growth
132
- fulfillment
133
- enablement
134
- self-managed
135
- saas
136
- ]
137
-
138
- tests.sort_by do |test|
139
- ordering.index(test.stage) || ordering.size
140
- end.group_by(&:stage)
141
- end
142
-
143
- def generate_testcase_listing_by_status(tests:, tests_by_status:)
144
- failed_tests = tests_by_status['failed']
145
- passed_tests = tests_by_status['passed']
146
- other_tests = tests.reject do |test|
147
- test.status == 'failed' || test.status == 'passed'
148
- end
149
-
150
- [
151
- (failed_listings(failed_tests) if failed_tests),
152
- (passed_listings(passed_tests) if passed_tests),
153
- (other_listings(other_tests) if other_tests.any?)
154
- ].compact.join("\n\n")
155
- end
156
-
157
- def failed_listings(failed_tests)
158
- generate_testcase_listing(failed_tests)
159
- end
160
-
161
- def passed_listings(passed_tests)
162
- <<~MARKDOWN.chomp
163
- <details><summary>Passed tests:</summary>
164
-
165
- #{generate_testcase_listing(passed_tests, passed: true)}
166
-
167
- </details>
168
- MARKDOWN
169
- end
170
-
171
- def other_listings(other_tests)
172
- <<~MARKDOWN.chomp
173
- <details><summary>Other tests:</summary>
174
-
175
- #{generate_testcase_listing(other_tests)}
176
-
177
- </details>
178
- MARKDOWN
179
- end
180
-
181
- def generate_testcase_listing(tests, passed: false)
182
- body = tests.group_by(&:testcase).map do |testcase, tests_with_same_testcase|
183
- tests_with_same_testcase.sort_by!(&:name)
184
- [
185
- generate_test_text(testcase, tests_with_same_testcase, passed),
186
- generate_test_job(tests_with_same_testcase),
187
- generate_test_status(tests_with_same_testcase),
188
- generate_test_actions(tests_with_same_testcase)
189
- ].join(' | ')
190
- end.join("\n")
191
-
192
- <<~MARKDOWN.chomp
193
- | Test | Job | Status | Action |
194
- | - | - | - | - |
195
- #{body}
196
- MARKDOWN
197
- end
198
-
199
- def generate_test_text(testcase, tests_with_same_testcase, passed)
200
- text = tests_with_same_testcase.map(&:name).uniq.join(', ')
201
-
202
- if testcase && !passed
203
- "[#{text}](#{testcase})"
204
- else
205
- text
206
- end
207
- end
208
-
209
- def generate_test_job(tests_with_same_testcase)
210
- tests_with_same_testcase.map do |test|
211
- ci_job_id = test.ci_job_url[/\d+\z/]
212
-
213
- "[#{ci_job_id}](#{test.ci_job_url})#{' ~"quarantine"' if test.quarantine?}"
214
- end.uniq.join(', ')
215
- end
216
-
217
- def generate_test_status(tests_with_same_testcase)
218
- tests_with_same_testcase.map(&:status).uniq.map do |status|
219
- %(~"#{status}")
220
- end.join(', ')
221
- end
222
-
223
- def generate_test_actions(tests_with_same_testcase)
224
- # All failed tests would be grouped together, meaning that
225
- # if one failed, all the tests here would be failed too.
226
- # So this check is safe. Same applies to 'passed'.
227
- # But all other status might be mixing together,
228
- # we cannot assume other statuses.
229
- if tests_with_same_testcase.first.status == 'failed'
230
- tests_having_failure_issue =
231
- tests_with_same_testcase.select(&:failure_issue)
232
-
233
- if tests_having_failure_issue.any?
234
- items = tests_having_failure_issue.uniq(&:failure_issue).map do |test|
235
- "<li>[ ] [failure issue](#{test.failure_issue})</li>"
236
- end.join(' ')
237
-
238
- "<ul>#{items}</ul>"
239
- else
240
- '<ul><li>[ ] failure issue exists or was created</li></ul>'
241
- end
242
- else
243
- '-'
244
- end
245
- end
246
-
247
- def generate_qa_issue_relation
248
- return unless Runtime::Env.qa_issue_url
249
-
250
- <<~MARKDOWN.chomp
251
- ## Release QA issue
252
-
253
- * #{Runtime::Env.qa_issue_url}
254
-
255
- /relate #{Runtime::Env.qa_issue_url}
256
- MARKDOWN
257
- end
258
-
259
- def generate_link_to_dashboard
260
- return unless Runtime::Env.qa_run_type
261
-
262
- <<~MARKDOWN.chomp
263
- ## Link to Grafana dashboard for run-type of #{Runtime::Env.qa_run_type}
264
-
265
- * https://dashboards.quality.gitlab.net/d/tR_SmBDVk/main-runs?orgId=1&refresh=1m&var-run_type=#{Runtime::Env.qa_run_type}
266
- MARKDOWN
267
- end
268
-
269
- def fetch_pipeline_failed_jobs
270
- failed_jobs = []
271
-
272
- ci_project_client = Gitlab.client(
273
- endpoint: Runtime::Env.ci_api_v4_url,
274
- private_token: ci_project_token)
275
-
276
- gitlab.handle_gitlab_client_exceptions do
277
- failed_jobs = ci_project_client.pipeline_jobs(
278
- Runtime::Env.ci_project_id,
279
- Runtime::Env.ci_pipeline_id,
280
- scope: 'failed')
281
- end
282
-
283
- failed_jobs
284
- end
285
- end
286
- end
287
- end
288
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'http'
4
- require 'json'
5
-
6
- module GitlabQuality
7
- module TestTooling
8
- module Report
9
- class TestHealthIssueFinder < ReportAsIssue
10
- HEALTH_PROBLEM_TYPE_TO_LABEL = {
11
- 'pass-after-retry' => 'test-health:pass-after-retry',
12
- 'slow' => 'test-health:slow',
13
- 'failures' => 'test-health:failures'
14
- }.freeze
15
-
16
- def initialize(health_problem_type: [], **kwargs)
17
- super(**kwargs)
18
-
19
- @health_problem_type = health_problem_type
20
- end
21
-
22
- def found_existing_unhealthy_test_issue?
23
- issue_url = invoke!
24
-
25
- !issue_url.nil? && !issue_url.empty?
26
- end
27
-
28
- def run!
29
- existing_issue_found = nil
30
-
31
- applicable_tests.each do |test|
32
- issues = find_issues_by_hash(test_hash(test), state: 'opened', labels: search_labels)
33
- next if issues.empty?
34
-
35
- existing_issue_found = issues.first.web_url
36
- puts "Found an existing test health issue of type #{health_problem_type} for test #{test.file}:#{test.line_number}: #{existing_issue_found}."
37
- break
38
- end
39
-
40
- puts "Did not find an existing test health issue of type #{health_problem_type}." unless existing_issue_found
41
-
42
- existing_issue_found
43
- end
44
-
45
- def applicable_tests
46
- applicable_tests = []
47
-
48
- TestResults::Builder.new(file_glob: files, token: token, project: project).test_results_per_file do |test_results|
49
- applicable_tests = test_results.select { |test| test_is_applicable?(test) }
50
- end
51
-
52
- applicable_tests
53
- end
54
-
55
- private
56
-
57
- attr_reader :health_problem_type
58
-
59
- # Be mindful about the number of tests this method would return,
60
- # as we will make at least one API request per test.
61
- def test_is_applicable?(test)
62
- expected_test_status =
63
- case health_problem_type
64
- when 'failures'
65
- 'failed'
66
- else
67
- 'passed'
68
- end
69
-
70
- test.status == expected_test_status
71
- end
72
-
73
- def search_labels
74
- ['test', HEALTH_PROBLEM_TYPE_TO_LABEL[health_problem_type]]
75
- end
76
- end
77
- end
78
- end
79
- end