gitlab_quality-test_tooling 1.14.0 → 1.17.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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +73 -70
  3. data/exe/flaky-test-issues +4 -4
  4. data/lefthook.yml +13 -0
  5. data/lib/gitlab_quality/test_tooling/gitlab_client/branches_client.rb +1 -1
  6. data/lib/gitlab_quality/test_tooling/gitlab_client/commits_client.rb +6 -4
  7. data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb +12 -13
  8. data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +6 -6
  9. data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_client.rb +6 -3
  10. data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_dry_client.rb +4 -2
  11. data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +41 -28
  12. data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +1 -1
  13. data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +78 -43
  14. data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +1 -4
  15. data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +0 -3
  16. data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +4 -8
  17. data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +5 -3
  18. data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +71 -78
  19. data/lib/gitlab_quality/test_tooling/runtime/env.rb +5 -1
  20. data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_blocking_processor.rb +33 -16
  21. data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_quarantine_processor.rb +34 -19
  22. data/lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb +21 -0
  23. data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +70 -9
  24. data/lib/gitlab_quality/test_tooling/version.rb +1 -1
  25. metadata +4 -4
@@ -13,11 +13,19 @@ module GitlabQuality
13
13
  # - Find issue by test hash
14
14
  # - Reopen issue if it already exists, but is closed
15
15
  class FlakyTestIssue < ReportAsIssue
16
+ include Concerns::GroupAndCategoryLabels
16
17
  include Concerns::IssueReports
17
18
 
18
- NEW_ISSUE_LABELS = Set.new(['type::maintenance', 'priority::3', 'severity::3', 'failure::flaky-test']).freeze
19
- FOUND_IN_MR_LABEL = 'found:in MR'
20
- FOUND_IN_MASTER_LABEL = 'found:master'
19
+ IDENTITY_LABELS = ['test', 'failure::flaky-test', 'automation:bot-authored'].freeze
20
+ NEW_ISSUE_LABELS = Set.new(['type::maintenance', 'priority::3', 'severity::3', *IDENTITY_LABELS]).freeze
21
+ SEARCH_LABELS = ['test'].freeze
22
+ FOUND_IN_MR_LABEL = '~"found:in MR"'
23
+ FOUND_IN_MASTER_LABEL = '~"found:master"'
24
+ REPORT_SECTION_HEADER = '### Flakiness reports'
25
+ REPORTS_DOCUMENTATION = <<~DOC
26
+ Flaky tests were detected, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html)
27
+ to learn more about how to reproduce them.
28
+ DOC
21
29
 
22
30
  def initialize(
23
31
  token:,
@@ -25,18 +33,16 @@ module GitlabQuality
25
33
  base_issue_labels: nil,
26
34
  confidential: false,
27
35
  dry_run: false,
28
- merge_request_iid: nil,
29
36
  project: nil,
30
37
  **_kwargs)
31
38
  super(token: token, input_files: input_files, project: project, confidential: confidential, dry_run: dry_run)
32
39
 
33
40
  @base_issue_labels = Set.new(base_issue_labels)
34
- @merge_request_iid = merge_request_iid
35
41
  end
36
42
 
37
43
  private
38
44
 
39
- attr_reader :base_issue_labels, :merge_request_iid
45
+ attr_reader :base_issue_labels
40
46
 
41
47
  def run!
42
48
  puts "Reporting flaky tests in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
@@ -44,62 +50,91 @@ module GitlabQuality
44
50
  TestResults::Builder.new(files).test_results_per_file do |test_results|
45
51
  puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
46
52
 
47
- test_results.each do |test|
48
- next if test.status != 'passed' # We only want failed tests that passed in the end
49
-
50
- create_flaky_issue(test)
51
- end
53
+ process_test_results(test_results)
52
54
  end
53
55
  end
54
56
 
55
- def new_issue_title(test)
56
- "Flaky test in #{super}"
57
- end
57
+ def process_test_results(test_results)
58
+ test_results.each do |test|
59
+ next unless test_is_applicable?(test)
58
60
 
59
- def new_issue_labels(_test)
60
- found_label =
61
- if !merge_request_iid || merge_request_iid.empty?
62
- FOUND_IN_MASTER_LABEL
63
- else
64
- FOUND_IN_MR_LABEL
65
- end
61
+ puts " => Reporting flakiness for test '#{test.name}'..."
66
62
 
