gitlab_quality-test_tooling 1.22.0 → 1.23.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 755ed1e2f1e0a7ac2e1d1264eeed686c76fb23c90d5eb6ebfbcaa64f931045f6
4
- data.tar.gz: 5bbc1d0b7b93d8db268e2b0b0081b3878ff4117e97b2a3426023180cdec945ec
3
+ metadata.gz: c63a7a25d0e3a05f515954353898bb8d0dcb9e7a90634087e738f5cb16174c57
4
+ data.tar.gz: 386833833dc0fd5cdc6334d3d613a38544fbc885c5ccb396070614f0e889555f
5
5
  SHA512:
6
- metadata.gz: 3078229a51d390624b6e148444735f38581383319e377db26986d7d1b77d2b53447b440d1fc3eecb8eaf52e92a3cc0c927e4ab44930e34bfb83165e88b011097
7
- data.tar.gz: a0ac07071de3a7fe50144f2527faff4a6e5c5530477681b5ebf1137fc1d89aa8991f2aa88ef255bd0ffa84c3a3d8440ab91e3b3b7ef4c28fed9ae3b11c7709e0
6
+ metadata.gz: bb13a1c06a626b1152115262e0606480e78eb9aaafd6ed5e673cd72d6b1a268ab7659c020dbab743ebb872e71c1332905319d0ed6970cbde8bf0168c208fa2c3
7
+ data.tar.gz: 40652c63561438f37f5610e5b22f93c6a0404c53d4dc65b17713bbf5469bf769dd13691361f02f5aa8229aab706f7e349db6abf663cf45745aaa040456461db7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab_quality-test_tooling (1.22.0)
4
+ gitlab_quality-test_tooling (1.23.0)
5
5
  activesupport (>= 6.1, < 7.2)
6
6
  amatch (~> 0.4.1)
7
7
  gitlab (~> 4.19)
@@ -12,11 +12,57 @@ module GitlabQuality
12
12
  REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?<pipeline_url>\S+)\)/
13
13
  LATEST_REPORTS_TO_SHOW = 10
14
14
 
15
+ class ReportsList
16
+ def initialize(preserved_content:, section_header:, reports:, extra_content:)
17
+ @preserved_content = preserved_content
18
+ @section_header = section_header
19
+ @reports = reports
20
+ @extra_content = extra_content
21
+ end
22
+
23
+ def self.report_list_item(test, item_extra_content: nil)
24
+ "1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')}) #{item_extra_content}".strip
25
+ end
26
+
27
+ def reports_count
28
+ reports.size
29
+ end
30
+
31
+ def to_s
32
+ [
33
+ preserved_content,
34
+ "#{section_header} (#{reports_count})",
35
+ reports_list(reports),
36
+ extra_content
37
+ ].reject(&:blank?).compact.join("\n\n")
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :preserved_content, :section_header, :reports, :extra_content
43
+
44
+ def reports_list(reports)
45
+ sorted_reports = reports.sort.reverse
46
+
47
+ if sorted_reports.size > LATEST_REPORTS_TO_SHOW
48
+ [
49
+ "Last 10 reports:",
50
+ sorted_reports[...LATEST_REPORTS_TO_SHOW].join("\n"),
51
+ "<details><summary>See #{sorted_reports.size - LATEST_REPORTS_TO_SHOW} more reports</summary>",
52
+ sorted_reports[LATEST_REPORTS_TO_SHOW..].join("\n"),
53
+ "</details>"
54
+ ].join("\n\n")
55
+ else
56
+ sorted_reports.join("\n")
57
+ end
58
+ end
59
+ end
60
+
15
61
  def initial_reports_section(test)
16
62
  <<~REPORTS
17
63
  ### Reports (1)
18
64
 
19
- #{report_list_item(test)}
65
+ #{ReportsList.report_list_item(test)}
20
66
  REPORTS
21
67
  end
22
68
 
@@ -27,14 +73,14 @@ module GitlabQuality
27
73
  item_extra_content: nil,
28
74
  reports_extra_content: nil)
29
75
  preserved_content = current_reports_content.split(reports_section_header).first&.strip
