gitlab-qa 6.1.3 → 6.5.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: 52046fa3ef635b05a1b01e3c4d4ec0b3b0cad5abdbdcc52b2d54e07f6b0e19fe
4
- data.tar.gz: 6107e5c74d71e9ef3d9c423168676ce877b1745de1e8ba7d7570a2ac001b9c57
3
+ metadata.gz: 97ec064795e04d091b6d04bff27d6c163635a1a08d1e9b28efedf17111f2b1a6
4
+ data.tar.gz: 8318fb6919671c0af482d9dda37f751d15d60732b6654eca890fd87118f9f8d3
5
5
  SHA512:
6
- metadata.gz: d3161f3b19e78dffc3baa9baf1b104d925eaf5d3cd091ab28b58d9718ffc3fc5432d53b58b8b2559c0a46a78ac2d852ee28f99a8d5117f882e0cbf8a1e4b5e7c
7
- data.tar.gz: 89cad7516fedeb377c8720a00a49553778d6cd6f0075471fd6f44c53bfec6714dbf3a510f5bd3239ae1082f5cca006a911d2758744d885b38e4bfc025ea747f8
6
+ metadata.gz: dcc298d8f343ee158a4e1229748a96853555082f20de96ed3b5851e413fdc47a832eebd9d0f27f8aba959aacdcbd28c0409beea9abbf8ca7a8feeb18cc066171
7
+ data.tar.gz: 8bdf4528158b4f5c5f09bb87f4604dc3eda02c73d98a064714375f24f0e245c636fefb5a6e5b2f7c93db8d46053b5ebeb7eb3821fd17ae8c04eb459f8c0154fd
@@ -229,44 +229,6 @@ ee:instance-quarantine:
229
229
  variables:
230
230
  QA_RSPEC_TAGS: "--tag quarantine --tag ~orchestrated"
231
231
 
232
- ce:docker:
233
- extends:
234
- - .test
235
- - .high-capacity
236
- - .ce-qa
237
- - .rspec-report-opts
238
- variables:
239
- QA_RSPEC_TAGS: "--tag docker"
240
-
241
- ce:docker-quarantine:
242
- extends:
243
- - .test
244
- - .high-capacity
245
- - .ce-qa
246
- - .quarantine
247
- - .rspec-report-opts
248
- variables:
249
- QA_RSPEC_TAGS: "--tag docker --tag quarantine"
250
-
251
- ee:docker:
252
- extends:
253
- - .test
254
- - .high-capacity
255
- - .ee-qa
256
- - .rspec-report-opts
257
- variables:
258
- QA_RSPEC_TAGS: "--tag docker"
259
-
260
- ee:docker-quarantine:
261
- extends:
262
- - .test
263
- - .high-capacity
264
- - .ee-qa
265
- - .quarantine
266
- - .rspec-report-opts
267
- variables:
268
- QA_RSPEC_TAGS: "--tag docker --tag quarantine"
269
-
270
232
  ce:relative_url:
271
233
  extends:
272
234
  - .test
@@ -32,4 +32,4 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-qa/commits/mast
32
32
  - Checklist after merging:
33
33
  - [ ] [Create a tag for the new release version](docs/release_process.md#how-to).
34
34
 
35
- /label ~Quality ~backstage
35
+ /label ~Quality ~"feature::maintenance"
@@ -83,6 +83,7 @@ All environment variables used by GitLab QA should be defined in [`lib/gitlab/qa
83
83
  | `JIRA_ADMIN_USERNAME` |- | Username for authenticating with Jira server as admin. | No|
84
84
  | `JIRA_ADMIN_PASSWORD` |- | Password for authenticating with Jira server as admin. | No|
85
85
  | `CACHE_NAMESPACE_NAME` | `true` | Cache namespace name for groups. | No|
86
+ | `DEPLOY_VERSION` |- | The version of GitLab being tested against. | No|
86
87
 
87
88
  ## [Supported Remote Grid environment variables](./running_against_remote_grid.md)
88
89
 
@@ -97,9 +97,12 @@ module Gitlab
97
97
  end
98
98
 
99
99
  module Report
100
+ autoload :GitlabIssueClient, 'gitlab/qa/report/gitlab_issue_client'
101
+ autoload :BaseTestResults, 'gitlab/qa/report/base_test_results'
100
102
  autoload :JsonTestResults, 'gitlab/qa/report/json_test_results'
101
103
  autoload :JUnitTestResults, 'gitlab/qa/report/junit_test_results'
102
104
  autoload :PrepareStageReports, 'gitlab/qa/report/prepare_stage_reports'
105
+ autoload :ReportAsIssue, 'gitlab/qa/report/report_as_issue'
103
106
  autoload :ResultsInIssues, 'gitlab/qa/report/results_in_issues'
104
107
  autoload :SummaryTable, 'gitlab/qa/report/summary_table'
105
108
  autoload :TestResult, 'gitlab/qa/report/test_result'
@@ -128,10 +128,10 @@ module Gitlab
128
128
  end
129
129
  end
130
130
 
131
- def wait
131
+ def wait_until_ready
132
132
  return if skip_availability_check
133
133
 
134
- if Availability.new(name, relative_path: relative_path, scheme: scheme, protocol_port: port.to_i).check(180)
134
+ if Availability.new(name, relative_path: relative_path, scheme: scheme, protocol_port: port.to_i).check(300)
135
135
  sleep 12 # TODO, handle that better
136
136
  puts ' -> GitLab is available.'
137
137
  else
@@ -30,7 +30,8 @@ module Gitlab
30
30
  begin
31
31
  run_psql 'template1'
32
32
  rescue StandardError
33
- retry if Time.now - start < 30
33
+ sleep 5
34
+ retry if Time.now - start < 60
34
35
  raise
35
36
  end
36
37
  end
@@ -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,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module QA
5
+ module Report
6
+ class BaseTestResults
7
+ include Enumerable
8
+
9
+ def initialize(path)
10
+ @results = parse(path)
11
+ @testcases = process
12
+ end
13
+
14
+ def each(&block)
15
+ testcases.each(&block)
16
+ end
17
+
18
+ def write(path)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :results, :testcases
25
+
26
+ def parse(path)
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def process
31
+ raise NotImplementedError
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -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
@@ -5,15 +5,23 @@ require 'json'
5
5
  module Gitlab
6
6
  module QA
7
7
  module Report
8
- class JsonTestResults
9
- include Enumerable
8
+ class JsonTestResults < BaseTestResults
9
+ def write(path)
10
+ json = results.merge('examples' => testcases.map(&:report))
10
11
 
11
- def initialize(file)
12
- @testcases = JSON.parse(File.read(file))['examples'].map { |test| TestResult.from_json(test) }
12
+ File.write(path, JSON.pretty_generate(json))
13
13
  end
14
14
 
15
- def each(&block)
16
- @testcases.each(&block)
15
+ private
16
+
17
+ def parse(path)
18
+ JSON.parse(File.read(path))
19
+ end
20
+
21
+ def process
22
+ results['examples'].map do |test|
23
+ TestResult.from_json(test)
24
+ end
17
25
  end
18
26
  end
19
27
  end
@@ -5,15 +5,21 @@ require 'nokogiri'
5
5
  module Gitlab
6
6
  module QA
7
7
  module Report
8
- class JUnitTestResults
9
- include Enumerable
8
+ class JUnitTestResults < BaseTestResults
9
+ def write(path)
10
+ # Ignore it for now
11
+ end
12
+
13
+ private
10
14
 
11
- def initialize(file)
12
- @testcases = Nokogiri::XML(File.read(file)).xpath('//testcase').map { |test| TestResult.from_junit(test) }
15
+ def parse(path)
16
+ Nokogiri::XML.parse(File.read(path))
13
17
  end
14
18
 
15
- def each(&block)
16
- @testcases.each(&block)
19
+ def process
20
+ results.xpath('//testcase').map do |test|
21
+ TestResult.from_junit(test)
22
+ end
17
23
  end
18
24
  end
19
25
  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,58 +1,29 @@
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
- Dir.glob(files).each do |file|
48
- puts "Reporting tests in #{file}"
49
- extension = File.extname(file)
18
+ Dir.glob(files).each do |path|
19
+ puts "Reporting tests in #{path}"
20
+ extension = File.extname(path)
50
21
 
51
22
  case extension
52
23
  when '.json'
53
- test_results = Report::JsonTestResults.new(file)
24
+ test_results = Report::JsonTestResults.new(path)
54
25
  when '.xml'
55
- test_results = Report::JUnitTestResults.new(file)
26
+ test_results = Report::JUnitTestResults.new(path)
56
27
  else
57
28
  raise "Unknown extension #{extension}"
58
29
  end
@@ -60,52 +31,8 @@ module Gitlab
60
31
  test_results.each do |test|
61
32
  report_test(test)
62
33
  end
63
- end
64
- end
65
-
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
34
 
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
35
+ test_results.write(path)
109
36
  end
110
37
  end
111
38
 
@@ -115,6 +42,7 @@ module Gitlab
115
42
  puts "Reporting test: #{test.file} | #{test.name}"
116
43
 
117
44
  issue = find_issue(test)
45
+
118
46
  if issue
119
47
  puts "Found existing issue: #{issue.web_url}"
120
48
  else
@@ -122,42 +50,38 @@ module Gitlab
122
50
  puts "Created new issue: #{issue.web_url}"
123
51
  end
124
52
 
53
+ test.testcase ||= issue.web_url
54
+
125
55
  update_labels(issue, test)
126
56
  note_status(issue, test)
127
57
 
128
58
  puts "Issue updated"
129
59
  end
130
60
 
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
61
  def find_issue(test)
145
- handle_gitlab_client_exceptions do
146
- return Gitlab.issue(project, id_from_testcase_url(test.testcase)) if test.testcase
62
+ title = title_from_test(test)
63
+ issues =
64
+ gitlab.find_issues(
65
+ iid: iid_from_testcase_url(test.testcase),
66
+ options: { search: search_term(test) }) do |issue|
67
+ issue.state == 'opened' && issue.title.strip == title
68
+ end
147
69
 
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) }
70
+ warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
151
71
 
152
- warn(%(Too many issues found with the file path "#{test.file}" and name "#{test.name}")) if issues.many?
72
+ issues.first
73
+ end
153
74
 
154
- issues.first
155
- end
75
+ def create_issue(test)
76
+ gitlab.create_issue(
77
+ title: title_from_test(test),
78
+ description: "### Full description\n\n#{search_safe(test.name)}\n\n### File path\n\n#{test.file}",
79
+ labels: 'status::automated'
80
+ )
156
81
  end
157
- # rubocop:enable Metrics/AbcSize
158
82
 
159
- def id_from_testcase_url(url)
160
- url.split('/').last.to_i
83
+ def iid_from_testcase_url(url)
84
+ url && url.split('/').last.to_i
161
85
  end
162
86
 
163
87
  def search_term(test)
@@ -185,7 +109,7 @@ module Gitlab
185
109
 
186
110
  note = note_content(test)
187
111
 
188
- handle_gitlab_client_exceptions do
112
+ gitlab.handle_gitlab_client_exceptions do
189
113
  Gitlab.issue_discussions(project, issue.iid, order_by: 'created_at', sort: 'asc').each do |discussion|
190
114
  return add_note_to_discussion(issue.iid, discussion.id) if new_note_matches_discussion?(note, discussion)
191
115
  end
@@ -241,12 +165,11 @@ module Gitlab
241
165
  end
242
166
 
243
167
  def add_note_to_discussion(issue_iid, discussion_id)
244
- handle_gitlab_client_exceptions do
168
+ gitlab.handle_gitlab_client_exceptions do
245
169
  Gitlab.add_note_to_issue_discussion_as_thread(project, issue_iid, discussion_id, body: failure_summary)
246
170
  end
247
171
  end
248
172
 
249
- # rubocop:disable Metrics/AbcSize
250
173
  def update_labels(issue, test)
251
174
  labels = issue.labels
252
175
  labels.delete_if { |label| label.start_with?("#{pipeline}::") }
@@ -254,11 +177,8 @@ module Gitlab
254
177
  labels << "Enterprise Edition" if ee_test?(test)
255
178
  quarantine_job? ? labels << "quarantine" : labels.delete("quarantine")
256
179
 
257
- handle_gitlab_client_exceptions do
258
- Gitlab.edit_issue(project, issue.iid, labels: labels)
259
- end
180
+ gitlab.edit_issue(iid: issue.iid, options: { labels: labels })
260
181
  end
261
- # rubocop:enable Metrics/AbcSize
262
182
 
263
183
  def ee_test?(test)
264
184
  test.file =~ %r{features/ee/(api|browser_ui)}
@@ -279,46 +199,6 @@ module Gitlab
279
199
 
280
200
  Runtime::Env.pipeline_from_project_name
281
201
  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
202
  end
323
203
  end
324
204
  end
@@ -4,60 +4,114 @@ module Gitlab
4
4
  module QA
5
5
  module Report
6
6
  class TestResult
7
- attr_accessor :name, :file, :skipped, :failures, :testcase
8
-
9
- def self.from_json(test)
10
- new.tap do |test_result|
11
- test_result.name = test['full_description']
12
- test_result.file = test['file_path']
13
- test_result.skipped = test['status'] == 'pending'
14
- test_result.failures = failures_from_json_exceptions(test)
15
- test_result.testcase = test['testcase']
16
- end
7
+ def self.from_json(report)
8
+ JsonTestResult.new(report)
17
9
  end
18
10
 
19
- def self.from_junit(test)
20
- new.tap do |test_result|
21
- test_result.name = test['name']
22
- test_result.file = test['file']
23
- test_result.skipped = test.search('skipped').any?
24
- test_result.failures = failures_from_junit_exceptions(test)
25
- end
11
+ def self.from_junit(report)
12
+ JUnitTestResult.new(report)
26
13
  end
27
14
 
28
- def self.failures_from_json_exceptions(test)
29
- return [] unless test.key?('exceptions')
15
+ attr_accessor :report, :failures
30
16
 
31
- test['exceptions'].map do |exception|
32
- spec_file_first_index = exception['backtrace'].rindex do |line|
33
- line.include?(File.basename(test['file_path']))
34
- end
17
+ def initialize(report)
18
+ self.report = report
19
+ self.failures = failures_from_exceptions
20
+ end
35
21
 
36
- {
37
- 'message' => "#{exception['class']}: #{exception['message']}",
38
- 'stacktrace' => "#{exception['message_lines'].join("\n")}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}"
39
- }
40
- end
22
+ def name
23
+ raise NotImplementedError
41
24
  end
42
- private_class_method :failures_from_json_exceptions
43
25
 
44
- def self.failures_from_junit_exceptions(test)
45
- failures = test.search('failure')
46
- return [] if failures.empty?
26
+ def file
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def skipped
31
+ raise NotImplementedError
32
+ end
33
+
34
+ private
35
+
36
+ def failures_from_exceptions
37
+ raise NotImplementedError
38
+ end
39
+
40
+ class JsonTestResult < TestResult
41
+ def name
42
+ report['full_description']
43
+ end
44
+
45
+ def file
46
+ report['file_path']
47
+ end
48
+
49
+ def skipped
50
+ report['status'] == 'pending'
51
+ end
52
+
53
+ def testcase
54
+ report['testcase']
55
+ end
47
56
 
48
- failures.map do |exception|
49
- trace = exception.content.split("\n").map(&:strip)
50
- spec_file_first_index = trace.rindex do |line|
51
- line.include?(File.basename(test['file']))
57
+ def testcase=(new_testcase)
58
+ report['testcase'] = new_testcase
59
+ end
60
+
61
+ private
62
+
63
+ # rubocop:disable Metrics/AbcSize
64
+ def failures_from_exceptions
65
+ return [] unless report.key?('exceptions')
66
+
67
+ report['exceptions'].map do |exception|
68
+ spec_file_first_index = exception['backtrace'].rindex do |line|
69
+ line.include?(File.basename(report['file_path']))
70
+ end
71
+
72
+ {
73
+ 'message' => "#{exception['class']}: #{exception['message']}",
74
+ 'stacktrace' => "#{exception['message_lines'].join("\n")}\n#{exception['backtrace'].slice(0..spec_file_first_index).join("\n")}"
75
+ }
52
76
  end
77
+ end
78
+ # rubocop:enable Metrics/AbcSize
79
+ end
80
+
81
+ class JUnitTestResult < TestResult
82
+ def name
83
+ report['name']
84
+ end
53
85
 
54
- {
55
- 'message' => "#{exception['type']}: #{exception['message']}",
56
- 'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
57
- }
86
+ def file
87
+ report['file']
88
+ end
89
+
90
+ def skipped
91
+ report.search('skipped').any?
92
+ end
93
+
94
+ attr_accessor :testcase # Ignore it for now
95
+
96
+ private
97
+
98
+ def failures_from_exceptions
99
+ failures = report.search('failure')
100
+ return [] if failures.empty?
101
+
102
+ failures.map do |exception|
103
+ trace = exception.content.split("\n").map(&:strip)
104
+ spec_file_first_index = trace.rindex do |line|
105
+ line.include?(File.basename(report['file']))
106
+ end
107
+
108
+ {
109
+ 'message' => "#{exception['type']}: #{exception['message']}",
110
+ 'stacktrace' => trace.slice(0..spec_file_first_index).join("\n")
111
+ }
112
+ end
58
113
  end
59
114
  end
60
- private_class_method :failures_from_junit_exceptions
61
115
  end
62
116
  end
63
117
  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
@@ -62,6 +62,8 @@ module Gitlab
62
62
  'KNAPSACK_TEST_FILE_PATTERN' => :knapsack_test_file_pattern,
63
63
  'KNAPSACK_TEST_DIR' => :knapsack_test_dir,
64
64
  'CI' => :ci,
65
+ 'CI_JOB_ID' => :ci_job_id,
66
+ 'CI_JOB_URL' => :ci_job_url,
65
67
  'CI_RUNNER_ID' => :ci_runner_id,
66
68
  'CI_SERVER_HOST' => :ci_server_host,
67
69
  'CI_SERVER_PERSONAL_ACCESS_TOKEN' => :ci_server_personal_access_token,
@@ -81,11 +83,16 @@ module Gitlab
81
83
  'JIRA_HOSTNAME' => :jira_hostname,
82
84
  'JIRA_ADMIN_USERNAME' => :jira_admin_username,
83
85
  'JIRA_ADMIN_PASSWORD' => :jira_admin_password,
84
- 'CACHE_NAMESPACE_NAME' => :cache_namespace_name
86
+ 'CACHE_NAMESPACE_NAME' => :cache_namespace_name,
87
+ 'DEPLOY_VERSION' => :deploy_version
85
88
  }.freeze
86
89
 
87
- ENV_VARIABLES.each_value do |accessor|
88
- send(:attr_accessor, accessor) # rubocop:disable GitlabSecurity/PublicSend
90
+ ENV_VARIABLES.each do |env_name, method_name|
91
+ attr_writer(method_name)
92
+
93
+ define_method(method_name) do
94
+ ENV[env_name] || instance_variable_get("@#{method_name}")
95
+ end
89
96
  end
90
97
 
91
98
  def gitlab_username
@@ -112,10 +119,6 @@ module Gitlab
112
119
  ENV['CI_JOB_TOKEN']
113
120
  end
114
121
 
115
- def ci_job_url
116
- ENV['CI_JOB_URL']
117
- end
118
-
119
122
  def ci_pipeline_source
120
123
  ENV['CI_PIPELINE_SOURCE']
121
124
  end
@@ -124,30 +127,14 @@ module Gitlab
124
127
  ENV['CI_PROJECT_NAME']
125
128
  end
126
129
 
127
- def ci_slack_webhook_url
128
- ENV['CI_SLACK_WEBHOOK_URL']
129
- end
130
-
131
130
  def pipeline_from_project_name
132
131
  ci_project_name.to_s.start_with?('gitlab-qa') ? 'master' : ci_project_name
133
132
  end
134
133
 
135
- def slack_qa_channel
136
- ENV['SLACK_QA_CHANNEL']
137
- end
138
-
139
- def slack_icon_emoji
140
- ENV['SLACK_ICON_EMOJI']
141
- end
142
-
143
134
  def run_id
144
135
  @run_id ||= "gitlab-qa-run-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(4)}"
145
136
  end
146
137
 
147
- def qa_access_token
148
- ENV['GITLAB_QA_ACCESS_TOKEN']
149
- end
150
-
151
138
  def dev_access_token_variable
152
139
  env_value_if_defined('GITLAB_QA_DEV_ACCESS_TOKEN')
153
140
  end
@@ -227,10 +214,6 @@ module Gitlab
227
214
  enabled?(ENV['QA_SKIP_PULL'], default: false)
228
215
  end
229
216
 
230
- def gitlab_qa_formless_login_token
231
- env_value_if_defined('GITLAB_QA_FORMLESS_LOGIN_TOKEN')
232
- end
233
-
234
217
  private
235
218
 
236
219
  def enabled?(value, default: true)
@@ -15,6 +15,8 @@ module Gitlab
15
15
  @database = 'postgres'
16
16
  @spec_suite = 'Test::Instance::All'
17
17
  @network = 'test'
18
+ @env = {}
19
+ @tag = 'gitaly_cluster'
18
20
  end
19
21
 
20
22
  # rubocop:disable Metrics/AbcSize
@@ -51,14 +53,17 @@ module Gitlab
51
53
  gitlab.instance do
52
54
  puts "Running Gitaly Cluster specs!"
53
55
 
54
- rspec_args << "--" unless rspec_args.include?('--')
55
- rspec_args << %w[--tag gitaly_cluster]
56
+ if @tag
57
+ rspec_args << "--" unless rspec_args.include?('--')
58
+ rspec_args << "--tag" << @tag
59
+ end
56
60
 
57
61
  Component::Specs.perform do |specs|
58
62
  specs.suite = spec_suite
59
63
  specs.release = gitlab.release
60
64
  specs.network = gitlab.network
61
65
  specs.args = [gitlab.address, *rspec_args]
66
+ specs.env = @env
62
67
  end
63
68
  end
64
69
  end
@@ -3,105 +3,62 @@ module Gitlab
3
3
  module Scenario
4
4
  module Test
5
5
  module Integration
6
- class Praefect < Scenario::Template
7
- # rubocop:disable Metrics/AbcSize
8
- def perform(release, *rspec_args)
9
- Docker::Volumes.new.with_temporary_volumes do |volumes|
10
- # Create the Praefect database before enabling Praefect
11
- Component::Gitlab.perform do |gitlab|
12
- gitlab.release = QA::Release.new(release)
13
- gitlab.name = 'gitlab'
14
- gitlab.network = 'test'
15
- gitlab.volumes = volumes
16
- gitlab.exec_commands = [
17
- 'gitlab-psql -d template1 -c "CREATE DATABASE praefect_production OWNER gitlab"',
18
- 'mkdir -p /var/opt/gitlab/git-data/repositories/praefect',
19
- 'chown -R git:root /var/opt/gitlab/git-data/repositories'
20
- ]
6
+ class Praefect < GitalyCluster
7
+ attr_reader :gitlab_name, :spec_suite
21
8
 
22
- gitlab.act do
23
- prepare
24
- start
25
- reconfigure
26
- process_exec_commands
27
- wait
28
- teardown!
29
- end
30
- end
9
+ def initialize
10
+ super
31
11
 
32
- # Restart GitLab with Praefect enabled and then run the tests
33
- Component::Gitlab.perform do |gitlab|
34
- gitlab.release = QA::Release.new(release)
35
- gitlab.name = 'gitlab'
36
- gitlab.network = 'test'
37
- gitlab.volumes = volumes
38
- gitlab.omnibus_config = omnibus_config_with_praefect
39
-
40
- gitlab.act do
41
- prepare_gitlab_omnibus_config
42
- start
43
- reconfigure
44
- wait
45
-
46
- puts "Running Praefect specs!"
47
-
48
- Component::Specs.perform do |specs|
49
- specs.suite = 'Test::Instance::All'
50
- specs.release = gitlab.release
51
- specs.network = gitlab.network
52
- specs.args = [gitlab.address, *rspec_args]
53
- specs.env = { QA_PRAEFECT_REPOSITORY_STORAGE: 'default' }
54
- end
55
-
56
- teardown
57
- end
58
- end
59
- end
12
+ @tag = nil
13
+ @env = { QA_PRAEFECT_REPOSITORY_STORAGE: 'default' }
60
14
  end
61
- # rubocop:enable Metrics/AbcSize
62
-
63
- private
64
15
 
65
- def omnibus_config_with_praefect
16
+ def gitlab_omnibus_configuration
66
17
  <<~OMNIBUS
67
- gitaly['enable'] = true;
18
+ external_url 'http://#{@gitlab_name}.#{@network}';
19
+
20
+ git_data_dirs({
21
+ 'default' => {
22
+ 'gitaly_address' => 'tcp://#{@praefect_node_name}.#{@network}:2305',
23
+ 'gitaly_token' => 'PRAEFECT_EXTERNAL_TOKEN'
24
+ },
25
+ 'gitaly' => {
26
+ 'gitaly_address' => 'tcp://#{@gitlab_name}.#{@network}:8075',
27
+ 'path' => '/var/opt/gitlab/git-data'
28
+ }
29
+ });
68
30
  gitaly['listen_addr'] = '0.0.0.0:8075';
69
31
  gitaly['auth_token'] = 'secret-token';
70
32
  gitaly['storage'] = [
71
- {
72
- 'name' => 'praefect-gitaly-0',
73
- 'path' => '/var/opt/gitlab/git-data/repositories/praefect'
74
- },
75
33
  {
76
34
  'name' => 'gitaly',
77
- 'path' => '/var/opt/gitlab/git-data/repositories/gitaly'
35
+ 'path' => '/var/opt/gitlab/git-data/repositories'
78
36
  }
79
37
  ];
80
- praefect['enable'] = true;
81
- praefect['listen_addr'] = '0.0.0.0:2305';
82
- praefect['auth_token'] = 'secret-token';
83
- praefect['virtual_storages'] = {
84
- 'default' => {
85
- 'praefect-gitaly-0' => {
86
- 'address' => 'tcp://localhost:8075',
87
- 'token' => 'secret-token',
88
- 'primary' => true
89
- }
90
- }
91
- };
92
- praefect['database_host'] = '/var/opt/gitlab/postgresql';
93
- praefect['database_user'] = 'gitlab';
94
- praefect['database_dbname'] = 'praefect_production';
95
- praefect['postgres_queue_enabled'] = true;
96
38
  gitlab_rails['gitaly_token'] = 'secret-token';
97
- git_data_dirs({
98
- 'default' => {
99
- 'gitaly_address' => 'tcp://localhost:2305'
39
+ gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN';
40
+ prometheus['scrape_configs'] = [
41
+ {
42
+ 'job_name' => 'praefect',
43
+ 'static_configs' => [
44
+ 'targets' => [
45
+ '#{@praefect_node_name}.#{@network}:9652'
46
+ ]
47
+ ]
100
48
  },
101
- 'gitaly' => {
102
- 'path' => '/var/opt/gitlab/git-data/repositories/gitaly'
49
+ {
50
+ 'job_name' => 'praefect-gitaly',
51
+ 'static_configs' => [
52
+ 'targets' => [
53
+ '#{@primary_node_name}.#{@network}:9236',
54
+ '#{@secondary_node_name}.#{@network}:9236',
55
+ '#{@tertiary_node_name}.#{@network}:9236'
56
+ ]
57
+ ]
103
58
  }
104
- });
59
+ ];
60
+ grafana['disable_login_form'] = false;
61
+ grafana['admin_password'] = 'GRAFANA_ADMIN_PASSWORD';
105
62
  OMNIBUS
106
63
  end
107
64
  end
@@ -15,7 +15,7 @@ module Gitlab
15
15
  reconfigure
16
16
 
17
17
  restart
18
- wait
18
+ wait_until_ready
19
19
  teardown
20
20
  end
21
21
  end
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module QA
3
- VERSION = '6.1.3'.freeze
3
+ VERSION = '6.5.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.1.3
4
+ version: 6.5.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-10 00:00:00.000000000 Z
11
+ date: 2020-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: climate_control
@@ -257,9 +257,12 @@ 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/base_test_results.rb
261
+ - lib/gitlab/qa/report/gitlab_issue_client.rb
260
262
  - lib/gitlab/qa/report/json_test_results.rb
261
263
  - lib/gitlab/qa/report/junit_test_results.rb
262
264
  - lib/gitlab/qa/report/prepare_stage_reports.rb
265
+ - lib/gitlab/qa/report/report_as_issue.rb
263
266
  - lib/gitlab/qa/report/results_in_issues.rb
264
267
  - lib/gitlab/qa/report/summary_table.rb
265
268
  - lib/gitlab/qa/report/test_result.rb