67
- NEW_ISSUE_LABELS + base_issue_labels + [found_label]
63
+ issues = find_issues_by_hash(test_hash(test), state: 'opened', labels: SEARCH_LABELS)
64
+ issues << create_issue(test) if issues.empty?
65
+
66
+ update_reports(issues, test)
67
+ collect_issues(test, issues)
68
+ end
68
69
  end
69
70
 
70
- def new_issue_description(test)
71
- super + [
72
- "Flaky tests were detected, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) " \
73
- "to learn more about how to reproduce them.",
74
- initial_reports_section(test)
75
- ].compact.join("\n\n")
71
+ def test_is_applicable?(test)
72
+ test.status == 'passed' # We only want failed tests that passed in the end
76
73
  end
77
74
 
78
- def create_flaky_issue(test)
79
- puts " => Finding existing issues for flaky test '#{test.name}' (run time: #{test.run_time} seconds)..."
75
+ def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
76
+ (base_issue_labels + super).to_a
77
+ end
80
78
 
81
- issues = find_issues_by_hash(test_hash(test))
79
+ def update_reports(issues, test)
82
80
  issues.each do |issue|
83
- puts " => Existing issue link #{issue.web_url}."
81
+ puts " => Adding the flaky test to the existing issue: #{issue.web_url}"
82
+ add_report_to_issue(issue: issue, test: test, related_issues: (issues - [issue]))
83
+ end
84
+ end
84
85
 
85
- puts " => Adding the flaky test to the existing issue..."
86
- add_report_to_issue(issue, test)
86
+ def add_report_to_issue(issue:, test:, related_issues:)
87
+ reports_note = existing_reports_note(issue: issue)
88
+ note_body = [
89
+ report_body(reports_note: reports_note, test: test),
90
+ identity_labels_quick_action,
91
+ relate_issues_quick_actions(related_issues)
92
+ ].join("\n")
93
+
94
+ if reports_note
95
+ gitlab.edit_issue_note(
96
+ issue_iid: issue.iid,
97
+ note_id: reports_note.id,
98
+ note: note_body
99
+ )
100
+ else
101
+ gitlab.create_issue_note(iid: issue.iid, note: note_body)
102
+ end
103
+ end
87
104
 
88
- if issue.state == 'closed'
89
- puts " => Issue is closed. Reopening it."
90
- reopen_issue(issue)
91
- end
105
+ def existing_reports_note(issue:)
106
+ gitlab.find_issue_notes(iid: issue.iid).find do |note|
107
+ note.body.start_with?(REPORT_SECTION_HEADER)
92
108
  end
109
+ end
93
110
 
94
- create_issue(test) unless issues.any?
111
+ def report_body(reports_note:, test:)
112
+ increment_reports(
113
+ current_reports_content: reports_note&.body.to_s,
114
+ test: test,
115
+ reports_section_header: REPORT_SECTION_HEADER,
116
+ item_extra_content: found_label,
117
+ reports_extra_content: REPORTS_DOCUMENTATION
118
+ )
119
+ end
120
+
121
+ def found_label
122
+ if ENV.key?('CI_MERGE_REQUEST_IID')
123
+ FOUND_IN_MR_LABEL
124
+ else
125
+ FOUND_IN_MASTER_LABEL
126
+ end
95
127
  end
96
128
 
97
- def add_report_to_issue(issue, test)
98
- gitlab.edit_issue(iid: issue.iid, options: { description: add_report_to_issue_description(issue, test) })
129
+ def identity_labels_quick_action
130
+ labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
131
+ %(/label #{labels_list})
99
132
  end
100
133
 
101
- def reopen_issue(issue)
102
- gitlab.edit_issue(iid: issue.iid, options: { state_event: 'reopen' })
134
+ def relate_issues_quick_actions(issues)
135
+ issues.map do |issue|
136
+ "/relate #{issue.web_url}"
137
+ end.join("\n")
103
138
  end
104
139
  end
105
140
  end
@@ -205,12 +205,9 @@ module GitlabQuality
205
205
 
206
206
  def generate_test_text(testcase, tests_with_same_testcase, passed)
207
207
  text = tests_with_same_testcase.map(&:name).uniq.join(', ')
208
- encoded_text = ERB::Util.url_encode(text)
209
208
 
210
209
  if testcase && !passed
211
- # Workaround for reducing system notes on testcase issues
212
- # The first regex extracts the link to the issues list page from a link to a single issue show page by removing the issue id.
213
- "[#{text}](#{testcase.match(%r{[\s\S]+/[^/\d]+})}?state=opened&search=#{encoded_text})"
210
+ "[#{text}](#{testcase})"
214
211
  else
215
212
  text
216
213
  end
@@ -99,7 +99,6 @@ module GitlabQuality
99
99
  end
100
100
 
101
101
  def update_issue(issue:, spec_run_time:)
102
- state_event = issue.state == 'closed' ? 'reopen' : nil
103
102
  updated_description = <<~MARKDOWN.chomp
104
103
  #{issue.description}
105
104
 
@@ -110,8 +109,6 @@ module GitlabQuality
110
109
  description: updated_description
111
110
  }
