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.
- checksums.yaml +4 -4
- data/Gemfile.lock +73 -70
- data/exe/flaky-test-issues +4 -4
- data/lefthook.yml +13 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/branches_client.rb +1 -1
- data/lib/gitlab_quality/test_tooling/gitlab_client/commits_client.rb +6 -4
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb +12 -13
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +6 -6
- data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_client.rb +6 -3
- data/lib/gitlab_quality/test_tooling/gitlab_client/merge_requests_dry_client.rb +4 -2
- data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +41 -28
- data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +78 -43
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +1 -4
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +0 -3
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +4 -8
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +5 -3
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +71 -78
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +5 -1
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_blocking_processor.rb +33 -16
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_quarantine_processor.rb +34 -19
- data/lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb +21 -0
- data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +70 -9
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +4 -4
|
@@ -24,30 +24,34 @@ module GitlabQuality
|
|
|
24
24
|
# @param [TestMetaUpdater] context instance of TestMetaUpdater
|
|
25
25
|
def execute(spec, context) # rubocop:disable Metrics/AbcSize
|
|
26
26
|
@context = context
|
|
27
|
-
|
|
27
|
+
@existing_mrs = nil
|
|
28
28
|
@file_path = spec["file_path"]
|
|
29
|
+
testcase = spec["testcase"]
|
|
29
30
|
devops_stage = spec["stage"]
|
|
31
|
+
product_group = spec["product_group"]
|
|
30
32
|
@failure_issue_url = spec["failure_issue"]
|
|
31
33
|
@example_name = spec["name"]
|
|
32
34
|
@issue_id = failure_issue_url.split('/').last # split url segment, last segment of path is the issue id
|
|
33
|
-
@mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name)
|
|
35
|
+
@mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name).truncate(72, omission: '')
|
|
34
36
|
@failure_issue = context.fetch_issue(iid: issue_id)
|
|
35
37
|
|
|
36
|
-
return unless proceed_with_merge_request?
|
|
37
|
-
|
|
38
38
|
@file_contents = context.get_file_contents(file_path)
|
|
39
39
|
|
|
40
|
-
new_content, changed_line_no = add_quarantine_metadata
|
|
40
|
+
new_content, @changed_line_no = add_quarantine_metadata
|
|
41
|
+
|
|
42
|
+
return unless proceed_with_merge_request?
|
|
41
43
|
|
|
42
44
|
branch = context.create_branch("#{issue_id}-quarantine-#{SecureRandom.hex(4)}", example_name, context.ref)
|
|
43
45
|
|
|
44
46
|
context.commit_changes(branch, <<~COMMIT_MESSAGE, file_path, new_content)
|
|
45
47
|
Quarantine end-to-end test
|
|
46
48
|
|
|
47
|
-
Quarantine #{example_name}
|
|
49
|
+
#{"Quarantine #{example_name}".truncate(72)}
|
|
48
50
|
COMMIT_MESSAGE
|
|
49
51
|
|
|
50
|
-
context.
|
|
52
|
+
gitlab_bot_user_id = context.user_id_for_username(Runtime::Env.gitlab_bot_username)
|
|
53
|
+
|
|
54
|
+
merge_request = context.create_merge_request(mr_title, branch, gitlab_bot_user_id) do
|
|
51
55
|
<<~MARKDOWN
|
|
52
56
|
## What does this MR do?
|
|
53
57
|
|
|
@@ -55,6 +59,10 @@ module GitlabQuality
|
|
|
55
59
|
|
|
56
60
|
This test was identified in the reliable e2e test report: #{context.report_issue}
|
|
57
61
|
|
|
62
|
+
[Testcase link](#{testcase})
|
|
63
|
+
|
|
64
|
+
[Spec metrics link](#{context.single_spec_metrics_link(example_name)})
|
|
65
|
+
|
|
58
66
|
### E2E Test Failure issue(s)
|
|
59
67
|
|
|
60
68
|
#{failure_issue_url}
|
|
@@ -79,12 +87,20 @@ module GitlabQuality
|
|
|
79
87
|
`qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
|
|
80
88
|
-->
|
|
81
89
|
/label ~"devops::#{devops_stage}"
|
|
90
|
+
#{context.label_from_product_group(product_group)}
|
|
82
91
|
|
|
83
92
|
<div align="center">
|
|
84
93
|
(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})
|
|
85
94
|
</div>
|
|
86
95
|
MARKDOWN
|
|
87
96
|
end
|
|
97
|
+
|
|
98
|
+
if merge_request
|
|
99
|
+
context.add_processed_record({ file_path => changed_line_no })
|
|
100
|
+
Runtime::Logger.info(" Created MR for quarantine: #{merge_request.web_url}")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
merge_request
|
|
88
104
|
end
|
|
89
105
|
|
|
90
106
|
# Performs post processing. Takes a list of MRs and posts them in a note on report_issue and Slack
|
|
@@ -114,20 +130,21 @@ module GitlabQuality
|
|
|
114
130
|
|
|
115
131
|
private
|
|
116
132
|
|
|
117
|
-
attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name,
|
|
133
|
+
attr_reader :context, :file_path, :file_contents, :failure_issue_url, :example_name,
|
|
134
|
+
:issue_id, :mr_title, :failure_issue, :changed_line_no
|
|
118
135
|
|
|
119
136
|
# Checks if the failure issue is closed or if there is already an MR open
|
|
120
137
|
#
|
|
121
138
|
# @return [Boolean]
|
|
122
|
-
def proceed_with_merge_request?
|
|
139
|
+
def proceed_with_merge_request? # rubocop:disable Metrics/AbcSize
|
|
123
140
|
if context.issue_is_closed?(failure_issue)
|
|
124
|
-
|
|
141
|
+
Runtime::Logger.info(" Failure issue '#{failure_issue_url}' is closed. Will not proceed with creating MR.")
|
|
125
142
|
return false
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
elsif context.record_processed?(file_path, changed_line_no)
|
|
144
|
+
Runtime::Logger.info(" Record already processed for #{file_path}:#{changed_line_no}. Will not proceed with creating MR.")
|
|
145
|
+
return false
|
|
146
|
+
elsif existing_mrs&.any?
|
|
147
|
+
Runtime::Logger.info(" An open MR already exists for '#{example_name}': #{existing_mrs.first['web_url']}. Will not proceed with creating MR.")
|
|
131
148
|
return false
|
|
132
149
|
end
|
|
133
150
|
|
|
@@ -143,8 +160,8 @@ module GitlabQuality
|
|
|
143
160
|
context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
|
|
144
161
|
indentation = context.indentation(line)
|
|
145
162
|
|
|
146
|
-
if line.include?(',') && line.split.last != 'do'
|
|
147
|
-
line[line.
|
|
163
|
+
if line.sub(DESCRIPTION_REGEX, '').include?(',') && line.split.last != 'do'
|
|
164
|
+
line[line.rindex(',')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ',', quarantine_type: quarantine_type)
|
|
148
165
|
else
|
|
149
166
|
line[line.rindex(' ')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ' ', quarantine_type: quarantine_type)
|
|
150
167
|
end
|
|
@@ -160,8 +177,6 @@ module GitlabQuality
|
|
|
160
177
|
case context.issue_scoped_label(failure_issue, 'failure')&.split('::')&.last
|
|
161
178
|
when 'new', 'investigating'
|
|
162
179
|
':investigating'
|
|
163
|
-
when 'external-dependency'
|
|
164
|
-
':external_dependency'
|
|
165
180
|
when 'broken-test'
|
|
166
181
|
':broken'
|
|
167
182
|
when 'bug'
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/core_ext/string/filters'
|
|
4
|
+
|
|
3
5
|
module GitlabQuality
|
|
4
6
|
module TestTooling
|
|
5
7
|
module TestMeta
|
|
6
8
|
module Processor
|
|
7
9
|
class MetaProcessor
|
|
8
10
|
class << self
|
|
11
|
+
DESCRIPTION_REGEX = /('.*?')|(".*?")/
|
|
12
|
+
|
|
9
13
|
def execute
|
|
10
14
|
raise 'method not implemented'
|
|
11
15
|
end
|
|
@@ -15,6 +19,23 @@ module GitlabQuality
|
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
private_class_method :new
|
|
22
|
+
|
|
23
|
+
# Fetch existing MRs for given mr title
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<Gitlab::ObjectifiedHash>]
|
|
26
|
+
def existing_mrs
|
|
27
|
+
@existing_mrs ||= context.existing_merge_requests(title: mr_title)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the index of the end of test description
|
|
31
|
+
#
|
|
32
|
+
# @param [String] line The line containing the test description
|
|
33
|
+
# @return [Integer]
|
|
34
|
+
def end_of_description_index(line)
|
|
35
|
+
description_length = line.match(DESCRIPTION_REGEX)[0].length
|
|
36
|
+
description_start_index = line.index(DESCRIPTION_REGEX)
|
|
37
|
+
description_start_index + description_length
|
|
38
|
+
end
|
|
18
39
|
end
|
|
19
40
|
end
|
|
20
41
|
end
|
|
@@ -8,7 +8,7 @@ module GitlabQuality
|
|
|
8
8
|
class TestMetaUpdater
|
|
9
9
|
include TestTooling::Concerns::FindSetDri
|
|
10
10
|
|
|
11
|
-
attr_reader :project, :ref, :report_issue
|
|
11
|
+
attr_reader :project, :ref, :report_issue, :processed_records
|
|
12
12
|
|
|
13
13
|
TEST_PLATFORM_MAINTAINERS_SLACK_CHANNEL_ID = 'C0437FV9KBN' # test-platform-maintainers
|
|
14
14
|
|
|
@@ -19,6 +19,7 @@ module GitlabQuality
|
|
|
19
19
|
@ref = ref
|
|
20
20
|
@dry_run = dry_run
|
|
21
21
|
@processor = processor
|
|
22
|
+
@processed_records = {}
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def invoke!
|
|
@@ -33,10 +34,20 @@ module GitlabQuality
|
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# Add processed records
|
|
38
|
+
#
|
|
39
|
+
# @param [Hash<String,Integer>] record the processed record
|
|
40
|
+
# @option record [String] :file_path the path to the spec file
|
|
41
|
+
# @option spec [Intenger] :changed_line_no the line number change in file_path
|
|
42
|
+
# @return [Hash<String,Integer>] processed_records
|
|
43
|
+
def add_processed_record(record)
|
|
44
|
+
@processed_records.merge!(record)
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
# Fetch contents of file from the repository
|
|
37
48
|
#
|
|
38
|
-
# [String] file_path path to the file
|
|
39
|
-
# [String] contents of the file
|
|
49
|
+
# @param [String] file_path path to the file
|
|
50
|
+
# @return [String] contents of the file
|
|
40
51
|
def get_file_contents(file_path)
|
|
41
52
|
repository_files = GitlabClient::RepositoryFilesClient.new(token: token, project: project, file_path: file_path)
|
|
42
53
|
repository_files.file_contents
|
|
@@ -110,8 +121,10 @@ module GitlabQuality
|
|
|
110
121
|
# @param [String] example_name the example
|
|
111
122
|
# @param [Gitlab::ObjectifiedHash] branch the branch
|
|
112
123
|
# @param [Integer] assignee_id
|
|
124
|
+
# @param [Array<Integer>] reviewer_ids
|
|
125
|
+
# @param [String] labels comma seperated list of labels
|
|
113
126
|
# @return [Gitlab::ObjectifiedHash] the created merge request
|
|
114
|
-
def create_merge_request(title, branch, assignee_id = nil, labels = '')
|
|
127
|
+
def create_merge_request(title, branch, assignee_id = nil, reviewer_ids = [], labels = '')
|
|
115
128
|
description = yield
|
|
116
129
|
|
|
117
130
|
merge_request_client.create_merge_request(
|
|
@@ -120,7 +133,8 @@ module GitlabQuality
|
|
|
120
133
|
target_branch: ref,
|
|
121
134
|
description: description,
|
|
122
135
|
labels: labels,
|
|
123
|
-
assignee_id: assignee_id
|
|
136
|
+
assignee_id: assignee_id,
|
|
137
|
+
reviewer_ids: reviewer_ids)
|
|
124
138
|
end
|
|
125
139
|
|
|
126
140
|
# Check if issue is closed
|
|
@@ -153,8 +167,12 @@ module GitlabQuality
|
|
|
153
167
|
# @param [String] note the note to post
|
|
154
168
|
# @return [Gitlab::ObjectifiedHash]
|
|
155
169
|
def post_note_on_report_issue(note)
|
|
156
|
-
iid = report_issue
|
|
157
|
-
|
|
170
|
+
iid = report_issue&.split('/')&.last # split url segment, last segment of path is the issue id
|
|
171
|
+
if iid
|
|
172
|
+
issue_client.create_issue_note(iid: iid, note: note)
|
|
173
|
+
else
|
|
174
|
+
Runtime::Logger.info("#{self.class.name}##{__method__} Note was NOT posted on report issue: #{report_issue}")
|
|
175
|
+
end
|
|
158
176
|
end
|
|
159
177
|
|
|
160
178
|
# Post a note of merge reqest
|
|
@@ -175,7 +193,15 @@ module GitlabQuality
|
|
|
175
193
|
def fetch_dri_id(product_group, devops_stage)
|
|
176
194
|
assignee_handle = ENV.fetch('QA_TEST_DRI_HANDLE', nil) || set_dri_via_group(product_group, devops_stage)
|
|
177
195
|
|
|
178
|
-
[
|
|
196
|
+
[user_id_for_username(assignee_handle), assignee_handle]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Fetch id for the given GitLab username/handle
|
|
200
|
+
#
|
|
201
|
+
# @param [String] username
|
|
202
|
+
# @return [Integer]
|
|
203
|
+
def user_id_for_username(username)
|
|
204
|
+
issue_client.find_user_id(username: username)
|
|
179
205
|
end
|
|
180
206
|
|
|
181
207
|
# Post a message on Slack
|
|
@@ -217,6 +243,34 @@ module GitlabQuality
|
|
|
217
243
|
merge_request_client.find(options: { search: title, in: 'title', state: 'opened' })
|
|
218
244
|
end
|
|
219
245
|
|
|
246
|
+
# Checks if changes has already been made to given file and line number
|
|
247
|
+
#
|
|
248
|
+
# @param [String] file_path path to the file
|
|
249
|
+
# @param [Integer] changed_line_no updated line number
|
|
250
|
+
# @return [Boolean]
|
|
251
|
+
def record_processed?(file_path, changed_line_no)
|
|
252
|
+
processed_records[file_path] && processed_records[file_path] == changed_line_no
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Infers product group label from the provided product group
|
|
256
|
+
#
|
|
257
|
+
# @param [String] product_group product group
|
|
258
|
+
# @return [String]
|
|
259
|
+
def label_from_product_group(product_group)
|
|
260
|
+
label = labels_inference.infer_labels_from_product_group(product_group).to_a.first
|
|
261
|
+
|
|
262
|
+
label ? %(/label ~"#{label}") : ''
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Returns the link to the Grafana dashboard for single spec metrics
|
|
266
|
+
#
|
|
267
|
+
# @param [String] example_name the full example name
|
|
268
|
+
# @return [String]
|
|
269
|
+
def single_spec_metrics_link(example_name)
|
|
270
|
+
base_url = "https://dashboards.quality.gitlab.net/d/cW0UMgv7k/single-spec-metrics?orgId=1&var-run_type=All&var-name="
|
|
271
|
+
base_url + CGI.escape(example_name)
|
|
272
|
+
end
|
|
273
|
+
|
|
220
274
|
private
|
|
221
275
|
|
|
222
276
|
attr_reader :token, :specs_file, :dry_run, :processor
|
|
@@ -234,7 +288,7 @@ module GitlabQuality
|
|
|
234
288
|
#
|
|
235
289
|
# @return [GitlabIssueDryClient | GitlabIssueClient]
|
|
236
290
|
def issue_client
|
|
237
|
-
@issue_client ||= (dry_run ? GitlabClient::IssuesDryClient : GitlabClient::IssuesClient).new(token: token, project:
|
|
291
|
+
@issue_client ||= (dry_run ? GitlabClient::IssuesDryClient : GitlabClient::IssuesClient).new(token: token, project: project)
|
|
238
292
|
end
|
|
239
293
|
|
|
240
294
|
# Returns the MergeRequestDryClient or MergeRequest based on the value of dry_run
|
|
@@ -246,6 +300,13 @@ module GitlabQuality
|
|
|
246
300
|
project: project
|
|
247
301
|
)
|
|
248
302
|
end
|
|
303
|
+
|
|
304
|
+
# Returns a cached instance of GitlabQuality::TestTooling::LabelsInference
|
|
305
|
+
#
|
|
306
|
+
# @return [GitlabQuality::TestTooling::LabelsInference]
|
|
307
|
+
def labels_inference
|
|
308
|
+
@labels_inference ||= GitlabQuality::TestTooling::LabelsInference.new
|
|
309
|
+
end
|
|
249
310
|
end
|
|
250
311
|
end
|
|
251
312
|
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.17.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-01
|
|
11
|
+
date: 2024-03-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: climate_control
|
|
@@ -201,7 +201,7 @@ dependencies:
|
|
|
201
201
|
version: '6.1'
|
|
202
202
|
- - "<"
|
|
203
203
|
- !ruby/object:Gem::Version
|
|
204
|
-
version: '7.
|
|
204
|
+
version: '7.1'
|
|
205
205
|
type: :runtime
|
|
206
206
|
prerelease: false
|
|
207
207
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -211,7 +211,7 @@ dependencies:
|
|
|
211
211
|
version: '6.1'
|
|
212
212
|
- - "<"
|
|
213
213
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: '7.
|
|
214
|
+
version: '7.1'
|
|
215
215
|
- !ruby/object:Gem::Dependency
|
|
216
216
|
name: amatch
|
|
217
217
|
requirement: !ruby/object:Gem::Requirement
|