gitlab-qa 6.1.2 → 6.4.1

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: 6f02b51e013ff64f6e9080771f02e439efcf9093cedba40ca625fd10a932436e
4
- data.tar.gz: 7089a36078ed197e4abb318f78d67d8f04289803c5bbfb6598fcd0761d071e6b
3
+ metadata.gz: 156602d47187662c86a6cdf07d6e737cdf8083c1098a411dc612ffbdaeb0f5fc
4
+ data.tar.gz: f0678a3a1587fbe094ecb3804b2d4a3e6b040ee27e2c7dc766c9b9a1f3495341
5
5
  SHA512:
6
- metadata.gz: 4906ac6a4ddacb5061c870a2e7e484741c60b488a8efb5051187d768c7a9a990d73b4bb5ba47ff9cf48980a63197da25890c428b27aaddb5caf69e06931ced0e
7
- data.tar.gz: 8dd810d48fcbbd70f858ff4fd511a9c79f29131f13968b2e990483a79e927cd6f7841fcbf47dd6ea63933ea09b7319ff630b1d337085e4a4a731eb606a9c8cc8
6
+ metadata.gz: 4def52124e2d59910e64c29699b55faabd46e29bca5030938ecd93aec69b638fb855a6943ffaad91ede0c98d012b47fedbb6f340cbf74188cb1cf1386d2d136b
7
+ data.tar.gz: 92fca187c1845867f066921c3ff7718de149043526c5f85f6108db4053554b993e85c8f382c084cd49a20f1198c0456c89ea067182d9d0ade2a43801a2e7b382
@@ -94,7 +94,7 @@ release:
94
94
  - exe/gitlab-qa ${QA_SCENARIO:=Test::Instance::Image} ${RELEASE:=$DEFAULT_RELEASE} -- $QA_TESTS $QA_RSPEC_TAGS $RSPEC_REPORT_OPTS || test_run_exit_code=$?
95
95
  - exe/gitlab-qa-report --update-screenshot-path "gitlab-qa-run-*/**/rspec-*.xml"
96
96
  - export GITLAB_QA_ACCESS_TOKEN="$GITLAB_QA_PRODUCTION_ACCESS_TOKEN"
97
- - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
97
+ - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ] || [[ "$TOP_UPSTREAM_SOURCE_JOB" == https://ops.gitlab.net* ]]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
98
98
  - exit $test_run_exit_code
99
99
 
100
100
  .ce-qa:
@@ -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
@@ -370,7 +332,7 @@ ce:update:
370
332
  script:
371
333
  - exe/gitlab-qa Test::Omnibus::Update ${RELEASE:=CE} ${RELEASE:=CE} -- $RSPEC_REPORT_OPTS || test_run_exit_code=$?
372
334
  - export GITLAB_QA_ACCESS_TOKEN="$GITLAB_QA_PRODUCTION_ACCESS_TOKEN"
373
- - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT"; fi
335
+ - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ] || [[ "$TOP_UPSTREAM_SOURCE_JOB" == https://ops.gitlab.net* ]]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
374
336
  - exit $test_run_exit_code
375
337
  extends:
376
338
  - .test
@@ -384,7 +346,7 @@ ce:update-quarantine:
384
346
  script:
385
347
  - exe/gitlab-qa Test::Omnibus::Update ${RELEASE:=CE} ${RELEASE:=CE} -- --tag quarantine --tag ~orchestrated $RSPEC_REPORT_OPTS || test_run_exit_code=$?
386
348
  - export GITLAB_QA_ACCESS_TOKEN="$GITLAB_QA_PRODUCTION_ACCESS_TOKEN"
387
- - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT"; fi
349
+ - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ] || [[ "$TOP_UPSTREAM_SOURCE_JOB" == https://ops.gitlab.net* ]]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
388
350
  - exit $test_run_exit_code
389
351
  extends:
390
352
  - .test
@@ -397,7 +359,7 @@ ee:update:
397
359
  script:
398
360
  - exe/gitlab-qa Test::Omnibus::Update ${RELEASE:=EE} ${RELEASE:=EE} -- $RSPEC_REPORT_OPTS || test_run_exit_code=$?
399
361
  - export GITLAB_QA_ACCESS_TOKEN="$GITLAB_QA_PRODUCTION_ACCESS_TOKEN"
400
- - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT"; fi
362
+ - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ] || [[ "$TOP_UPSTREAM_SOURCE_JOB" == https://ops.gitlab.net* ]]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
401
363
  - exit $test_run_exit_code
402
364
  extends:
403
365
  - .test
@@ -411,7 +373,7 @@ ee:update-quarantine:
411
373
  script:
412
374
  - exe/gitlab-qa Test::Omnibus::Update ${RELEASE:=EE} ${RELEASE:=EE} -- --tag quarantine --tag ~orchestrated $RSPEC_REPORT_OPTS || test_run_exit_code=$?
413
375
  - export GITLAB_QA_ACCESS_TOKEN="$GITLAB_QA_PRODUCTION_ACCESS_TOKEN"
414
- - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT"; fi
376
+ - if [ "$TOP_UPSTREAM_SOURCE_REF" == "master" ] || [[ "$TOP_UPSTREAM_SOURCE_JOB" == https://ops.gitlab.net* ]]; then exe/gitlab-qa-report --report-in-issues "gitlab-qa-run-*/**/rspec-*.json" --project "$QA_TESTCASES_REPORTING_PROJECT" || true; fi
415
377
  - exit $test_run_exit_code
416
378
  extends:
417
379
  - .test
@@ -676,6 +638,7 @@ ce:kubernetes:
676
638
  - .high-capacity
677
639
  - .ce-qa
678
640
  - .rspec-report-opts
641
+ allow_failure: true
679
642
  variables:
680
643
  QA_SCENARIO: "Test::Integration::Kubernetes"
681
644
 
@@ -695,6 +658,7 @@ ee:kubernetes:
695
658
  - .high-capacity
696
659
  - .ee-qa
697
660
  - .rspec-report-opts
661
+ allow_failure: true
698
662
  variables:
699
663
  QA_SCENARIO: "Test::Integration::Kubernetes"
700
664
 
@@ -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
@@ -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::ReadTimeout, 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['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)
@@ -24,7 +24,7 @@ module Gitlab
24
24
  start
25
25
  reconfigure
26
26
  process_exec_commands
27
- wait
27
+ wait_until_ready
28
28
  teardown!
29
29
  end
30
30
  end
@@ -41,7 +41,7 @@ module Gitlab
41
41
  prepare_gitlab_omnibus_config
42
42
  start
43
43
  reconfigure
44
- wait
44
+ wait_until_ready
45
45
 
46
46
  puts "Running Praefect specs!"
47
47
 
@@ -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.2'.freeze
3
+ VERSION = '6.4.1'.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.2
4
+ version: 6.4.1
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-03 00:00:00.000000000 Z
11
+ date: 2020-10-12 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