gitlab_quality-test_tooling 1.22.0 → 1.23.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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +55 -37
- data/lib/gitlab_quality/test_tooling/report/failed_test_issue.rb +17 -41
- data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +22 -6
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +15 -38
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +8 -6
- data/lib/gitlab_quality/test_tooling/stack_trace_comparator.rb +36 -0
- data/lib/gitlab_quality/test_tooling/test_result/base_test_result.rb +16 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c63a7a25d0e3a05f515954353898bb8d0dcb9e7a90634087e738f5cb16174c57
|
4
|
+
data.tar.gz: 386833833dc0fd5cdc6334d3d613a38544fbc885c5ccb396070614f0e889555f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb13a1c06a626b1152115262e0606480e78eb9aaafd6ed5e673cd72d6b1a268ab7659c020dbab743ebb872e71c1332905319d0ed6970cbde8bf0168c208fa2c3
|
7
|
+
data.tar.gz: 40652c63561438f37f5610e5b22f93c6a0404c53d4dc65b17713bbf5469bf769dd13691361f02f5aa8229aab706f7e349db6abf663cf45745aaa040456461db7
|
data/Gemfile.lock
CHANGED
@@ -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
|
-
|
35
|
-
|
36
|
-
reports_extra_content
|
37
|
-
|
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
|
-
|
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
|
-
|
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
|
99
|
+
if current_reports_note
|
106
100
|
gitlab.edit_issue_note(
|
107
101
|
issue_iid: issue.iid,
|
108
|
-
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
|
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
|
-
|
193
|
+
stack_trace_comparator = StackTraceComparator.new(test_stacktrace, note_stacktrace)
|
209
194
|
|
210
|
-
if
|
211
|
-
puts " => [DEBUG] Note #{issue.web_url}#note_#{note.id} has an acceptable diff ratio of #{
|
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 (#{
|
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
|
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:
|
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
|
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
|
-
|
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
|
-
|
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
|
96
|
+
if current_reports_note
|
94
97
|
gitlab.edit_issue_note(
|
95
98
|
issue_iid: issue.iid,
|
96
|
-
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
|
113
|
+
def add_report_for_test(current_reports_content:, test:)
|
111
114
|
increment_reports(
|
112
|
-
current_reports_content:
|
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
|
-
|
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
|
219
|
-
FAILURE_STACKTRACE_REGEX) || full_stacktrace
|
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
|
-
|
240
|
-
|
241
|
-
|
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 (#{
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
76
|
+
if current_reports_note
|
75
77
|
gitlab.edit_issue_note(
|
76
78
|
issue_iid: issue.iid,
|
77
|
-
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
|
93
|
+
def add_report_for_test(current_reports_content:, test:)
|
92
94
|
increment_reports(
|
93
|
-
current_reports_content:
|
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
|
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.
|
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-
|
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
|