gitlab-qa 6.2.0 → 6.3.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: 20da7f808929fe216ed49cefd35aeeab33c0e0a2c35517aa5ef15569271e2d17
4
- data.tar.gz: d87695289a8d8f13f4c9baa5547a46e45f4777e07adcbfc78570f4f35a0db71f
3
+ metadata.gz: 5c99ceb7cdd5d4c53dabf353157bbaf334c7c67febb2afc93620e8bca0fe3774
4
+ data.tar.gz: a0d26ee7c892402f37cb9bc0e311f649687878fb088bca8c881ce64a5a0bcd54
5
5
  SHA512:
6
- metadata.gz: ba356e43063cf62c035364bf6e929d2545d4ad9d6042159cf68b58cce15af7099843ab556622076b705fa5f633d7defc3158cbdaaa948fd2014e79eb5bfbe290
7
- data.tar.gz: ea5048047d6c5816e0401d0bf104757be344145740063255581fbbadc27c69334ad737a23b81049ede31f488f3f13858411a1126ca07b3ee14c6a87ad8e9b030
6
+ metadata.gz: 6984d1bcbfb7939d2b806f9857b6403db776458eba0c89ee719be35f563b4a96076a80ca2cf8bb765e4eccf819e7554343c59b08c702d8d45d4d0c85bcde618c
7
+ data.tar.gz: 4c44818ae663d2eece632825d8efd12535312f71a98e7211ab8b431b8dec5d8561d339a1b69e3884797f3324cb049b64164da4be4c0064c329de600cf731d020
@@ -97,9 +97,11 @@ module Gitlab
97
97
  end
98
98
 
99
99
  module Report
100
+ autoload :GitlabIssueClient, 'gitlab/qa/report/gitlab_issue_client'
100
101
  autoload :JsonTestResults, 'gitlab/qa/report/json_test_results'
101
102
  autoload :JUnitTestResults, 'gitlab/qa/report/junit_test_results'
102
103
  autoload :PrepareStageReports, 'gitlab/qa/report/prepare_stage_reports'
104
+ autoload :ReportAsIssue, 'gitlab/qa/report/report_as_issue'
103
105
  autoload :ResultsInIssues, 'gitlab/qa/report/results_in_issues'
104
106
  autoload :SummaryTable, 'gitlab/qa/report/summary_table'
105
107
  autoload :TestResult, 'gitlab/qa/report/test_result'
@@ -23,6 +23,8 @@ module Gitlab
23
23
 
24
24
  @docker.login(**release.login_params) if release.login_params
25
25
 
26
+ @docker.pull(release.qa_image, release.qa_tag) unless Runtime::Env.skip_pull?
27
+
26
28
  puts "Running test suite `#{suite}` for #{release.project_name}"
27
29
 
