gitlab-qa 4.20.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +85 -97
- data/docs/what_tests_can_be_run.md +18 -0
- data/exe/gitlab-qa-report +8 -0
- data/gitlab-qa.gemspec +2 -0
- data/lib/gitlab/qa.rb +3 -0
- data/lib/gitlab/qa/report/results_in_issues.rb +191 -0
- data/lib/gitlab/qa/reporter.rb +68 -0
- data/lib/gitlab/qa/runner.rb +1 -15
- data/lib/gitlab/qa/runtime/env.rb +17 -1
- data/lib/gitlab/qa/runtime/token_finder.rb +44 -0
- data/lib/gitlab/qa/version.rb +1 -1
- metadata +35 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbeeb65a1b5a859f9908822611d61985da8ba9dd3118a56fe9e52ce0e645e09b
|
4
|
+
data.tar.gz: 78f96fa6fe4c8d7e4139e689ff08fd8c71df18d3d111917c9c672b36b6376936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b43e0971ca42b9a1fe77475ee94c92c164e3f0a3d8f6a45129f899f67a4708d469b36509df84614d33763d6f469d9702d86ab498aa1151fadfc4297c0b96b5f
|
7
|
+
data.tar.gz: cbbd9b29c55a243614d127b9e6840f6b8a1241e8984d87b05c0c728ac445f0c6e4b8d3cdb85f0a8dc45e1cb6dd82350eb52237354792f292a63a3e6cbd3806ff
|
data/.gitlab-ci.yml
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
-
|
1
|
+
.default-rules:
|
2
|
+
rules:
|
3
|
+
- if: '$CI_COMMIT_TAG || $RELEASE'
|
4
|
+
when: never
|
5
|
+
- if: '$RELEASE == null && $CI_JOB_NAME =~ /staging/'
|
6
|
+
when: manual
|
7
|
+
- if: '$CI_MERGE_REQUEST_ID || $CI_COMMIT_REF_NAME == "master"'
|
2
8
|
|
3
|
-
|
4
|
-
- docker:19.03.0-dind
|
9
|
+
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-ruby-2.6
|
5
10
|
|
6
11
|
stages:
|
7
12
|
- check
|
@@ -10,11 +15,10 @@ stages:
|
|
10
15
|
- notify
|
11
16
|
|
12
17
|
variables:
|
13
|
-
|
14
|
-
DOCKER_DRIVER: overlay
|
18
|
+
DOCKER_DRIVER: overlay2
|
15
19
|
DOCKER_HOST: tcp://docker:2375
|
16
20
|
QA_ARTIFACTS_DIR: $CI_PROJECT_DIR
|
17
|
-
QA_CAN_TEST_GIT_PROTOCOL_V2: '
|
21
|
+
QA_CAN_TEST_GIT_PROTOCOL_V2: 'true'
|
18
22
|
|
19
23
|
cache:
|
20
24
|
key: "ruby:2.6"
|
@@ -29,27 +33,37 @@ before_script:
|
|
29
33
|
fi
|
30
34
|
- export LANG=C.UTF-8
|
31
35
|
|
32
|
-
check:
|
36
|
+
.check-base:
|
37
|
+
extends: .default-rules
|
33
38
|
stage: check
|
34
|
-
|
35
|
-
|
36
|
-
except:
|
37
|
-
- triggers
|
38
|
-
tags:
|
39
|
-
- docker
|
39
|
+
script:
|
40
|
+
- bundle exec $CI_JOB_NAME
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
rubocop:
|
43
|
+
extends: .check-base
|
44
|
+
|
45
|
+
rspec:
|
46
|
+
extends: .check-base
|
47
|
+
|
48
|
+
release:
|
49
|
+
stage: release
|
50
|
+
rules:
|
51
|
+
- if: '$CI_COMMIT_TAG'
|
52
|
+
script:
|
53
|
+
- gem update --system
|
54
|
+
- ruby --version
|
55
|
+
- gem env version
|
56
|
+
- gem build gitlab-qa.gemspec
|
57
|
+
- gem push gitlab-qa*.gem
|
58
|
+
artifacts:
|
59
|
+
paths:
|
60
|
+
- gitlab-qa*.gem
|
61
|
+
expire_in: 30 days
|
49
62
|
|
50
63
|
.test:
|
51
64
|
stage: test
|
52
|
-
|
65
|
+
services:
|
66
|
+
- docker:19.03.0-dind
|
53
67
|
tags:
|
54
68
|
- docker
|
55
69
|
artifacts:
|
@@ -63,23 +77,33 @@ check:rspec:
|
|
63
77
|
.ce-qa:
|
64
78
|
variables:
|
65
79
|
DEFAULT_RELEASE: "CE"
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
80
|
+
rules:
|
81
|
+
- if: '$CI_COMMIT_TAG || $RELEASE =~ /gitlab-ee/'
|
82
|
+
when: never
|
83
|
+
- if: '$RELEASE == null && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
84
|
+
when: manual
|
85
|
+
- if: '$RELEASE =~ /gitlab-ce/ && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
86
|
+
when: manual
|
87
|
+
- if: '$CI_MERGE_REQUEST_ID && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
88
|
+
when: manual
|
89
|
+
- if: '$RELEASE == null || $RELEASE =~ /gitlab-ce/ || $CI_MERGE_REQUEST_ID || $CI_COMMIT_REF_NAME == "master"'
|
70
90
|
|
71
91
|
.ee-qa:
|
72
92
|
variables:
|
73
93
|
DEFAULT_RELEASE: "EE"
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
94
|
+
rules:
|
95
|
+
- if: '$CI_COMMIT_TAG || $RELEASE =~ /gitlab-ce/'
|
96
|
+
when: never
|
97
|
+
- if: '$RELEASE == null && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
98
|
+
when: manual
|
99
|
+
- if: '$RELEASE =~ /gitlab-ee/ && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
100
|
+
when: manual
|
101
|
+
- if: '$CI_MERGE_REQUEST_ID && $CI_JOB_NAME =~ /quarantine|praefect|custom/'
|
102
|
+
when: manual
|
103
|
+
- if: '$RELEASE == null || $RELEASE =~ /gitlab-ee/ || $CI_MERGE_REQUEST_ID || $CI_COMMIT_REF_NAME == "master"'
|
78
104
|
|
79
105
|
.only-qa:
|
80
|
-
|
81
|
-
variables:
|
82
|
-
- $RELEASE
|
106
|
+
extends: .default-rules
|
83
107
|
|
84
108
|
.high-capacity:
|
85
109
|
tags:
|
@@ -100,7 +124,6 @@ check:rspec:
|
|
100
124
|
|
101
125
|
.quarantine:
|
102
126
|
allow_failure: true
|
103
|
-
when: manual
|
104
127
|
|
105
128
|
.echo-custom-variables-before-calling-gitlab-qa:
|
106
129
|
script:
|
@@ -133,7 +156,7 @@ ce:custom-parallel:
|
|
133
156
|
- .ce-qa
|
134
157
|
- .rspec-report-opts
|
135
158
|
- .echo-custom-variables-before-calling-gitlab-qa
|
136
|
-
|
159
|
+
allow_failure: true
|
137
160
|
parallel: 10
|
138
161
|
|
139
162
|
ee:custom-parallel:
|
@@ -143,7 +166,7 @@ ee:custom-parallel:
|
|
143
166
|
- .ee-qa
|
144
167
|
- .rspec-report-opts
|
145
168
|
- .echo-custom-variables-before-calling-gitlab-qa
|
146
|
-
|
169
|
+
allow_failure: true
|
147
170
|
parallel: 10
|
148
171
|
|
149
172
|
ce:instance:
|
@@ -188,24 +211,6 @@ ee:instance-quarantine:
|
|
188
211
|
- .quarantine
|
189
212
|
- .rspec-report-opts
|
190
213
|
|
191
|
-
ce:docker:
|
192
|
-
script:
|
193
|
-
- exe/gitlab-qa Test::Instance::Image ${RELEASE:=CE} -- --tag docker $RSPEC_REPORT_OPTS
|
194
|
-
extends:
|
195
|
-
- .test
|
196
|
-
- .high-capacity
|
197
|
-
- .ce-qa
|
198
|
-
- .rspec-report-opts
|
199
|
-
|
200
|
-
ee:docker:
|
201
|
-
script:
|
202
|
-
- exe/gitlab-qa Test::Instance::Image ${RELEASE:=EE} -- --tag docker $RSPEC_REPORT_OPTS
|
203
|
-
extends:
|
204
|
-
- .test
|
205
|
-
- .high-capacity
|
206
|
-
- .ee-qa
|
207
|
-
- .rspec-report-opts
|
208
|
-
|
209
214
|
ce:relative_url:
|
210
215
|
script:
|
211
216
|
- exe/gitlab-qa Test::Instance::RelativeUrl ${RELEASE:=CE} -- $RSPEC_REPORT_OPTS
|
@@ -753,7 +758,7 @@ ce:praefect:
|
|
753
758
|
- .knapsack-variables
|
754
759
|
- .rspec-report-opts
|
755
760
|
parallel: 5
|
756
|
-
|
761
|
+
allow_failure: true
|
757
762
|
|
758
763
|
ee:praefect:
|
759
764
|
script:
|
@@ -765,7 +770,7 @@ ee:praefect:
|
|
765
770
|
- .knapsack-variables
|
766
771
|
- .rspec-report-opts
|
767
772
|
parallel: 5
|
768
|
-
|
773
|
+
allow_failure: true
|
769
774
|
|
770
775
|
ce:smtp:
|
771
776
|
script:
|
@@ -785,29 +790,6 @@ ee:smtp:
|
|
785
790
|
- .ee-qa
|
786
791
|
- .rspec-report-opts
|
787
792
|
|
788
|
-
.notify_upstream_commit:
|
789
|
-
stage: notify
|
790
|
-
except:
|
791
|
-
variables:
|
792
|
-
- $TOP_UPSTREAM_SOURCE_PROJECT == null
|
793
|
-
- $TOP_UPSTREAM_SOURCE_SHA == null
|
794
|
-
before_script:
|
795
|
-
- gem install gitlab --no-document
|
796
|
-
|
797
|
-
notify_upstream_commit:success:
|
798
|
-
extends:
|
799
|
-
- .notify_upstream_commit
|
800
|
-
script:
|
801
|
-
- bin/notify_upstream_commit success
|
802
|
-
when: on_success
|
803
|
-
|
804
|
-
notify_upstream_commit:failure:
|
805
|
-
extends:
|
806
|
-
- .notify_upstream_commit
|
807
|
-
script:
|
808
|
-
- bin/notify_upstream_commit failure
|
809
|
-
when: on_failure
|
810
|
-
|
811
793
|
# This job requires the `GITLAB_QA_ACCESS_TOKEN` and `GITLAB_QA_DEV_ACCESS_TOKEN`
|
812
794
|
# variable to be passed when triggered.
|
813
795
|
staging:
|
@@ -818,23 +800,29 @@ staging:
|
|
818
800
|
- .test
|
819
801
|
- .high-capacity
|
820
802
|
- .only-qa
|
821
|
-
|
803
|
+
allow_failure: true
|
822
804
|
|
823
|
-
|
824
|
-
stage:
|
805
|
+
.notify_upstream_commit:
|
806
|
+
stage: notify
|
807
|
+
image: ruby:2.6
|
808
|
+
before_script:
|
809
|
+
- gem install gitlab --no-document
|
810
|
+
|
811
|
+
notify_upstream_commit:success:
|
812
|
+
extends: .notify_upstream_commit
|
813
|
+
script:
|
814
|
+
- bin/notify_upstream_commit success
|
825
815
|
rules:
|
826
|
-
- if: '$
|
816
|
+
- if: '$TOP_UPSTREAM_SOURCE_PROJECT && $TOP_UPSTREAM_SOURCE_SHA'
|
827
817
|
when: on_success
|
818
|
+
|
819
|
+
notify_upstream_commit:failure:
|
820
|
+
extends: .notify_upstream_commit
|
828
821
|
script:
|
829
|
-
-
|
830
|
-
|
831
|
-
-
|
832
|
-
|
833
|
-
- gem push gitlab-qa*.gem
|
834
|
-
artifacts:
|
835
|
-
paths:
|
836
|
-
- gitlab-qa*.gem
|
837
|
-
expire_in: 30 days
|
822
|
+
- bin/notify_upstream_commit failure
|
823
|
+
rules:
|
824
|
+
- if: '$TOP_UPSTREAM_SOURCE_PROJECT && $TOP_UPSTREAM_SOURCE_SHA'
|
825
|
+
when: on_failure
|
838
826
|
|
839
827
|
.notify_slack:
|
840
828
|
image: alpine
|
@@ -847,10 +835,11 @@ release:
|
|
847
835
|
notify_slack:
|
848
836
|
extends:
|
849
837
|
- .notify_slack
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
838
|
+
rules:
|
839
|
+
- if: '$TOP_UPSTREAM_SOURCE_JOB && $NOTIFY_CHANNEL'
|
840
|
+
when: on_failure
|
841
|
+
- if: '$TOP_UPSTREAM_SOURCE_JOB && $TOP_UPSTREAM_SOURCE_REF == "master"'
|
842
|
+
when: on_failure
|
854
843
|
script:
|
855
844
|
- export RELEASE=${TOP_UPSTREAM_SOURCE_REF:-$RELEASE}
|
856
845
|
- echo "NOTIFY_CHANNEL is ${NOTIFY_CHANNEL:=qa-$TOP_UPSTREAM_SOURCE_REF}"
|
@@ -858,4 +847,3 @@ notify_slack:
|
|
858
847
|
- echo "CI_PIPELINE_URL is $CI_PIPELINE_URL"
|
859
848
|
- echo "TOP_UPSTREAM_SOURCE_JOB is $TOP_UPSTREAM_SOURCE_JOB"
|
860
849
|
- bin/slack $NOTIFY_CHANNEL "☠️ QA against $RELEASE failed! ☠️ See $CI_PIPELINE_URL (triggered from $TOP_UPSTREAM_SOURCE_JOB)" ci_failing
|
861
|
-
when: on_failure
|
@@ -613,6 +613,24 @@ Example:
|
|
613
613
|
$ gitlab-qa Test::Instance::Smoke ee:<tag> https://staging.gitlab.com
|
614
614
|
```
|
615
615
|
|
616
|
+
### `Test::Instance::RepositoryStorage`
|
617
|
+
|
618
|
+
This scenario will run a limited number of tests that are tagged with `:repository_storage`.
|
619
|
+
|
620
|
+
These tests verify features related to multiple repository storages.
|
621
|
+
|
622
|
+
**Required environment variables:**
|
623
|
+
|
624
|
+
- `QA_ADDITIONAL_REPOSITORY_STORAGE`: The name of the non-default repository storage.
|
625
|
+
|
626
|
+
Example:
|
627
|
+
|
628
|
+
```
|
629
|
+
$ export QA_ADDITIONAL_REPOSITORY_STORAGE=secondary
|
630
|
+
|
631
|
+
$ gitlab-qa Test::Instance::RepositoryStorage
|
632
|
+
```
|
633
|
+
|
616
634
|
----
|
617
635
|
|
618
636
|
[Back to README.md](../README.md)
|
data/gitlab-qa.gemspec
CHANGED
@@ -27,5 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency 'rubocop', '~> 0.54.0'
|
28
28
|
spec.add_development_dependency 'rubocop-rspec', '1.20.1'
|
29
29
|
spec.add_development_dependency 'webmock', '3.7.0'
|
30
|
+
spec.add_runtime_dependency 'activesupport', '~> 6.0.2'
|
31
|
+
spec.add_runtime_dependency 'gitlab', '~> 4.11.0'
|
30
32
|
spec.add_runtime_dependency 'nokogiri', '~> 1.10'
|
31
33
|
end
|
data/lib/gitlab/qa.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module Gitlab
|
2
2
|
module QA
|
3
3
|
autoload :Release, 'gitlab/qa/release'
|
4
|
+
autoload :Reporter, 'gitlab/qa/reporter'
|
4
5
|
autoload :Runner, 'gitlab/qa/runner'
|
5
6
|
|
6
7
|
module Runtime
|
7
8
|
autoload :Env, 'gitlab/qa/runtime/env'
|
9
|
+
autoload :TokenFinder, 'gitlab/qa/runtime/token_finder'
|
8
10
|
end
|
9
11
|
|
10
12
|
module Scenario
|
@@ -86,6 +88,7 @@ module Gitlab
|
|
86
88
|
|
87
89
|
module Report
|
88
90
|
autoload :PrepareStageReports, 'gitlab/qa/report/prepare_stage_reports'
|
91
|
+
autoload :ResultsInIssues, 'gitlab/qa/report/results_in_issues'
|
89
92
|
end
|
90
93
|
end
|
91
94
|
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'gitlab'
|
5
|
+
require 'active_support/core_ext/enumerable'
|
6
|
+
|
7
|
+
module Gitlab
|
8
|
+
# Monkey patch the Gitlab client to use the correct API path
|
9
|
+
class Client
|
10
|
+
def team_member(project, id)
|
11
|
+
get("/projects/#{url_encode project}/members/all/#{id}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module QA
|
16
|
+
module Report
|
17
|
+
# Uses the API to create or update GitLab issues with the results of tests from RSpec report files.
|
18
|
+
# The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
|
19
|
+
class ResultsInIssues
|
20
|
+
MAINTAINER_ACCESS_LEVEL = 40
|
21
|
+
MAX_TITLE_LENGTH = 255
|
22
|
+
|
23
|
+
def initialize(token:, input_files:, project: nil)
|
24
|
+
@token = token
|
25
|
+
@files = Array(input_files)
|
26
|
+
@project = project
|
27
|
+
end
|
28
|
+
|
29
|
+
def invoke!
|
30
|
+
configure_gitlab_client
|
31
|
+
|
32
|
+
validate_input!
|
33
|
+
|
34
|
+
puts "Reporting test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
35
|
+
|
36
|
+
Dir.glob(files).each do |file|
|
37
|
+
puts "Reporting tests in #{file}"
|
38
|
+
Nokogiri::XML(File.open(file)).xpath('//testcase').each do |test|
|
39
|
+
report_test(test)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :files, :token, :project
|
47
|
+
|
48
|
+
def validate_input!
|
49
|
+
assert_project!
|
50
|
+
assert_input_files!(files)
|
51
|
+
assert_user_permission!
|
52
|
+
end
|
53
|
+
|
54
|
+
def assert_project!
|
55
|
+
return if project
|
56
|
+
|
57
|
+
abort "Please provide a valid project ID or path with the `-p/--project` option!"
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_input_files!(files)
|
61
|
+
return if Dir.glob(files).any?
|
62
|
+
|
63
|
+
abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
|
64
|
+
end
|
65
|
+
|
66
|
+
def assert_user_permission!
|
67
|
+
user = Gitlab.user
|
68
|
+
member = Gitlab.team_member(project, user.id)
|
69
|
+
|
70
|
+
abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
|
71
|
+
rescue Gitlab::Error::NotFound
|
72
|
+
abort_not_permitted
|
73
|
+
end
|
74
|
+
|
75
|
+
def abort_not_permitted
|
76
|
+
abort "You must have at least Maintainer access to the project to use this feature."
|
77
|
+
end
|
78
|
+
|
79
|
+
def configure_gitlab_client
|
80
|
+
Gitlab.configure do |config|
|
81
|
+
config.endpoint = Runtime::Env.gitlab_api_base
|
82
|
+
config.private_token = token
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def report_test(test)
|
87
|
+
return if test.search('skipped').any?
|
88
|
+
|
89
|
+
puts "Reporting test name: #{test['name']} | #{test['file']}"
|
90
|
+
|
91
|
+
issue = find_issue(test)
|
92
|
+
if issue
|
93
|
+
puts "Found existing issue: #{issue.web_url}"
|
94
|
+
else
|
95
|
+
issue = create_issue(test)
|
96
|
+
puts "Created new issue: #{issue.web_url}"
|
97
|
+
end
|
98
|
+
|
99
|
+
update_labels(issue, test)
|
100
|
+
note_status(issue, test)
|
101
|
+
|
102
|
+
puts "Issue updated"
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_issue(test)
|
106
|
+
puts "Creating issue for file: #{test['file']} | name: #{test['name']}"
|
107
|
+
|
108
|
+
Gitlab.create_issue(
|
109
|
+
project,
|
110
|
+
title_from_test(test),
|
111
|
+
{ description: "### Full description\n\n#{search_safe(test['name'])}\n\n### File path\n\n#{test['file']}", labels: 'status::automated' }
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_issue(test)
|
116
|
+
issues = Gitlab.search_in_project(project, 'issues', search_term(test))
|
117
|
+
.auto_paginate
|
118
|
+
.select { |issue| issue.state == 'opened' && issue.title.strip == title_from_test(test) }
|
119
|
+
|
120
|
+
warn(%(Too many issues found with the file path "#{test['file']}" and name "#{test['name']}")) if issues.many?
|
121
|
+
|
122
|
+
issues.first
|
123
|
+
end
|
124
|
+
|
125
|
+
def search_term(test)
|
126
|
+
%("#{test['file']}" "#{search_safe(test['name'])}")
|
127
|
+
end
|
128
|
+
|
129
|
+
def title_from_test(test)
|
130
|
+
title = "Results for #{test['file']} | #{search_safe(test['name'])}".strip
|
131
|
+
|
132
|
+
return title unless title.length > MAX_TITLE_LENGTH
|
133
|
+
|
134
|
+
"#{title[0...MAX_TITLE_LENGTH - 3]}..."
|
135
|
+
end
|
136
|
+
|
137
|
+
def search_safe(value)
|
138
|
+
value.delete('"')
|
139
|
+
end
|
140
|
+
|
141
|
+
def note_status(issue, test)
|
142
|
+
return if failures(test).empty?
|
143
|
+
|
144
|
+
errors = failures(test).each_with_object([]) do |failure, text|
|
145
|
+
text << <<~TEXT
|
146
|
+
Error:
|
147
|
+
```
|
148
|
+
#{failure['message']}
|
149
|
+
```
|
150
|
+
|
151
|
+
Stacktrace:
|
152
|
+
```
|
153
|
+
#{failure.content}
|
154
|
+
```
|
155
|
+
TEXT
|
156
|
+
end.join("\n\n")
|
157
|
+
|
158
|
+
Gitlab.create_issue_note(project, issue.iid, ":x: ~\"#{pipeline}::failed\" in job `#{Runtime::Env.ci_job_name}` in #{Runtime::Env.ci_job_url}\n\n#{errors}")
|
159
|
+
end
|
160
|
+
|
161
|
+
def update_labels(issue, test)
|
162
|
+
labels = issue.labels
|
163
|
+
labels.delete_if { |label| label.start_with?("#{pipeline}::") }
|
164
|
+
labels << (failures(test).empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
|
165
|
+
|
166
|
+
Gitlab.edit_issue(project, issue.iid, labels: labels)
|
167
|
+
end
|
168
|
+
|
169
|
+
def failures(test)
|
170
|
+
test.search('failure')
|
171
|
+
end
|
172
|
+
|
173
|
+
def pipeline
|
174
|
+
# Gets the name of the pipeline the test was run in, to be used as the key of a scoped label
|
175
|
+
#
|
176
|
+
# Tests can be run in several pipelines:
|
177
|
+
# gitlab-qa, nightly, master, staging, canary, production, preprod, and MRs
|
178
|
+
#
|
179
|
+
# Some of those run in their own project, so CI_PROJECT_NAME is the name we need. Those are:
|
180
|
+
# nightly, staging, canary, production, and preprod
|
181
|
+
#
|
182
|
+
# MR, master, and gitlab-qa tests run in gitlab-qa, but we only want to report tests run on master
|
183
|
+
# because the other pipelines will be monitored by the author of the MR that triggered them.
|
184
|
+
# So we assume that we're reporting a master pipeline if the project name is 'gitlab-qa'.
|
185
|
+
|
186
|
+
Runtime::Env.ci_project_name == 'gitlab-qa' ? 'master' : Runtime::Env.ci_project_name
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module QA
|
5
|
+
class Reporter
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
7
|
+
def self.invoke(args)
|
8
|
+
report_options = {}
|
9
|
+
|
10
|
+
options = OptionParser.new do |opts|
|
11
|
+
opts.banner = 'Usage: gitlab-qa-reporter [options]'
|
12
|
+
|
13
|
+
opts.on('--prepare-stage-reports FILES', 'Prepare separate reports for each Stage from the provided JUnit XML files') do |files|
|
14
|
+
report_options[:prepare_stage_reports] = true
|
15
|
+
report_options[:input_files] = files if files
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('--report-in-issues FILES', String, 'Report test results from JUnit XML files in GitLab issues') do |files|
|
19
|
+
report_options[:report_in_issues] = true
|
20
|
+
report_options[:input_files] = files if files
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('-p', '--project PROJECT_ID', String, 'A valid project ID. Can be an integer or a group/project string. Required by --report-in-issues') do |value|
|
24
|
+
report_options[:project] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-t', '--token ACCESS_TOKEN', String, 'A valid access token. Used by --report-in-issues') do |value|
|
28
|
+
report_options[:token] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on_tail('-v', '--version', 'Show the version') do
|
32
|
+
require 'gitlab/qa/version'
|
33
|
+
puts "#{$PROGRAM_NAME} : #{VERSION}"
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on_tail('-h', '--help', 'Show the usage') do
|
38
|
+
puts opts
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.parse(args)
|
43
|
+
end
|
44
|
+
|
45
|
+
if args.any?
|
46
|
+
if report_options[:prepare_stage_reports]
|
47
|
+
report_options.delete(:prepare_stage_reports)
|
48
|
+
Gitlab::QA::Report::PrepareStageReports.new(**report_options).invoke!
|
49
|
+
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
if report_options[:report_in_issues]
|
54
|
+
report_options.delete(:report_in_issues)
|
55
|
+
report_options[:token] = Runtime::TokenFinder.find_token!(report_options[:token])
|
56
|
+
Gitlab::QA::Report::ResultsInIssues.new(**report_options).invoke!
|
57
|
+
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
else
|
61
|
+
puts options
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# rubocop:enable Metrics/AbcSize
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/gitlab/qa/runner.rb
CHANGED
@@ -2,6 +2,7 @@ require 'optparse'
|
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module QA
|
5
|
+
# rubocop:disable Metrics/AbcSize
|
5
6
|
class Runner
|
6
7
|
# These options are implemented in the QA framework (i.e., in the CE/EE codebase)
|
7
8
|
# They're included here so that gitlab-qa treats them as valid options
|
@@ -13,10 +14,7 @@ module Gitlab
|
|
13
14
|
['--loop', 'Execute tests in a loop']
|
14
15
|
].freeze
|
15
16
|
|
16
|
-
# rubocop:disable Metrics/AbcSize
|
17
17
|
def self.run(args)
|
18
|
-
report_options = {}
|
19
|
-
|
20
18
|
options = OptionParser.new do |opts|
|
21
19
|
opts.banner = 'Usage: gitlab-qa [options] Scenario URL [[--] path] [rspec_options]'
|
22
20
|
|
@@ -24,11 +22,6 @@ module Gitlab
|
|
24
22
|
opts.on(*opt)
|
25
23
|
end
|
26
24
|
|
27
|
-
opts.on('--prepare-stage-reports FILES', 'Prepare separate reports for each Stage from the provided JUnit XML files') do |files|
|
28
|
-
report_options[:prepare_stage_reports] = true
|
29
|
-
report_options[:input_files] = files if files
|
30
|
-
end
|
31
|
-
|
32
25
|
opts.on_tail('-v', '--version', 'Show the version') do
|
33
26
|
require 'gitlab/qa/version'
|
34
27
|
puts "#{$PROGRAM_NAME} : #{VERSION}"
|
@@ -44,13 +37,6 @@ module Gitlab
|
|
44
37
|
end
|
45
38
|
|
46
39
|
if args.size >= 1
|
47
|
-
if report_options[:prepare_stage_reports]
|
48
|
-
report_options.delete(:prepare_stage_reports)
|
49
|
-
Gitlab::QA::Report::PrepareStageReports.new(**report_options).invoke!
|
50
|
-
|
51
|
-
exit
|
52
|
-
end
|
53
|
-
|
54
40
|
Scenario
|
55
41
|
.const_get(args.shift)
|
56
42
|
.perform(*args)
|
@@ -12,6 +12,7 @@ module Gitlab
|
|
12
12
|
'QA_REMOTE_GRID_ACCESS_KEY' => :remote_grid_access_key,
|
13
13
|
'QA_REMOTE_GRID_PROTOCOL' => :remote_grid_protocol,
|
14
14
|
'QA_BROWSER' => :browser,
|
15
|
+
'GITLAB_API_BASE' => :api_base,
|
15
16
|
'GITLAB_ADMIN_USERNAME' => :admin_username,
|
16
17
|
'GITLAB_ADMIN_PASSWORD' => :admin_password,
|
17
18
|
'GITLAB_USERNAME' => :user_username,
|
@@ -22,7 +23,6 @@ module Gitlab
|
|
22
23
|
'GITLAB_FORKER_PASSWORD' => :forker_password,
|
23
24
|
'GITLAB_USER_TYPE' => :user_type,
|
24
25
|
'GITLAB_SANDBOX_NAME' => :gitlab_sandbox_name,
|
25
|
-
'GITLAB_QA_ACCESS_TOKEN' => :qa_access_token,
|
26
26
|
'GITLAB_QA_ADMIN_ACCESS_TOKEN' => :qa_admin_access_token,
|
27
27
|
'GITHUB_ACCESS_TOKEN' => :github_access_token,
|
28
28
|
'GITLAB_URL' => :gitlab_url,
|
@@ -78,6 +78,22 @@ module Gitlab
|
|
78
78
|
send(:attr_accessor, accessor) # rubocop:disable GitlabSecurity/PublicSend
|
79
79
|
end
|
80
80
|
|
81
|
+
def gitlab_api_base
|
82
|
+
ENV['GITLAB_API_BASE'] || 'https://gitlab.com/api/v4'
|
83
|
+
end
|
84
|
+
|
85
|
+
def ci_job_name
|
86
|
+
ENV['CI_JOB_NAME']
|
87
|
+
end
|
88
|
+
|
89
|
+
def ci_job_url
|
90
|
+
ENV['CI_JOB_URL']
|
91
|
+
end
|
92
|
+
|
93
|
+
def ci_project_name
|
94
|
+
ENV['CI_PROJECT_NAME']
|
95
|
+
end
|
96
|
+
|
81
97
|
def run_id
|
82
98
|
@run_id ||= "gitlab-qa-run-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(4)}"
|
83
99
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module QA
|
5
|
+
module Runtime
|
6
|
+
class TokenFinder
|
7
|
+
def self.find_token!(token, suffix: nil)
|
8
|
+
new(token, suffix).find_token!
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :token, :suffix
|
12
|
+
|
13
|
+
def initialize(token, suffix)
|
14
|
+
@token = token
|
15
|
+
@suffix = suffix
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_token!
|
19
|
+
find_token_from_attrs || find_token_from_env || find_token_from_file
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_token_from_attrs
|
23
|
+
token
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_token_from_env
|
27
|
+
Env.qa_access_token
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_token_from_file
|
31
|
+
@token_from_file ||= File.read(token_file_path).strip
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
fail "Please provide a valid access token with the `-t/--token` option, the `GITLAB_QA_ACCESS_TOKEN` environment variable, or in the `#{token_file_path}` file!"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def token_file_path
|
39
|
+
@token_file_path ||= File.expand_path("../api_token#{"_#{suffix}" if suffix}", __dir__)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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:
|
4
|
+
version: 5.0.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-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: climate_control
|
@@ -122,6 +122,34 @@ dependencies:
|
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 3.7.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 6.0.2
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 6.0.2
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: gitlab
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 4.11.0
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 4.11.0
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: nokogiri
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,6 +169,7 @@ email:
|
|
141
169
|
- grzesiek.bizon@gmail.com
|
142
170
|
executables:
|
143
171
|
- gitlab-qa
|
172
|
+
- gitlab-qa-report
|
144
173
|
extensions: []
|
145
174
|
extra_rdoc_files: []
|
146
175
|
files:
|
@@ -172,6 +201,7 @@ files:
|
|
172
201
|
- docs/waits.md
|
173
202
|
- docs/what_tests_can_be_run.md
|
174
203
|
- exe/gitlab-qa
|
204
|
+
- exe/gitlab-qa-report
|
175
205
|
- fixtures/ldap/1_add_nodes.ldif
|
176
206
|
- fixtures/ldap/2_add_users.ldif
|
177
207
|
- fixtures/ldap/3_add_groups.ldif
|
@@ -195,8 +225,11 @@ files:
|
|
195
225
|
- lib/gitlab/qa/docker/volumes.rb
|
196
226
|
- lib/gitlab/qa/release.rb
|
197
227
|
- lib/gitlab/qa/report/prepare_stage_reports.rb
|
228
|
+
- lib/gitlab/qa/report/results_in_issues.rb
|
229
|
+
- lib/gitlab/qa/reporter.rb
|
198
230
|
- lib/gitlab/qa/runner.rb
|
199
231
|
- lib/gitlab/qa/runtime/env.rb
|
232
|
+
- lib/gitlab/qa/runtime/token_finder.rb
|
200
233
|
- lib/gitlab/qa/scenario/actable.rb
|
201
234
|
- lib/gitlab/qa/scenario/cli_commands.rb
|
202
235
|
- lib/gitlab/qa/scenario/template.rb
|