gitlab_quality-test_tooling 2.8.0 → 2.10.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/.rspec +2 -1
- data/Gemfile.lock +10 -2
- data/README.md +27 -3
- data/exe/failed-test-issues +53 -8
- data/exe/feature-readiness-check +61 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/issue_concern.rb +34 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/concerns/work_item_concern.rb +114 -0
- data/lib/gitlab_quality/test_tooling/feature_readiness/operational_readiness_check.rb +82 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_graphql_client.rb +54 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +32 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_dry_client.rb +3 -3
- data/lib/gitlab_quality/test_tooling/gitlab_client/labels_client.rb +13 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_client.rb +240 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/work_items_dry_client.rb +25 -0
- data/lib/gitlab_quality/test_tooling/labels_inference.rb +4 -0
- data/lib/gitlab_quality/test_tooling/report/failed_test_issue.rb +6 -6
- data/lib/gitlab_quality/test_tooling/report/health_problem_reporter.rb +120 -20
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/prepare_stage_reports.rb +1 -1
- data/lib/gitlab_quality/test_tooling/runtime/env.rb +11 -6
- data/lib/gitlab_quality/test_tooling/test_metrics_exporter/support/gcs_tools.rb +18 -5
- data/lib/gitlab_quality/test_tooling/test_result/base_test_result.rb +2 -1
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +29 -6
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module GitlabClient
|
6
|
+
# The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
|
7
|
+
class WorkItemsClient < GitlabGraphqlClient
|
8
|
+
def work_item(workitem_iid: nil)
|
9
|
+
query = <<~GQL
|
10
|
+
query {
|
11
|
+
namespace(fullPath: "#{group}") {
|
12
|
+
workItem(iid: "#{workitem_iid}") {
|
13
|
+
#{work_item_fields}
|
14
|
+
#{work_item_widgets}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
GQL
|
19
|
+
post(query)[:workItem]
|
20
|
+
end
|
21
|
+
|
22
|
+
def group_work_items(labels: [], cursor: '', state: 'opened', created_after: nil, extras: [])
|
23
|
+
query = <<~GQL
|
24
|
+
query {
|
25
|
+
group(fullPath: "#{group}") {
|
26
|
+
workItems(after: "#{cursor}",#{created_after ? " createdAfter: \"#{created_after}\"," : ''} labelName: [#{labels.map { |label| "\"#{label}\"" }.join(', ')}], state: #{state}) {
|
27
|
+
nodes {
|
28
|
+
#{construct_extras(extras)}
|
29
|
+
}
|
30
|
+
pageInfo {
|
31
|
+
hasNextPage
|
32
|
+
endCursor
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
GQL
|
38
|
+
begin
|
39
|
+
post(query)[:workItems]
|
40
|
+
rescue StandardError => e
|
41
|
+
puts "Error: #{e}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_discussion(id:, note:)
|
46
|
+
post(
|
47
|
+
<<~GQL
|
48
|
+
mutation CreateDiscussion {
|
49
|
+
createDiscussion(input: {noteableId: "#{id}", body: "#{note}"}) {
|
50
|
+
clientMutationId
|
51
|
+
errors
|
52
|
+
note {
|
53
|
+
#{note_fields}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
GQL
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_discussion_note(work_item_id:, discussion_id:, text:)
|
62
|
+
query = <<~GQL
|
63
|
+
mutation CreateNote {
|
64
|
+
createNote(input: { discussionId: "#{discussion_id}", noteableId: "#{work_item_id}", body: "#{text}" }) {
|
65
|
+
clientMutationId
|
66
|
+
errors
|
67
|
+
note {
|
68
|
+
#{note_fields}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
GQL
|
73
|
+
post(query)
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_linked_items(work_item_id:, item_ids:, link_type:)
|
77
|
+
query = <<~GQL
|
78
|
+
mutation WorkItemAddLinkedItems {
|
79
|
+
workItemAddLinkedItems(
|
80
|
+
input: { id: "#{work_item_id}", workItemsIds: [#{item_ids.map { |id| "\"#{id}\"" }.join(', ')}], linkType: #{link_type} }
|
81
|
+
) {
|
82
|
+
clientMutationId
|
83
|
+
}
|
84
|
+
}
|
85
|
+
GQL
|
86
|
+
post(query)
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_labels(work_item_id:, label_ids:)
|
90
|
+
query =
|
91
|
+
<<~GQL
|
92
|
+
mutation WorkItemUpdate {
|
93
|
+
workItemUpdate(input: { id: "#{work_item_id}", labelsWidget: { addLabelIds: [#{label_ids.map { |id| "\"#{id}\"" }.join(', ')}] } }) {
|
94
|
+
clientMutationId
|
95
|
+
errors
|
96
|
+
}
|
97
|
+
}
|
98
|
+
GQL
|
99
|
+
post(query)
|
100
|
+
end
|
101
|
+
|
102
|
+
def paginated_call(method_name, args)
|
103
|
+
results = []
|
104
|
+
|
105
|
+
# Check if the method exists
|
106
|
+
raise ArgumentError, "Unknown method: #{method_name}" unless respond_to?(method_name, true)
|
107
|
+
|
108
|
+
method_obj = method(method_name)
|
109
|
+
|
110
|
+
loop do
|
111
|
+
# Call the method directly using the method object
|
112
|
+
response = method_obj.call(**args)
|
113
|
+
|
114
|
+
break unless response
|
115
|
+
|
116
|
+
results += response[:nodes]
|
117
|
+
break unless response[:pageInfo][:hasNextPage]
|
118
|
+
|
119
|
+
args[:cursor] = response[:pageInfo][:endCursor]
|
120
|
+
end
|
121
|
+
|
122
|
+
results
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
attr_reader :group
|
128
|
+
|
129
|
+
def construct_extras(extras)
|
130
|
+
extras_string = ""
|
131
|
+
extras.each do |extra|
|
132
|
+
next unless respond_to?(extra, true)
|
133
|
+
|
134
|
+
method_obj = method(extra)
|
135
|
+
extras_string += method_obj.call
|
136
|
+
extras_string += "\n"
|
137
|
+
end
|
138
|
+
extras_string
|
139
|
+
end
|
140
|
+
|
141
|
+
# https://docs.gitlab.com/api/graphql/reference/#workitem
|
142
|
+
def work_item_fields
|
143
|
+
<<~GQL
|
144
|
+
author {
|
145
|
+
username
|
146
|
+
}
|
147
|
+
createdAt
|
148
|
+
iid
|
149
|
+
id
|
150
|
+
project {
|
151
|
+
fullPath
|
152
|
+
id
|
153
|
+
}
|
154
|
+
state
|
155
|
+
title
|
156
|
+
webUrl
|
157
|
+
workItemType {
|
158
|
+
name
|
159
|
+
}
|
160
|
+
GQL
|
161
|
+
end
|
162
|
+
|
163
|
+
def work_item_widgets
|
164
|
+
<<~GQL
|
165
|
+
widgets(onlyTypes: [LINKED_ITEMS, NOTES, LABELS, HIERARCHY]) {
|
166
|
+
... on WorkItemWidgetNotes {
|
167
|
+
discussions(filter: ONLY_COMMENTS) {
|
168
|
+
nodes {
|
169
|
+
notes {
|
170
|
+
nodes {
|
171
|
+
#{note_fields}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
}
|
177
|
+
... on WorkItemWidgetLinkedItems {
|
178
|
+
linkedItems {
|
179
|
+
nodes {
|
180
|
+
linkType
|
181
|
+
workItem {
|
182
|
+
#{work_item_fields}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
... on WorkItemWidgetLabels{
|
189
|
+
labels{
|
190
|
+
nodes{
|
191
|
+
title
|
192
|
+
}
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
... on WorkItemWidgetHierarchy {
|
197
|
+
children {
|
198
|
+
nodes{
|
199
|
+
#{work_item_fields}
|
200
|
+
}
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
GQL
|
205
|
+
end
|
206
|
+
|
207
|
+
# https://docs.gitlab.com/api/graphql/reference/#note
|
208
|
+
def note_fields
|
209
|
+
<<~GQL
|
210
|
+
author {
|
211
|
+
username
|
212
|
+
}
|
213
|
+
awardEmoji {
|
214
|
+
nodes {
|
215
|
+
#{emoji_fields}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
body
|
219
|
+
id
|
220
|
+
url
|
221
|
+
discussion {
|
222
|
+
id
|
223
|
+
}
|
224
|
+
GQL
|
225
|
+
end
|
226
|
+
|
227
|
+
# https://docs.gitlab.com/api/graphql/reference/#awardemoji
|
228
|
+
def emoji_fields
|
229
|
+
<<~GQL
|
230
|
+
emoji
|
231
|
+
name
|
232
|
+
user {
|
233
|
+
username
|
234
|
+
}
|
235
|
+
GQL
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitlabQuality
|
4
|
+
module TestTooling
|
5
|
+
module GitlabClient
|
6
|
+
class WorkItemsDryClient < WorkItemsClient
|
7
|
+
def create_discussion(id:, note:)
|
8
|
+
puts "The following discussion would have been posted on #{project}##{id} epic:\n\n #{note}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_discussion_note(work_item_id:, discussion_id:, text:)
|
12
|
+
puts "The following discussion note would have been posted on #{project}##{work_item_id} (discussion #{discussion_id}) text:\n\n #{text}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_linked_items(work_item_id:, item_ids:, link_type: 'BLOCKED_BY')
|
16
|
+
puts "The following items would have been linked on #{project}##{work_item_id} (link type: #{link_type}) item_ids: #{item_ids.join(', ')}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_labels(work_item_id:, label_ids:)
|
20
|
+
puts "The following labels would have been added to #{project}##{work_item_id} work item: #{label_ids.join(', ')}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -190,7 +190,7 @@ module GitlabQuality
|
|
190
190
|
note_stacktrace = sanitize_stacktrace(stacktrace: note.body, regex: ISSUE_STACKTRACE_REGEX)
|
191
191
|
return note_stacktrace if note_stacktrace
|
192
192
|
|
193
|
-
|
193
|
+
Runtime::Logger.debug " => Stacktrace couldn't be found for #{issue.web_url}#note_#{note.id}!"
|
194
194
|
end
|
195
195
|
|
196
196
|
def sanitize_stacktrace(stacktrace:, regex:)
|
@@ -199,7 +199,7 @@ module GitlabQuality
|
|
199
199
|
if stacktrace_match
|
200
200
|
stacktrace_match[:stacktrace].gsub(/^\s*#.*$/, '').gsub(/^[[:space:]]+/, '').strip
|
201
201
|
else
|
202
|
-
|
202
|
+
Runtime::Logger.debug " => Stacktrace doesn't match the regex (#{regex})!"
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
@@ -209,7 +209,7 @@ module GitlabQuality
|
|
209
209
|
stack_trace_comparator = StackTraceComparator.new(test_stacktrace, note_stacktrace)
|
210
210
|
|
211
211
|
if stack_trace_comparator.lower_or_equal_to_diff_ratio?(max_diff_ratio)
|
212
|
-
|
212
|
+
Runtime::Logger.debug " => Note #{issue.web_url}#note_#{note.id} has an acceptable diff ratio of #{stack_trace_comparator.diff_percent}%."
|
213
213
|
# The `Gitlab::ObjectifiedHash` class overrides `#hash` which is used by `Hash#[]=` to compute the hash key.
|
214
214
|
# 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.
|
215
215
|
# See:
|
@@ -217,9 +217,9 @@ module GitlabQuality
|
|
217
217
|
# - https://github.com/NARKOZ/gitlab/commit/cbdbd1e32623f018a8fae39932a8e3bc4d929abb?_pjax=%23js-repo-pjax-container#r44484494
|
218
218
|
stack_trace_comparator.diff_ratio
|
219
219
|
else
|
220
|
-
|
221
|
-
|
222
|
-
|
220
|
+
Runtime::Logger.debug " => Found note #{issue.web_url}#note_#{note.id} but stacktraces are too different (#{stack_trace_comparator.diff_percent}%).\n"
|
221
|
+
Runtime::Logger.debug " => Issue stacktrace:\n----------------\n#{note_stacktrace}\n----------------\n"
|
222
|
+
Runtime::Logger.debug " => Failure stacktrace:\n----------------\n#{test_stacktrace}\n----------------\n"
|
223
223
|
end
|
224
224
|
end
|
225
225
|
end
|
@@ -15,13 +15,29 @@ module GitlabQuality
|
|
15
15
|
class HealthProblemReporter < ReportAsIssue
|
16
16
|
include Concerns::GroupAndCategoryLabels
|
17
17
|
include Concerns::IssueReports
|
18
|
+
include TestMetricsExporter::Support::GcsTools
|
18
19
|
|
19
20
|
BASE_SEARCH_LABELS = ['test'].freeze
|
20
21
|
FOUND_IN_MR_LABEL = '~"found:in MR"'
|
21
22
|
FOUND_IN_MASTER_LABEL = '~"found:master"'
|
22
23
|
|
23
|
-
def initialize(
|
24
|
-
|
24
|
+
def initialize(
|
25
|
+
input_files: [],
|
26
|
+
dry_run: false,
|
27
|
+
issue_update_enabled: true,
|
28
|
+
gcs_enabled: false,
|
29
|
+
gcs_project_id: nil,
|
30
|
+
gcs_bucket: nil,
|
31
|
+
gcs_credentials: nil,
|
32
|
+
**kwargs)
|
33
|
+
super(input_files: input_files, dry_run: dry_run, **kwargs)
|
34
|
+
|
35
|
+
@dry_run = dry_run
|
36
|
+
@issue_update_enabled = issue_update_enabled
|
37
|
+
@gcs_enabled = gcs_enabled
|
38
|
+
@gcs_project_id = gcs_project_id
|
39
|
+
@gcs_bucket = gcs_bucket
|
40
|
+
@gcs_credentials = gcs_credentials
|
25
41
|
end
|
26
42
|
|
27
43
|
def most_recent_report_date_for_issue(issue_iid:)
|
@@ -33,6 +49,8 @@ module GitlabQuality
|
|
33
49
|
|
34
50
|
private
|
35
51
|
|
52
|
+
attr_reader :dry_run, :issue_update_enabled, :gcs_enabled, :gcs_project_id, :gcs_bucket, :gcs_credentials
|
53
|
+
|
36
54
|
def problem_type
|
37
55
|
'unhealthy'
|
38
56
|
end
|
@@ -70,43 +88,89 @@ module GitlabQuality
|
|
70
88
|
end
|
71
89
|
|
72
90
|
def run!
|
73
|
-
|
91
|
+
Runtime::Logger.info "issue creation/update is disabled." unless issue_update_enabled
|
92
|
+
Runtime::Logger.info "push to GCS is disabled." unless gcs_enabled
|
93
|
+
return unless test_reporting_enabled?
|
74
94
|
|
95
|
+
Runtime::Logger.info "Reporting tests in `#{files.join(',')}`."
|
75
96
|
TestResults::Builder.new(file_glob: files, token: token, project: project).test_results_per_file do |test_results|
|
76
|
-
|
97
|
+
Runtime::Logger.info "=> Processing #{test_results.count} tests in #{test_results.path}"
|
77
98
|
|
78
99
|
process_test_results(test_results)
|
79
100
|
end
|
80
101
|
end
|
81
102
|
|
82
103
|
def process_test_results(test_results)
|
83
|
-
|
104
|
+
applicable_tests = test_results.select { |test| test_is_applicable?(test) }
|
105
|
+
return if applicable_tests.empty?
|
106
|
+
|
107
|
+
process_issues_for_tests(applicable_tests) if issue_update_enabled
|
84
108
|
|
85
|
-
|
86
|
-
|
109
|
+
if gcs_enabled
|
110
|
+
tests_data = build_tests_data(applicable_tests)
|
111
|
+
test_results_filename = File.basename(test_results.path)
|
112
|
+
|
113
|
+
push_test_to_gcs(tests_data, test_results_filename)
|
114
|
+
end
|
87
115
|
|
88
|
-
|
116
|
+
Runtime::Logger.info " => Reported #{applicable_tests.size} #{problem_type} tests."
|
117
|
+
end
|
118
|
+
|
119
|
+
def process_issues_for_tests(tests)
|
120
|
+
tests.each do |test|
|
121
|
+
Runtime::Logger.info " => Processing issues for #{problem_type} test '#{test.name}'..."
|
122
|
+
issues = existing_test_health_issues_for_test(test)
|
123
|
+
create_or_update_test_health_issues(issues, test)
|
124
|
+
end
|
125
|
+
end
|
89
126
|
|
90
|
-
|
127
|
+
def existing_test_health_issues_for_test(test)
|
128
|
+
find_issues_by_hash(test_hash(test), state: 'opened', labels: search_labels)
|
129
|
+
end
|
91
130
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
131
|
+
def build_tests_data(tests)
|
132
|
+
tests.map do |test|
|
133
|
+
Runtime::Logger.info " => Building data for #{problem_type} test '#{test.name}'..."
|
134
|
+
issues = existing_test_health_issues_for_test(test)
|
135
|
+
test_datum(test, issues)
|
136
|
+
end
|
137
|
+
end
|
98
138
|
|
99
|
-
|
100
|
-
|
101
|
-
|
139
|
+
def create_or_update_test_health_issues(issues, test)
|
140
|
+
if issues.empty?
|
141
|
+
issues << create_issue(test)
|
142
|
+
else
|
143
|
+
# Keep issues description up-to-date
|
144
|
+
update_issues(issues, test)
|
102
145
|
end
|
103
146
|
|
104
|
-
|
147
|
+
update_reports(issues, test)
|
148
|
+
collect_issues(test, issues)
|
149
|
+
end
|
150
|
+
|
151
|
+
def push_test_to_gcs(tests_data, test_results_filename)
|
152
|
+
Runtime::Logger.info "will push the test data to GCS"
|
153
|
+
|
154
|
+
gcs_client(project_id: gcs_project_id, credentials: gcs_credentials, dry_run: dry_run).put_object(
|
155
|
+
gcs_bucket,
|
156
|
+
gcs_metrics_file_name(test_results_filename),
|
157
|
+
tests_data.to_json,
|
158
|
+
force: true,
|
159
|
+
content_type: 'application/json'
|
160
|
+
)
|
161
|
+
|
162
|
+
Runtime::Logger.info "Successfully pushed the #{gcs_metrics_file_name(test_results_filename)} file to the GCS bucket."
|
163
|
+
end
|
164
|
+
|
165
|
+
def gcs_metrics_file_name(test_results_filename)
|
166
|
+
today = Time.now.strftime('%Y-%m-%d')
|
167
|
+
|
168
|
+
"#{today}-#{test_results_filename}"
|
105
169
|
end
|
106
170
|
|
107
171
|
def update_reports(issues, test)
|
108
172
|
issues.each do |issue|
|
109
|
-
|
173
|
+
Runtime::Logger.info " => Reporting #{problem_type} test to existing issue: #{issue.web_url}"
|
110
174
|
add_report_to_issue(issue: issue, test: test, related_issues: (issues - [issue]))
|
111
175
|
end
|
112
176
|
end
|
@@ -162,6 +226,42 @@ module GitlabQuality
|
|
162
226
|
end
|
163
227
|
end
|
164
228
|
|
229
|
+
# We report a test if we have issue update and/or push to GCS enabled.
|
230
|
+
def test_reporting_enabled?
|
231
|
+
issue_update_enabled || gcs_enabled
|
232
|
+
end
|
233
|
+
|
234
|
+
# rubocop:disable Metrics/AbcSize
|
235
|
+
def test_datum(test, issues)
|
236
|
+
feature_category = test.feature_category.to_s.strip.empty? ? nil : test.feature_category
|
237
|
+
product_group = test.product_group.to_s.strip.empty? ? nil : test.product_group
|
238
|
+
|
239
|
+
if !product_group && feature_category
|
240
|
+
labels_inference = GitlabQuality::TestTooling::LabelsInference
|
241
|
+
product_group = labels_inference.new.product_group_from_feature_category(feature_category)
|
242
|
+
end
|
243
|
+
|
244
|
+
{
|
245
|
+
feature_category: feature_category,
|
246
|
+
created_at: Time.now.utc.iso8601,
|
247
|
+
description: test.name,
|
248
|
+
filename: test.file,
|
249
|
+
gitlab_project_id: Runtime::Env.ci_project_id,
|
250
|
+
product_group: product_group,
|
251
|
+
id: test.example_id,
|
252
|
+
hash: test_hash(test),
|
253
|
+
issue_url: issues.first&.web_url,
|
254
|
+
job_id: Runtime::Env.ci_job_id,
|
255
|
+
job_web_url: test.ci_job_url,
|
256
|
+
pipeline_id: Runtime::Env.ci_pipeline_id,
|
257
|
+
pipeline_ref: Runtime::Env.ci_commit_ref_name,
|
258
|
+
pipeline_web_url: Runtime::Env.ci_pipeline_url,
|
259
|
+
stacktrace: test.full_stacktrace,
|
260
|
+
test_level: test.level
|
261
|
+
}
|
262
|
+
end
|
263
|
+
# rubocop:enable Metrics/AbcSize
|
264
|
+
|
165
265
|
def found_label
|
166
266
|
if ENV.key?('CI_MERGE_REQUEST_IID')
|
167
267
|
FOUND_IN_MR_LABEL
|
@@ -17,7 +17,7 @@ module GitlabQuality
|
|
17
17
|
include Concerns::GroupAndCategoryLabels
|
18
18
|
|
19
19
|
NEW_ISSUE_LABELS = Set.new([
|
20
|
-
'test', 'automation
|
20
|
+
'test', 'automation:bot-authored', 'type::maintenance', 'maintenance::performance',
|
21
21
|
'priority::3', 'severity::3', 'knapsack_report'
|
22
22
|
]).freeze
|
23
23
|
SEARCH_LABELS = %w[test maintenance::performance knapsack_report].freeze
|
@@ -6,7 +6,7 @@ module GitlabQuality
|
|
6
6
|
module TestTooling
|
7
7
|
module Report
|
8
8
|
class PrepareStageReports
|
9
|
-
EXTRACT_STAGE_FROM_TEST_FILE_REGEX = %r{(?:api|browser_ui)/(?:[0-9]+_)?(?<stage>[
|
9
|
+
EXTRACT_STAGE_FROM_TEST_FILE_REGEX = %r{(?:api|browser_ui)/(?:[0-9]+_)?(?<stage>[\w]+)/}i
|
10
10
|
|
11
11
|
def initialize(input_files:)
|
12
12
|
@input_files = input_files
|
@@ -11,19 +11,20 @@ module GitlabQuality
|
|
11
11
|
using Rainbow
|
12
12
|
|
13
13
|
ENV_VARIABLES = {
|
14
|
-
'GITLAB_QA_ISSUE_URL' => :qa_issue_url,
|
15
14
|
'CI_COMMIT_REF_NAME' => :ci_commit_ref_name,
|
15
|
+
'CI_JOB_ID' => :ci_job_id,
|
16
16
|
'CI_JOB_NAME' => :ci_job_name,
|
17
17
|
'CI_JOB_URL' => :ci_job_url,
|
18
|
-
'CI_PROJECT_ID' => :ci_project_id,
|
19
|
-
'CI_PROJECT_NAME' => :ci_project_name,
|
20
|
-
'CI_PROJECT_PATH' => :ci_project_path,
|
21
18
|
'CI_PIPELINE_ID' => :ci_pipeline_id,
|
22
19
|
'CI_PIPELINE_NAME' => :ci_pipeline_name,
|
23
20
|
'CI_PIPELINE_URL' => :ci_pipeline_url,
|
24
|
-
'
|
21
|
+
'CI_PROJECT_ID' => :ci_project_id,
|
22
|
+
'CI_PROJECT_NAME' => :ci_project_name,
|
23
|
+
'CI_PROJECT_PATH' => :ci_project_path,
|
25
24
|
'DEPLOY_VERSION' => :deploy_version,
|
26
|
-
'
|
25
|
+
'GITLAB_QA_ISSUE_URL' => :qa_issue_url,
|
26
|
+
'QA_GITLAB_CI_TOKEN' => :gitlab_ci_token,
|
27
|
+
'SLACK_QA_CHANNEL' => :slack_qa_channel
|
27
28
|
}.freeze
|
28
29
|
|
29
30
|
ENV_VARIABLES.each do |env_name, method_name|
|
@@ -56,6 +57,10 @@ module GitlabQuality
|
|
56
57
|
env_var_value_if_defined('GITLAB_API_BASE') || ci_api_v4_url
|
57
58
|
end
|
58
59
|
|
60
|
+
def gitlab_graphql_api_base
|
61
|
+
env_var_value_if_defined('GITLAB_GRAPHQL_API_BASE')
|
62
|
+
end
|
63
|
+
|
59
64
|
def pipeline_from_project_name
|
60
65
|
%w[gitlab gitaly].any? { |str| ci_project_name.to_s.start_with?(str) } ? default_branch : ci_project_name
|
61
66
|
end
|
@@ -12,11 +12,15 @@ module GitlabQuality
|
|
12
12
|
# @param project_id [String]
|
13
13
|
# @param credentials [String]
|
14
14
|
# @return [Fog::Storage::Google]
|
15
|
-
def gcs_client(project_id:, credentials:)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
def gcs_client(project_id:, credentials:, dry_run: false)
|
16
|
+
if dry_run
|
17
|
+
GCSMockClient.new
|
18
|
+
else
|
19
|
+
Fog::Storage::Google.new(
|
20
|
+
google_project: project_id || raise("Missing Google project_id"),
|
21
|
+
**gcs_creds(credentials)
|
22
|
+
)
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
# GCS Credentials
|
@@ -29,6 +33,15 @@ module GitlabQuality
|
|
29
33
|
|
30
34
|
{ google_json_key_string: json_key }
|
31
35
|
end
|
36
|
+
|
37
|
+
class GCSMockClient
|
38
|
+
def initialize(*_args, **_kwargs); end
|
39
|
+
|
40
|
+
def put_object(gcs_bucket, filename, data, **_kwargs)
|
41
|
+
Runtime::Logger.info "The #{filename} file would have been pushed to the #{gcs_bucket} bucket, with this content:"
|
42
|
+
Runtime::Logger.info data
|
43
|
+
end
|
44
|
+
end
|
32
45
|
end
|
33
46
|
end
|
34
47
|
end
|
@@ -13,7 +13,8 @@ module GitlabQuality
|
|
13
13
|
"could not be found (502)",
|
14
14
|
"Error reference number: 502",
|
15
15
|
"(502): `GitLab is not responding`",
|
16
|
-
"<head><title>502 Bad Gateway</title></head>"
|
16
|
+
"<head><title>502 Bad Gateway</title></head>",
|
17
|
+
"14:connections to all backends failing"
|
17
18
|
].freeze
|
18
19
|
|
19
20
|
SHARED_EXAMPLES_CALLERS = %w[include_examples it_behaves_like].freeze
|