gitlab-qa 6.7.0 → 6.11.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 +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
|