112
111
 
113
- issue_attrs[:state_event] = state_event if state_event
114
-
115
112
  gitlab.edit_issue(iid: issue.iid, options: issue_attrs)
116
113
  puts " => Added a report in #{issue.web_url}!"
117
114
  end
@@ -190,8 +190,8 @@ module GitlabQuality
190
190
  end
191
191
 
192
192
  def failure_issues(test)
193
- find_issues_for_test(
194
- test,
193
+ find_issues_by_hash(
194
+ test_hash(test),
195
195
  state: 'opened',
196
196
  labels: base_issue_labels + Set.new(%w[test]),
197
197
  not_labels: exclude_labels_for_search
@@ -268,7 +268,7 @@ module GitlabQuality
268
268
  if stacktrace_match
269
269
  stacktrace_match[:stacktrace].gsub(/^\s*#.*$/, '').gsub(/^[[:space:]]+/, '').strip
270
270
  else
271
- puts " => [DEBUG] Stacktrace doesn't match the regex (#{regex}):\n----------------\n#{stacktrace}\n----------------\n"
271
+ puts " => [DEBUG] Stacktrace doesn't match the regex (#{regex})!"
272
272
  end
273
273
  end
274
274
 
@@ -347,7 +347,7 @@ module GitlabQuality
347
347
  state_event = issue.state == 'closed' ? 'reopen' : nil
348
348
 
349
349
  issue_attrs = {
350
- description: add_report_to_issue_description(issue, test),
350
+ description: increment_reports(current_reports_content: issue.description, test: test),
351
351
  labels: up_to_date_labels(test: test, issue: issue)
352
352
  }
353
353
  issue_attrs[:state_event] = state_event if state_event
@@ -356,10 +356,6 @@ module GitlabQuality
356
356
  puts " => Added a report in '#{issue.title}': #{issue.web_url}!"
357
357
  end
358
358
 
359
- def new_issue_title(test)
360
- "Failure in #{super}"
361
- end
362
-
363
359
  def screenshot_section(test)
364
360
  return unless test.screenshot?
365
361
 
@@ -60,7 +60,7 @@ module GitlabQuality
60
60
  | Description | `#{test.name}` |
61
61
  | Test level | #{test.level} |
62
62
  | Hash | `#{test_hash(test)}` |
63
- | Duration | #{test.run_time} seconds |
63
+ | Reference duration | #{test.run_time} seconds |
64
64
  | Expected duration | < #{test.max_duration_for_test} seconds |
65
65
  #{"| Test case | #{test.testcase} |" if test.testcase}
66
66
  DESCRIPTION
@@ -142,8 +142,10 @@ module GitlabQuality
142
142
  labels
143
143
  end
144
144
 
145
- def find_issues_by_hash(test_hash)
146
- search_options = { search: test_hash }
145
+ def find_issues_by_hash(test_hash, labels: Set.new, not_labels: Set.new, state: nil)
146
+ search_options = { search: test_hash, labels: labels.to_a, not: { labels: not_labels.to_a } }
147
+ search_options[:state] = state if state
148
+ search_options[:in] = 'description'
147
149
  gitlab.find_issues(options: search_options)
148
150
  end
149
151
 
@@ -10,16 +10,21 @@ module GitlabQuality
10
10
  # - Find issue by title (with test description or test file)
11
11
  # - Add test metadata, duration to the issue with group and category labels
12
12
  class SlowTestIssue < ReportAsIssue
13
- include TestTooling::Concerns::FindSetDri
14
13
  include Concerns::GroupAndCategoryLabels
14
+ include Concerns::IssueReports
15
15
 
16
- NEW_ISSUE_LABELS = Set.new(['test', 'type::maintenance', 'maintenance::performance', 'priority::3', 'severity::3', 'rspec profiling', 'rspec:slow test']).freeze
17
- SEARCH_LABELS = %w[test maintenance::performance].freeze
16
+ IDENTITY_LABELS = ['test', 'rspec:slow test', 'rspec profiling', 'automation:bot-authored'].freeze
17
+ NEW_ISSUE_LABELS = Set.new(['test', 'type::maintenance', 'maintenance::performance', 'priority::3', 'severity::3']).freeze
18
+ SEARCH_LABELS = ['test'].freeze
19
+ FOUND_IN_MR_LABEL = '~"found:in MR"'
20
+ FOUND_IN_MASTER_LABEL = '~"found:master"'
21
+ REPORT_SECTION_HEADER = '### Slowness reports'
22
+ REPORTS_DOCUMENTATION = <<~DOC
23
+ Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)
24
+ to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests).
18
25
 
19
- JOB_URL_REGEX = %r{(?<job_url>https://(?<host>[\w.]+)/(?<project_path>[\w\-./]+)/-/jobs/\d+)}
20
- REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?<pipeline_url>.+)\)$/
21
-
22
- MultipleIssuesFound = Class.new(StandardError)
26
+ Add `allowed_to_be_slow: true` to the RSpec test if this is a legit slow test and close the issue.
27
+ DOC
23
28
 
24
29
  private
25
30
 
@@ -29,99 +34,87 @@ module GitlabQuality
29
34
  TestResults::Builder.new(files).test_results_per_file do |test_results|
30
35
  puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
31
36
 
32
- test_results.each do |test|
33
- create_slow_issue(test) if test.slow_test?
34
- end
37
+ process_test_results(test_results)
35
38
  end
36
39
  end
37
40
 
38
- def new_issue_title(test)
39
- "Slow test in #{super}"
40
- end
41
+ def process_test_results(test_results)
42
+ test_results.each do |test|
43
+ next unless test.slow_test?
41
44
 
42
- def new_issue_description(test)
43
- super +
44
- <<~DESCRIPTION.chomp
45
- Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)
46
- to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests).
45
+ puts " => Reporting slowness for test '#{test.name}'..."
47
46
 
