gitlab-qa 6.2.0 → 6.3.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 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