28
30
  name = "#{release.project_name}-qa-#{SecureRandom.hex(4)}"
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gitlab'
4
+
5
+ module Gitlab
6
+ # Monkey patch the Gitlab client to use the correct API path and add required methods
7
+ class Client
8
+ def team_member(project, id)
9
+ get("/projects/#{url_encode(project)}/members/all/#{id}")
10
+ end
11
+
12
+ def issue_discussions(project, issue_id, options = {})
13
+ get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
14
+ end
15
+
16
+ def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
17
+ post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
18
+ end
19
+ end
20
+
21
+ module QA
22
+ module Report
23
+ # The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
24
+ class GitlabIssueClient
25
+ MAINTAINER_ACCESS_LEVEL = 40
26
+ RETRY_BACK_OFF_DELAY = 60
27
+ MAX_RETRY_ATTEMPTS = 3
28
+
29
+ def initialize(token:, project:)
30
+ @token = token
31
+ @project = project
32
+ @retry_backoff = 0
33
+
34
+ configure_gitlab_client
35
+ end
36
+
37
+ def assert_user_permission!
38
+ handle_gitlab_client_exceptions do
39
+ user = Gitlab.user
40
+ member = Gitlab.team_member(project, user.id)
41
+
42
+ abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
43
+ end
44
+ rescue Gitlab::Error::NotFound
45
+ abort_not_permitted
46
+ end
47
+
48
+ def find_issues(iid:, options: {}, &select)
49
+ select ||= :itself
50
+
51
+ handle_gitlab_client_exceptions do
52
+ return [Gitlab.issue(project, iid)] if iid
53
+
54
+ Gitlab.issues(project, options)
55
+ .auto_paginate
56
+ .select(&select)
57
+ end
58
+ end
59
+
60
+ def create_issue(title:, description:, labels:)
61
+ puts "Creating issue..."
62
+
63
+ handle_gitlab_client_exceptions do
64
+ Gitlab.create_issue(
65
+ project,
66
+ title,
67
+ { description: description, labels: labels }
68
+ )
69
+ end
70
+ end
71
+
72
+ def edit_issue(iid:, options: {})
73
+ handle_gitlab_client_exceptions do
74
+ Gitlab.edit_issue(project, iid, options)
75
+ end
76
+ end
77
+
78
+ def handle_gitlab_client_exceptions
79
+ yield
80
+ rescue Gitlab::Error::NotFound
81
+ # This error could be raised in assert_user_permission!
82
+ # If so, we want it to terminate at that point
83
+ raise
84
+ rescue SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::InternalServerError, Gitlab::Error::Parsing => e
85
+ @retry_backoff += RETRY_BACK_OFF_DELAY
86
+
87
+ raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
88
+
89
+ warn_exception(e)
90
+ warn("Sleeping for #{@retry_backoff} seconds before retrying...")
91
+ sleep @retry_backoff
92
+
93
+ retry
94
+ rescue StandardError => e
95
+ pipeline = QA::Runtime::Env.pipeline_from_project_name
96
+ channel = pipeline == "canary" ? "qa-production" : "qa-#{pipeline}"
97
+ error_msg = warn_exception(e)
98
+ slack_options = {
99
+ channel: channel,
100
+ icon_emoji: ':ci_failing:',
101
+ message: <<~MSG
102
+ An unexpected error occurred while reporting test results in issues.
103
+ The error occurred in job: #{QA::Runtime::Env.ci_job_url}
104
+ `#{error_msg}`
105
+ MSG
106
+ }
107
+ puts "Posting Slack message to channel: #{channel}"
108
+
109
+ Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
110
+ end
111
+
112
+ private
113
+
114
+ attr_reader :token, :project
115
+
116
+ def configure_gitlab_client
117
+ Gitlab.configure do |config|
118
+ config.endpoint = Runtime::Env.gitlab_api_base
119
+ config.private_token = token
120
+ end
121
+ end
122
+
123
+ def abort_not_permitted
124
+ abort "You must have at least Maintainer access to the project to use this feature."
125
+ end
126
+
127
+ def warn_exception(error)
128
+ error_msg = "#{error.class.name} #{error.message}"
129
+ warn(error_msg)
130
+ error_msg
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module QA
5
+ module Report
6
+ class ReportAsIssue
7
+ def initialize(token:, input_files:, project: nil)
8
+ @gitlab = GitlabIssueClient.new(token: token, project: project)
9
+ @files = Array(input_files)
10
+ @project = project
11
+ end
12
+
13
+ def invoke!
14
+ validate_input!
15
+
16
+ run!
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :gitlab, :files, :project
22
+
23
+ def run!
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def validate_input!
28
+ assert_project!
29
+ assert_input_files!(files)
30
+ gitlab.assert_user_permission!
31
+ end
32
+
33
+ def assert_project!
34
+ return if project
35
+
36
+ abort "Please provide a valid project ID or path with the `-p/--project` option!"
37
+ end
38
+
39
+ def assert_input_files!(files)
40
+ return if Dir.glob(files).any?
41
+
42
+ abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,47 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'nokogiri'
4
- require 'gitlab'
5
4
  require 'active_support/core_ext/enumerable'
6
5
 
7
6
  module Gitlab
8
- # Monkey patch the Gitlab client to use the correct API path and add required methods
9
- class Client
10
- def team_member(project, id)
11
- get("/projects/#{url_encode(project)}/members/all/#{id}")
12
- end
13
-
14
- def issue_discussions(project, issue_id, options = {})
15
- get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
16
- end
17
-
18
- def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
19
- post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
20
- end
21
- end
22
-
23
7
  module QA
24
8
  module Report
25
9
  # Uses the API to create or update GitLab issues with the results of tests from RSpec report files.
26
- # The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
27
- class ResultsInIssues
28
- MAINTAINER_ACCESS_LEVEL = 40
10
+ class ResultsInIssues < ReportAsIssue
29
11
  MAX_TITLE_LENGTH = 255
30
- RETRY_BACK_OFF_DELAY = 60
31
- MAX_RETRY_ATTEMPTS = 3
32
-
33
- def initialize(token:, input_files:, project: nil)
34
- @token = token
35
- @files = Array(input_files)
36
- @project = project
37
- @retry_backoff = 0
38
- end
39
-
40
- def invoke!
41
- configure_gitlab_client
42
12
 
43
- validate_input!
13
+ private
44
14
 
15
+ def run!
45
16
  puts "Reporting test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
46
17
 
47
18
  Dir.glob(files).each do |file|
@@ -63,58 +34,13 @@ module Gitlab
63
34
  end
64
35
  end
65
36
 