48
- Add `allowed_to_be_slow: true` to the RSpec test if this is a legit slow test and close the issue.
47
+ issues = find_issues_by_hash(test_hash(test), state: 'opened', labels: SEARCH_LABELS)
48
+ issues << create_issue(test) if issues.empty?
49
49
 
50
- #{reports_section(test)}
51
- DESCRIPTION
50
+ update_reports(issues, test)
51
+ collect_issues(test, issues)
52
+ end
52
53
  end
53
54
 
54
- def reports_section(test)
55
- <<~REPORTS
56
- ### Reports (1)
57
-
58
- #{report_list_item(test)}
59
- REPORTS
55
+ def test_is_applicable?(test)
56
+ test.slow_test?
60
57
  end
61
58
 
62
- def report_list_item(test)
63
- "1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')})"
59
+ def update_reports(issues, test)
60
+ issues.each do |issue|
61
+ puts " => Adding the slow test to the existing issue: #{issue.web_url}"
62
+ add_report_to_issue(issue: issue, test: test, related_issues: (issues - [issue]))
63
+ end
64
64
  end
65
65
 
66
- def slow_test_issues(test)
67
- find_issues_for_test(
68
- test,
69
- state: 'opened',
70
- labels: SEARCH_LABELS
71
- )
66
+ def add_report_to_issue(issue:, test:, related_issues:)
67
+ reports_note = existing_reports_note(issue: issue)
68
+ note_body = [
69
+ report_body(reports_note: reports_note, test: test),
70
+ identity_labels_quick_action,
71
+ relate_issues_quick_actions(related_issues)
72
+ ].join("\n")
73
+
74
+ if reports_note
75
+ gitlab.edit_issue_note(
76
+ issue_iid: issue.iid,
77
+ note_id: reports_note.id,
78
+ note: note_body
79
+ )
80
+ else
81
+ gitlab.create_issue_note(iid: issue.iid, note: note_body)
82
+ end
72
83
  end
73
84
 
