gitlab-qa 6.2.0 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +38 -0
- data/.gitlab/merge_request_templates/Release.md +1 -1
- data/docs/what_tests_can_be_run.md +17 -0
- data/lib/gitlab/qa.rb +4 -0
- data/lib/gitlab/qa/component/gitlab.rb +2 -2
- data/lib/gitlab/qa/component/postgresql.rb +2 -1
- data/lib/gitlab/qa/component/specs.rb +2 -0
- data/lib/gitlab/qa/report/base_test_results.rb +36 -0
- data/lib/gitlab/qa/report/gitlab_issue_client.rb +135 -0
- data/lib/gitlab/qa/report/json_test_results.rb +14 -6
- data/lib/gitlab/qa/report/junit_test_results.rb +12 -6
- data/lib/gitlab/qa/report/report_as_issue.rb +47 -0
- data/lib/gitlab/qa/report/results_in_issues.rb +33 -153
- data/lib/gitlab/qa/report/test_result.rb +95 -41
- data/lib/gitlab/qa/reporter.rb +4 -19
- data/lib/gitlab/qa/runtime/env.rb +8 -26
- data/lib/gitlab/qa/scenario/test/integration/actioncable.rb +36 -0
- data/lib/gitlab/qa/scenario/test/integration/gitaly_cluster.rb +7 -2
- data/lib/gitlab/qa/scenario/test/integration/praefect.rb +41 -84
- data/lib/gitlab/qa/scenario/test/omnibus/image.rb +1 -1
- data/lib/gitlab/qa/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5774bb53da5791580f00788fede32864529d0221748c86fa62e76a74a73e1fcd
|
|
4
|
+
data.tar.gz: f94d8a02877d5f15988357cc208b5acde672822881a9d92c9a3cda4c2d4e588b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d853b45ec499807f46691b719283544d7c3260e24ccec1fa0632e55e885201f38bac15ebb147672ea949fdaf830c81f8a4a827c46c2fb135a32d40b445cfaf8
|
|
7
|
+
data.tar.gz: a66c9779a3f128e0dc2226b4a9306b9be33567b6cc21ec1e9fab3ef0d245b8f2b20c29ec9a71b9b264e558489e2cb0749e57fd08992b2776b40709c0a0317805
|
data/.gitlab-ci.yml
CHANGED
|
@@ -729,6 +729,44 @@ ee:packages-quarantine:
|
|
|
729
729
|
variables:
|
|
730
730
|
QA_SCENARIO: "Test::Integration::Packages"
|
|
731
731
|
|
|
732
|
+
ce:actioncable:
|
|
733
|
+
extends:
|
|
734
|
+
- .test
|
|
735
|
+
- .high-capacity
|
|
736
|
+
- .ce-qa
|
|
737
|
+
- .rspec-report-opts
|
|
738
|
+
variables:
|
|
739
|
+
QA_SCENARIO: "Test::Integration::Actioncable"
|
|
740
|
+
|
|
741
|
+
ce:actioncable-quarantine:
|
|
742
|
+
extends:
|
|
743
|
+
- .test
|
|
744
|
+
- .high-capacity
|
|
745
|
+
- .ce-qa
|
|
746
|
+
- .quarantine
|
|
747
|
+
- .rspec-report-opts
|
|
748
|
+
variables:
|
|
749
|
+
QA_SCENARIO: "Test::Integration::Actioncable"
|
|
750
|
+
|
|
751
|
+
ee:actioncable:
|
|
752
|
+
extends:
|
|
753
|
+
- .test
|
|
754
|
+
- .high-capacity
|
|
755
|
+
- .ee-qa
|
|
756
|
+
- .rspec-report-opts
|
|
757
|
+
variables:
|
|
758
|
+
QA_SCENARIO: "Test::Integration::Actioncable"
|
|
759
|
+
|
|
760
|
+
ee:actioncable-quarantine:
|
|
761
|
+
extends:
|
|
762
|
+
- .test
|
|
763
|
+
- .high-capacity
|
|
764
|
+
- .ee-qa
|
|
765
|
+
- .quarantine
|
|
766
|
+
- .rspec-report-opts
|
|
767
|
+
variables:
|
|
768
|
+
QA_SCENARIO: "Test::Integration::Actioncable"
|
|
769
|
+
|
|
732
770
|
ee:elasticsearch:
|
|
733
771
|
extends:
|
|
734
772
|
- .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 ~
|
|
35
|
+
/label ~Quality ~"feature::maintenance"
|
|
@@ -545,6 +545,23 @@ $ export EE_LICENSE=$(cat /path/to/GitLab.gitlab_license)
|
|
|
545
545
|
$ gitlab-qa Test::Integration::Jira EE
|
|
546
546
|
```
|
|
547
547
|
|
|
548
|
+
### `Test::Integration::Actioncable CE|EE|<full image address>`
|
|
549
|
+
|
|
550
|
+
This tests the real-time assignees feature by setting
|
|
551
|
+
`actioncable['enable'] = true` in the Omnibus configuration
|
|
552
|
+
before starting the GitLab container.
|
|
553
|
+
|
|
554
|
+
To run tests against the GitLab container, a GitLab QA (`gitlab/gitlab-qa`)
|
|
555
|
+
container is spun up and tests are run from it by running the
|
|
556
|
+
`Test::Instance::All` scenario with the `--tag actioncable` RSpec parameter,
|
|
557
|
+
which runs only the tests with `:actioncable` metadata.
|
|
558
|
+
|
|
559
|
+
Example:
|
|
560
|
+
|
|
561
|
+
```
|
|
562
|
+
$ gitlab-qa Test::Integration::Actioncable CE
|
|
563
|
+
```
|
|
564
|
+
|
|
548
565
|
### `Test::Instance::Any CE|EE|<full image address>:nightly|latest|any_tag http://your.instance.gitlab`
|
|
549
566
|
|
|
550
567
|
This tests that a live GitLab instance works as expected by running tests
|
data/lib/gitlab/qa.rb
CHANGED
|
@@ -39,6 +39,7 @@ module Gitlab
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
module Integration
|
|
42
|
+
autoload :Actioncable, 'gitlab/qa/scenario/test/integration/actioncable'
|
|
42
43
|
autoload :Geo, 'gitlab/qa/scenario/test/integration/geo'
|
|
43
44
|
autoload :LDAP, 'gitlab/qa/scenario/test/integration/ldap'
|
|
44
45
|
autoload :LDAPNoTLS, 'gitlab/qa/scenario/test/integration/ldap_no_tls'
|
|
@@ -97,9 +98,12 @@ module Gitlab
|
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
module Report
|
|
101
|
+
autoload :GitlabIssueClient, 'gitlab/qa/report/gitlab_issue_client'
|
|
102
|
+
autoload :BaseTestResults, 'gitlab/qa/report/base_test_results'
|
|
100
103
|
autoload :JsonTestResults, 'gitlab/qa/report/json_test_results'
|
|
101
104
|
autoload :JUnitTestResults, 'gitlab/qa/report/junit_test_results'
|
|
102
105
|
autoload :PrepareStageReports, 'gitlab/qa/report/prepare_stage_reports'
|
|
106
|
+
autoload :ReportAsIssue, 'gitlab/qa/report/report_as_issue'
|
|
103
107
|
autoload :ResultsInIssues, 'gitlab/qa/report/results_in_issues'
|
|
104
108
|
autoload :SummaryTable, 'gitlab/qa/report/summary_table'
|
|
105
109
|
autoload :TestResult, 'gitlab/qa/report/test_result'
|
|
@@ -128,10 +128,10 @@ module Gitlab
|
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
def
|
|
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(
|
|
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
|
-
|
|
8
|
+
class JsonTestResults < BaseTestResults
|
|
9
|
+
def write(path)
|
|
10
|
+
json = results.merge('examples' => testcases.map(&:report))
|
|
10
11
|
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
8
|
+
class JUnitTestResults < BaseTestResults
|
|
9
|
+
def write(path)
|
|
10
|
+
# Ignore it for now
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
10
14
|
|
|
11
|
-
def
|
|
12
|
-
|
|
15
|
+
def parse(path)
|
|
16
|
+
Nokogiri::XML.parse(File.read(path))
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
def
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 |
|
|
48
|
-
puts "Reporting tests in #{
|
|
49
|
-
extension = File.extname(
|
|
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(
|
|
24
|
+
test_results = Report::JsonTestResults.new(path)
|
|
54
25
|
when '.xml'
|
|
55
|
-
test_results = Report::JUnitTestResults.new(
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
issues.first
|
|
73
|
+
end
|
|
153
74
|
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
20
|
-
new
|
|
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
|
-
|
|
29
|
-
return [] unless test.key?('exceptions')
|
|
15
|
+
attr_accessor :report, :failures
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
def initialize(report)
|
|
18
|
+
self.report = report
|
|
19
|
+
self.failures = failures_from_exceptions
|
|
20
|
+
end
|
|
35
21
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
data/lib/gitlab/qa/reporter.rb
CHANGED
|
@@ -60,34 +60,19 @@ module Gitlab
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
if args.any?
|
|
63
|
-
if report_options
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -85,8 +87,12 @@ module Gitlab
|
|
|
85
87
|
'DEPLOY_VERSION' => :deploy_version
|
|
86
88
|
}.freeze
|
|
87
89
|
|
|
88
|
-
ENV_VARIABLES.
|
|
89
|
-
|
|
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
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
def gitlab_username
|
|
@@ -113,10 +119,6 @@ module Gitlab
|
|
|
113
119
|
ENV['CI_JOB_TOKEN']
|
|
114
120
|
end
|
|
115
121
|
|
|
116
|
-
def ci_job_url
|
|
117
|
-
ENV['CI_JOB_URL']
|
|
118
|
-
end
|
|
119
|
-
|
|
120
122
|
def ci_pipeline_source
|
|
121
123
|
ENV['CI_PIPELINE_SOURCE']
|
|
122
124
|
end
|
|
@@ -125,30 +127,14 @@ module Gitlab
|
|
|
125
127
|
ENV['CI_PROJECT_NAME']
|
|
126
128
|
end
|
|
127
129
|
|
|
128
|
-
def ci_slack_webhook_url
|
|
129
|
-
ENV['CI_SLACK_WEBHOOK_URL']
|
|
130
|
-
end
|
|
131
|
-
|
|
132
130
|
def pipeline_from_project_name
|
|
133
131
|
ci_project_name.to_s.start_with?('gitlab-qa') ? 'master' : ci_project_name
|
|
134
132
|
end
|
|
135
133
|
|
|
136
|
-
def slack_qa_channel
|
|
137
|
-
ENV['SLACK_QA_CHANNEL']
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def slack_icon_emoji
|
|
141
|
-
ENV['SLACK_ICON_EMOJI']
|
|
142
|
-
end
|
|
143
|
-
|
|
144
134
|
def run_id
|
|
145
135
|
@run_id ||= "gitlab-qa-run-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(4)}"
|
|
146
136
|
end
|
|
147
137
|
|
|
148
|
-
def qa_access_token
|
|
149
|
-
ENV['GITLAB_QA_ACCESS_TOKEN']
|
|
150
|
-
end
|
|
151
|
-
|
|
152
138
|
def dev_access_token_variable
|
|
153
139
|
env_value_if_defined('GITLAB_QA_DEV_ACCESS_TOKEN')
|
|
154
140
|
end
|
|
@@ -228,10 +214,6 @@ module Gitlab
|
|
|
228
214
|
enabled?(ENV['QA_SKIP_PULL'], default: false)
|
|
229
215
|
end
|
|
230
216
|
|
|
231
|
-
def gitlab_qa_formless_login_token
|
|
232
|
-
env_value_if_defined('GITLAB_QA_FORMLESS_LOGIN_TOKEN')
|
|
233
|
-
end
|
|
234
|
-
|
|
235
217
|
private
|
|
236
218
|
|
|
237
219
|
def enabled?(value, default: true)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Gitlab
|
|
2
|
+
module QA
|
|
3
|
+
module Scenario
|
|
4
|
+
module Test
|
|
5
|
+
module Integration
|
|
6
|
+
class Actioncable < Scenario::Template
|
|
7
|
+
def perform(release, *rspec_args)
|
|
8
|
+
Component::Gitlab.perform do |gitlab|
|
|
9
|
+
gitlab.release = QA::Release.new(release)
|
|
10
|
+
gitlab.name = 'gitlab-actioncable'
|
|
11
|
+
gitlab.network = 'test'
|
|
12
|
+
gitlab.omnibus_config = <<~OMNIBUS
|
|
13
|
+
actioncable['enable'] = true;
|
|
14
|
+
OMNIBUS
|
|
15
|
+
|
|
16
|
+
gitlab.instance do
|
|
17
|
+
puts "Running actioncable specs!"
|
|
18
|
+
|
|
19
|
+
rspec_args << "--" unless rspec_args.include?('--')
|
|
20
|
+
rspec_args << %w[--tag actioncable]
|
|
21
|
+
|
|
22
|
+
Component::Specs.perform do |specs|
|
|
23
|
+
specs.suite = 'Test::Instance::All'
|
|
24
|
+
specs.release = gitlab.release
|
|
25
|
+
specs.network = gitlab.network
|
|
26
|
+
specs.args = [gitlab.address, *rspec_args]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -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
|
-
|
|
55
|
-
|
|
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 <
|
|
7
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
start
|
|
25
|
-
reconfigure
|
|
26
|
-
process_exec_commands
|
|
27
|
-
wait
|
|
28
|
-
teardown!
|
|
29
|
-
end
|
|
30
|
-
end
|
|
9
|
+
def initialize
|
|
10
|
+
super
|
|
31
11
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
16
|
+
def gitlab_omnibus_configuration
|
|
66
17
|
<<~OMNIBUS
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
'
|
|
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
|
data/lib/gitlab/qa/version.rb
CHANGED
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.
|
|
4
|
+
version: 6.6.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-
|
|
11
|
+
date: 2020-10-26 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
|
|
@@ -285,6 +288,7 @@ files:
|
|
|
285
288
|
- lib/gitlab/qa/scenario/test/instance/smoke.rb
|
|
286
289
|
- lib/gitlab/qa/scenario/test/instance/staging.rb
|
|
287
290
|
- lib/gitlab/qa/scenario/test/instance/staging_geo.rb
|
|
291
|
+
- lib/gitlab/qa/scenario/test/integration/actioncable.rb
|
|
288
292
|
- lib/gitlab/qa/scenario/test/integration/elasticsearch.rb
|
|
289
293
|
- lib/gitlab/qa/scenario/test/integration/geo.rb
|
|
290
294
|
- lib/gitlab/qa/scenario/test/integration/gitaly_cluster.rb
|