30
- reports = report_lines(current_reports_content) + [report_list_item(test, item_extra_content: item_extra_content)]
31
-
32
- [
33
- preserved_content,
34
- "#{reports_section_header} (#{reports.size})",
35
- reports_list(reports),
36
- reports_extra_content
37
- ].reject(&:blank?).compact.join("\n\n")
76
+ reports = report_lines(current_reports_content) + [ReportsList.report_list_item(test, item_extra_content: item_extra_content)]
77
+
78
+ ReportsList.new(
79
+ preserved_content: preserved_content,
80
+ section_header: reports_section_header,
81
+ reports: reports,
82
+ extra_content: reports_extra_content
83
+ )
38
84
  end
39
85
 
40
86
  def failed_issue_job_url(issue)
@@ -55,40 +101,12 @@ module GitlabQuality
55
101
  content.lines.grep(REPORT_ITEM_REGEX).map(&:strip)
56
102
  end
57
103
 
58
- def reports_list(reports)
59
- sorted_reports = reports.sort.reverse
60
-
61
- if sorted_reports.size > LATEST_REPORTS_TO_SHOW
62
- [
63
- "Last 10 reports:",
64
- sorted_reports[...LATEST_REPORTS_TO_SHOW].join("\n"),
65
- "<details><summary>See #{sorted_reports.size - LATEST_REPORTS_TO_SHOW} more reports</summary>",
66
- sorted_reports[LATEST_REPORTS_TO_SHOW..].join("\n"),
67
- "</details>"
68
- ].join("\n\n")
69
- else
70
- sorted_reports.join("\n")
71
- end
72
- end
73
-
74
- def report_list_item(test, item_extra_content: nil)
75
- "1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')}) #{item_extra_content}".strip
76
- end
77
-
78
104
  def job_urls_from_description(issue_description, regex)
79
105
  issue_description.lines.filter_map do |line|
80
106
  match = line.match(regex)
81
107
  match[:job_url] if match
82
108
  end
83
109
  end
84
-
85
- def test_captures_to_report_items(test_captures)
86
- test_captures.map do |ci_job_url, _, _|
87
- report_list_item(GitlabQuality::TestTooling::TestResult::JsonTestResult.new(
88
- 'ci_job_url' => ci_job_url
89
- ))
90
- end
91
- end
92
110
  end
93
111
  end
94
112
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'amatch'
4
-
5
3
  module GitlabQuality
6
4
  module TestTooling
7
5
  module Report
@@ -15,7 +13,6 @@ module GitlabQuality
15
13
  class FailedTestIssue < ReportAsIssue
16
14
  include Concerns::GroupAndCategoryLabels
17
15
  include Concerns::IssueReports
18
- include Amatch
19
16
 
20
17
  IDENTITY_LABELS = ['test', 'automation:bot-authored'].freeze
21
18
  NEW_ISSUE_LABELS = Set.new(['type::maintenance', 'failure::new', 'priority::3', 'severity::3', *IDENTITY_LABELS]).freeze
@@ -25,10 +22,6 @@ module GitlabQuality
25
22
  REPORTS_DISCUSSION_HEADER = '### Failure reports'
26
23
  REPORT_SECTION_HEADER = '#### Failure reports'
27
24
 
28
- IGNORED_FAILURES = [
29
- 'Net::ReadTimeout',
30
- '403 Forbidden - Your account has been blocked'
31
- ].freeze
32
25
  FAILURE_STACKTRACE_REGEX = %r{(?:(?:.*Failure/Error:(?<stacktrace>.+))|(?<stacktrace>.+))}m
33
26
  ISSUE_STACKTRACE_REGEX = /##### Stack trace\s*(```)#{FAILURE_STACKTRACE_REGEX}(```)\n*/m
34
27
  DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION = 0.15
@@ -92,20 +85,21 @@ module GitlabQuality
92
85
  end
93
86
  end
94
87
 
95
- def add_report_to_issue(issue:, test:, related_issues:)
88
+ def add_report_to_issue(issue:, test:, related_issues:) # rubocop:disable Metrics/AbcSize:
96
89
  reports_discussion = find_or_create_reports_discussion(issue: issue)