74
- def create_slow_issue(test)
75
- puts " => Finding existing issues for slow test '#{test.name}' (run time: #{test.run_time} seconds)..."
85
+ def existing_reports_note(issue:)
86
+ gitlab.find_issue_notes(iid: issue.iid).find do |note|
87
+ note.body.start_with?(REPORT_SECTION_HEADER)
88
+ end
89
+ end
76
90
 
77
- issues = slow_test_issues(test)
91
+ def report_body(reports_note:, test:)
92
+ increment_reports(
93
+ current_reports_content: reports_note&.body.to_s,
94
+ test: test,
95
+ reports_section_header: REPORT_SECTION_HEADER,
96
+ item_extra_content: found_label,
97
+ reports_extra_content: REPORTS_DOCUMENTATION
98
+ )
99
+ end
78
100
 
79
- if issues.blank?
80
- issues << create_issue(test)
101
+ def found_label
102
+ if ENV.key?('CI_MERGE_REQUEST_IID')
103
+ FOUND_IN_MR_LABEL
81
104
  else
82
- issues.each do |issue|
83
- puts " => Existing issue link #{issue['web_url']}"
84
-
85
- update_reports(issue, test)
86
- end
105
+ FOUND_IN_MASTER_LABEL
87
106
  end
88
-
89
- collect_issues(test, issues)
90
- rescue MultipleIssuesFound => e
91
- warn(e.message)
92
107
  end
93
108
 
94
- def update_reports(issue, test)
95
- # We reopen closed issues to not lose any history
96
- state_event = issue.state == 'closed' ? 'reopen' : nil
97
-
98
- issue_attrs = {
99
- description: up_to_date_issue_description(issue.description, test)
100
- }
101
-
102
- issue_attrs[:state_event] = state_event if state_event
103
-
104
- gitlab.edit_issue(iid: issue.iid, options: issue_attrs)
105
- puts " => Added a report in '#{issue.title}': #{issue.web_url}!"
109
+ def identity_labels_quick_action
110
+ labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
111
+ %(/label #{labels_list})
106
112
  end
107
113
 
108
- def up_to_date_issue_description(issue_description, test)
109
- new_issue_description =
110
- if issue_description.include?('### Reports')
111
- # We count the number of existing reports.
112
- reports_count = issue_description
113
- .scan(REPORT_ITEM_REGEX)
114
- .size.to_i + 1
115
- issue_description.sub(/^### Reports.*$/, "### Reports (#{reports_count})")
116
- else # For issue with the legacy format, we add the Reports section
117
- reports_count = issue_description
118
- .scan(JOB_URL_REGEX)
119
- .size.to_i + 1
120
-
121
- "#{issue_description}\n\n### Reports (#{reports_count})"
122
- end
123
-
124
- "#{new_issue_description}\n#{report_list_item(test)}"
114
+ def relate_issues_quick_actions(issues)
115
+ issues.map do |issue|
116
+ "/relate #{issue.web_url}"
117
+ end.join("\n")
125
118
  end
126
119
  end
127
120
  end
@@ -25,7 +25,7 @@ module GitlabQuality
25
25
 
26
26
  ENV_VARIABLES.each do |env_name, method_name|
27
27
  define_method(method_name) do
28
- env_var_value_if_defined(env_name) || (instance_variable_get("@#{method_name}") if instance_variable_defined?("@#{method_name}"))
28
+ env_var_value_if_defined(env_name) || (instance_variable_get(:"@#{method_name}") if instance_variable_defined?(:"@#{method_name}"))
29
29
  end
30
30
  end
31
31
 
@@ -33,6 +33,10 @@ module GitlabQuality
33
33
  env_var_value_if_defined('QA_LOG_LEVEL')&.upcase || 'INFO'
34
34
  end
35
35
 
36
+ def gitlab_bot_username
37
+ env_var_value_if_defined('GITLAB_BOT_USERNAME') || 'gitlab-bot'
38
+ end
39
+
36
40
  def log_path
37
41
  env_var_value_if_defined('QA_LOG_PATH') || host_artifacts_dir
38
42
  end
@@ -14,41 +14,48 @@ module GitlabQuality
14
14
  # @param [TestMetaUpdater] context instance of TestMetaUpdater
15
15
  def execute(spec, context) # rubocop:disable Metrics/AbcSize
16
16
  @context = context
17
-
17
+ @existing_mrs = nil
18
18
  @file_path = spec["file_path"]
19
+ testcase = spec["testcase"]
19
20
  devops_stage = spec["stage"]
20
21
  product_group = spec["product_group"]
21
22
  @example_name = spec["name"]
22
- @mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name)
23
-
24
- return unless proceed_with_merge_request?
23
+ @mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name).truncate(72, omission: '')
25
24
 
