gitlab_quality-test_tooling 1.15.0 → 1.18.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/Gemfile.lock +16 -6
- data/exe/flaky-test-issues +4 -4
- data/exe/generate-test-session +4 -0
- data/lefthook.yml +13 -0
- data/lib/gitlab_quality/test_tooling/gitlab_client/gitlab_client.rb +11 -12
- data/lib/gitlab_quality/test_tooling/gitlab_client/issues_client.rb +6 -6
- data/lib/gitlab_quality/test_tooling/report/concerns/issue_reports.rb +41 -28
- data/lib/gitlab_quality/test_tooling/report/concerns/utils.rb +1 -1
- data/lib/gitlab_quality/test_tooling/report/flaky_test_issue.rb +78 -43
- data/lib/gitlab_quality/test_tooling/report/generate_test_session.rb +6 -6
- data/lib/gitlab_quality/test_tooling/report/knapsack_report_issue.rb +0 -3
- data/lib/gitlab_quality/test_tooling/report/relate_failure_issue.rb +4 -10
- data/lib/gitlab_quality/test_tooling/report/report_as_issue.rb +4 -3
- data/lib/gitlab_quality/test_tooling/report/slow_test_issue.rb +71 -78
- data/lib/gitlab_quality/test_tooling/runtime/logger.rb +1 -1
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_blocking_processor.rb +9 -3
- data/lib/gitlab_quality/test_tooling/test_meta/processor/add_to_quarantine_processor.rb +9 -4
- data/lib/gitlab_quality/test_tooling/test_meta/processor/meta_processor.rb +12 -0
- data/lib/gitlab_quality/test_tooling/test_meta/test_meta_updater.rb +26 -0
- data/lib/gitlab_quality/test_tooling/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 948fbcd939a7800a4538113fddc717642c4d9fed7dab6d650cd9052c1832f19b
|
|
4
|
+
data.tar.gz: fbc4cee23cbcd3f7631ac6a03d41f2b8e287590777fef813289013d982efc4f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34c8a65eaf48e4ef9800df34a66f0086d3915252444091631fbd76273731008f70be149e3c92d5954644e55839d8a314eb3442411e3a389061e460d66c9d1ea2
|
|
7
|
+
data.tar.gz: 29f34824d1c5bba8dbe979a10c0e93a8c4c06e7b0b174b96b0150c19e3d6f71bb0fa9a8ef285a2d650af407547084a5e1a7bb2925817dce5d200b6755e864d4c
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
gitlab_quality-test_tooling (1.
|
|
5
|
-
activesupport (>= 6.1, < 7.
|
|
4
|
+
gitlab_quality-test_tooling (1.18.0)
|
|
5
|
+
activesupport (>= 6.1, < 7.2)
|
|
6
6
|
amatch (~> 0.4.1)
|
|
7
7
|
gitlab (~> 4.19)
|
|
8
8
|
http (~> 5.0)
|
|
@@ -16,10 +16,15 @@ PATH
|
|
|
16
16
|
GEM
|
|
17
17
|
remote: https://rubygems.org/
|
|
18
18
|
specs:
|
|
19
|
-
activesupport (7.
|
|
19
|
+
activesupport (7.1.3.2)
|
|
20
|
+
base64
|
|
21
|
+
bigdecimal
|
|
20
22
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
23
|
+
connection_pool (>= 2.2.5)
|
|
24
|
+
drb
|
|
21
25
|
i18n (>= 1.6, < 2)
|
|
22
26
|
minitest (>= 5.1)
|
|
27
|
+
mutex_m
|
|
23
28
|
tzinfo (~> 2.0)
|
|
24
29
|
addressable (2.8.6)
|
|
25
30
|
public_suffix (>= 2.0.2, < 6.0)
|
|
@@ -28,7 +33,9 @@ GEM
|
|
|
28
33
|
tins (~> 1.0)
|
|
29
34
|
ast (2.4.2)
|
|
30
35
|
backport (1.2.0)
|
|
36
|
+
base64 (0.2.0)
|
|
31
37
|
benchmark (0.3.0)
|
|
38
|
+
bigdecimal (3.1.6)
|
|
32
39
|
binding_of_caller (1.0.0)
|
|
33
40
|
debug_inspector (>= 0.0.1)
|
|
34
41
|
byebug (11.1.3)
|
|
@@ -41,6 +48,7 @@ GEM
|
|
|
41
48
|
coderay (1.1.3)
|
|
42
49
|
colored2 (3.1.2)
|
|
43
50
|
concurrent-ruby (1.2.3)
|
|
51
|
+
connection_pool (2.4.1)
|
|
44
52
|
cork (0.3.0)
|
|
45
53
|
colored2 (~> 3.1)
|
|
46
54
|
crack (0.4.5)
|
|
@@ -65,6 +73,7 @@ GEM
|
|
|
65
73
|
diff-lcs (1.5.0)
|
|
66
74
|
docile (1.4.0)
|
|
67
75
|
domain_name (0.6.20240107)
|
|
76
|
+
drb (2.2.1)
|
|
68
77
|
e2mmap (0.1.0)
|
|
69
78
|
faraday (2.9.0)
|
|
70
79
|
faraday-net_http (>= 2.0, < 3.2)
|
|
@@ -119,7 +128,7 @@ GEM
|
|
|
119
128
|
httparty (0.21.0)
|
|
120
129
|
mini_mime (>= 1.0.0)
|
|
121
130
|
multi_xml (>= 0.5.2)
|
|
122
|
-
i18n (1.14.
|
|
131
|
+
i18n (1.14.4)
|
|
123
132
|
concurrent-ruby (~> 1.0)
|
|
124
133
|
jaro_winkler (1.5.6)
|
|
125
134
|
json (2.7.1)
|
|
@@ -138,10 +147,11 @@ GEM
|
|
|
138
147
|
method_source (1.0.0)
|
|
139
148
|
mini_mime (1.1.5)
|
|
140
149
|
mini_portile2 (2.8.5)
|
|
141
|
-
minitest (5.
|
|
150
|
+
minitest (5.22.2)
|
|
142
151
|
mize (0.4.1)
|
|
143
152
|
protocol (~> 2.0)
|
|
144
153
|
multi_xml (0.6.0)
|
|
154
|
+
mutex_m (0.2.0)
|
|
145
155
|
nap (1.1.0)
|
|
146
156
|
nenv (0.3.0)
|
|
147
157
|
net-http (0.4.1)
|
|
@@ -318,4 +328,4 @@ DEPENDENCIES
|
|
|
318
328
|
webmock (= 3.7.0)
|
|
319
329
|
|
|
320
330
|
BUNDLED WITH
|
|
321
|
-
2.4
|
|
331
|
+
2.5.4
|
data/exe/flaky-test-issues
CHANGED
|
@@ -19,10 +19,6 @@ options = OptionParser.new do |opts|
|
|
|
19
19
|
params[:project] = project
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
opts.on('-m', '--merge_request_iid MERGE_REQUEST_IID', String, 'An integer merge request IID') do |merge_request_iid|
|
|
23
|
-
params[:merge_request_iid] = merge_request_iid
|
|
24
|
-
end
|
|
25
|
-
|
|
26
22
|
opts.on('--base-issue-labels BASE_ISSUE_LABELS', String,
|
|
27
23
|
'Comma-separated labels (without tilde) to add to new flaky test issues') do |base_issue_labels|
|
|
28
24
|
params[:base_issue_labels] = base_issue_labels.split(',')
|
|
@@ -32,6 +28,10 @@ options = OptionParser.new do |opts|
|
|
|
32
28
|
params[:token] = token
|
|
33
29
|
end
|
|
34
30
|
|
|
31
|
+
opts.on('-r', '--related-issues-file RELATED_ISSUES_FILE', String, 'The file path for the related issues') do |related_issues_file|
|
|
32
|
+
params[:related_issues_file] = related_issues_file
|
|
33
|
+
end
|
|
34
|
+
|
|
35
35
|
opts.on('--dry-run', "Perform a dry-run (don't create issues)") do
|
|
36
36
|
params[:dry_run] = true
|
|
37
37
|
end
|
data/exe/generate-test-session
CHANGED
|
@@ -31,6 +31,10 @@ options = OptionParser.new do |opts|
|
|
|
31
31
|
params[:issue_url_file] = issue_url_file
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
opts.on('--pipeline-stages STAGES', STRING, 'Comma-separated list of pipeline stages to include in test session issue') do |pipeline_stages|
|
|
35
|
+
params[:pipeline_stages] = pipeline_stages.split(',')
|
|
36
|
+
end
|
|
37
|
+
|
|
34
38
|
opts.on('--confidential', "Makes test session issue confidential") do
|
|
35
39
|
params[:confidential] = true
|
|
36
40
|
end
|
data/lefthook.yml
CHANGED
|
@@ -13,3 +13,16 @@ pre-push:
|
|
|
13
13
|
rubocop:
|
|
14
14
|
run: bundle exec rubocop
|
|
15
15
|
glob: '*.rb'
|
|
16
|
+
|
|
17
|
+
# Changelog git trailer for the first commit of the branch
|
|
18
|
+
changelog-on-first-commit:
|
|
19
|
+
run: |
|
|
20
|
+
first_commit_message=$(git log --format=%B -n 1 $(git log main..HEAD --pretty=format:"%h" | tail -1))
|
|
21
|
+
if ! echo ${first_commit_message} | grep "Changelog:"; then
|
|
22
|
+
echo Could not find a Changelog: git trailer on the first commit for this branch.
|
|
23
|
+
echo
|
|
24
|
+
echo Please add a trailer by amending the git commit message.
|
|
25
|
+
echo
|
|
26
|
+
echo See https://docs.gitlab.com/ee/development/changelog.html#overview for more info.
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
@@ -15,7 +15,7 @@ module GitlabQuality
|
|
|
15
15
|
@retry_backoff = 0
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def handle_gitlab_client_exceptions
|
|
18
|
+
def handle_gitlab_client_exceptions
|
|
19
19
|
yield
|
|
20
20
|
rescue Gitlab::Error::NotFound
|
|
21
21
|
# This error could be raised in assert_user_permission!
|
|
@@ -27,12 +27,20 @@ module GitlabQuality
|
|
|
27
27
|
|
|
28
28
|
raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
warn("#{e.class.name} #{e.message}")
|
|
31
31
|
warn("Sleeping for #{@retry_backoff} seconds before retrying...")
|
|
32
32
|
sleep @retry_backoff
|
|
33
33
|
|
|
34
34
|
retry
|
|
35
35
|
rescue StandardError => e
|
|
36
|
+
post_exception_to_slack(e) if Runtime::Env.ci_commit_ref_name == Runtime::Env.default_branch
|
|
37
|
+
|
|
38
|
+
raise e
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def post_exception_to_slack(error)
|
|
42
|
+
return unless ENV['CI_SLACK_WEBHOOK_URL']
|
|
43
|
+
|
|
36
44
|
pipeline = Runtime::Env.pipeline_from_project_name
|
|
37
45
|
channel = case pipeline
|
|
38
46
|
when "canary"
|
|
@@ -42,9 +50,6 @@ module GitlabQuality
|
|
|
42
50
|
else
|
|
43
51
|
"qa-#{pipeline}"
|
|
44
52
|
end
|
|
45
|
-
error_msg = warn_exception(e)
|
|
46
|
-
|
|
47
|
-
return unless Runtime::Env.ci_commit_ref_name == Runtime::Env.default_branch
|
|
48
53
|
|
|
49
54
|
slack_options = {
|
|
50
55
|
slack_webhook_url: ENV.fetch('CI_SLACK_WEBHOOK_URL', nil),
|
|
@@ -54,7 +59,7 @@ module GitlabQuality
|
|
|
54
59
|
message: <<~MSG
|
|
55
60
|
An unexpected error occurred while reporting test results in issues.
|
|
56
61
|
The error occurred in job: #{Runtime::Env.ci_job_url}
|
|
57
|
-
`#{
|
|
62
|
+
`#{error.class.name} #{error.message}`
|
|
58
63
|
MSG
|
|
59
64
|
}
|
|
60
65
|
puts "Posting Slack message to channel: #{channel}"
|
|
@@ -79,12 +84,6 @@ module GitlabQuality
|
|
|
79
84
|
private_token: token
|
|
80
85
|
)
|
|
81
86
|
end
|
|
82
|
-
|
|
83
|
-
def warn_exception(error)
|
|
84
|
-
error_msg = "#{error.class.name} #{error.message}"
|
|
85
|
-
warn(error_msg)
|
|
86
|
-
error_msg
|
|
87
|
-
end
|
|
88
87
|
end
|
|
89
88
|
end
|
|
90
89
|
end
|
|
@@ -48,6 +48,12 @@ module GitlabQuality
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def find_issue_notes(iid:)
|
|
52
|
+
handle_gitlab_client_exceptions do
|
|
53
|
+
client.issue_notes(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
51
57
|
def find_issue_discussions(iid:)
|
|
52
58
|
handle_gitlab_client_exceptions do
|
|
53
59
|
client.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
|
|
@@ -75,12 +81,6 @@ module GitlabQuality
|
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
|
|
78
|
-
def find_issue_notes(iid:)
|
|
79
|
-
handle_gitlab_client_exceptions do
|
|
80
|
-
client.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
84
|
def create_issue_note(iid:, note:)
|
|
85
85
|
handle_gitlab_client_exceptions do
|
|
86
86
|
client.create_issue_note(project, iid, note)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
|
|
3
5
|
module GitlabQuality
|
|
4
6
|
module TestTooling
|
|
5
7
|
module Report
|
|
6
8
|
module Concerns
|
|
7
9
|
module IssueReports
|
|
8
|
-
JOB_URL_REGEX
|
|
10
|
+
JOB_URL_REGEX = %r{(?<job_url>https://(?<host>[\w.]+)/(?<project_path>[\w\-./]+)/-/jobs/\d+)}
|
|
9
11
|
FAILED_JOB_DESCRIPTION_REGEX = /First happened in #{JOB_URL_REGEX}\./m
|
|
10
|
-
REPORT_ITEM_REGEX
|
|
12
|
+
REPORT_ITEM_REGEX = /^1\. \d{4}-\d{2}-\d{2}: #{JOB_URL_REGEX} \((?<pipeline_url>\S+)\)/
|
|
13
|
+
LATEST_REPORTS_TO_SHOW = 10
|
|
11
14
|
|
|
12
15
|
def initial_reports_section(test)
|
|
13
16
|
<<~REPORTS
|
|
@@ -17,22 +20,21 @@ module GitlabQuality
|
|
|
17
20
|
REPORTS
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.scan(REPORT_ITEM_REGEX)
|
|
29
|
-
.size.to_i + 1
|
|
30
|
-
issue_description.sub(/^### Reports.*$/, "### Reports (#{reports_count})")
|
|
31
|
-
else # For issue with the legacy format, we add the Reports section
|
|
32
|
-
update_legacy_issue_description(issue_description)
|
|
33
|
-
end
|
|
23
|
+
def increment_reports(
|
|
24
|
+
current_reports_content:,
|
|
25
|
+
test:,
|
|
26
|
+
reports_section_header: '### Reports',
|
|
27
|
+
item_extra_content: nil,
|
|
28
|
+
reports_extra_content: nil)
|
|
29
|
+
preserved_content = current_reports_content.split(reports_section_header).first&.strip
|
|
30
|
+
reports = report_lines(current_reports_content) + [report_list_item(test, item_extra_content: item_extra_content)]
|
|
34
31
|
|
|
35
|
-
[
|
|
32
|
+
[
|
|
33
|
+
preserved_content,
|
|
34
|
+
"#{reports_section_header} (#{reports.size})",
|
|
35
|
+
reports_list(reports),
|
|
36
|
+
reports_extra_content
|
|
37
|
+
].reject(&:blank?).compact.join("\n\n")
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def failed_issue_job_url(issue)
|
|
@@ -49,8 +51,28 @@ module GitlabQuality
|
|
|
49
51
|
|
|
50
52
|
private
|
|
51
53
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
+
def report_lines(content)
|
|
55
|
+
content.lines.grep(REPORT_ITEM_REGEX).map(&:strip)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def reports_list(reports)
|
|
59
|
+
sorted_reports = reports.sort.reverse
|
|
60
|
+
|
|
61
|
+
if sorted_reports.size > LATEST_REPORTS_TO_SHOW
|
|
62
|
+
[
|
|
63
|
+
"Last 10 reports:",
|
|
64
|
+
sorted_reports[...LATEST_REPORTS_TO_SHOW].join("\n"),
|
|
65
|
+
"<details><summary>See #{sorted_reports.size - LATEST_REPORTS_TO_SHOW} more reports</summary>",
|
|
66
|
+
sorted_reports[LATEST_REPORTS_TO_SHOW..].join("\n"),
|
|
67
|
+
"</details>"
|
|
68
|
+
].join("\n\n")
|
|
69
|
+
else
|
|
70
|
+
sorted_reports.join("\n")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def report_list_item(test, item_extra_content: nil)
|
|
75
|
+
"1. #{Time.new.utc.strftime('%F')}: #{test.ci_job_url} (#{ENV.fetch('CI_PIPELINE_URL', 'pipeline url is missing')}) #{item_extra_content}".strip
|
|
54
76
|
end
|
|
55
77
|
|
|
56
78
|
def job_urls_from_description(issue_description, regex)
|
|
@@ -60,15 +82,6 @@ module GitlabQuality
|
|
|
60
82
|
end
|
|
61
83
|
end
|
|
62
84
|
|
|
63
|
-
def update_legacy_issue_description(issue_description)
|
|
64
|
-
test_captures = issue_description.scan(JOB_URL_REGEX)
|
|
65
|
-
reports_count = test_captures.size.to_i + 1
|
|
66
|
-
|
|
67
|
-
updated_description = "#{issue_description}\n\n### Reports (#{reports_count})\n"
|
|
68
|
-
updated_description = [updated_description, *test_captures_to_report_items(test_captures)].join("\n") unless test_captures.empty?
|
|
69
|
-
updated_description
|
|
70
|
-
end
|
|
71
|
-
|
|
72
85
|
def test_captures_to_report_items(test_captures)
|
|
73
86
|
test_captures.map do |ci_job_url, _, _|
|
|
74
87
|
report_list_item(GitlabQuality::TestTooling::TestResult::JsonTestResult.new(
|
|
@@ -13,11 +13,19 @@ module GitlabQuality
|
|
|
13
13
|
# - Find issue by test hash
|
|
14
14
|
# - Reopen issue if it already exists, but is closed
|
|
15
15
|
class FlakyTestIssue < ReportAsIssue
|
|
16
|
+
include Concerns::GroupAndCategoryLabels
|
|
16
17
|
include Concerns::IssueReports
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
IDENTITY_LABELS = ['test', 'failure::flaky-test', 'automation:bot-authored'].freeze
|
|
20
|
+
NEW_ISSUE_LABELS = Set.new(['type::maintenance', 'priority::3', 'severity::3', *IDENTITY_LABELS]).freeze
|
|
21
|
+
SEARCH_LABELS = ['test'].freeze
|
|
22
|
+
FOUND_IN_MR_LABEL = '~"found:in MR"'
|
|
23
|
+
FOUND_IN_MASTER_LABEL = '~"found:master"'
|
|
24
|
+
REPORT_SECTION_HEADER = '### Flakiness reports'
|
|
25
|
+
REPORTS_DOCUMENTATION = <<~DOC
|
|
26
|
+
Flaky tests were detected, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html)
|
|
27
|
+
to learn more about how to reproduce them.
|
|
28
|
+
DOC
|
|
21
29
|
|
|
22
30
|
def initialize(
|
|
23
31
|
token:,
|
|
@@ -25,18 +33,16 @@ module GitlabQuality
|
|
|
25
33
|
base_issue_labels: nil,
|
|
26
34
|
confidential: false,
|
|
27
35
|
dry_run: false,
|
|
28
|
-
merge_request_iid: nil,
|
|
29
36
|
project: nil,
|
|
30
37
|
**_kwargs)
|
|
31
38
|
super(token: token, input_files: input_files, project: project, confidential: confidential, dry_run: dry_run)
|
|
32
39
|
|
|
33
40
|
@base_issue_labels = Set.new(base_issue_labels)
|
|
34
|
-
@merge_request_iid = merge_request_iid
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
private
|
|
38
44
|
|
|
39
|
-
attr_reader :base_issue_labels
|
|
45
|
+
attr_reader :base_issue_labels
|
|
40
46
|
|
|
41
47
|
def run!
|
|
42
48
|
puts "Reporting flaky tests in `#{files.join(',')}` as issues in project `#{project}` via the API at `#{Runtime::Env.gitlab_api_base}`."
|
|
@@ -44,62 +50,91 @@ module GitlabQuality
|
|
|
44
50
|
TestResults::Builder.new(files).test_results_per_file do |test_results|
|
|
45
51
|
puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
|
|
46
52
|
|
|
47
|
-
test_results
|
|
48
|
-
next if test.status != 'passed' # We only want failed tests that passed in the end
|
|
49
|
-
|
|
50
|
-
create_flaky_issue(test)
|
|
51
|
-
end
|
|
53
|
+
process_test_results(test_results)
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
def process_test_results(test_results)
|
|
58
|
+
test_results.each do |test|
|
|
59
|
+
next unless test_is_applicable?(test)
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
found_label =
|
|
61
|
-
if !merge_request_iid || merge_request_iid.empty?
|
|
62
|
-
FOUND_IN_MASTER_LABEL
|
|
63
|
-
else
|
|
64
|
-
FOUND_IN_MR_LABEL
|
|
65
|
-
end
|
|
61
|
+
puts " => Reporting flakiness for test '#{test.name}'..."
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
issues = find_issues_by_hash(test_hash(test), state: 'opened', labels: SEARCH_LABELS)
|
|
64
|
+
issues << create_issue(test) if issues.empty?
|
|
65
|
+
|
|
66
|
+
update_reports(issues, test)
|
|
67
|
+
collect_issues(test, issues)
|
|
68
|
+
end
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
"Flaky tests were detected, please refer to the [Flaky tests documentation](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) " \
|
|
73
|
-
"to learn more about how to reproduce them.",
|
|
74
|
-
initial_reports_section(test)
|
|
75
|
-
].compact.join("\n\n")
|
|
71
|
+
def test_is_applicable?(test)
|
|
72
|
+
test.status == 'passed' # We only want failed tests that passed in the end
|
|
76
73
|
end
|
|
77
74
|
|
|
78
|
-
def
|
|
79
|
-
|
|
75
|
+
def up_to_date_labels(test:, issue: nil, new_labels: Set.new)
|
|
76
|
+
(base_issue_labels + super).to_a
|
|
77
|
+
end
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
def update_reports(issues, test)
|
|
82
80
|
issues.each do |issue|
|
|
83
|
-
puts " =>
|
|
81
|
+
puts " => Adding the flaky test to the existing issue: #{issue.web_url}"
|
|
82
|
+
add_report_to_issue(issue: issue, test: test, related_issues: (issues - [issue]))
|
|
83
|
+
end
|
|
84
|
+
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
def add_report_to_issue(issue:, test:, related_issues:)
|
|
87
|
+
reports_note = existing_reports_note(issue: issue)
|
|
88
|
+
note_body = [
|
|
89
|
+
report_body(reports_note: reports_note, test: test),
|
|
90
|
+
identity_labels_quick_action,
|
|
91
|
+
relate_issues_quick_actions(related_issues)
|
|
92
|
+
].join("\n")
|
|
93
|
+
|
|
94
|
+
if reports_note
|
|
95
|
+
gitlab.edit_issue_note(
|
|
96
|
+
issue_iid: issue.iid,
|
|
97
|
+
note_id: reports_note.id,
|
|
98
|
+
note: note_body
|
|
99
|
+
)
|
|
100
|
+
else
|
|
101
|
+
gitlab.create_issue_note(iid: issue.iid, note: note_body)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
end
|
|
105
|
+
def existing_reports_note(issue:)
|
|
106
|
+
gitlab.find_issue_notes(iid: issue.iid).find do |note|
|
|
107
|
+
note.body.start_with?(REPORT_SECTION_HEADER)
|
|
92
108
|
end
|
|
109
|
+
end
|
|
93
110
|
|
|
94
|
-
|
|
111
|
+
def report_body(reports_note:, test:)
|
|
112
|
+
increment_reports(
|
|
113
|
+
current_reports_content: reports_note&.body.to_s,
|
|
114
|
+
test: test,
|
|
115
|
+
reports_section_header: REPORT_SECTION_HEADER,
|
|
116
|
+
item_extra_content: found_label,
|
|
117
|
+
reports_extra_content: REPORTS_DOCUMENTATION
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def found_label
|
|
122
|
+
if ENV.key?('CI_MERGE_REQUEST_IID')
|
|
123
|
+
FOUND_IN_MR_LABEL
|
|
124
|
+
else
|
|
125
|
+
FOUND_IN_MASTER_LABEL
|
|
126
|
+
end
|
|
95
127
|
end
|
|
96
128
|
|
|
97
|
-
def
|
|
98
|
-
|
|
129
|
+
def identity_labels_quick_action
|
|
130
|
+
labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
|
|
131
|
+
%(/label #{labels_list})
|
|
99
132
|
end
|
|
100
133
|
|
|
101
|
-
def
|
|
102
|
-
|
|
134
|
+
def relate_issues_quick_actions(issues)
|
|
135
|
+
issues.map do |issue|
|
|
136
|
+
"/relate #{issue.web_url}"
|
|
137
|
+
end.join("\n")
|
|
103
138
|
end
|
|
104
139
|
end
|
|
105
140
|
end
|
|
@@ -7,15 +7,16 @@ module GitlabQuality
|
|
|
7
7
|
module TestTooling
|
|
8
8
|
module Report
|
|
9
9
|
class GenerateTestSession < ReportAsIssue
|
|
10
|
-
def initialize(ci_project_token:, **kwargs)
|
|
10
|
+
def initialize(ci_project_token:, pipeline_stages: nil, **kwargs)
|
|
11
11
|
super
|
|
12
12
|
@ci_project_token = ci_project_token
|
|
13
|
+
@pipeline_stages = Set.new(pipeline_stages)
|
|
13
14
|
@issue_type = 'issue'
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
private
|
|
17
18
|
|
|
18
|
-
attr_reader :ci_project_token
|
|
19
|
+
attr_reader :ci_project_token, :pipeline_stages
|
|
19
20
|
|
|
20
21
|
# rubocop:disable Metrics/AbcSize
|
|
21
22
|
def run!
|
|
@@ -27,6 +28,8 @@ module GitlabQuality
|
|
|
27
28
|
TestResults::JsonTestResults.new(path).to_a
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
tests = tests.select { |test| pipeline_stages.include? test.report["stage"] } unless pipeline_stages.empty?
|
|
32
|
+
|
|
30
33
|
issue = gitlab.create_issue(
|
|
31
34
|
title: "#{Time.now.strftime('%Y-%m-%d')} Test session report | #{Runtime::Env.qa_run_type}",
|
|
32
35
|
description: generate_description(tests),
|
|
@@ -205,12 +208,9 @@ module GitlabQuality
|
|
|
205
208
|
|
|
206
209
|
def generate_test_text(testcase, tests_with_same_testcase, passed)
|
|
207
210
|
text = tests_with_same_testcase.map(&:name).uniq.join(', ')
|
|
208
|
-
encoded_text = ERB::Util.url_encode(text)
|
|
209
211
|
|
|
210
212
|
if testcase && !passed
|
|
211
|
-
#
|
|
212
|
-
# The first regex extracts the link to the issues list page from a link to a single issue show page by removing the issue id.
|
|
213
|
-
"[#{text}](#{testcase.match(%r{[\s\S]+/[^/\d]+})}?state=opened&search=#{encoded_text})"
|
|
213
|
+
"[#{text}](#{testcase})"
|
|
214
214
|
else
|
|
215
215
|
text
|
|
216
216
|
end
|
|
@@ -99,7 +99,6 @@ module GitlabQuality
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def update_issue(issue:, spec_run_time:)
|
|
102
|
-
state_event = issue.state == 'closed' ? 'reopen' : nil
|
|
103
102
|
updated_description = <<~MARKDOWN.chomp
|
|
104
103
|
#{issue.description}
|
|
105
104
|
|
|
@@ -110,8 +109,6 @@ module GitlabQuality
|
|
|
110
109
|
description: updated_description
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
issue_attrs[:state_event] = state_event if state_event
|
|
114
|
-
|
|
115
112
|
gitlab.edit_issue(iid: issue.iid, options: issue_attrs)
|
|
116
113
|
puts " => Added a report in #{issue.web_url}!"
|
|
117
114
|
end
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'nokogiri'
|
|
4
|
-
require 'active_support/core_ext/enumerable'
|
|
5
4
|
require 'rubygems/text'
|
|
6
|
-
require 'active_support/core_ext/integer/time'
|
|
7
5
|
require 'amatch'
|
|
8
6
|
|
|
9
7
|
module GitlabQuality
|
|
@@ -190,8 +188,8 @@ module GitlabQuality
|
|
|
190
188
|
end
|
|
191
189
|
|
|
192
190
|
def failure_issues(test)
|
|
193
|
-
|
|
194
|
-
test,
|
|
191
|
+
find_issues_by_hash(
|
|
192
|
+
test_hash(test),
|
|
195
193
|
state: 'opened',
|
|
196
194
|
labels: base_issue_labels + Set.new(%w[test]),
|
|
197
195
|
not_labels: exclude_labels_for_search
|
|
@@ -339,7 +337,7 @@ module GitlabQuality
|
|
|
339
337
|
def new_issue_due_date(test)
|
|
340
338
|
return unless test.product_group?
|
|
341
339
|
|
|
342
|
-
Date.today
|
|
340
|
+
Date.today.next_month
|
|
343
341
|
end
|
|
344
342
|
|
|
345
343
|
def update_reports(issue, test)
|
|
@@ -347,7 +345,7 @@ module GitlabQuality
|
|
|
347
345
|
state_event = issue.state == 'closed' ? 'reopen' : nil
|
|
348
346
|
|
|
349
347
|
issue_attrs = {
|
|
350
|
-
description:
|
|
348
|
+
description: increment_reports(current_reports_content: issue.description, test: test),
|
|
351
349
|
labels: up_to_date_labels(test: test, issue: issue)
|
|
352
350
|
}
|
|
353
351
|
issue_attrs[:state_event] = state_event if state_event
|
|
@@ -356,10 +354,6 @@ module GitlabQuality
|
|
|
356
354
|
puts " => Added a report in '#{issue.title}': #{issue.web_url}!"
|
|
357
355
|
end
|
|
358
356
|
|
|
359
|
-
def new_issue_title(test)
|
|
360
|
-
"Failure in #{super}"
|
|
361
|
-
end
|
|
362
|
-
|
|
363
357
|
def screenshot_section(test)
|
|
364
358
|
return unless test.screenshot?
|
|
365
359
|
|
|
@@ -60,7 +60,6 @@ module GitlabQuality
|
|
|
60
60
|
| Description | `#{test.name}` |
|
|
61
61
|
| Test level | #{test.level} |
|
|
62
62
|
| Hash | `#{test_hash(test)}` |
|
|
63
|
-
| Duration | #{test.run_time} seconds |
|
|
64
63
|
| Expected duration | < #{test.max_duration_for_test} seconds |
|
|
65
64
|
#{"| Test case | #{test.testcase} |" if test.testcase}
|
|
66
65
|
DESCRIPTION
|
|
@@ -142,8 +141,10 @@ module GitlabQuality
|
|
|
142
141
|
labels
|
|
143
142
|
end
|
|
144
143
|
|
|
145
|
-
def find_issues_by_hash(test_hash)
|
|
146
|
-
search_options = { search: test_hash }
|
|
144
|
+
def find_issues_by_hash(test_hash, labels: Set.new, not_labels: Set.new, state: nil)
|
|
145
|
+
search_options = { search: test_hash, labels: labels.to_a, not: { labels: not_labels.to_a } }
|
|
146
|
+
search_options[:state] = state if state
|
|
147
|
+
search_options[:in] = 'description'
|
|
147
148
|
gitlab.find_issues(options: search_options)
|
|
148
149
|
end
|
|
149
150
|
|
|
@@ -10,16 +10,21 @@ module GitlabQuality
|
|
|
10
10
|
# - Find issue by title (with test description or test file)
|
|
11
11
|
# - Add test metadata, duration to the issue with group and category labels
|
|
12
12
|
class SlowTestIssue < ReportAsIssue
|
|
13
|
-
include TestTooling::Concerns::FindSetDri
|
|
14
13
|
include Concerns::GroupAndCategoryLabels
|
|
14
|
+
include Concerns::IssueReports
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
IDENTITY_LABELS = ['test', 'rspec:slow test', 'rspec profiling', 'automation:bot-authored'].freeze
|
|
17
|
+
NEW_ISSUE_LABELS = Set.new(['test', 'type::maintenance', 'maintenance::performance', 'priority::3', 'severity::3']).freeze
|
|
18
|
+
SEARCH_LABELS = ['test'].freeze
|
|
19
|
+
FOUND_IN_MR_LABEL = '~"found:in MR"'
|
|
20
|
+
FOUND_IN_MASTER_LABEL = '~"found:master"'
|
|
21
|
+
REPORT_SECTION_HEADER = '### Slowness reports'
|
|
22
|
+
REPORTS_DOCUMENTATION = <<~DOC
|
|
23
|
+
Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)
|
|
24
|
+
to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests).
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
MultipleIssuesFound = Class.new(StandardError)
|
|
26
|
+
Add `allowed_to_be_slow: true` to the RSpec test if this is a legit slow test and close the issue.
|
|
27
|
+
DOC
|
|
23
28
|
|
|
24
29
|
private
|
|
25
30
|
|
|
@@ -29,99 +34,87 @@ module GitlabQuality
|
|
|
29
34
|
TestResults::Builder.new(files).test_results_per_file do |test_results|
|
|
30
35
|
puts "=> Reporting #{test_results.count} tests in #{test_results.path}"
|
|
31
36
|
|
|
32
|
-
test_results
|
|
33
|
-
create_slow_issue(test) if test.slow_test?
|
|
34
|
-
end
|
|
37
|
+
process_test_results(test_results)
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
def process_test_results(test_results)
|
|
42
|
+
test_results.each do |test|
|
|
43
|
+
next unless test.slow_test?
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
super +
|
|
44
|
-
<<~DESCRIPTION.chomp
|
|
45
|
-
Slow tests were detected, please see the [test speed best practices guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#test-speed)
|
|
46
|
-
to improve them. More context available about this issue in the [top slow tests guide](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#top-slow-tests).
|
|
45
|
+
puts " => Reporting slowness for test '#{test.name}'..."
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
issues = find_issues_by_hash(test_hash(test), state: 'opened', labels: SEARCH_LABELS)
|
|
48
|
+
issues << create_issue(test) if issues.empty?
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
update_reports(issues, test)
|
|
51
|
+
collect_issues(test, issues)
|
|
52
|
+
end
|
|
52
53
|
end
|
|
53
54
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
### Reports (1)
|
|
57
|
-
|
|
58
|
-
#{report_list_item(test)}
|
|
59
|
-
REPORTS
|
|
55
|
+
def test_is_applicable?(test)
|
|
56
|
+
test.slow_test?
|
|
60
57
|
end
|
|
61
58
|
|
|
62
|
-
def
|
|
63
|
-
|
|
59
|
+
def update_reports(issues, test)
|
|
60
|
+
issues.each do |issue|
|
|
61
|
+
puts " => Adding the slow test to the existing issue: #{issue.web_url}"
|
|
62
|
+
add_report_to_issue(issue: issue, test: test, related_issues: (issues - [issue]))
|
|
63
|
+
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
def add_report_to_issue(issue:, test:, related_issues:)
|
|
67
|
+
reports_note = existing_reports_note(issue: issue)
|
|
68
|
+
note_body = [
|
|
69
|
+
report_body(reports_note: reports_note, test: test),
|
|
70
|
+
identity_labels_quick_action,
|
|
71
|
+
relate_issues_quick_actions(related_issues)
|
|
72
|
+
].join("\n")
|
|
73
|
+
|
|
74
|
+
if reports_note
|
|
75
|
+
gitlab.edit_issue_note(
|
|
76
|
+
issue_iid: issue.iid,
|
|
77
|
+
note_id: reports_note.id,
|
|
78
|
+
note: note_body
|
|
79
|
+
)
|
|
80
|
+
else
|
|
81
|
+
gitlab.create_issue_note(iid: issue.iid, note: note_body)
|
|
82
|
+
end
|
|
72
83
|
end
|
|
73
84
|
|
|
74
|
-
def
|
|
75
|
-
|
|
85
|
+
def existing_reports_note(issue:)
|
|
86
|
+
gitlab.find_issue_notes(iid: issue.iid).find do |note|
|
|
87
|
+
note.body.start_with?(REPORT_SECTION_HEADER)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
def report_body(reports_note:, test:)
|
|
92
|
+
increment_reports(
|
|
93
|
+
current_reports_content: reports_note&.body.to_s,
|
|
94
|
+
test: test,
|
|
95
|
+
reports_section_header: REPORT_SECTION_HEADER,
|
|
96
|
+
item_extra_content: "(#{test.run_time} seconds) #{found_label}",
|
|
97
|
+
reports_extra_content: REPORTS_DOCUMENTATION
|
|
98
|
+
)
|
|
99
|
+
end
|
|
78
100
|
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
def found_label
|
|
102
|
+
if ENV.key?('CI_MERGE_REQUEST_IID')
|
|
103
|
+
FOUND_IN_MR_LABEL
|
|
81
104
|
else
|
|
82
|
-
|
|
83
|
-
puts " => Existing issue link #{issue['web_url']}"
|
|
84
|
-
|
|
85
|
-
update_reports(issue, test)
|
|
86
|
-
end
|
|
105
|
+
FOUND_IN_MASTER_LABEL
|
|
87
106
|
end
|
|
88
|
-
|
|
89
|
-
collect_issues(test, issues)
|
|
90
|
-
rescue MultipleIssuesFound => e
|
|
91
|
-
warn(e.message)
|
|
92
107
|
end
|
|
93
108
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
issue_attrs = {
|
|
99
|
-
description: up_to_date_issue_description(issue.description, test)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
issue_attrs[:state_event] = state_event if state_event
|
|
103
|
-
|
|
104
|
-
gitlab.edit_issue(iid: issue.iid, options: issue_attrs)
|
|
105
|
-
puts " => Added a report in '#{issue.title}': #{issue.web_url}!"
|
|
109
|
+
def identity_labels_quick_action
|
|
110
|
+
labels_list = IDENTITY_LABELS.map { |label| %(~"#{label}") }.join(' ')
|
|
111
|
+
%(/label #{labels_list})
|
|
106
112
|
end
|
|
107
113
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
reports_count = issue_description
|
|
113
|
-
.scan(REPORT_ITEM_REGEX)
|
|
114
|
-
.size.to_i + 1
|
|
115
|
-
issue_description.sub(/^### Reports.*$/, "### Reports (#{reports_count})")
|
|
116
|
-
else # For issue with the legacy format, we add the Reports section
|
|
117
|
-
reports_count = issue_description
|
|
118
|
-
.scan(JOB_URL_REGEX)
|
|
119
|
-
.size.to_i + 1
|
|
120
|
-
|
|
121
|
-
"#{issue_description}\n\n### Reports (#{reports_count})"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
"#{new_issue_description}\n#{report_list_item(test)}"
|
|
114
|
+
def relate_issues_quick_actions(issues)
|
|
115
|
+
issues.map do |issue|
|
|
116
|
+
"/relate #{issue.web_url}"
|
|
117
|
+
end.join("\n")
|
|
125
118
|
end
|
|
126
119
|
end
|
|
127
120
|
end
|
|
@@ -38,7 +38,7 @@ module GitlabQuality
|
|
|
38
38
|
console_log = console_logger(source: source, level: Env.log_level)
|
|
39
39
|
file_log = file_logger(source: source, path: log_path)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
ActiveSupport::BroadcastLogger.new(console_log, file_log, file_log)
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -16,10 +16,11 @@ module GitlabQuality
|
|
|
16
16
|
@context = context
|
|
17
17
|
@existing_mrs = nil
|
|
18
18
|
@file_path = spec["file_path"]
|
|
19
|
+
testcase = spec["testcase"]
|
|
19
20
|
devops_stage = spec["stage"]
|
|
20
21
|
product_group = spec["product_group"]
|
|
21
22
|
@example_name = spec["name"]
|
|
22
|
-
@mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name)
|
|
23
|
+
@mr_title = format("%{prefix} %{example_name}", prefix: '[PROMOTE TO BLOCKING]', example_name: example_name).truncate(72, omission: '')
|
|
23
24
|
|
|
24
25
|
@file_contents = context.get_file_contents(file_path)
|
|
25
26
|
|
|
@@ -48,8 +49,13 @@ module GitlabQuality
|
|
|
48
49
|
|
|
49
50
|
This test was identified in the reliable e2e test report: #{context.report_issue}
|
|
50
51
|
|
|
52
|
+
[Testcase link](#{testcase})
|
|
53
|
+
|
|
54
|
+
[Spec metrics link](#{context.single_spec_metrics_link(example_name)})
|
|
55
|
+
|
|
51
56
|
/label ~"Quality" ~"QA" ~"type::maintenance"
|
|
52
57
|
/label ~"devops::#{devops_stage}"
|
|
58
|
+
#{context.label_from_product_group(product_group)}
|
|
53
59
|
|
|
54
60
|
<div align="center">
|
|
55
61
|
(This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
|
|
@@ -120,8 +126,8 @@ module GitlabQuality
|
|
|
120
126
|
end
|
|
121
127
|
|
|
122
128
|
context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
|
|
123
|
-
if line.include?(',')
|
|
124
|
-
line[line.index(',')] = format(BLOCKING_METADATA, suffix: ',')
|
|
129
|
+
if line.sub(DESCRIPTION_REGEX, '').include?(',')
|
|
130
|
+
line[line.index(',', end_of_description_index(line))] = format(BLOCKING_METADATA, suffix: ',')
|
|
125
131
|
else
|
|
126
132
|
line[line.rindex(' ')] = format(BLOCKING_METADATA, suffix: ' ')
|
|
127
133
|
end
|
|
@@ -26,11 +26,13 @@ module GitlabQuality
|
|
|
26
26
|
@context = context
|
|
27
27
|
@existing_mrs = nil
|
|
28
28
|
@file_path = spec["file_path"]
|
|
29
|
+
testcase = spec["testcase"]
|
|
29
30
|
devops_stage = spec["stage"]
|
|
31
|
+
product_group = spec["product_group"]
|
|
30
32
|
@failure_issue_url = spec["failure_issue"]
|
|
31
33
|
@example_name = spec["name"]
|
|
32
34
|
@issue_id = failure_issue_url.split('/').last # split url segment, last segment of path is the issue id
|
|
33
|
-
@mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name)
|
|
35
|
+
@mr_title = format("%{prefix} %{example_name}", prefix: '[QUARANTINE]', example_name: example_name).truncate(72, omission: '')
|
|
34
36
|
@failure_issue = context.fetch_issue(iid: issue_id)
|
|
35
37
|
|
|
36
38
|
@file_contents = context.get_file_contents(file_path)
|
|
@@ -57,6 +59,10 @@ module GitlabQuality
|
|
|
57
59
|
|
|
58
60
|
This test was identified in the reliable e2e test report: #{context.report_issue}
|
|
59
61
|
|
|
62
|
+
[Testcase link](#{testcase})
|
|
63
|
+
|
|
64
|
+
[Spec metrics link](#{context.single_spec_metrics_link(example_name)})
|
|
65
|
+
|
|
60
66
|
### E2E Test Failure issue(s)
|
|
61
67
|
|
|
62
68
|
#{failure_issue_url}
|
|
@@ -81,6 +87,7 @@ module GitlabQuality
|
|
|
81
87
|
`qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb`.
|
|
82
88
|
-->
|
|
83
89
|
/label ~"devops::#{devops_stage}"
|
|
90
|
+
#{context.label_from_product_group(product_group)}
|
|
84
91
|
|
|
85
92
|
<div align="center">
|
|
86
93
|
(This MR was automatically generated by [`gitlab_quality-test_tooling`](https://gitlab.com/gitlab-org/ruby/gems/gitlab_quality-test_tooling) at #{Time.now.utc})
|
|
@@ -153,7 +160,7 @@ module GitlabQuality
|
|
|
153
160
|
context.update_matched_line(matched_lines.last, file_contents.dup) do |line|
|
|
154
161
|
indentation = context.indentation(line)
|
|
155
162
|
|
|
156
|
-
if line.include?(',') && line.split.last != 'do'
|
|
163
|
+
if line.sub(DESCRIPTION_REGEX, '').include?(',') && line.split.last != 'do'
|
|
157
164
|
line[line.rindex(',')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ',', quarantine_type: quarantine_type)
|
|
158
165
|
else
|
|
159
166
|
line[line.rindex(' ')] = format(QUARANTINE_METADATA.rstrip, issue_url: failure_issue_url, indentation: indentation, suffix: ' ', quarantine_type: quarantine_type)
|
|
@@ -170,8 +177,6 @@ module GitlabQuality
|
|
|
170
177
|
case context.issue_scoped_label(failure_issue, 'failure')&.split('::')&.last
|
|
171
178
|
when 'new', 'investigating'
|
|
172
179
|
':investigating'
|
|
173
|
-
when 'external-dependency'
|
|
174
|
-
':external_dependency'
|
|
175
180
|
when 'broken-test'
|
|
176
181
|
':broken'
|
|
177
182
|
when 'bug'
|
|
@@ -8,6 +8,8 @@ module GitlabQuality
|
|
|
8
8
|
module Processor
|
|
9
9
|
class MetaProcessor
|
|
10
10
|
class << self
|
|
11
|
+
DESCRIPTION_REGEX = /('.*?')|(".*?")/
|
|
12
|
+
|
|
11
13
|
def execute
|
|
12
14
|
raise 'method not implemented'
|
|
13
15
|
end
|
|
@@ -24,6 +26,16 @@ module GitlabQuality
|
|
|
24
26
|
def existing_mrs
|
|
25
27
|
@existing_mrs ||= context.existing_merge_requests(title: mr_title)
|
|
26
28
|
end
|
|
29
|
+
|
|
30
|
+
# Returns the index of the end of test description
|
|
31
|
+
#
|
|
32
|
+
# @param [String] line The line containing the test description
|
|
33
|
+
# @return [Integer]
|
|
34
|
+
def end_of_description_index(line)
|
|
35
|
+
description_length = line.match(DESCRIPTION_REGEX)[0].length
|
|
36
|
+
description_start_index = line.index(DESCRIPTION_REGEX)
|
|
37
|
+
description_start_index + description_length
|
|
38
|
+
end
|
|
27
39
|
end
|
|
28
40
|
end
|
|
29
41
|
end
|
|
@@ -252,6 +252,25 @@ module GitlabQuality
|
|
|
252
252
|
processed_records[file_path] && processed_records[file_path] == changed_line_no
|
|
253
253
|
end
|
|
254
254
|
|
|
255
|
+
# Infers product group label from the provided product group
|
|
256
|
+
#
|
|
257
|
+
# @param [String] product_group product group
|
|
258
|
+
# @return [String]
|
|
259
|
+
def label_from_product_group(product_group)
|
|
260
|
+
label = labels_inference.infer_labels_from_product_group(product_group).to_a.first
|
|
261
|
+
|
|
262
|
+
label ? %(/label ~"#{label}") : ''
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Returns the link to the Grafana dashboard for single spec metrics
|
|
266
|
+
#
|
|
267
|
+
# @param [String] example_name the full example name
|
|
268
|
+
# @return [String]
|
|
269
|
+
def single_spec_metrics_link(example_name)
|
|
270
|
+
base_url = "https://dashboards.quality.gitlab.net/d/cW0UMgv7k/single-spec-metrics?orgId=1&var-run_type=All&var-name="
|
|
271
|
+
base_url + CGI.escape(example_name)
|
|
272
|
+
end
|
|
273
|
+
|
|
255
274
|
private
|
|
256
275
|
|
|
257
276
|
attr_reader :token, :specs_file, :dry_run, :processor
|
|
@@ -281,6 +300,13 @@ module GitlabQuality
|
|
|
281
300
|
project: project
|
|
282
301
|
)
|
|
283
302
|
end
|
|
303
|
+
|
|
304
|
+
# Returns a cached instance of GitlabQuality::TestTooling::LabelsInference
|
|
305
|
+
#
|
|
306
|
+
# @return [GitlabQuality::TestTooling::LabelsInference]
|
|
307
|
+
def labels_inference
|
|
308
|
+
@labels_inference ||= GitlabQuality::TestTooling::LabelsInference.new
|
|
309
|
+
end
|
|
284
310
|
end
|
|
285
311
|
end
|
|
286
312
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab_quality-test_tooling
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- GitLab Quality
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
11
|
+
date: 2024-03-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: climate_control
|
|
@@ -201,7 +201,7 @@ dependencies:
|
|
|
201
201
|
version: '6.1'
|
|
202
202
|
- - "<"
|
|
203
203
|
- !ruby/object:Gem::Version
|
|
204
|
-
version: '7.
|
|
204
|
+
version: '7.2'
|
|
205
205
|
type: :runtime
|
|
206
206
|
prerelease: false
|
|
207
207
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -211,7 +211,7 @@ dependencies:
|
|
|
211
211
|
version: '6.1'
|
|
212
212
|
- - "<"
|
|
213
213
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: '7.
|
|
214
|
+
version: '7.2'
|
|
215
215
|
- !ruby/object:Gem::Dependency
|
|
216
216
|
name: amatch
|
|
217
217
|
requirement: !ruby/object:Gem::Requirement
|