97
- failure_discussion_note = find_failure_discussion_note(issue: issue, test: test, reports_discussion: reports_discussion)
90
+ current_reports_note = find_failure_discussion_note(issue: issue, test: test, reports_discussion: reports_discussion)
91
+ new_reports_list = add_report_for_test(current_reports_content: current_reports_note&.body.to_s, test: test)
98
92
 
99
93
  note_body = [
100
- report_body(reports_note: failure_discussion_note, test: test),
94
+ new_reports_list.to_s,
101
95
  identity_labels_quick_action,
102
96
  relate_issues_quick_actions(related_issues)
103
97
  ].join("\n")
104
98
 
105
- if failure_discussion_note
99
+ if current_reports_note
106
100
  gitlab.edit_issue_note(
107
101
  issue_iid: issue.iid,
108
- note_id: failure_discussion_note.id,
102
+ note_id: current_reports_note.id,
109
103
  note: note_body
110
104
  )
111
105
  else
@@ -158,6 +152,8 @@ module GitlabQuality
158
152
 
159
153
  clean_test_stacktrace = cleaned_stack_trace_from_test(test: test)
160
154
 
155
+ # We're skipping the first note of the discussion as this is the "non-collapsible note", aka
156
+ # the "header note", which doesn't contain any stack trace.
161
157
  reports_discussion.notes[1..].each_with_object({}) do |note, memo|
162
158
  clean_note_stacktrace = cleaned_stack_trace_from_note(issue: issue, note: note)