66
- private
67
-
68
- attr_reader :files, :token, :project
69
-
70
- def validate_input!
71
- assert_project!
72
- assert_input_files!(files)
73
- assert_user_permission!
74
- end
75
-
76
- def assert_project!
77
- return if project
78
-
79
- abort "Please provide a valid project ID or path with the `-p/--project` option!"
80
- end
81
-
82
- def assert_input_files!(files)
83
- return if Dir.glob(files).any?
84
-
85
- abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
86
- end
87
-
88
- def assert_user_permission!
89
- handle_gitlab_client_exceptions do
90
- user = Gitlab.user
91
- member = Gitlab.team_member(project, user.id)
92
-
93
- abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
94
- end
95
- rescue Gitlab::Error::NotFound
96
- abort_not_permitted
97
- end
98
-
99
- def abort_not_permitted
100
- abort "You must have at least Maintainer access to the project to use this feature."
101
- end
102
-
103
- def configure_gitlab_client
104
- handle_gitlab_client_exceptions do
105
- Gitlab.configure do |config|
106
- config.endpoint = Runtime::Env.gitlab_api_base
107
- config.private_token = token
108
- end
109
- end
110
- end
111
-
112
37
  def report_test(test)
113
38
  return if test.skipped
114
39
 
115
40
  puts "Reporting test: #{test.file} | #{test.name}"
116
41
 
117
42
  issue = find_issue(test)
43
+
118
44
  if issue
119
45
  puts "Found existing issue: #{issue.web_url}"
120
46
  else
@@ -128,36 +54,30 @@ module Gitlab
128
54
  puts "Issue updated"
129
55
  end
130
56
 
131
- def create_issue(test)
132
- puts "Creating issue..."
133
-
134
- handle_gitlab_client_exceptions do
135
- Gitlab.create_issue(
136
- project,
137
- title_from_test(test),
138
- { description: "### Full description\n\n#{search_safe(test.name)}\n\n### File path\n\n#{test.file}", labels: 'status::automated' }
139
- )
140
- end
141
- end
142
-
143
- # rubocop:disable Metrics/AbcSize
144
57
  def find_issue(test)
145
- handle_gitlab_client_exceptions do
146
- return Gitlab.issue(project, id_from_testcase_url(test.testcase)) if test.testcase
58
+ title = title_from_test(test)
59
+ issues =
60
+ gitlab.find_issues(
61
+ iid: iid_from_testcase_url(test.testcase),
62
+ options: { search: search_term(test) }) do |issue|
63
+ issue.state == 'opened' && issue.title.strip == title
64
+ end
147
65
 
148
- issues = Gitlab.issues(project, { search: search_term(test) })
149
- .auto_paginate
150
- .select { |issue| issue.state == 'opened' && issue.title.strip == title_from_test(test) }
66
+ warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
151
67
 
152
- warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
68
+ issues.first
69
+ end
153
70
 
154
- issues.first
155
- end
71
+ def create_issue(test)
72
+ gitlab.create_issue(
73
+ title: title_from_test(test),
74
+ description: "### Full description\n\n#{search_safe(test.name)}\n\n### File path\n\n#{test.file}",
75
+ labels: 'status::automated'
76
+ )
156
77
  end
157
- # rubocop:enable Metrics/AbcSize
158
78
 
159
- def id_from_testcase_url(url)
160
- url.split('/').last.to_i
79
+ def iid_from_testcase_url(url)
80
+ url && url.split('/').last.to_i
161
81
  end
162
82
 
163
83
  def search_term(test)
@@ -185,7 +105,7 @@ module Gitlab
185
105
 
186
106
  note = note_content(test)
187
107
 
188
- handle_gitlab_client_exceptions do
108
+ gitlab.handle_gitlab_client_exceptions do
189
109
  Gitlab.issue_discussions(project, issue.iid, order_by: 'created_at', sort: 'asc').each do |discussion|
190
110
  return add_note_to_discussion(issue.iid, discussion.id) if new_note_matches_discussion?(note, discussion)
191
111
  end
@@ -241,12 +161,11 @@ module Gitlab
241
161
  end
242
162
 
243
163
  def add_note_to_discussion(issue_iid, discussion_id)
244
- handle_gitlab_client_exceptions do
164
+ gitlab.handle_gitlab_client_exceptions do
245
165
  Gitlab.add_note_to_issue_discussion_as_thread(project, issue_iid, discussion_id, body: failure_summary)
246
166
  end
247
167
  end
248
168
 
249
- # rubocop:disable Metrics/AbcSize
250
169
  def update_labels(issue, test)
251
170
  labels = issue.labels
252
171
  labels.delete_if { |label| label.start_with?("#{pipeline}::") }
