gitlab-qa 6.7.0 → 6.11.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 +28 -2
- data/docs/running_specific_orchestrated_tests.md +1 -1
- data/gitlab-qa.gemspec +2 -2
- data/lib/gitlab/qa.rb +3 -0
- data/lib/gitlab/qa/component/base.rb +7 -0
- data/lib/gitlab/qa/docker/engine.rb +8 -0
- data/lib/gitlab/qa/report/base_test_results.rb +6 -3
- data/lib/gitlab/qa/report/generate_test_session.rb +203 -0
- data/lib/gitlab/qa/report/gitlab_issue_client.rb +24 -7
- data/lib/gitlab/qa/report/gitlab_issue_dry_client.rb +28 -0
- data/lib/gitlab/qa/report/json_test_results.rb +2 -2
- data/lib/gitlab/qa/report/junit_test_results.rb +2 -2
- data/lib/gitlab/qa/report/relate_failure_issue.rb +167 -0
- data/lib/gitlab/qa/report/report_as_issue.rb +105 -3
- data/lib/gitlab/qa/report/results_in_issues.rb +31 -95
- data/lib/gitlab/qa/report/test_result.rb +31 -1
- data/lib/gitlab/qa/reporter.rb +30 -2
- data/lib/gitlab/qa/runtime/env.rb +35 -3
- data/lib/gitlab/qa/version.rb +1 -1
- metadata +7 -4
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module QA
|
5
|
+
module Report
|
6
|
+
class GitlabIssueDryClient < GitlabIssueClient
|
7
|
+
def create_issue(title:, description:, labels:)
|
8
|
+
attrs = { description: description, labels: labels }
|
9
|
+
|
10
|
+
puts "The following issue would have been created:"
|
11
|
+
puts "project: #{project}, title: #{title}, attrs: #{attrs}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def edit_issue(iid:, options: {})
|
15
|
+
puts "The #{project}##{iid} issue would have been updated with: #{options}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_issue_note(iid:, note:)
|
19
|
+
puts "The following note would have been posted on #{project}##{iid} issue: #{note}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
|
23
|
+
puts "The following discussion note would have been posted on #{project}##{iid} (discussion #{discussion_id}) issue: #{body}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -6,7 +6,7 @@ module Gitlab
|
|
6
6
|
module QA
|
7
7
|
module Report
|
8
8
|
class JsonTestResults < BaseTestResults
|
9
|
-
def write
|
9
|
+
def write
|
10
10
|
json = results.merge('examples' => testcases.map(&:report))
|
11
11
|
|
12
12
|
File.write(path, JSON.pretty_generate(json))
|
@@ -14,7 +14,7 @@ module Gitlab
|
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
def parse
|
17
|
+
def parse
|
18
18
|
JSON.parse(File.read(path))
|
19
19
|
end
|
20
20
|
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'active_support/core_ext/enumerable'
|
5
|
+
require 'rubygems/text'
|
6
|
+
|
7
|
+
module Gitlab
|
8
|
+
module QA
|
9
|
+
module Report
|
10
|
+
# Uses the API to create or update GitLab issues with the results of tests from RSpec report files.
|
11
|
+
class RelateFailureIssue < ReportAsIssue
|
12
|
+
DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION = 0.05
|
13
|
+
STACKTRACE_REGEX = %r{### Stack trace\s*(```)\s*.*(Failure/Error:.+)(\1)}m.freeze
|
14
|
+
NEW_ISSUE_LABELS = Set.new(%w[QA Quality test failure::investigating priority::2]).freeze
|
15
|
+
|
16
|
+
MultipleIssuesFound = Class.new(StandardError)
|
17
|
+
|
18
|
+
def initialize(max_diff_ratio: DEFAULT_MAX_DIFF_RATIO_FOR_DETECTION, **kwargs)
|
19
|
+
super
|
20
|
+
@max_diff_ratio = max_diff_ratio.to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :max_diff_ratio
|
26
|
+
|
27
|
+
def run!
|
28
|
+
puts "Reporting test failures in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
29
|
+
|
30
|
+
test_results_per_file do |test_results|
|
31
|
+
puts "=> Reporting tests in #{test_results.path}"
|
32
|
+
|
33
|
+
test_results.each do |test|
|
34
|
+
next if test.failures.empty?
|
35
|
+
|
36
|
+
relate_test_to_issue(test)
|
37
|
+
end
|
38
|
+
|
39
|
+
test_results.write
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def relate_test_to_issue(test)
|
44
|
+
puts " => Searching issues for test '#{test.name}'..."
|
45
|
+
|
46
|
+
begin
|
47
|
+
issue = find_or_create_issue(test)
|
48
|
+
return unless issue
|
49
|
+
|
50
|
+
update_labels(issue, test)
|
51
|
+
post_failed_job_note(issue, test)
|
52
|
+
puts " => Marked #{issue.web_url} as related to #{test.testcase}."
|
53
|
+
rescue MultipleIssuesFound => e
|
54
|
+
warn(e.message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_or_create_issue(test)
|
59
|
+
issue, diff_ratio = find_failure_issue(test)
|
60
|
+
|
61
|
+
if issue
|
62
|
+
puts " => Found issue #{issue.web_url} for test '#{test.name}' with a diff ratio of #{(diff_ratio * 100).round(2)}%."
|
63
|
+
else
|
64
|
+
issue = create_issue(test)
|
65
|
+
puts " => Created new issue: #{issue.web_url} for test '#{test.name}'." if issue
|
66
|
+
end
|
67
|
+
|
68
|
+
issue
|
69
|
+
end
|
70
|
+
|
71
|
+
def failure_issues(test)
|
72
|
+
gitlab.find_issues(options: { state: 'opened', labels: 'QA' }).select do |issue|
|
73
|
+
issue_title = issue.title.strip
|
74
|
+
issue_title.include?(test.name) || issue_title.include?(partial_file_path(test.file))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_relevant_failure_issues(test) # rubocop:disable Metrics/AbcSize
|
79
|
+
ld = Class.new.extend(Gem::Text).method(:levenshtein_distance)
|
80
|
+
first_test_failure_stacktrace = test.failures.first['message_lines'].join("\n")
|
81
|
+
|
82
|
+
# Search with the `search` param returns 500 errors, so we filter by ~QA and then filter further in Ruby
|
83
|
+
failure_issues(test).each_with_object({}) do |issue, memo|
|
84
|
+
relevant_issue_stacktrace = find_issue_stacktrace(issue)
|
85
|
+
next unless relevant_issue_stacktrace
|
86
|
+
|
87
|
+
distance = ld.call(first_test_failure_stacktrace, relevant_issue_stacktrace)
|
88
|
+
diff_ratio = (distance.to_f / first_test_failure_stacktrace.size).round(3)
|
89
|
+
if diff_ratio <= max_diff_ratio
|
90
|
+
puts " => [DEBUG] Issue #{issue} has an acceptable diff ratio of #{(diff_ratio * 100).round(2)}%."
|
91
|
+
memo[issue] = diff_ratio
|
92
|
+
else
|
93
|
+
puts " => [DEBUG] Found issue #{issue.web_url} but stacktraces are too different (#{(diff_ratio * 100).round(2)}%)."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_issue_stacktrace(issue)
|
99
|
+
issue_stacktrace_match = issue.description.match(STACKTRACE_REGEX)
|
100
|
+
|
101
|
+
if issue_stacktrace_match
|
102
|
+
issue_stacktrace_match[2].gsub(/^#.*$/, '').strip
|
103
|
+
else
|
104
|
+
puts " => [DEBUG] Stacktrace couldn't be found for #{issue.web_url}:\n\n#{issue.description}\n\n----------------------------------\n"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_failure_issue(test)
|
109
|
+
relevant_issues = find_relevant_failure_issues(test)
|
110
|
+
|
111
|
+
return nil if relevant_issues.empty?
|
112
|
+
|
113
|
+
best_matching_issue, smaller_diff_ratio = relevant_issues.min_by { |_, diff_ratio| diff_ratio }
|
114
|
+
|
115
|
+
unless relevant_issues.values.count(smaller_diff_ratio) == 1 # rubocop:disable Style/IfUnlessModifier
|
116
|
+
raise(MultipleIssuesFound, %(Too many issues found for test '#{test.name}' (`#{test.file}`)!))
|
117
|
+
end
|
118
|
+
|
119
|
+
test.failure_issue ||= best_matching_issue.web_url
|
120
|
+
|
121
|
+
[best_matching_issue, smaller_diff_ratio]
|
122
|
+
end
|
123
|
+
|
124
|
+
def new_issue_description(test)
|
125
|
+
super + [
|
126
|
+
"\n\n### Stack trace",
|
127
|
+
"```\n#{test.failures.first['message_lines'].join("\n")}\n```",
|
128
|
+
"First happened in #{test.ci_job_url}."
|
129
|
+
].join("\n\n")
|
130
|
+
end
|
131
|
+
|
132
|
+
def deploy_environment_label
|
133
|
+
environment = Runtime::Env.deploy_environment
|
134
|
+
|
135
|
+
case environment
|
136
|
+
when 'production'
|
137
|
+
'found:gitlab.com'
|
138
|
+
when 'canary', 'staging'
|
139
|
+
"found:#{environment}.gitlab.com"
|
140
|
+
when 'preprod'
|
141
|
+
'found:pre.gitlab.com'
|
142
|
+
when 'staging-orchestrated', 'nightly', 'master'
|
143
|
+
"found:#{environment}"
|
144
|
+
else
|
145
|
+
raise "No `found:*` label for the `#{environment}` environment!"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def new_issue_labels(test)
|
150
|
+
NEW_ISSUE_LABELS + up_to_date_labels(test: test)
|
151
|
+
end
|
152
|
+
|
153
|
+
def up_to_date_labels(test:, issue: nil)
|
154
|
+
super << deploy_environment_label
|
155
|
+
end
|
156
|
+
|
157
|
+
def post_failed_job_note(issue, test)
|
158
|
+
gitlab.create_issue_note(iid: issue.iid, note: "/relate #{test.testcase}")
|
159
|
+
end
|
160
|
+
|
161
|
+
def new_issue_title(test)
|
162
|
+
"Failure in #{super}"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module Gitlab
|
4
6
|
module QA
|
5
7
|
module Report
|
6
8
|
class ReportAsIssue
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
MAX_TITLE_LENGTH = 255
|
10
|
+
|
11
|
+
def initialize(token:, input_files:, project: nil, dry_run: false, **kwargs)
|
10
12
|
@project = project
|
13
|
+
@gitlab = (dry_run ? GitlabIssueDryClient : GitlabIssueClient).new(token: token, project: project)
|
14
|
+
@files = Array(input_files)
|
11
15
|
end
|
12
16
|
|
13
17
|
def invoke!
|
@@ -24,6 +28,18 @@ module Gitlab
|
|
24
28
|
raise NotImplementedError
|
25
29
|
end
|
26
30
|
|
31
|
+
def new_issue_title(test)
|
32
|
+
"#{partial_file_path(test.file)} | #{search_safe(test.name)}".strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def new_issue_description(test)
|
36
|
+
"### Full description\n\n#{search_safe(test.name)}\n\n### File path\n\n#{test.file}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_issue_labels(test)
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
27
43
|
def validate_input!
|
28
44
|
assert_project!
|
29
45
|
assert_input_files!(files)
|
@@ -41,6 +57,92 @@ module Gitlab
|
|
41
57
|
|
42
58
|
abort "Please provide valid JUnit report files. No files were found matching `#{files.join(',')}`"
|
43
59
|
end
|
60
|
+
|
61
|
+
def test_results_per_file
|
62
|
+
Dir.glob(files).each do |path|
|
63
|
+
extension = File.extname(path)
|
64
|
+
|
65
|
+
test_results =
|
66
|
+
case extension
|
67
|
+
when '.json'
|
68
|
+
Report::JsonTestResults.new(path)
|
69
|
+
when '.xml'
|
70
|
+
Report::JUnitTestResults.new(path)
|
71
|
+
else
|
72
|
+
raise "Unknown extension #{extension}"
|
73
|
+
end
|
74
|
+
|
75
|
+
yield test_results
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_issue(test)
|
80
|
+
gitlab.create_issue(
|
81
|
+
title: title_from_test(test),
|
82
|
+
description: new_issue_description(test),
|
83
|
+
labels: new_issue_labels(test).to_a
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def issue_labels(issue)
|
88
|
+
issue&.labels&.to_set || Set.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_labels(issue, test)
|
92
|
+
new_labels = up_to_date_labels(test: test, issue: issue)
|
93
|
+
|
94
|
+
return if issue_labels(issue) == new_labels
|
95
|
+
|
96
|
+
gitlab.edit_issue(iid: issue.iid, options: { labels: new_labels.to_a })
|
97
|
+
end
|
98
|
+
|
99
|
+
def up_to_date_labels(test:, issue: nil)
|
100
|
+
labels = issue_labels(issue)
|
101
|
+
labels << "Enterprise Edition" if ee_test?(test)
|
102
|
+
quarantine_job? ? labels << "quarantine" : labels.delete("quarantine")
|
103
|
+
|
104
|
+
labels
|
105
|
+
end
|
106
|
+
|
107
|
+
def ee_test?(test)
|
108
|
+
test.file =~ %r{features/ee/(api|browser_ui)}
|
109
|
+
end
|
110
|
+
|
111
|
+
def quarantine_job?
|
112
|
+
Runtime::Env.ci_job_name&.include?('quarantine')
|
113
|
+
end
|
114
|
+
|
115
|
+
def partial_file_path(path)
|
116
|
+
path.match(/((api|browser_ui).*)/i)[1]
|
117
|
+
end
|
118
|
+
|
119
|
+
def title_from_test(test)
|
120
|
+
title = new_issue_title(test)
|
121
|
+
|
122
|
+
return title unless title.length > MAX_TITLE_LENGTH
|
123
|
+
|
124
|
+
"#{title[0...MAX_TITLE_LENGTH - 3]}..."
|
125
|
+
end
|
126
|
+
|
127
|
+
def search_safe(value)
|
128
|
+
value.delete('"')
|
129
|
+
end
|
130
|
+
|
131
|
+
def pipeline
|
132
|
+
# Gets the name of the pipeline the test was run in, to be used as the key of a scoped label
|
133
|
+
#
|
134
|
+
# Tests can be run in several pipelines:
|
135
|
+
# gitlab-qa, nightly, master, staging, canary, production, preprod, and MRs
|
136
|
+
#
|
137
|
+
# Some of those run in their own project, so CI_PROJECT_NAME is the name we need. Those are:
|
138
|
+
# nightly, staging, canary, production, and preprod
|
139
|
+
#
|
140
|
+
# MR, master, and gitlab-qa tests run in gitlab-qa, but we only want to report tests run on master
|
141
|
+
# because the other pipelines will be monitored by the author of the MR that triggered them.
|
142
|
+
# So we assume that we're reporting a master pipeline if the project name is 'gitlab-qa'.
|
143
|
+
|
144
|
+
@pipeline ||= Runtime::Env.pipeline_from_project_name
|
145
|
+
end
|
44
146
|
end
|
45
147
|
end
|
46
148
|
end
|
@@ -8,37 +8,23 @@ module Gitlab
|
|
8
8
|
module Report
|
9
9
|
# Uses the API to create or update GitLab issues with the results of tests from RSpec report files.
|
10
10
|
class ResultsInIssues < ReportAsIssue
|
11
|
-
MAX_TITLE_LENGTH = 255
|
12
|
-
|
13
11
|
private
|
14
12
|
|
15
13
|
def run!
|
16
14
|
puts "Reporting test results in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
17
15
|
|
18
|
-
|
19
|
-
puts "Reporting tests in #{path}"
|
20
|
-
extension = File.extname(path)
|
21
|
-
|
22
|
-
case extension
|
23
|
-
when '.json'
|
24
|
-
test_results = Report::JsonTestResults.new(path)
|
25
|
-
when '.xml'
|
26
|
-
test_results = Report::JUnitTestResults.new(path)
|
27
|
-
else
|
28
|
-
raise "Unknown extension #{extension}"
|
29
|
-
end
|
16
|
+
test_results_per_file do |test_results|
|
17
|
+
puts "Reporting tests in #{test_results.path}"
|
30
18
|
|
31
19
|
test_results.each do |test|
|
32
20
|
report_test(test)
|
33
21
|
end
|
34
22
|
|
35
|
-
test_results.write
|
23
|
+
test_results.write
|
36
24
|
end
|
37
25
|
end
|
38
26
|
|
39
27
|
def report_test(test)
|
40
|
-
return if test.skipped
|
41
|
-
|
42
28
|
puts "Reporting test: #{test.file} | #{test.name}"
|
43
29
|
|
44
30
|
issue = find_issue(test)
|
@@ -46,16 +32,23 @@ module Gitlab
|
|
46
32
|
if issue
|
47
33
|
puts "Found existing issue: #{issue.web_url}"
|
48
34
|
else
|
35
|
+
# Don't create new issues for skipped tests
|
36
|
+
return if test.skipped
|
37
|
+
|
49
38
|
issue = create_issue(test)
|
50
39
|
puts "Created new issue: #{issue.web_url}"
|
51
40
|
end
|
52
41
|
|
53
42
|
test.testcase ||= issue.web_url
|
54
43
|
|
55
|
-
update_labels(issue, test)
|
56
|
-
note_status(issue, test)
|
44
|
+
labels_updated = update_labels(issue, test)
|
45
|
+
note_posted = note_status(issue, test)
|
57
46
|
|
58
|
-
|
47
|
+
if labels_updated || note_posted
|
48
|
+
puts "Issue updated."
|
49
|
+
else
|
50
|
+
puts "Test passed, no update needed."
|
51
|
+
end
|
59
52
|
end
|
60
53
|
|
61
54
|
def find_issue(test)
|
@@ -72,12 +65,14 @@ module Gitlab
|
|
72
65
|
issues.first
|
73
66
|
end
|
74
67
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
68
|
+
def new_issue_labels(test)
|
69
|
+
%w[status::automated]
|
70
|
+
end
|
71
|
+
|
72
|
+
def up_to_date_labels(test:, issue: nil)
|
73
|
+
labels = super
|
74
|
+
labels.delete_if { |label| label.start_with?("#{pipeline}::") }
|
75
|
+
labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
|
81
76
|
end
|
82
77
|
|
83
78
|
def iid_from_testcase_url(url)
|
@@ -85,37 +80,22 @@ module Gitlab
|
|
85
80
|
end
|
86
81
|
|
87
82
|
def search_term(test)
|
88
|
-
%("#{test.file}" "#{search_safe(test.name)}")
|
89
|
-
end
|
90
|
-
|
91
|
-
def title_from_test(test)
|
92
|
-
title = "#{partial_file_path(test.file)} | #{search_safe(test.name)}".strip
|
93
|
-
|
94
|
-
return title unless title.length > MAX_TITLE_LENGTH
|
95
|
-
|
96
|
-
"#{title[0...MAX_TITLE_LENGTH - 3]}..."
|
97
|
-
end
|
98
|
-
|
99
|
-
def partial_file_path(path)
|
100
|
-
path.match(/((api|browser_ui).*)/i)[1]
|
101
|
-
end
|
102
|
-
|
103
|
-
def search_safe(value)
|
104
|
-
value.delete('"')
|
83
|
+
%("#{partial_file_path(test.file)}" "#{search_safe(test.name)}")
|
105
84
|
end
|
106
85
|
|
107
86
|
def note_status(issue, test)
|
108
|
-
return if test.
|
87
|
+
return false if test.skipped
|
88
|
+
return false if test.failures.empty?
|
109
89
|
|
110
90
|
note = note_content(test)
|
111
91
|
|
112
|
-
gitlab.
|
113
|
-
|
114
|
-
return add_note_to_discussion(issue.iid, discussion.id) if new_note_matches_discussion?(note, discussion)
|
115
|
-
end
|
116
|
-
|
117
|
-
Gitlab.create_issue_note(project, issue.iid, note)
|
92
|
+
gitlab.find_issue_discussions(iid: issue.iid).each do |discussion|
|
93
|
+
return gitlab.add_note_to_issue_discussion_as_thread(iid: issue.iid, discussion_id: discussion.id, body: failure_summary) if new_note_matches_discussion?(note, discussion)
|
118
94
|
end
|
95
|
+
|
96
|
+
gitlab.create_issue_note(iid: issue.iid, note: note)
|
97
|
+
|
98
|
+
true
|
119
99
|
end
|
120
100
|
|
121
101
|
def note_content(test)
|
@@ -143,10 +123,6 @@ module Gitlab
|
|
143
123
|
summary.join(' ')
|
144
124
|
end
|
145
125
|
|
146
|
-
def quarantine_job?
|
147
|
-
Runtime::Env.ci_job_name&.include?('quarantine')
|
148
|
-
end
|
149
|
-
|
150
126
|
def new_note_matches_discussion?(note, discussion)
|
151
127
|
note_error = error_and_stack_trace(note)
|
152
128
|
discussion_error = error_and_stack_trace(discussion.notes.first['body'])
|
@@ -157,47 +133,7 @@ module Gitlab
|
|
157
133
|
end
|
158
134
|
|
159
135
|
def error_and_stack_trace(text)
|
160
|
-
|
161
|
-
|
162
|
-
warn "Could not find `Error:` in text: #{text}" if result.empty?
|
163
|
-
|
164
|
-
result
|
165
|
-
end
|
166
|
-
|
167
|
-
def add_note_to_discussion(issue_iid, discussion_id)
|
168
|
-
gitlab.handle_gitlab_client_exceptions do
|
169
|
-
Gitlab.add_note_to_issue_discussion_as_thread(project, issue_iid, discussion_id, body: failure_summary)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def update_labels(issue, test)
|
174
|
-
labels = issue.labels
|
175
|
-
labels.delete_if { |label| label.start_with?("#{pipeline}::") }
|
176
|
-
labels << (test.failures.empty? ? "#{pipeline}::passed" : "#{pipeline}::failed")
|
177
|
-
labels << "Enterprise Edition" if ee_test?(test)
|
178
|
-
quarantine_job? ? labels << "quarantine" : labels.delete("quarantine")
|
179
|
-
|
180
|
-
gitlab.edit_issue(iid: issue.iid, options: { labels: labels })
|
181
|
-
end
|
182
|
-
|
183
|
-
def ee_test?(test)
|
184
|
-
test.file =~ %r{features/ee/(api|browser_ui)}
|
185
|
-
end
|
186
|
-
|
187
|
-
def pipeline
|
188
|
-
# Gets the name of the pipeline the test was run in, to be used as the key of a scoped label
|
189
|
-
#
|
190
|
-
# Tests can be run in several pipelines:
|
191
|
-
# gitlab-qa, nightly, master, staging, canary, production, preprod, and MRs
|
192
|
-
#
|
193
|
-
# Some of those run in their own project, so CI_PROJECT_NAME is the name we need. Those are:
|
194
|
-
# nightly, staging, canary, production, and preprod
|
195
|
-
#
|
196
|
-
# MR, master, and gitlab-qa tests run in gitlab-qa, but we only want to report tests run on master
|
197
|
-
# because the other pipelines will be monitored by the author of the MR that triggered them.
|
198
|
-
# So we assume that we're reporting a master pipeline if the project name is 'gitlab-qa'.
|
199
|
-
|
200
|
-
Runtime::Env.pipeline_from_project_name
|
136
|
+
text.strip[/Error:(.*)/m, 1].to_s
|
201
137
|
end
|
202
138
|
end
|
203
139
|
end
|