163
159
  diff_ratio = diff_ratio_between_test_and_note_stacktraces(
@@ -171,7 +167,7 @@ module GitlabQuality
171
167
  end
172
168
 
173
169
  def cleaned_stack_trace_from_test(test:)
174
- sanitize_stacktrace(stacktrace: full_stacktrace(test: test), regex: FAILURE_STACKTRACE_REGEX) || full_stacktrace(test: test)
170
+ sanitize_stacktrace(stacktrace: test.full_stacktrace, regex: FAILURE_STACKTRACE_REGEX) || test.full_stacktrace
175
171
  end
176
172
 
177
173
  def cleaned_stack_trace_from_note(issue:, note:)
@@ -191,53 +187,33 @@ module GitlabQuality
191
187
  end
192
188
  end
193
189
 
194
- def full_stacktrace(test:)
195
- test.failures.each do |failure|
196
- message = failure['message'] || ""
197
- message_lines = failure['message_lines'] || []
198
-
199
- next if IGNORED_FAILURES.any? { |e| message.include?(e) }
200
-
201
- return message_lines.empty? ? message : message_lines.join("\n")
202
- end
203
- end
204
-
205
190
  def diff_ratio_between_test_and_note_stacktraces(issue:, note:, test_stacktrace:, note_stacktrace:)
206
191
  return if note_stacktrace.nil?
207
192
 
208
- diff_ratio = compare_stack_traces(test_stacktrace, note_stacktrace)
193
+ stack_trace_comparator = StackTraceComparator.new(test_stacktrace, note_stacktrace)
209
194
 
210
- if diff_ratio <= max_diff_ratio
211
- puts " => [DEBUG] Note #{issue.web_url}#note_#{note.id} has an acceptable diff ratio of #{(diff_ratio * 100).round(2)}%."
195
+ if stack_trace_comparator.lower_or_equal_to_diff_ratio?(max_diff_ratio)
196
+ puts " => [DEBUG] Note #{issue.web_url}#note_#{note.id} has an acceptable diff ratio of #{stack_trace_comparator.diff_percent}%."
212
197
  # The `Gitlab::ObjectifiedHash` class overrides `#hash` which is used by `Hash#[]=` to compute the hash key.
213
198
  # This leads to a `TypeError Exception: no implicit conversion of Hash into Integer` error, so we convert the object to a hash before using it as a Hash key.
214
199
  # See:
215
200
  # - https://gitlab.com/gitlab-org/gitlab-qa/-/merge_requests/587#note_453336995
216
201
  # - https://github.com/NARKOZ/gitlab/commit/cbdbd1e32623f018a8fae39932a8e3bc4d929abb?_pjax=%23js-repo-pjax-container#r44484494
217
- diff_ratio
202
+ stack_trace_comparator.diff_ratio
218
203
  else
219
- puts " => [DEBUG] Found note #{issue.web_url}#note_#{note.id} but stacktraces are too different (#{(diff_ratio * 100).round(2)}%).\n"
204
+ puts " => [DEBUG] Found note #{issue.web_url}#note_#{note.id} but stacktraces are too different (#{stack_trace_comparator.diff_percent}%).\n"
220
205
  puts " => [DEBUG] Issue stacktrace:\n----------------\n#{note_stacktrace}\n----------------\n"
221
206
  puts " => [DEBUG] Failure stacktrace:\n----------------\n#{test_stacktrace}\n----------------\n"
222
207
  end
223
208
  end
224
209
 
225
- def compare_stack_traces(stack_trace_first, stack_trace_second)
226
- calculate_diff_ratio(stack_trace_first, stack_trace_second)
227
- end
228
-
229
- def calculate_diff_ratio(stack_trace_first, stack_trace_second)
230
- distance = Levenshtein.new(stack_trace_first).match(stack_trace_second)
231
- distance.zero? ? 0.0 : (distance.to_f / stack_trace_first.size).round(3)
232
- end
233
-
234
- def report_body(reports_note:, test:)
210
+ def add_report_for_test(current_reports_content:, test:)
235
211
  increment_reports(
236
- current_reports_content: reports_note&.body.to_s,
212
+ current_reports_content: current_reports_content,
237
213
  test: test,
238
214
  reports_section_header: REPORT_SECTION_HEADER,
239
215
  item_extra_content: found_label,
240
- reports_extra_content: "##### Stack trace\n\n```\n#{full_stacktrace(test: test)}\n```"
216
+ reports_extra_content: "##### Stack trace\n\n```\n#{test.full_stacktrace}\n```"
241
217
  )
242
218
  end
243
219
 
@@ -83,17 +83,20 @@ module GitlabQuality
83
83
  end
84
84
 
85
85
  def add_report_to_issue(issue:, test:, related_issues:)
86
- reports_note = existing_reports_note(issue: issue)
86
+ current_reports_note = existing_reports_note(issue: issue)
87
+ new_reports_list = add_report_for_test(current_reports_content: current_reports_note&.body.to_s, test: test)
88
+
87
89
  note_body = [
88
- report_body(reports_note: reports_note, test: test),
90
+ new_reports_list.to_s,
91
+ flakiness_status_labels_quick_action(new_reports_list.reports_count),
89
92
  identity_labels_quick_action,
90
93
  relate_issues_quick_actions(related_issues)
91
94
  ].join("\n")
92
95
 
93
- if reports_note
96
+ if current_reports_note
94
97
  gitlab.edit_issue_note(
95
98
  issue_iid: issue.iid,
96
- note_id: reports_note.id,
99
+ note_id: current_reports_note.id,
97
100
  note: note_body
98
101
  )
99
102
  else
@@ -107,9 +110,9 @@ module GitlabQuality
107
110
  end
108
111
  end
109
112
 
110
- def report_body(reports_note:, test:)
113
+ def add_report_for_test(current_reports_content:, test:)
111
114
  increment_reports(
112
- current_reports_content: reports_note&.body.to_s,
115
+ current_reports_content: current_reports_content,
113
116
  test: test,
114
117
  reports_section_header: REPORT_SECTION_HEADER,
115
118
  item_extra_content: found_label,
@@ -125,6 +128,19 @@ module GitlabQuality
125
128
  end
126
129
  end
127
130
 
131
+ def flakiness_status_labels_quick_action(reports_count)
132
+ case reports_count
133
+ when 1000..Float::INFINITY
134
+ '/label ~"flakiness::1"'
135
+ when 500..999
136
+ '/label ~"flakiness::2"'
137
+ when 10..499
138
+ '/label ~"flakiness::3"'
139
+ else
140
+ '/label ~"flakiness::4"'
141
+ end
142
+ end
143
+
128
144
  def identity_labels_quick_action
129
145
  labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
130
146
  %(/label #{labels_list})
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'nokogiri'
4
4
  require 'rubygems/text'
5
- require 'amatch'
6
5
 
7
6
  module GitlabQuality
8
7
  module TestTooling
@@ -17,7 +16,6 @@ module GitlabQuality
17
16
  include TestTooling::Concerns::FindSetDri
18
17
  include Concerns::GroupAndCategoryLabels
19
18
  include Concerns::IssueReports
20
- include Amatch
21
19
 
22
20
  DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION = 0.15
23
21
  SYSTEMIC_EXCEPTIONS_THRESHOLD = 10
@@ -26,10 +24,6 @@ module GitlabQuality
26
24
  ISSUE_STACKTRACE_REGEX = /### Stack trace\s*(```)#{FAILURE_STACKTRACE_REGEX}(```)\n*\n###/m
27
25
 
28
26
  NEW_ISSUE_LABELS = Set.new(%w[test failure::new priority::2]).freeze
29
- IGNORED_FAILURES = [
30
- 'Net::ReadTimeout',
31
- '403 Forbidden - Your account has been blocked'
32
- ].freeze
33
27
  SCREENSHOT_IGNORED_ERRORS = ['500 Internal Server Error', 'fabricate_via_api!', 'Error Code 500'].freeze
34
28
 
35
29
  MultipleIssuesFound = Class.new(StandardError)
@@ -168,7 +162,9 @@ module GitlabQuality
168
162
 
169
163
  next if pipeline != pipeline_env_from_job_url(job_url_from_issue)
170
164
 
171
- compare_stack_traces(cleaned_stack_trace_from_test(test), cleaned_stack_trace_from_issue(issue)) < max_diff_ratio
165
+ stack_trace_comparator = StackTraceComparator.new(cleaned_stack_trace_from_test(test), cleaned_stack_trace_from_issue(issue))
166
+
167
+ stack_trace_comparator.lower_than_diff_ratio?(max_diff_ratio)
172
168
  end
173
169
  end
174
170
 
@@ -196,17 +192,6 @@ module GitlabQuality
196
192
  )
197
193
  end
198
194
 
199
- def full_stacktrace(test)
200
- test.failures.each do |failure|
201
- message = failure['message'] || ""
202
- message_lines = failure['message_lines'] || []
203
-
204
- next if IGNORED_FAILURES.any? { |e| message.include?(e) }
205
-
206
- return message_lines.empty? ? message : message_lines.join("\n")
207
- end
208
- end
209
-
210
195
  def cleaned_stack_trace_from_issue(issue)
211
196
  relevant_issue_stacktrace = find_issue_stacktrace(issue)
212
197
  return unless relevant_issue_stacktrace
@@ -215,20 +200,11 @@ module GitlabQuality
215
200
  end
216
201
 
217
202
  def cleaned_stack_trace_from_test(test)
218
- test_failure_stacktrace = sanitize_stacktrace(full_stacktrace(test),
219
- FAILURE_STACKTRACE_REGEX) || full_stacktrace(test)
203
+ test_failure_stacktrace = sanitize_stacktrace(test.full_stacktrace,
204
+ FAILURE_STACKTRACE_REGEX) || test.full_stacktrace
220
205
  remove_unique_resource_names(test_failure_stacktrace)
221
206
  end
222
207
 
223
- def compare_stack_traces(stack_trace_first, stack_trace_second)
224
- calculate_diff_ratio(stack_trace_first, stack_trace_second)
225
- end
226
-
227
- def calculate_diff_ratio(stack_trace_first, stack_trace_second)
228
- distance = Levenshtein.new(stack_trace_first).match(stack_trace_second)
229
- distance.zero? ? 0.0 : (distance.to_f / stack_trace_first.size).round(3)
230
- end
231
-
232
208
  def find_relevant_failure_issues(test) # rubocop:disable Metrics/AbcSize
233
209
  clean_first_test_failure_stacktrace = cleaned_stack_trace_from_test(test)
234
210
  # Search with the `search` param returns 500 errors, so we filter by `base_issue_labels` and then filter further in Ruby
@@ -236,17 +212,18 @@ module GitlabQuality
236
212
  clean_relevant_issue_stacktrace = cleaned_stack_trace_from_issue(issue)
237
213
  next if clean_relevant_issue_stacktrace.nil?
238
214
 
239
- diff_ratio = compare_stack_traces(clean_first_test_failure_stacktrace, clean_relevant_issue_stacktrace)
240
- if diff_ratio <= max_diff_ratio
241
- puts " => [DEBUG] Issue #{issue.web_url} has an acceptable diff ratio of #{(diff_ratio * 100).round(2)}%."
215
+ stack_trace_comparator = StackTraceComparator.new(clean_first_test_failure_stacktrace, clean_relevant_issue_stacktrace)
216
+
217
+ if stack_trace_comparator.lower_or_equal_to_diff_ratio?(max_diff_ratio)
218
+ puts " => [DEBUG] Issue #{issue.web_url} has an acceptable diff ratio of #{stack_trace_comparator.diff_percent}%."
242
219
  # The `Gitlab::ObjectifiedHash` class overrides `#hash` which is used by `Hash#[]=` to compute the hash key.
243
220
  # This leads to a `TypeError Exception: no implicit conversion of Hash into Integer` error, so we convert the object to a hash before using it as a Hash key.
244
221
  # See:
245
222
  # - https://gitlab.com/gitlab-org/gitlab-qa/-/merge_requests/587#note_453336995
246
223
  # - https://github.com/NARKOZ/gitlab/commit/cbdbd1e32623f018a8fae39932a8e3bc4d929abb?_pjax=%23js-repo-pjax-container#r44484494
247
- memo[issue.to_h] = diff_ratio
224
+ memo[issue.to_h] = stack_trace_comparator.diff_ratio
248
225
  else
249
- puts " => [DEBUG] Found issue #{issue.web_url} but stacktraces are too different (#{(diff_ratio * 100).round(2)}%).\n"
226
+ puts " => [DEBUG] Found issue #{issue.web_url} but stacktraces are too different (#{stack_trace_comparator.diff_percent}%).\n"
250
227
  puts " => [DEBUG] Issue stacktrace:\n----------------\n#{clean_relevant_issue_stacktrace}\n----------------\n"
251
228
  puts " => [DEBUG] Failure stacktrace:\n----------------\n#{clean_first_test_failure_stacktrace}\n----------------\n"
252
229
  end
@@ -295,7 +272,7 @@ module GitlabQuality
295
272
  def new_issue_description(test)
296
273
  super + [
297
274
  "\n### Stack trace",
298
- "```\n#{full_stacktrace(test)}\n```",
275
+ "```\n#{test.full_stacktrace}\n```",
299
276
  screenshot_section(test),
300
277
  system_log_errors_section(test),
301
278
  initial_reports_section(test)
@@ -345,7 +322,7 @@ module GitlabQuality
345
322
  state_event = issue.state == 'closed' ? 'reopen' : nil
346
323
 
347
324
  issue_attrs = {
348
- description: increment_reports(current_reports_content: issue.description, test: test),
325
+ description: increment_reports(current_reports_content: issue.description, test: test).to_s,
349
326
  labels: up_to_date_labels(test: test, issue: issue)
350
327
  }
351
328
  issue_attrs[:state_event] = state_event if state_event
@@ -357,7 +334,7 @@ module GitlabQuality
357
334
  def screenshot_section(test)
358
335
  return unless test.screenshot?
359
336
 
360
- failure = full_stacktrace(test)
337
+ failure = test.full_stacktrace
361
338
  return if SCREENSHOT_IGNORED_ERRORS.any? { |e| failure.include?(e) }
362
339
 
363
340
  relative_url = gitlab.upload_file(file_fullpath: test.screenshot_image)
@@ -374,7 +351,7 @@ module GitlabQuality
374
351
  return false unless test.failures?
375
352
 
376
353
  puts " => Systemic failures detected: #{systemic_failure_messages}" if systemic_failure_messages.any?
377
- failure_to_ignore = IGNORED_FAILURES + systemic_failure_messages
354
+ failure_to_ignore = TestResult::BaseTestResult::IGNORED_FAILURES + systemic_failure_messages
378
355
 
379
356
  reason = ignored_failure_reason(test.failures, failure_to_ignore)
380
357
 
@@ -64,17 +64,19 @@ module GitlabQuality
64
64
  end
65
65
 
66
66
  def add_report_to_issue(issue:, test:, related_issues:)
67
- reports_note = existing_reports_note(issue: issue)
67
+ current_reports_note = existing_reports_note(issue: issue)
68
+ new_reports_list = add_report_for_test(current_reports_content: current_reports_note&.body.to_s, test: test)
69
+
68
70
  note_body = [
69
- report_body(reports_note: reports_note, test: test),
71
+ new_reports_list.to_s,
70
72
  identity_labels_quick_action,
71
73
  relate_issues_quick_actions(related_issues)
72
74
  ].join("\n")
73
75
 
74
- if reports_note
76
+ if current_reports_note
75
77
  gitlab.edit_issue_note(
76
78
  issue_iid: issue.iid,
77
- note_id: reports_note.id,
79
+ note_id: current_reports_note.id,
78
80
  note: note_body
79
81
  )
80
82
  else
@@ -88,9 +90,9 @@ module GitlabQuality
88
90
  end
89
91
  end
90
92
 
91
- def report_body(reports_note:, test:)
93
+ def add_report_for_test(current_reports_content:, test:)
92
94
  increment_reports(
93
- current_reports_content: reports_note&.body.to_s,
95
+ current_reports_content: current_reports_content,
94
96
  test: test,
95
97
  reports_section_header: REPORT_SECTION_HEADER,
96
98
  item_extra_content: "(#{test.run_time} seconds) #{found_label}",
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'amatch'
4
+
5
+ module GitlabQuality
6
+ module TestTooling
7
+ class StackTraceComparator
8
+ include Amatch
9
+
10
+ def initialize(first_trace, second_trace)
11
+ @first_trace = first_trace
12
+ @second_trace = second_trace
13
+ end
14
+
15
+ def diff_ratio
16
+ @diff_ratio ||= (1 - first_trace.levenshtein_similar(second_trace))
17
+ end
18
+
19
+ def diff_percent
20
+ (diff_ratio * 100).round(2)
21
+ end
22
+
23
+ def lower_than_diff_ratio?(max_diff_ratio)
24
+ diff_ratio < max_diff_ratio
25
+ end
26
+
27
+ def lower_or_equal_to_diff_ratio?(max_diff_ratio)
28
+ diff_ratio <= max_diff_ratio
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :first_trace, :second_trace
34
+ end
35
+ end
36
+ end
@@ -4,6 +4,11 @@ module GitlabQuality
4
4
  module TestTooling
5
5
  module TestResult
6
6
  class BaseTestResult
7
+ IGNORED_FAILURES = [
8
+ 'Net::ReadTimeout',
9
+ '403 Forbidden - Your account has been blocked'
10
+ ].freeze
11
+
7
12
  attr_reader :report
8
13
 
9
14
  def initialize(report)
@@ -41,6 +46,17 @@ module GitlabQuality
41
46
  def failures?
42
47
  failures.any?
43
48
  end
49
+
50
+ def full_stacktrace
51
+ failures.each do |failure|
52
+ message = failure['message'] || ""
53
+ message_lines = failure['message_lines'] || []
54
+
55
+ next if IGNORED_FAILURES.any? { |e| message.include?(e) }
56
+
57
+ return message_lines.empty? ? message : message_lines.join("\n")
58
+ end
59
+ end
44
60
  end
45
61
  end
46
62
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GitlabQuality
4
4
  module TestTooling
5
- VERSION = "1.22.0"
5
+ VERSION = "1.23.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: 1.22.0
4
+ version: 1.23.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: 2024-04-10 00:00:00.000000000 Z
11
+ date: 2024-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -441,6 +441,7 @@ files:
441
441
  - lib/gitlab_quality/test_tooling/runtime/logger.rb
442
442
  - lib/gitlab_quality/test_tooling/slack/post_to_slack.rb
443
443
  - lib/gitlab_quality/test_tooling/slack/post_to_slack_dry.rb
444
+ - lib/gitlab_quality/test_tooling/stack_trace_comparator.rb
444
445
  - lib/gitlab_quality/test_tooling/summary_table.rb
445
446
  - lib/gitlab_quality/test_tooling/support/http_request.rb
446
447
  - lib/gitlab_quality/test_tooling/system_logs/finders/json_log_finder.rb