26
25
  @file_contents = context.get_file_contents(file_path)
27
26
 
28
- new_content, changed_line_no = add_blocking_metadata
27
+ new_content, @changed_line_no = add_blocking_metadata
29
28
 
30
- return if changed_line_no.negative?
29
+ return unless proceed_with_merge_request?
31
30
 
32
31
  branch = context.create_branch("blocking-promotion-#{SecureRandom.hex(4)}", example_name, context.ref)
33
32
 
34
33
  context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
35
34
  Promote end-to-end test to blocking
36
35
 
37
- Promote to blocking: #{example_name}
36
+ #{"Promote to blocking: #{example_name}".truncate(72)}
38
37
  COMMIT_MESSAGE
39
38
 
40
- assignee_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage)
39
+ reviewer_id, assignee_handle = context.fetch_dri_id(product_group, devops_stage)
40
+
41
+ gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
41
42
 
42
- merge_request = context.create_merge_request(mr_title, branch, assignee_id) do
43
+ merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id, [reviewer_id]) do
43
44
  <<~MARKDOWN
44
45
  ## What does this MR do?
45
46
 
46
47
  Promotes the test [`#{example_name}`](https://gitlab.com/#{context.project}/-/blob/#{context.ref}/#{file_path}#L#{changed_line_no + 1})
47
48
  to the blocking bucket
48
49
 
50
+ This test was identified in the reliable e2e test report: #{context.report_issue}
51
+
52
+ [Testcase link](#{testcase})
53
+
54
+ [Spec metrics link](#{context.single_spec_metrics_link(example_name)})
49
55
 
50
56
  /label ~"Quality" ~"QA" ~"type::maintenance"
51
57
  /label ~"devops::#{devops_stage}"
58
+ #{context.label_from_product_group(product_group)}
52
59
 
53
60
  <div align="center">
54
61
  (This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
@@ -62,6 +69,11 @@ module GitlabQuality
62
69
  If you think this MR should not be merged, please close it and add a note of the reason to the blocking report: #{context.report_issue}
63
70
  MARKDOWN
64
71
 
72
+ if merge_request
73
+ context.add_processed_record({ file_path => changed_line_no })
74
+ Runtime::Logger.info(" Created MR for promotion to blocking: #{merge_request.web_url}")
75
+ end
76
+
65
77
  merge_request
66
78
  end
67
79
 
@@ -82,15 +94,20 @@ module GitlabQuality
82
94
 
83
95
  private
84
96
 
85
- attr_reader :context, :file_path, :file_contents, :example_name, :mr_title
97
+ attr_reader :context, :file_path, :file_contents, :example_name, :mr_title, :changed_line_no
86
98
 
87
99
  # Checks if there is already an MR open
88
100
  #
89
101
  # @return [Boolean]
90
- def proceed_with_merge_request?
91
- open_mrs = context.existing_merge_requests(title: mr_title)
92
- if open_mrs.any?
93
- puts " An open MR already exists for '#{example_name}': #{open_mrs.first['web_url']}. Will not proceed with creating MR."
102
+ def proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
103
+ if changed_line_no.negative?
104
+ Runtime::Logger.info(" No lines were changed in #{file_path}. Will not proceed with creating MR.")
105
+ return false
106
+ elsif context.record_processed?(file_path, changed_line_no)
107
+ Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
108
+ return false
109
+ elsif existing_mrs&.any?
110
+ Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
94
111
  return false
95
112
  end
96
113
 
@@ -109,8 +126,8 @@ module GitlabQuality
109
126
  end
110
127
 
111
128
  context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
112
- if line.include?(',')
113
- line[line.index(',')] = format(BLOCKING_METADATA, suffix: ',')
129
+ if line.sub(DESCRIPTION_REGEX, '').include?(',')
130
+ line[line.index(',', end_of_description_index(line))] = format(BLOCKING_METADATA, suffix: ',')
114
131
  else
115
132
  line[line.rindex(' ')] = format(BLOCKING_METADATA, suffix: ' ')
116
133
  end