@@ -254,11 +173,8 @@ module Gitlab
254
173
  labels << "Enterprise Edition" if ee_test?(test)
255
174
  quarantine_job? ? labels << "quarantine" : labels.delete("quarantine")
256
175
 
257
- handle_gitlab_client_exceptions do
258
- Gitlab.edit_issue(project, issue.iid, labels: labels)
259
- end
176
+ gitlab.edit_issue(iid: issue.iid, options: { labels: labels })
260
177
  end
261
- # rubocop:enable Metrics/AbcSize
262
178
 
263
179
  def ee_test?(test)
264
180
  test.file =~ %r{features/ee/(api|browser_ui)}
@@ -279,46 +195,6 @@ module Gitlab
279
195
 
280
196
  Runtime::Env.pipeline_from_project_name
281
197
  end
282
-
283
- def handle_gitlab_client_exceptions
284
- yield
285
- rescue Gitlab::Error::NotFound
286
- # This error could be raised in assert_user_permission!
287
- # If so, we want it to terminate at that point
288
- raise
289
- rescue SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::InternalServerError, Gitlab::Error::Parsing => e
290
- @retry_backoff += RETRY_BACK_OFF_DELAY
291
-
292
- raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
293
-
294
- warn_exception(e)
295
- warn("Sleeping for #{@retry_backoff} seconds before retrying...")
296
- sleep @retry_backoff
297
-
298
- retry
299
- rescue StandardError => e
300
- pipeline = QA::Runtime::Env.pipeline_from_project_name
301
- channel = pipeline == "canary" ? "qa-production" : "qa-#{pipeline}"
302
- error_msg = warn_exception(e)
303
- slack_options = {
304
- channel: channel,
305
- icon_emoji: ':ci_failing:',
306
- message: <<~MSG
307
- An unexpected error occurred while reporting test results in issues.
308
- The error occurred in job: #{QA::Runtime::Env.ci_job_url}
309
- `#{error_msg}`
310
- MSG
311
- }
312
- puts "Posting Slack message to channel: #{channel}"
313
-
314
- Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
315
- end
316
-
317
- def warn_exception(error)
318
- error_msg = "#{error.class.name} #{error.message}"
319
- warn(error_msg)
320
- error_msg
321
- end
322
198
  end
323
199
  end
324
200
  end
@@ -60,34 +60,19 @@ module Gitlab
60
60
  end
61
61
 
62
62
  if args.any?
63
- if report_options[:prepare_stage_reports]
64
- report_options.delete(:prepare_stage_reports)
63
+ if report_options.delete(:prepare_stage_reports)
65
64
  Gitlab::QA::Report::PrepareStageReports.new(**report_options).invoke!
66
65
 
67
- exit
68
- end
69
-
70
- if report_options[:report_in_issues]
71
- report_options.delete(:report_in_issues)
66
+ elsif report_options.delete(:report_in_issues)
72
67
  report_options[:token] = Runtime::TokenFinder.find_token!(report_options[:token])
73
68
  Gitlab::QA::Report::ResultsInIssues.new(**report_options).invoke!
74
69
 
75
- exit
76
- end
77
-
78
- if slack_options[:post_to_slack]
79
- slack_options.delete(:post_to_slack)
70
+ elsif slack_options.delete(:post_to_slack)
80
71
  Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
81
72
 
82
- exit
83
- end
84
-
85
- if report_options[:update_screenshot_path]
86
- report_options.delete(:update_screenshot_path)
87
-
73
+ elsif report_options.delete(:update_screenshot_path)
88
74
  Gitlab::QA::Report::UpdateScreenshotPath.new(**report_options).invoke!
89
75
 
90
- exit
91
76
  end
92
77
  else
93
78
  puts options
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module QA
3
- VERSION = '6.2.0'.freeze
3
+ VERSION = '6.3.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-qa
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.0
4
+ version: 6.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grzegorz Bizon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-17 00:00:00.000000000 Z
11
+ date: 2020-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -257,9 +257,11 @@ files:
257
257
  - lib/gitlab/qa/docker/shellout.rb
258
258
  - lib/gitlab/qa/docker/volumes.rb
259
259
  - lib/gitlab/qa/release.rb
260
+ - lib/gitlab/qa/report/gitlab_issue_client.rb
260
261
  - lib/gitlab/qa/report/json_test_results.rb
261
262
  - lib/gitlab/qa/report/junit_test_results.rb
262
263
  - lib/gitlab/qa/report/prepare_stage_reports.rb
264
+ - lib/gitlab/qa/report/report_as_issue.rb
263
265
  - lib/gitlab/qa/report/results_in_issues.rb
264
266
  - lib/gitlab/qa/report/summary_table.rb
265
267
  - lib/gitlab/qa/report/test_result.rb